<commit_before>#!/usr/bin/env node

const YAML = require('yamljs');
const fs = require('fs');
const list = [];
const tagMap = {};
const pageMap = {};
const PRINT = true;
const NO_PRINT = false;

getFiles('./_wiki', 'wiki', list);
getFiles('./_posts', 'blog', list);

const dataList = list.map(file => collectData(file))
    .filter((row) => row != null)
    .filter((row) => row.public != 'false')
    .sort(lexicalOrderingBy('fileName'))

dataList.forEach(function collectTagMap(data) {
    if (!data.tag) {
        return;
    }

    data.tag.forEach(tag => {
        if (!tagMap[tag]) {
            tagMap[tag] = [];
        }
        tagMap[tag].push({
            fileName: data.fileName,
            // updated: data.updated || data.date,
        });
    });
});

for (const tag in tagMap) {
    tagMap[tag].sort(lexicalOrderingBy('fileName'));
}

dataList.sort(lexicalOrderingBy('fileName'))
    .forEach((page) => {
        pageMap[page.fileName] =
            {
                type: page.type,
                title: page.title,
                summary: page.summary,
                parent: page.parent,
                url: page.url,
                updated: page.updated || page.date,
                children: [],
            };
    });

dataList.forEach(page => {
    if (page.parent && page.parent != 'index') {

        const parent = pageMap[page.parent];

        if (parent && parent.children) {
            parent.children.push(page.fileName);
        }
    }
});

saveTagFiles(tagMap, pageMap);
saveTagCount(tagMap);
saveMetaDataFiles(pageMap);

function lexicalOrderingBy(property) {
    return (a, b) => a[property].toLowerCase()
        .localeCompare(b[property].toLowerCase())
}

/**
 * tag 하나의 정보 파일을 만든다.
 * 각 태그 하나는 하나의 json 파일을 갖게 된다.
 * 예를 들어 math 라는 태그가 있다면 ./data/tag/math.json 파일이 만들어진다.
 * json 파일의 내용은 fileName과 collection으로 구성된다.
 * 다음은 GNU.json 파일의 예이다.
 *
{
  "fileName": "agile",
  "collection": {
    "agile": {
      "type": "wiki",
      "title": "애자일(agile)에 대한 토막글 모음",
      "summary": "",
      "parent": "software-engineering",
      "url": "/wiki/agile",
      "updated": "2020-01-20 21:57:44 +0900",
      "children": []
    },
    "Tompson-s-rule-for-first-time-telescope-makers": {
      "type": "wiki",
      "title": "망원경 규칙 (Telescope Rule)",
      "summary": "4인치 반사경을 만든 다음에 6인치 반사경을 만드는 것이, 6인치 반사경 하나 만드는 것보다 더 빠르다",
      "parent": "proverb",
      "url": "/wiki/Tompson-s-rule-for-first-time-telescope-makers",
      "updated": "2019-11-24 09:36:53 +0900",
      "children": []
    }
  }
}
*/

function saveTagFiles(tagMap, pageMap) {
    fs.mkdirSync('./data/tag', { recursive: true }, (err) => {
        if (err) {
            return console.log(err);
        }
    })

    const completedTags = {};

    for (const tag in tagMap) {
        if (completedTags[tag.toLowerCase()]) {
            console.log("중복 태그가 있습니다.", tag);
            break;
        }
        completedTags[tag.toLowerCase()] = true;

        const collection = [];
        const tagDatas = tagMap[tag];

        for (const index in tagDatas) {
            const tagData = tagDatas[index];
            const data = pageMap[tagData.fileName]

            const documentId = (data.type === 'wiki')
                ? tagData.fileName
                : data.url;

            collection.push(documentId);
        }

        saveToFile(`./data/tag/${tag}.json`, JSON.stringify(collection), NO_PRINT);
    }
}

/**
 * 파일 하나의 정보 파일을 만든다.
 * 각 파일 하나는 자신만의 정보를 갖는 json 파일을 갖게 된다.
 * 예를 들어 math.md 라는 파일이 있다면 ./data/metadata/math.json 파일이 만들어진다.
 * json 파일의 내용은 자신의 metadata와 자식 문서들의 목록이 된다.
 */
function saveMetaDataFiles(pageMap) {
    for (const page in pageMap) {
        const data = pageMap[page];
        const fileName = data.url.replace(/^[/]wiki[/]/, '');
        const dirName = `./data/metadata/${fileName}`
            .replace(/(\/\/)/g, '/')
            .replace(/[/][^/]*$/, '');

        fs.mkdirSync(dirName, { recursive: true }, (err) => {
            if (err) {
                return console.log(err);
            }
        })

        saveToFile(`./data/metadata/${fileName}.json`, JSON.stringify(data), NO_PRINT);
    }
}

/**
 * 태그 하나가 갖는 자식 문서의 수를 파일로 저장한다.
 */
function saveTagCount(tagMap) {
    const list = [];
    for (const tag in tagMap) {
        list.push({
            name: tag,
            size: tagMap[tag].length
        });
    }
    const sortedList = list.sort((lexicalOrderingBy('name')));

    saveToFile("./data/tag_count.json", JSON.stringify(sortedList, null, 1), PRINT);
}

/**
 * 주어진 문자열을 파일로 저장합니다.
 *
 * @param fileLocation 파일 이름을 포함한 저장할 경로
 * @param dataString 파일의 내용이 될 문자열
 * @param isPrintWhenSuccess 파일이 저장되었을 때 표준 출력으로 메시지를 띄우려 한다면 true
 */
function saveToFile(fileLocation, dataString, isPrintWhenSuccess) {
    fs.writeFile(fileLocation, dataString, function(err) {
        if (err) {
            return console.log(err);
        }
        if (isPrintWhenSuccess) {
            console.log(`The file "${fileLocation}" has been saved.`);
        }
    });
}

function parseInfo(file, info) {
    if (info === null) {
        return undefined;
    }

    const obj = {
        fileName: file.path.replace(/^\.\/_wiki\/(.+)?\.md$/, '$1'),
        type: file.type,
        url: '',
        modified: fs.statSync(file.path).mtime
    };

    const rawData = info.split('\n');

    rawData.forEach(str => {
        const result = /^\s*([^:]+):\s*(.+)\s*$/.exec(str);

        if (result == null) {
            return;
        }

        const key = result[1].trim();
        const val = result[2].trim()
            .replace(/\[{2}\/?|\]{2}/g, '')    // 문서 이름 앞뒤의 [[  ]], [[/ ]] 를 제거한다.
        ;

        obj[key] = val;
    });

    if (file.type === 'blog') {
        obj.url = '/blog/' + obj.date.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, '$1/$2/$3/');
        obj.url += obj.fileName.replace(/^.*[/]\d{4}-\d{2}-\d{2}-([^/]*)\.md$/, '$1');

    } else if (file.type === 'wiki') {
        obj.url = file.path
            .replace(/^\.\/_wiki/, '/wiki')
            .replace(/\.md$/, '');
    }

    if (obj.tag) {
        obj.tag = obj.tag.split(/\s+/);
    }
    return obj;
}

function isDirectory(path) {
    return fs.lstatSync(path).isDirectory();
}

function isMarkdown(fileName) {
    return /\.md$/.test(fileName);
}

function getFiles(path, type, array, testFileList = null) {

    fs.readdirSync(path).forEach(fileName => {

        const subPath = `${path}/${fileName}`;

        if (isDirectory(subPath)) {
            return getFiles(subPath, type, array, testFileList);
        }
        if (isMarkdown(fileName)) {
            if(testFileList && !testFileList.includes(fileName)) {
                return;
            }

            const obj = {
                'path': `${path}/${fileName}`,
                'type': type,
                'name': fileName,
                'children': [],
            };
            return array.push(obj);
        }
    });
}

function collectData(file) {
    const data = fs.readFileSync(file.path, 'utf8');
    return parseInfo(file, data.split('---')[1]);
}
<commit_msg>메인 로직을 main 함수로 그루핑하라<commit_after>#!/usr/bin/env node

const YAML = require('yamljs');
const fs = require('fs');
const list = [];
const tagMap = {};
const pageMap = {};
const PRINT = true;
const NO_PRINT = false;

main();

function main() {
    getFiles('./_wiki', 'wiki', list);
    getFiles('./_posts', 'blog', list);

    const dataList = list.map(file => collectData(file))
        .filter((row) => row != null)
        .filter((row) => row.public != 'false')
        .sort(lexicalOrderingBy('fileName'))

    dataList.forEach(function collectTagMap(data) {
        if (!data.tag) {
            return;
        }

        data.tag.forEach(tag => {
            if (!tagMap[tag]) {
                tagMap[tag] = [];
            }
            tagMap[tag].push({
                fileName: data.fileName,
                // updated: data.updated || data.date,
            });
        });
    });

    for (const tag in tagMap) {
        tagMap[tag].sort(lexicalOrderingBy('fileName'));
    }

    dataList.sort(lexicalOrderingBy('fileName'))
        .forEach((page) => {
            pageMap[page.fileName] =
                {
                    type: page.type,
                    title: page.title,
                    summary: page.summary,
                    parent: page.parent,
                    url: page.url,
                    updated: page.updated || page.date,
                    children: [],
                };
        });

    dataList.forEach(page => {
        if (page.parent && page.parent != 'index') {

            const parent = pageMap[page.parent];

            if (parent && parent.children) {
                parent.children.push(page.fileName);
            }
        }
    });

    saveTagFiles(tagMap, pageMap);
    saveTagCount(tagMap);
    saveMetaDataFiles(pageMap);
}

function lexicalOrderingBy(property) {
    return (a, b) => a[property].toLowerCase()
        .localeCompare(b[property].toLowerCase())
}

/**
 * tag 하나의 정보 파일을 만든다.
 * 각 태그 하나는 하나의 json 파일을 갖게 된다.
 * 예를 들어 math 라는 태그가 있다면 ./data/tag/math.json 파일이 만들어진다.
 * json 파일의 내용은 fileName과 collection으로 구성된다.
 * 다음은 GNU.json 파일의 예이다.
 *
{
  "fileName": "agile",
  "collection": {
    "agile": {
      "type": "wiki",
      "title": "애자일(agile)에 대한 토막글 모음",
      "summary": "",
      "parent": "software-engineering",
      "url": "/wiki/agile",
      "updated": "2020-01-20 21:57:44 +0900",
      "children": []
    },
    "Tompson-s-rule-for-first-time-telescope-makers": {
      "type": "wiki",
      "title": "망원경 규칙 (Telescope Rule)",
      "summary": "4인치 반사경을 만든 다음에 6인치 반사경을 만드는 것이, 6인치 반사경 하나 만드는 것보다 더 빠르다",
      "parent": "proverb",
      "url": "/wiki/Tompson-s-rule-for-first-time-telescope-makers",
      "updated": "2019-11-24 09:36:53 +0900",
      "children": []
    }
  }
}
*/

function saveTagFiles(tagMap, pageMap) {
    fs.mkdirSync('./data/tag', { recursive: true }, (err) => {
        if (err) {
            return console.log(err);
        }
    })

    const completedTags = {};

    for (const tag in tagMap) {
        if (completedTags[tag.toLowerCase()]) {
            console.log("중복 태그가 있습니다.", tag);
            break;
        }
        completedTags[tag.toLowerCase()] = true;

        const collection = [];
        const tagDatas = tagMap[tag];

        for (const index in tagDatas) {
            const tagData = tagDatas[index];
            const data = pageMap[tagData.fileName]

            const documentId = (data.type === 'wiki')
                ? tagData.fileName
                : data.url;

            collection.push(documentId);
        }

        saveToFile(`./data/tag/${tag}.json`, JSON.stringify(collection), NO_PRINT);
    }
}

/**
 * 파일 하나의 정보 파일을 만든다.
 * 각 파일 하나는 자신만의 정보를 갖는 json 파일을 갖게 된다.
 * 예를 들어 math.md 라는 파일이 있다면 ./data/metadata/math.json 파일이 만들어진다.
 * json 파일의 내용은 자신의 metadata와 자식 문서들의 목록이 된다.
 */
function saveMetaDataFiles(pageMap) {
    for (const page in pageMap) {
        const data = pageMap[page];
        const fileName = data.url.replace(/^[/]wiki[/]/, '');
        const dirName = `./data/metadata/${fileName}`
            .replace(/(\/\/)/g, '/')
            .replace(/[/][^/]*$/, '');

        fs.mkdirSync(dirName, { recursive: true }, (err) => {
            if (err) {
                return console.log(err);
            }
        })

        saveToFile(`./data/metadata/${fileName}.json`, JSON.stringify(data), NO_PRINT);
    }
}

/**
 * 태그 하나가 갖는 자식 문서의 수를 파일로 저장한다.
 */
function saveTagCount(tagMap) {
    const list = [];
    for (const tag in tagMap) {
        list.push({
            name: tag,
            size: tagMap[tag].length
        });
    }
    const sortedList = list.sort((lexicalOrderingBy('name')));

    saveToFile("./data/tag_count.json", JSON.stringify(sortedList, null, 1), PRINT);
}

/**
 * 주어진 문자열을 파일로 저장합니다.
 *
 * @param fileLocation 파일 이름을 포함한 저장할 경로
 * @param dataString 파일의 내용이 될 문자열
 * @param isPrintWhenSuccess 파일이 저장되었을 때 표준 출력으로 메시지를 띄우려 한다면 true
 */
function saveToFile(fileLocation, dataString, isPrintWhenSuccess) {
    fs.writeFile(fileLocation, dataString, function(err) {
        if (err) {
            return console.log(err);
        }
        if (isPrintWhenSuccess) {
            console.log(`The file "${fileLocation}" has been saved.`);
        }
    });
}

function parseInfo(file, info) {
    if (info === null) {
        return undefined;
    }

    const obj = {
        fileName: file.path.replace(/^\.\/_wiki\/(.+)?\.md$/, '$1'),
        type: file.type,
        url: '',
        modified: fs.statSync(file.path).mtime
    };

    const rawData = info.split('\n');

    rawData.forEach(str => {
        const result = /^\s*([^:]+):\s*(.+)\s*$/.exec(str);

        if (result == null) {
            return;
        }

        const key = result[1].trim();
        const val = result[2].trim()
            .replace(/\[{2}\/?|\]{2}/g, '')    // 문서 이름 앞뒤의 [[  ]], [[/ ]] 를 제거한다.
        ;

        obj[key] = val;
    });

    if (file.type === 'blog') {
        obj.url = '/blog/' + obj.date.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, '$1/$2/$3/');
        obj.url += obj.fileName.replace(/^.*[/]\d{4}-\d{2}-\d{2}-([^/]*)\.md$/, '$1');

    } else if (file.type === 'wiki') {
        obj.url = file.path
            .replace(/^\.\/_wiki/, '/wiki')
            .replace(/\.md$/, '');
    }

    if (obj.tag) {
        obj.tag = obj.tag.split(/\s+/);
    }
    return obj;
}

function isDirectory(path) {
    return fs.lstatSync(path).isDirectory();
}

function isMarkdown(fileName) {
    return /\.md$/.test(fileName);
}

function getFiles(path, type, array, testFileList = null) {

    fs.readdirSync(path).forEach(fileName => {

        const subPath = `${path}/${fileName}`;

        if (isDirectory(subPath)) {
            return getFiles(subPath, type, array, testFileList);
        }
        if (isMarkdown(fileName)) {
            if(testFileList && !testFileList.includes(fileName)) {
                return;
            }

            const obj = {
                'path': `${path}/${fileName}`,
                'type': type,
                'name': fileName,
                'children': [],
            };
            return array.push(obj);
        }
    });
}

function collectData(file) {
    const data = fs.readFileSync(file.path, 'utf8');
    return parseInfo(file, data.split('---')[1]);
}
<|endoftext|>


<commit_before>import Joi from 'joi';
import Boom from 'boom';
import google from 'googleapis';
import {DatabaseConnector} from 'cloud-core';
import GAuth from '../gmail-oauth-helpers'
const {GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REDIRECT_URL} = process.env;

const OAuth2 = google.auth.OAuth2;

const {
  AuthHelpers,
  IMAPConnection,
  IMAPErrors,
} = require('isomorphic-core');

const SCOPES = [
  'https://www.googleapis.com/auth/userinfo.email',  // email address
  'https://www.googleapis.com/auth/userinfo.profile',  // G+ profile
  'https://mail.google.com/',  // email
  'https://www.google.com/m8/feeds',  // contacts
  'https://www.googleapis.com/auth/calendar',  // calendar
];

const upsertAccount = (accountParams, credentials) => {
  return DatabaseConnector.forShared().then(({Account}) =>
    Account.upsertWithCredentials(accountParams, credentials)
  );
}

/**
 * How Gmail Auth works:
 *
 * 0. The N1 AccountSettingsPageGmail, upon mounting, (via the
 * OAuthSignInPage) opens in the user's default browser a link to
 * GET /auth/gmail?state=SOME_KEY. We use the default browser because
 * people are likely already signed in there.
 *
 * 1. the /auth/gmail route uses our Nylas Gmail Client Secret (which is
 * why we have to do Gmail auth in N1 cloud) and redirects the user to the
 * Google account sign in page where they enter their credentials and 2FA
 * (if they have any).
 *
 * 2. Upon successful auth, Gmail redirects back to GMAIL_REDIRECT_URL,
 * which is set to /auth/gmail/oauthcallback. Here we exchange the code
 * we're given for a Google OAuth token. We also access the user's Google
 * profile to extract their name and email. We finally use those
 * connection settings to log into Google via IMAP. We then create an
 * Account object on N1 Cloud and create an N1 Cloud Account token that n1
 * can use to access the account object (and credentials) on N1 Cloud.
 * Once successful we create a `PendingAuthResponse` which will allow N1
 * to access the credentials via a one-use key that it set in the OAuth
 * State parameter in step 0
 *
 * 3. N1 now polls /auth/gmail/token with the key use used in step 0. Once
 * the corresponding `PendingAuthResponse` shows up in our database, we
 * give the xoauth2 token and an N1 Cloud Account token back to N1.
 *
 * 4. The xoauth2 token that we give back to N1 only works for an hour.
 * Every hour or so N1 must request a new one via the /auth/gmail/refresh
 * endpoint. Using the N1 Cloud Account token, we can lookup the account,
 * get the original Google refresh token, then use that refresh token to
 * get a new xoauth2 token and return it to N1.
 */
export default function registerAuthRoutes(server) {
  server.route({
    method: 'POST',
    path: '/auth',
    config: AuthHelpers.imapAuthRouteConfig(),
    handler: AuthHelpers.imapAuthHandler(upsertAccount),
  });

  /**
   * Gmail Auth Step 1
   */
  server.route({
    method: 'GET',
    path: '/auth/gmail',
    config: {
      description: 'Redirects to Gmail OAuth',
      tags: ['accounts'],
      auth: false,
      validate: {
        query: {
          state: Joi.string().required(),
        },
      },
    },
    async handler(request, reply) {
      request.logger.info("Redirecting to Gmail OAuth")
      try {
        const oauthClient = GAuth.newOAuthClient();
        const authUrl = oauthClient.generateAuthUrl({
          access_type: 'offline',
          prompt: 'consent',
          scope: SCOPES,
          state: request.query.state,
        });
        reply.redirect(authUrl)
      } catch (err) {
        reply(Boom.wrap(err))
      }
    },
  });

  /**
   * Gmail Auth Step 2
   */
  server.route({
    method: 'GET',
    path: '/auth/gmail/oauthcallback',
    config: {
      description: 'Authenticates a new account.',
      tags: ['accounts'],
      auth: false,
      validate: {
        query: {
          state: Joi.string().required(),
          code: Joi.string(),
          error: Joi.string(),
        },
      },
    },

    async handler(request, reply) {
      request.logger.info("Have Google OAuth Code. Exchanging for token")
      const log = request.logger
      const code = request.query.code
      const n1Key = request.query.state
      const error = request.query.error // Google sometimes passes the error back here

      try {
        const client = GAuth.newOAuthClient()
        const tok = await GAuth.exchangeCodeForGoogleToken(client, code);
        const profile = await GAuth.fetchGoogleProfile(client);
        const settings = GAuth.imapSettings(tok, profile)
        request.logger.info("Resolving IMAP connection")
        settings.resolved = await GAuth.resolveIMAPSettings(settings, log)
        const account = await GAuth.createCloudAccount(settings, profile)
        request.logger.info("Creating PendingAuthResponse")
        await GAuth.createPendingAuthResponse(account, settings, n1Key)
      } catch (err) {
        request.logger.error("Error: %s, Source: %s", err.message, err.source)
        const res = {
          state_string: n1Key,
          google_client_id: GMAIL_CLIENT_ID,
          redirect_uri: GMAIL_REDIRECT_URL,
          error: err.message,
        }
        if (error === 'access_denied') {
          res.try_again = true
          res.access_denied = true
        } else if (err instanceof IMAPErrors.IMAPAuthenticationError) {
          res.try_again = true
          res.imap_auth_error = true
        } else if (err.message.includes("invalid_grant")) {
          res.try_again = true
          res.invalid_grant = true
        }

        return reply.view('gmail-auth-failure', res)
      }

      reply.view('gmail-auth-success')
    },
  });

  /**
   * Gmail Auth Step 3
   * N1 continues to poll this endpoint with the original key it set in
   * the state parameter during Step 1
   */
  server.route({
    method: "GET",
    path: "/auth/gmail/token",
    config: {
      auth: false,
      validate: {
        query: {
          key: Joi.string().required(),
        },
      },
    },
    async handler(request, reply) {
      const {PendingAuthResponse} = await DatabaseConnector.forShared();
      let tokenData = null;
      try {
        tokenData = await PendingAuthResponse.find({where:
          {pendingAuthKey: request.query.key},
        })
        if (!tokenData) {
          return reply(Boom.notFound())
        }
      } catch (err) {
        return reply(Boom.notFound())
      }
      if (!tokenData.response) {
        return reply(Boom.badImplementation("Malformed PendingAuthResponse", tokenData))
      }
      await tokenData.destroy()
      return reply(tokenData.response)
    },
  });

  server.route({
    method: "POST",
    path: "/auth/gmail/refresh",
    handler(request, reply) {
      const {account} = request.auth.credentials;
      const oauthClient = new OAuth2(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REDIRECT_URL);
      const credentials = account.decryptedCredentials();
      oauthClient.setCredentials({ refresh_token: credentials.refresh_token });
      oauthClient.refreshAccessToken((err, tokens) => {
        if (err != null) {
          console.log(err);
          reply('Backend error: could not refresh Gmail access token. Please try again.').code(400);
        }

        const res = {}
        res.access_token = tokens.access_token;
        res.xoauth2 = IMAPConnection.generateXOAuth2Token(account.emailAddress,
                                                          tokens.access_token);
        res.expiry_date = Math.floor(tokens.expiry_date / 1000);
        reply(res).code(200);
      });
    },
  });
}
<commit_msg>[cloud-api] When token refresh fails, correctly stop execution<commit_after>import Joi from 'joi';
import Boom from 'boom';
import google from 'googleapis';
import {DatabaseConnector} from 'cloud-core';
import GAuth from '../gmail-oauth-helpers'
const {GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REDIRECT_URL} = process.env;

const OAuth2 = google.auth.OAuth2;

const {
  AuthHelpers,
  IMAPConnection,
  IMAPErrors,
} = require('isomorphic-core');

const SCOPES = [
  'https://www.googleapis.com/auth/userinfo.email',  // email address
  'https://www.googleapis.com/auth/userinfo.profile',  // G+ profile
  'https://mail.google.com/',  // email
  'https://www.google.com/m8/feeds',  // contacts
  'https://www.googleapis.com/auth/calendar',  // calendar
];

const upsertAccount = (accountParams, credentials) => {
  return DatabaseConnector.forShared().then(({Account}) =>
    Account.upsertWithCredentials(accountParams, credentials)
  );
}

/**
 * How Gmail Auth works:
 *
 * 0. The N1 AccountSettingsPageGmail, upon mounting, (via the
 * OAuthSignInPage) opens in the user's default browser a link to
 * GET /auth/gmail?state=SOME_KEY. We use the default browser because
 * people are likely already signed in there.
 *
 * 1. the /auth/gmail route uses our Nylas Gmail Client Secret (which is
 * why we have to do Gmail auth in N1 cloud) and redirects the user to the
 * Google account sign in page where they enter their credentials and 2FA
 * (if they have any).
 *
 * 2. Upon successful auth, Gmail redirects back to GMAIL_REDIRECT_URL,
 * which is set to /auth/gmail/oauthcallback. Here we exchange the code
 * we're given for a Google OAuth token. We also access the user's Google
 * profile to extract their name and email. We finally use those
 * connection settings to log into Google via IMAP. We then create an
 * Account object on N1 Cloud and create an N1 Cloud Account token that n1
 * can use to access the account object (and credentials) on N1 Cloud.
 * Once successful we create a `PendingAuthResponse` which will allow N1
 * to access the credentials via a one-use key that it set in the OAuth
 * State parameter in step 0
 *
 * 3. N1 now polls /auth/gmail/token with the key use used in step 0. Once
 * the corresponding `PendingAuthResponse` shows up in our database, we
 * give the xoauth2 token and an N1 Cloud Account token back to N1.
 *
 * 4. The xoauth2 token that we give back to N1 only works for an hour.
 * Every hour or so N1 must request a new one via the /auth/gmail/refresh
 * endpoint. Using the N1 Cloud Account token, we can lookup the account,
 * get the original Google refresh token, then use that refresh token to
 * get a new xoauth2 token and return it to N1.
 */
export default function registerAuthRoutes(server) {
  server.route({
    method: 'POST',
    path: '/auth',
    config: AuthHelpers.imapAuthRouteConfig(),
    handler: AuthHelpers.imapAuthHandler(upsertAccount),
  });

  /**
   * Gmail Auth Step 1
   */
  server.route({
    method: 'GET',
    path: '/auth/gmail',
    config: {
      description: 'Redirects to Gmail OAuth',
      tags: ['accounts'],
      auth: false,
      validate: {
        query: {
          state: Joi.string().required(),
        },
      },
    },
    async handler(request, reply) {
      request.logger.info("Redirecting to Gmail OAuth")
      try {
        const oauthClient = GAuth.newOAuthClient();
        const authUrl = oauthClient.generateAuthUrl({
          access_type: 'offline',
          prompt: 'consent',
          scope: SCOPES,
          state: request.query.state,
        });
        reply.redirect(authUrl)
      } catch (err) {
        reply(Boom.wrap(err))
      }
    },
  });

  /**
   * Gmail Auth Step 2
   */
  server.route({
    method: 'GET',
    path: '/auth/gmail/oauthcallback',
    config: {
      description: 'Authenticates a new account.',
      tags: ['accounts'],
      auth: false,
      validate: {
        query: {
          state: Joi.string().required(),
          code: Joi.string(),
          error: Joi.string(),
        },
      },
    },
    async handler(request, reply) {
      request.logger.info("Have Google OAuth Code. Exchanging for token")
      const log = request.logger
      const code = request.query.code
      const n1Key = request.query.state
      const error = request.query.error // Google sometimes passes the error back here

      try {
        const client = GAuth.newOAuthClient()
        const tok = await GAuth.exchangeCodeForGoogleToken(client, code);
        const profile = await GAuth.fetchGoogleProfile(client);
        const settings = GAuth.imapSettings(tok, profile)
        request.logger.info("Resolving IMAP connection")
        settings.resolved = await GAuth.resolveIMAPSettings(settings, log)
        const account = await GAuth.createCloudAccount(settings, profile)
        request.logger.info("Creating PendingAuthResponse")
        await GAuth.createPendingAuthResponse(account, settings, n1Key)
      } catch (err) {
        request.logger.error("Error: %s, Source: %s", err.message, err.source)
        const res = {
          state_string: n1Key,
          google_client_id: GMAIL_CLIENT_ID,
          redirect_uri: GMAIL_REDIRECT_URL,
          error: err.message,
        }
        if (error === 'access_denied') {
          res.try_again = true
          res.access_denied = true
        } else if (err instanceof IMAPErrors.IMAPAuthenticationError) {
          res.try_again = true
          res.imap_auth_error = true
        } else if (err.message.includes("invalid_grant")) {
          res.try_again = true
          res.invalid_grant = true
        }

        reply.view('gmail-auth-failure', res)
        return
      }

      reply.view('gmail-auth-success')
    },
  });

  /**
   * Gmail Auth Step 3
   * N1 continues to poll this endpoint with the original key it set in
   * the state parameter during Step 1
   */
  server.route({
    method: "GET",
    path: "/auth/gmail/token",
    config: {
      auth: false,
      validate: {
        query: {
          key: Joi.string().required(),
        },
      },
    },
    async handler(request, reply) {
      const {PendingAuthResponse} = await DatabaseConnector.forShared();
      let tokenData = null;
      try {
        tokenData = await PendingAuthResponse.find({where:
          {pendingAuthKey: request.query.key},
        })
        if (!tokenData) {
          return reply(Boom.notFound())
        }
      } catch (err) {
        return reply(Boom.notFound())
      }
      if (!tokenData.response) {
        return reply(Boom.badImplementation("Malformed PendingAuthResponse", tokenData))
      }
      await tokenData.destroy()
      return reply(tokenData.response)
    },
  });

  server.route({
    method: "POST",
    path: "/auth/gmail/refresh",
    handler(request, reply) {
      const {account} = request.auth.credentials;
      const oauthClient = new OAuth2(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REDIRECT_URL);
      const credentials = account.decryptedCredentials();
      oauthClient.setCredentials({ refresh_token: credentials.refresh_token });
      oauthClient.refreshAccessToken((err, tokens) => {
        if (err != null) {
          request.logger.error(err);
          reply('Backend error: could not refresh Gmail access token. Please try again.').code(400);
          return
        }

        const res = {}
        res.access_token = tokens.access_token;
        res.xoauth2 = IMAPConnection.generateXOAuth2Token(account.emailAddress,
                                                          tokens.access_token);
        res.expiry_date = Math.floor(tokens.expiry_date / 1000);
        reply(res).code(200);
      });
    },
  });
}
<|endoftext|>


<commit_before>import assets from '../data/assets';

import Storage     from '../plugins/Storage';
import GameControl from '../plugins/GameControl';
import Transitions from '../plugins/Transitions';

export default class Boot extends Phaser.State {

  init () {
    this.load.path = 'assets/';

    this.game.storage     = this.game.plugins.add(Storage, 'heart-star');
    this.game.controls    = this.game.plugins.add(GameControl);
    this.game.transitions = this.game.plugins.add(Transitions);

    this.physics.startSystem(Phaser.Physics.ARCADE);
  }

  preload () {
    this.load.pack('boot', null, assets);
  }

  create () {
    this._setupStage();

    this.state.start('Preload');
  }

  // --------------------------------------------------------------------------

  _setupStage () {
    this.input.maxPointers = 1;

    this.scale.pageAlignHorizontally   = true;
    this.stage.disableVisibilityChange = true;

    this.scale.setMinMax(480, 320, 720, 480);
    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

    this.stage.smoothed = false;
  }

}
<commit_msg>Phaser AP is default, removing.<commit_after>import assets from '../data/assets';

import Storage     from '../plugins/Storage';
import GameControl from '../plugins/GameControl';
import Transitions from '../plugins/Transitions';

export default class Boot extends Phaser.State {

  init () {
    this.load.path = 'assets/';

    this.game.storage     = this.game.plugins.add(Storage, 'heart-star');
    this.game.controls    = this.game.plugins.add(GameControl);
    this.game.transitions = this.game.plugins.add(Transitions);
  }

  preload () {
    this.load.pack('boot', null, assets);
  }

  create () {
    this._setupStage();

    this.state.start('Preload');
  }

  // --------------------------------------------------------------------------

  _setupStage () {
    this.input.maxPointers = 1;

    this.scale.pageAlignHorizontally   = true;
    this.stage.disableVisibilityChange = true;

    this.scale.setMinMax(480, 320, 720, 480);
    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

    this.stage.smoothed = false;
  }

}
<|endoftext|>


<commit_before>export default [
  {
    type: 'checkbox',
    label: 'Type',
    active: false,
    alwaysVisible: true,
    field: 'type',
    criteria: [],
    criteriaOptions: [
      { field: 'story', label: 'Story', default: true },
      { field: 'task', label: 'Task', default: true },
      { field: 'defect', label: 'Defect', default: true},
      { field: 'test', label: 'Test', default: true }
    ]
  },
  {
    type: 'checkbox',
    label: 'Estimate',
    active: false,
    alwaysVisible: true,
    defaultCriterLabel: 'All',
    field: 'estimate',
    criteria: [],
    criteriaOptions: [
      { field: '~', label: 'Unestimated', default: true },
      { field: 's', label: 'Small', default: true },
      { field: 'm', label: 'Medium', default: true },
      { field: 'l', label: 'Large', default: true },
      { field: 'xl', label: 'Extra Large', default: true }
    ]
  },
  {
    type: 'tags',
    label: 'Tagged with',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'tags',
    criteria: '',
    criteriaOptions: []
  },
  {
    type: 'members',
    label: 'Assigned to',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'assigned_to',
    criteria: '',
    criteriaOptions: [
      { field: 'unassigned', value: '', label: 'Unassigned', default: false }
    ]
  },
  {
    type: 'members',
    label: 'Created by',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'created_by',
    criteria: '',
    criteriaOptions: []
  }
];
<commit_msg>removing unsupported estimation filter<commit_after>export default [
  {
    type: 'checkbox',
    label: 'Type',
    active: false,
    alwaysVisible: true,
    field: 'type',
    criteria: [],
    criteriaOptions: [
      { field: 'story', label: 'Story', default: true },
      { field: 'task', label: 'Task', default: true },
      { field: 'defect', label: 'Defect', default: true},
      { field: 'test', label: 'Test', default: true }
    ]
  },
  // {
  //   type: 'checkbox',
  //   label: 'Estimate',
  //   active: false,
  //   alwaysVisible: true,
  //   defaultCriterLabel: 'All',
  //   field: 'estimate',
  //   criteria: [],
  //   criteriaOptions: [
  //     { field: '~', label: 'Unestimated', default: true },
  //     { field: 's', label: 'Small', default: true },
  //     { field: 'm', label: 'Medium', default: true },
  //     { field: 'l', label: 'Large', default: true },
  //     { field: 'xl', label: 'Extra Large', default: true }
  //   ]
  // },
  {
    type: 'tags',
    label: 'Tagged with',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'tags',
    criteria: '',
    criteriaOptions: []
  },
  {
    type: 'members',
    label: 'Assigned to',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'assigned_to',
    criteria: '',
    criteriaOptions: [
      { field: 'unassigned', value: '', label: 'Unassigned', default: false }
    ]
  },
  {
    type: 'members',
    label: 'Created by',
    active: false,
    alwaysVisible: false,
    defaultCriteriaLabel: 'None',
    field: 'created_by',
    criteria: '',
    criteriaOptions: []
  }
];
<|endoftext|>


<commit_before>import { observable, computed, transaction } from "mobx";
import Immutable from "seamless-immutable";
import { generate } from "shortid";

import elementMap from "../elements";

// TODO: REMOVE. Useful for testing
const allColors = [
  "#EF767A", "#456990", "#49BEAA", "#49DCB1", "#EEB868", "#EF767A", "#456990",
  "#49BEAA", "#49DCB1", "#EEB868", "#EF767A"
];

export default class SlidesStore {
  // Default slides state
  // history will be an array of slides arrays
  @observable history = Immutable.from([{
    currentSlideIndex: 0,
    currentElementIndex: null,
    slides: [{
      // Default first slide
      id: generate(),
      props: {},
      children: [],
      color: allColors[0]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[1]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[2]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[3]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[4]
    }
  ] }]);

  @observable historyIndex = 0;

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get slides() {
    return this.history[this.historyIndex].slides.asMutable({ deep: true });
  }

  @computed get currentSlideIndex() {
    return this.history[this.historyIndex].currentSlideIndex;
  }

  @computed get currentElementIndex() {
    return this.history[this.historyIndex].currentElementIndex;
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentSlide() {
    return this.slides[this.currentSlideIndex];
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentElement() {
    return this.currentElementIndex && this.currentSlide[this.currentElementIndex];
  }

  @computed get undoDisabled() {
    return this.historyIndex === 0 || this.history.length <= 1;
  }

  @computed get redoDisabled() {
    return this.historyIndex >= this.history.length - 1;
  }

  constructor(slides) {
    if (slides) {
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides
      }]);
    }
  }

  dropElement(elementType) {
    const slideToAddTo = this.currentSlide;
    const newSlidesArray = this.slides;

    slideToAddTo.children.push({
      ...elementMap[elementType],
      id: generate()
    });

    newSlidesArray[this.currentSlideIndex] = slideToAddTo;
    const elementIndex = this.currentElementIndex ?
        this.currentElementIndex + 1 :
        slideToAddTo.children.length - 1;

    this._addToHistory({
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: elementIndex,
      slides: newSlidesArray
    });
  }

  cloneHistory() {
    return this.history.asMutable({ deep: true });
  }

  setCurrentElementIndex(newIndex) {
    const history = this.cloneHistory();
    const snapshot = history[this.historyIndex];
    snapshot.currentElementIndex = newIndex;
    history.splice(history.length, 1);
    history.concat([snapshot]);
    this.history = Immutable.from(history);
  }

  setSelectedSlideIndex(newSlideIndex) {
    const history = this.cloneHistory();
    const snapshot = history[this.historyIndex];
    snapshot.currentElementIndex = null;
    snapshot.currentSlideIndex = newSlideIndex;
    history.splice(history.length, 1);
    history.concat([snapshot]);
    this.history = Immutable.from(history);
  }

  moveSlide(currentIndex, newIndex) {
    const slidesArray = this.slides;

    slidesArray.splice(newIndex, 0, slidesArray.splice(currentIndex, 1)[0]);

    this._addToHistory({
      currentSlideIndex: newIndex,
      slides: slidesArray
    });
  }

  addSlide() {
    const slidesArray = this.slides;

    // TODO: Figure out new slide defaults/interface
    const newSlide = {
      id: generate(),
      props: {},
      children: [],
      color: allColors[6]
    };

    const index = this.currentSlideIndex + 1;
    slidesArray.splice(index, 0, newSlide);

    this._addToHistory({
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  deleteSlide() {
    const slidesArray = this.slides;
    const index = this.currentSlideIndex === 0 ? 0 : this.currentSlideIndex - 1;

    slidesArray.splice(this.currentSlideIndex, 1);

    this._addToHistory({
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  undo() {
    // double check we're not trying to undo without history
    if (this.historyIndex === 0) {
      return;
    }

    this.historyIndex -= 1;
  }

  redo() {
    // Double check we've got a future to redo to
    if (this.historyIndex > this.history.length - 1) {
      return;
    }

    this.historyIndex += 1;
  }

  // TODO: Cap history length to some number to prevent absurd memory leaks
  _addToHistory(snapshot) {
    // Only notify observers once all expressions have completed
    transaction(() => {
      // If we have a future and we do an action, remove the future.
      if (this.historyIndex < this.history.length - 1) {
        this.history = this.history.slice(0, this.historyIndex + 1);
      }

      // Wrapp the new slides array in an array so they aren't concatted as individual slide objects
      this.history = this.history.concat([Immutable.from(snapshot)]);
      this.historyIndex += 1;
    });
  }
}
<commit_msg>don't clone all of history<commit_after>import { observable, computed, transaction } from "mobx";
import Immutable from "seamless-immutable";
import { generate } from "shortid";

import elementMap from "../elements";

// TODO: REMOVE. Useful for testing
const allColors = [
  "#EF767A", "#456990", "#49BEAA", "#49DCB1", "#EEB868", "#EF767A", "#456990",
  "#49BEAA", "#49DCB1", "#EEB868", "#EF767A"
];

export default class SlidesStore {
  // Default slides state
  // history will be an array of slides arrays
  @observable history = Immutable.from([{
    currentSlideIndex: 0,
    currentElementIndex: null,
    slides: [{
      // Default first slide
      id: generate(),
      props: {},
      children: [],
      color: allColors[0]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[1]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[2]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[3]
    }, {
      id: generate(),
      props: {},
      children: [],
      color: allColors[4]
    }
  ] }]);

  @observable historyIndex = 0;

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get slides() {
    return this.history[this.historyIndex].slides.asMutable({ deep: true });
  }

  @computed get currentSlideIndex() {
    return this.history[this.historyIndex].currentSlideIndex;
  }

  @computed get currentElementIndex() {
    return this.history[this.historyIndex].currentElementIndex;
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentSlide() {
    return this.slides[this.currentSlideIndex];
  }

  // Returns a new mutable object. Functions as a cloneDeep.
  @computed get currentElement() {
    return this.currentElementIndex && this.currentSlide[this.currentElementIndex];
  }

  @computed get undoDisabled() {
    return this.historyIndex === 0 || this.history.length <= 1;
  }

  @computed get redoDisabled() {
    return this.historyIndex >= this.history.length - 1;
  }

  constructor(slides) {
    if (slides) {
      this.history = Immutable.from([{
        currentSlideIndex: 0,
        currentElementIndex: null,
        slides
      }]);
    }
  }

  dropElement(elementType) {
    const slideToAddTo = this.currentSlide;
    const newSlidesArray = this.slides;

    slideToAddTo.children.push({
      ...elementMap[elementType],
      id: generate()
    });

    newSlidesArray[this.currentSlideIndex] = slideToAddTo;
    const elementIndex = this.currentElementIndex ?
        this.currentElementIndex + 1 :
        slideToAddTo.children.length - 1;

    this._addToHistory({
      currentSlideIndex: this.currentSlideIndex,
      currentElementIndex: elementIndex,
      slides: newSlidesArray
    });
  }

  setCurrentElementIndex(newIndex) {
    const snapshot = this.history[this.historyIndex].asMutable({ deep: true });
    snapshot.currentElementIndex = newIndex;

    transaction(() => {
      this.history = this.history.slice(0, -1);
      this.history = this.history.concat([snapshot]);
    });
  }

  setSelectedSlideIndex(newSlideIndex) {
    const snapshot = this.history[this.historyIndex].asMutable({ deep: true });
    snapshot.currentElementIndex = null;
    snapshot.currentSlideIndex = newSlideIndex;

    transaction(() => {
      this.history = this.history.slice(0, -1);
      this.history = this.history.concat([snapshot]);
    });
  }

  moveSlide(currentIndex, newIndex) {
    const slidesArray = this.slides;

    slidesArray.splice(newIndex, 0, slidesArray.splice(currentIndex, 1)[0]);

    this._addToHistory({
      currentSlideIndex: newIndex,
      slides: slidesArray
    });
  }

  addSlide() {
    const slidesArray = this.slides;

    // TODO: Figure out new slide defaults/interface
    const newSlide = {
      id: generate(),
      props: {},
      children: [],
      color: allColors[6]
    };

    const index = this.currentSlideIndex + 1;
    slidesArray.splice(index, 0, newSlide);

    this._addToHistory({
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  deleteSlide() {
    const slidesArray = this.slides;
    const index = this.currentSlideIndex === 0 ? 0 : this.currentSlideIndex - 1;

    slidesArray.splice(this.currentSlideIndex, 1);

    this._addToHistory({
      currentSlideIndex: index,
      currentElementIndex: null,
      slides: slidesArray
    });
  }

  undo() {
    // double check we're not trying to undo without history
    if (this.historyIndex === 0) {
      return;
    }

    this.historyIndex -= 1;
  }

  redo() {
    // Double check we've got a future to redo to
    if (this.historyIndex > this.history.length - 1) {
      return;
    }

    this.historyIndex += 1;
  }

  // TODO: Cap history length to some number to prevent absurd memory leaks
  _addToHistory(snapshot) {
    // Only notify observers once all expressions have completed
    transaction(() => {
      // If we have a future and we do an action, remove the future.
      if (this.historyIndex < this.history.length - 1) {
        this.history = this.history.slice(0, this.historyIndex + 1);
      }

      // Wrapp the new slides array in an array so they aren't concatted as individual slide objects
      this.history = this.history.concat([Immutable.from(snapshot)]);
      this.historyIndex += 1;
    });
  }
}
<|endoftext|>


<commit_before>var roleUpgrader = {

  /** @param {Creep} creep **/
  run: function(creep) {
    if(creep.carry.energy == 0) {
      var sources = creep.room.find(FIND_SOURCES);
      if(creep.harvest(sources[0]) == ERR_NOT_IN_RANGE) {
        creep.moveTo(sources[0]);
      }
    }
    else {
      if(creep.upgradeController(creep.room.controller) == ERR_NOT_IN_RANGE) {
        creep.moveTo(creep.room.controller);
      }
    }
  }
};

module.exports = roleUpgrader;
<commit_msg>Remove upgrader from root directory<commit_after><|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt)
{
  grunt.loadNpmTasks('grunt-contrib-less');

  // load all grunt tasks
  grunt.initConfig({
    less: {
      development: {
        options: {
          strictImports: true
        },
        files: {
          "css/style.css": "less/style.less"
        }
      }
    }
  });
}<commit_msg>Use watch to watch the directory for changes to the less files<commit_after>'use strict';

module.exports = function(grunt)
{
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // load all grunt tasks
  grunt.initConfig({
    watch: {
      styles: {
        files: ['**/*.less'],
        tasks: ['less'],
        options: {
          spawn: false,
        },
      },
    },
    less: {
      development: {
        options: {
          strictImports: true
        },
        files: {
          "css/style.css": "less/style.less"
        }
      }
    }
  });
}<|endoftext|>


<commit_before>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist'
            //../../www/wordpress-default/wp-content/themes/<%= pkg.name %>
        },

        pkg: grunt.file.readJSON('package.json'),
        watch: {
            compass: {
                files: ['sass/{,*/}*.{scss,sass}'],
                tasks: ['compass', 'autoprefixer', 'csso', 'csslint', 'version', 'shell']
            },
            js: {
                files: '<%= jshint.all %>',
                tasks: ['jshint', 'uglify', 'version', 'shell']
            },
            livereload: {
                // Browser live reloading
                // https://github.com/gruntjs/grunt-contrib-watch#live-reloading
                options: {
                    livereload: false
                },
                files: [
                    '<%%= yeoman.dist %>/assets/css/app.css',
                    '<%%= yeoman.dist %>/assets/js/scripts.min.js',
                    '<%%= yeoman.dist %>/templates/*.php',
                    '*.php'
                ]
            }
        },
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '.tmp/*',
                        '<%%= yeoman.dist %>/*'
                    ]
                }]
            }
        },
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/assets/js/{,*/}*.js',
                '!<%%= yeoman.app %>assets/js/vendor/*',
                '!<%%= yeoman.app %>/assets/js/plugins/*'
            ]
        },
        csslint: {
            options: {
                csslintrc: '.csslintrc'
            },
            strict: {
                options: {
                    import: false
                },
                src: ['<%%= yeoman.dist %>/assets/css/app.css']
            }
        },
        uglify: {
            options: {
                banner: '/*! <%= _.slugify(themeName) %> <%= (new Date).toISOString().split('T')[0] %> */\n'
            },
            dist: {
                options: {
                    sourceMap: '<%%= yeoman.dist %>/assets/js/map/source-map.js',
                    sourceMappingURL: 'map/source-map.js'
                },
                files: {
                    '.tmp/assets/js/scripts.min.js': [
                        '<%%= yeoman.app %>/assets/js/plugins/*.js',
                        '<%%= yeoman.app %>/assets/js/main.js'
                    ]
                }
            }
        },
        compass: {
            dist: {
                options: {
                    sassDir: '<%%= yeoman.app %>/sass',
                    cssDir: '.tmp',
                    imagesDir: '<%%= yeoman.app %>/assets/img',
                    javascriptsDir: '<%%= yeoman.app %>/assets/js',
                    fontsDir: '<%%= yeoman.app %>/assets/fonts',
                    outputStyle: 'compressed',
                    generatedImagesDir: '.tmp/assets/img/generated',
                    debugInfo: false
                }
            }
        },
        modernizr: {
            devFile: 'bower_components/modernizr/modernizr.js',
            outputFile: '.tmp/assets/js/vendor/modernizr-custom.js',
            files: [
                '<%%= yeoman.app %>/assets/scripts/{,*/}*.js',
                '<%%= yeoman.app %>/sass/{,*/}*.scss',
                '!Gruntfile.js',
                '!bower_components/{,*/}*.js'
            ],
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/assets/img',
                    src: '{,*/}*.svg',
                    dest: '<%%= yeoman.dist %>/assets/img'
                }]
            }
        },
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/assets/img',
                    src: '{,*/}*.{png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/assets/img'
                }]
            }
        },
        concurrent: {
            dist: [
                'uglify',
                'compass',
                'modernizr',
                'svgmin',
                'imagemin'
            ]
        },
        autoprefixer: {
            singleFile: {
                src: '.tmp/app.css',
                dest: '.tmp/app.ai.css'
            }
        },
        csso: {
            compress: {
                files: {
                    '.tmp/assets/css/app.css': ['.tmp/app.ai.css']
                }
            }
        },
        version: {
            options: {
                file: '.tmp/lib/scripts.php',
                css: '.tmp/assets/css/app.css',
                cssHandle: 'egzpo_main',
                js: '.tmp/assets/js/scripts.min.js',
                jsHandle: 'egzpo_scripts',
                modernizr: '.tmp/assets/js/vendor/modernizr-custom.js',
                jsModernizr: 'modernizr'
            }
        },
        copy: {
            scripts: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '.tmp',
                    src: [
                        'lib/scripts.php'
                    ]
                }]
            },
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,png,txt,php,css}',
                        '.htaccess',
                        'assets/img/{,*/}*.{webp,gif,ico}',
                        'assets/fonts/*',
                        'assets/js/vendor/*',
                        'lang/*',
                        'lib/*',
                        'templates/*'
                    ]
                }, {
                    expand: true,
                    cwd: '.tmp/',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        'assets/**/*',
                        'lib/*'
                    ]
                },
                // }, {
                //     expand: true,
                //     cwd: 'WORDPRESS_ROOT',
                //     dest: '../../www/wordpress-default/',
                //     src: [
                //         '*.txt'
                //     ]
                // }, {
                {
                    expand: true,
                    cwd: 'bower_components/jquery',
                    dest: '<%%= yeoman.dist %>/assets/js/vendor',
                    src: [
                        'jquery.min.js'
                    ]
                }]
            }
        },
        plato: {
            complexity: {
                options : {
                    jshint : grunt.file.readJSON('.jshintrc')
                },
                files: {
                    '.tmp/reports/js-complexity': [
                        '<%%= yeoman.app %>/assets/js/{,*/}*.js'
                    ]
                }
            }
        }
    });

    grunt.registerTask('build', [
        'clean',
        'concurrent',
        'autoprefixer',
        'csso',
        'copy:scripts',
        'version',
        'copy:dist'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'build',
        'csslint',
        'plato'
    ]);

    grunt.registerTask('travis', [
        'jshint',
        'csslint'
    ]);
};
<commit_msg>Simplified tasks<commit_after>// Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist',
            tmp: '.tmp'
        },

        pkg: grunt.file.readJSON('package.json'),

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            compass: {
                files: ['<%%= yeoman.app %>/sass/{,*/}*.{scss,sass}'],
                tasks: ['compass', 'autoprefixer', 'version']
            },
            js: {
                files: '<%= jshint.all %>',
                tasks: ['uglify', 'version']
            },
            livereload: {
                options: {
                    livereload: true
                },
                files: [
                    '<%%= yeoman.dist %>/assets/css/{,*/}*.css',
                    '<%%= yeoman.dist %>/assets/js/{,*/}*.js'
                ]
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: [
                        '<%%= yeoman.tmp %>/**/*',
                        '<%%= yeoman.dist %>/**',
                        '!<%%= yeoman.tmp %>/reports/**'
                    ]
                }]
            }
        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: [
                'Gruntfile.js',
                '<%%= yeoman.app %>/assets/js/{,*/}*.js',
                '!<%%= yeoman.app %>/assets/js/plugins/*',
                '!<%%= yeoman.dist %>/assets/js/{,*/}*.js'
            ]
        },
        csslint: {
            options: {
                csslintrc: '.csslintrc'
            },
            strict: {
                options: {
                    import: false
                },
                src: ['<%%= yeoman.dist %>/assets/css/app.css']
            }
        },

        // Compiles Sass to CSS and generates necessary files if requested
        compass: {
            options: {
                sassDir: '<%%= yeoman.app %>/sass',
                cssDir: '<%%= yeoman.dist %>/assets/css',
                generatedImagesDir: '<%%= yeoman.tmp %>/assets/img/generated',
                imagesDir: '<%%= yeoman.app %>/assets/img',
                javascriptsDir: '<%%= yeoman.app %>/assets/js',
                fontsDir: '<%%= yeoman.app %>/assets/fonts',
                importPath: 'bower_components',
                httpImagesPath: '/assets/img',
                httpGeneratedImagesPath: '/assets/img/generated',
                httpFontsPath: '/assets/fonts',
                relativeAssets: false,
                assetCacheBuster: false
            },
            dist: {
                options: {
                    generatedImagesDir: '<%%= yeoman.dist %>/images/generated'
                }
            },
            server: {
                options: {
                    debugInfo: true
                }
            }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>/assets/css',
                    src: '{,*/}*.css',
                    dest: '<%%= yeoman.dist %>/assets/css'
                }]
            }
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/assets/img',
                    src: '{,*/}*.{gif,png,jpg,jpeg}',
                    dest: '<%%= yeoman.dist %>/assets/img'
                }]
            }
        },
        svgmin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/assets/img',
                    src: '{,*/}*.svg',
                    dest: '<%%= yeoman.dist %>/assets/img'
                }]
            }
        },

        // Uglifies script files
        uglify: {
            options: {
                banner: '/*! <%= _.slugify(themeName) %> <%= (new Date).toISOString().split('T')[0] %> */\n'
            },
            dist: {
                options: {
                    sourceMap: '<%%= yeoman.dist %>/assets/js/map/source-map.js',
                    sourceMappingURL: 'map/source-map.js'
                },
                files: {
                    '<%%= yeoman.dist %>/assets/js/scripts.min.js': [
                        '<%%= yeoman.app %>/assets/js/plugins/*.js',
                        '<%%= yeoman.app %>/assets/js/main.js'
                    ]
                }
            }
        },

        // Generates a custom Modernizr build that includes only the tests you
        // reference in your app
        modernizr: {
            devFile: 'bower_components/modernizr/modernizr.js',
            outputFile: '<%%= yeoman.dist %>/assets/js/vendor/modernizr-custom.js',
            files: [
                '<%%= yeoman.dist %>/assets/js/{,*/}*.js',
                '<%%= yeoman.dist %>/assets/css/{,*/}*.css',
                '!Gruntfile.js',
                '!<%%= yeoman.app %>/*',
                '!<%%= yeoman.dist %>/assets/js/vendor/*'
            ],
            uglify: true
        },

        // Compress css files
        // csso: {
        //     compress: {
        //         files: {
        //             '<%%= yeoman.tmp %>/assets/css/app.css': ['<%%= yeoman.tmp %>/app.ai.css']
        //         }
        //     }
        // },

        // Renames files for browser caching purposes
        version: {
            options: {
                file: '<%%= yeoman.dist %>/lib/scripts.php',
                css: '<%%= yeoman.dist %>/assets/css/app.css',
                cssHandle: 'egzpo_main',
                js: '<%%= yeoman.dist %>/assets/js/scripts.min.js',
                jsHandle: 'egzpo_scripts',
                modernizr: '<%%= yeoman.dist %>/assets/js/vendor/modernizr-custom.js',
                jsModernizr: 'modernizr'
            }
        },

        // Copies remaining files to places other tasks can use
        copy: {
            dist: {
                files: [{
                    expand: true,
                    dot: true,
                    cwd: '<%%= yeoman.app %>',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        '*.{ico,png,txt,php,css}',
                        'assets/img/{,*/}*.{webp,ico}',
                        'assets/fonts/{,*/}*.*',
                        'lang/*',
                        'lib/*',
                        'templates/*'
                    ]
                }, {
                    expand: true,
                    cwd: '<%%= yeoman.tmp %>/',
                    dest: '<%%= yeoman.dist %>',
                    src: [
                        'assets/**/*',
                        'lib/*'
                    ]
                },
                {
                    expand: true,
                    cwd: 'bower_components/jquery',
                    dest: '<%%= yeoman.dist %>/assets/js/vendor',
                    src: [
                        'jquery.min.js'
                    ]
                }]
            }
        },

        // Generate reports about script files
        plato: {
            complexity: {
                options : {
                    jshint : grunt.file.readJSON('.jshintrc')
                },
                files: {
                    '<%%= yeoman.tmp %>/reports/js-complexity': [
                        '<%%= yeoman.app %>/assets/js/{,*/}*.js'
                    ]
                }
            }
        },

        // Run some tasks in parallel to speed up build process
        concurrent: {
            dist: [
                'uglify',
                'compass',
                'modernizr',
                'svgmin',
                'imagemin'
            ]
        }
    });

    grunt.registerTask('build', [
        'clean',
        'concurrent',
        'autoprefixer',
        'copy',
        'version'
    ]);

    grunt.registerTask('default', [
        'jshint',
        'build',
        'csslint',
        'plato'
    ]);

    grunt.registerTask('travis', [
        'jshint',
        'csslint'
    ]);
};
<|endoftext|>


<commit_before>/* @flow */

const _ = require('lodash')

const Buffer = require('./buffer')
const logger = require('../../logger')

const STEP_NAME = 'awaitWriteFinish'

const log = logger({
  component: `atom/${STEP_NAME}`
})

// Wait this delay (in milliseconds) after the last event for a given file
// before pushing this event to the next steps.
// TODO tweak the value (the initial value was chosen because it looks like a
//      good value, it is not something that was computed)
const DELAY = 200

/*::
import type { AtomWatcherEvent } from './event'

type WaitingItem = {
  events: AtomWatcherEvent[],
  nbCandidates: number,
  timeout: TimeoutID
}
*/

module.exports = {
  loop
}

// TODO add unit tests and logs

function sendReadyBatches (waiting /*: WaitingItem[] */, out /*: Buffer */) {
  while (waiting.length > 0) {
    if (waiting[0].nbCandidates !== 0) {
      break
    }
    const item = waiting.shift()
    clearTimeout(item.timeout)
    out.push(item.events)
  }
}

// Count the candidates for debouncing with future events
function countFileWriteEvents (events /*: AtomWatcherEvent[] */) /*: number */ {
  let nbCandidates = 0
  for (let i = 0; i < events.length; i++) {
    const event = events[i]
    if (event.incomplete) {
      continue
    }
    if (event.kind === 'file' && ['created', 'modified'].includes(event.action)) {
      nbCandidates++
    }
  }
  return nbCandidates
}

// Look if we can debounce some waiting events with the current events
function debounce (waiting /*: WaitingItem[] */, events /*: AtomWatcherEvent[] */) {
  for (let i = 0; i < events.length; i++) {
    const event = events[i]
    if (event.incomplete) {
      continue
    }
    if (event.kind === 'file' && ['modified', 'deleted'].includes(event.action)) {
      for (let j = 0; j < waiting.length; j++) {
        const w = waiting[j]
        if (w.nbCandidates === 0) { continue }
        for (let k = 0; k < w.events.length; k++) {
          const e = w.events[k]
          if (['created', 'modified'].includes(e.action) && e.path === event.path) {
            w.events.splice(k, 1)
            w.nbCandidates--
            if (event.action === 'modified') {
              _.update(event, [STEP_NAME, 'previousEvents'], previousEvents =>
                _.concat(_.toArray(previousEvents), [e])
              )
              // Preserve the action from the first event (it can be a created file)
              event.action = e.action
            }
            if (event.action === 'deleted' && e.action === 'created') {
              // It's just a temporary file that we can ignore
              log.debug(
                {createdEvent: e, deletedEvent: event},
                `Ignore ${e.kind} ${e.action} then ${event.action}`
              )
              events.splice(i, 1)
              i--
            }
            break
          }
        }
      }
    }
  }
}

// This is a port of awaitWriteFinish (aWF) from chokidar. It debounces write
// events for files, as we can have several of them in a short lapse of time,
// and computing the checksum several times in a row for the same file is not a
// good idea.
async function awaitWriteFinish (buffer /*: Buffer */, out /*: Buffer */) {
  const waiting /*: WaitingItem[] */ = []

  while (true) {
    const events = await buffer.pop()
    let nbCandidates = countFileWriteEvents(events)
    debounce(waiting, events)

    // Push the new batch of events in the queue
    const timeout = setTimeout(() => {
      out.push(waiting.shift().events)
      sendReadyBatches(waiting, out)
    }, DELAY)
    waiting.push({ events, nbCandidates, timeout })

    // Look if some batches can be sent without waiting
    sendReadyBatches(waiting, out)
  }
}

function loop (buffer /*: Buffer */, opts /*: {} */) /*: Buffer */ {
  const out = new Buffer()
  awaitWriteFinish(buffer, out)
    .catch(err => { log.error({err}) })
  return out
}
<commit_msg>core/local/steps/await_write_finish: Improve readability<commit_after>/* @flow */

const _ = require('lodash')

const Buffer = require('./buffer')
const logger = require('../../logger')

const STEP_NAME = 'awaitWriteFinish'

const log = logger({
  component: `atom/${STEP_NAME}`
})

// Wait this delay (in milliseconds) after the last event for a given file
// before pushing this event to the next steps.
// TODO tweak the value (the initial value was chosen because it looks like a
//      good value, it is not something that was computed)
const DELAY = 200

/*::
import type { AtomWatcherEvent } from './event'

type WaitingItem = {
  events: AtomWatcherEvent[],
  nbCandidates: number,
  timeout: TimeoutID
}
*/

module.exports = {
  loop
}

// TODO add unit tests and logs

function sendReadyBatches (waiting /*: WaitingItem[] */, out /*: Buffer */) {
  while (waiting.length > 0) {
    if (waiting[0].nbCandidates !== 0) {
      break
    }
    const item = waiting.shift()
    clearTimeout(item.timeout)
    out.push(item.events)
  }
}

// Count the candidates for debouncing with future events
function countFileWriteEvents (events /*: AtomWatcherEvent[] */) /*: number */ {
  let nbCandidates = 0
  for (let i = 0; i < events.length; i++) {
    const event = events[i]
    if (event.incomplete) {
      continue
    }
    if (event.kind === 'file' && ['created', 'modified'].includes(event.action)) {
      nbCandidates++
    }
  }
  return nbCandidates
}

// Look if we can debounce some waiting events with the current events
function debounce (waiting /*: WaitingItem[] */, events /*: AtomWatcherEvent[] */) {
  for (let i = 0; i < events.length; i++) {
    const event = events[i]

    if (event.incomplete) {
      continue
    }

    if (event.kind === 'file' && ['modified', 'deleted'].includes(event.action)) {
      for (let j = 0; j < waiting.length; j++) {
        const w = waiting[j]

        if (w.nbCandidates === 0) { continue }

        for (let k = 0; k < w.events.length; k++) {
          const e = w.events[k]

          if (['created', 'modified'].includes(e.action) && e.path === event.path) {
            w.events.splice(k, 1)
            w.nbCandidates--

            if (event.action === 'modified') {
              _.update(event, [STEP_NAME, 'previousEvents'], previousEvents =>
                _.concat(_.toArray(previousEvents), [e])
              )
              // Preserve the action from the first event (it can be a created file)
              event.action = e.action
            }

            if (event.action === 'deleted' && e.action === 'created') {
              // It's just a temporary file that we can ignore
              log.debug(
                {createdEvent: e, deletedEvent: event},
                `Ignore ${e.kind} ${e.action} then ${event.action}`
              )
              events.splice(i, 1)
              i--
            }

            break
          }
        }
      }
    }
  }
}

// This is a port of awaitWriteFinish (aWF) from chokidar. It debounces write
// events for files, as we can have several of them in a short lapse of time,
// and computing the checksum several times in a row for the same file is not a
// good idea.
async function awaitWriteFinish (buffer /*: Buffer */, out /*: Buffer */) {
  const waiting /*: WaitingItem[] */ = []

  while (true) {
    const events = await buffer.pop()
    let nbCandidates = countFileWriteEvents(events)
    debounce(waiting, events)

    // Push the new batch of events in the queue
    const timeout = setTimeout(() => {
      out.push(waiting.shift().events)
      sendReadyBatches(waiting, out)
    }, DELAY)
    waiting.push({ events, nbCandidates, timeout })

    // Look if some batches can be sent without waiting
    sendReadyBatches(waiting, out)
  }
}

function loop (buffer /*: Buffer */, opts /*: {} */) /*: Buffer */ {
  const out = new Buffer()
  awaitWriteFinish(buffer, out)
    .catch(err => { log.error({err}) })
  return out
}
<|endoftext|>


<commit_before>// EXEC UTILITY
// exec is not a task. But it is still a very useful command for quickly execute
// any kind of the Teleport methods. Look at the jest tests.
// - tpt -e --script build --type staging --server express-webrouter, for instance
// will execute the scripts/staging_build.sh script found in your express webrouter server.
// - if no script arg is specified, then you can mention a method, and Teleport will execute it
// like for instance tpt -e --method getConfig
// - you can also specify the python language '--lang py', and in that case, it will a python
// method from the python teleport part that can be called

import childProcess from 'child_process'
import fs from 'fs'
import path from 'path'

const platformScriptNames = [
  'build',
  'deploy',
  'push',
  'run'
]

export function exec () {
  const { app, program, project, server, type } = this
  let command, scriptFile
  // execute a script
  const script = program.script
  if (typeof script !== 'undefined' && server) {
    let platform = program.platform
    let typedScript = script
    // look how many platforms we have
    if (typeof project.platformTemplateNames === 'undefined') {
      project.platformTemplateNames = this.getPlatformTemplates()
    }
    if (project.platformTemplateNames.length === 1 && platform !== 'heroku') {
      platform = project.platformTemplateNames[0]
    }
    // check
    if (
      platformScriptNames.includes(script) && platform &&
      // NOTE : this is where we need to do a little hacky workaround.
      // our default platform value is heroku, but our scripts in our templates
      // are by default kubernetes if they don't have a prefix platform
      platform !== 'teleport-snips'
    ) {
      typedScript = `${platform}_${script}`
    }
    if (type) {
      typedScript = `${type.name}_${typedScript}`
    }
    // find the file
    const scriptFileDir = path.join(server.dir, 'scripts', `${typedScript}.sh`)
    scriptFile = fs.readFileSync(scriptFileDir).toString('utf-8')
    command = `cd ${server.dir} && sh scripts/${typedScript}.sh`
  } else if (typeof program.method !== 'string') {
    console.log('You didn\'t specify a method to be called, please use the --method option for that')
    return
  // check if it is a python command
  } else if (program.lang === 'py') {
    command = `python ${app.pythonDir} ${program.method.replace(/[ ]*,[ ]*|[ ]+/g, ' ')}`
  } else {
    // then it is not a script or python command to process
    // it is a node method to call
    // we can return after
    this.consoleInfo(`Let\'s exec this method ${program.method}`)
    return this[program.method]()
  }
  // venv check
  if (app.venvDir) {
    command = `source ${app.venvDir}/bin/activate && ${command}`
  }
  if (program.shell !== 'concurrently') {
    this.consoleInfo('Let\'s exec this command !')
    this.consoleLog(command)
    if (scriptFile) {
      this.consoleInfo('Which corresponds to : ')
      this.consoleLog(scriptFile)
    }
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else if (script) {
    this.execAddConcurrently(script, command)
    // execute the whole if it is the end of the combination
    if (this.mapIndex === (this.mapLength -1)) {
      this.execConcurrentlyOrDirectly(script)
    }
  } else {
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}

export function execAddConcurrently (script, command) {
  const concurrentlyCommandsString = `${script}ConcurrentlyCommands`
  if (typeof this[concurrentlyCommandsString] === 'undefined') (
    this[concurrentlyCommandsString] = []
  )
  this[`${script}ConcurrentlyCommands`].push(command)
}

export function execConcurrentlyOrDirectly(script) {
  const { app, program } = this
  if (program.shell !== 'concurrently') {
    this.consoleInfo(`Let\'s ${script}`)
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
  else if (program.process === 'sync') {
    // NOTE: unfortunately, when we need to deploy several servers,
    // we cannot throw the heroku deploy commands
    // into parallel concurrently processed.
    // So we keep using concurrently to still display on the fly
    // the logs from the child processes but they are still sync.
    const command = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `${app.concurrentlyDir} \"${concurrentlyCommand}\"`)
      .join(' && ')
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else {
    const concurrentlyCommandsString = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `\"${concurrentlyCommand}\"`)
      .join(' ')
    const command = `${app.concurrentlyDir} --kill-others ${concurrentlyCommandsString}`
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}
<commit_msg>added also kubernetes platform in condition<commit_after>// EXEC UTILITY
// exec is not a task. But it is still a very useful command for quickly execute
// any kind of the Teleport methods. Look at the jest tests.
// - tpt -e --script build --type staging --server express-webrouter, for instance
// will execute the scripts/staging_build.sh script found in your express webrouter server.
// - if no script arg is specified, then you can mention a method, and Teleport will execute it
// like for instance tpt -e --method getConfig
// - you can also specify the python language '--lang py', and in that case, it will a python
// method from the python teleport part that can be called

import childProcess from 'child_process'
import fs from 'fs'
import path from 'path'

const platformScriptNames = [
  'build',
  'deploy',
  'push',
  'run'
]

export function exec () {
  const { app, program, project, server, type } = this
  let command, scriptFile
  // execute a script
  const script = program.script
  if (typeof script !== 'undefined' && server) {
    let platform = program.platform
    let typedScript = script
    // look how many platforms we have
    if (typeof project.platformTemplateNames === 'undefined') {
      project.platformTemplateNames = this.getPlatformTemplates()
    }
    if (project.platformTemplateNames.length === 1 && platform !== 'heroku') {
      platform = project.platformTemplateNames[0]
    }
    // check
    if (
      platformScriptNames.includes(script) && platform &&
      // NOTE : this is where we need to do a little hacky workaround.
      // our default platform value is heroku, but our scripts in our templates
      // are by default kubernetes if they don't have a prefix platform
      (platform !== 'kubernetes' || platform !== 'teleport-snips')
    ) {
      typedScript = `${platform}_${script}`
    }
    if (type) {
      typedScript = `${type.name}_${typedScript}`
    }
    // find the file
    const scriptFileDir = path.join(server.dir, 'scripts', `${typedScript}.sh`)
    scriptFile = fs.readFileSync(scriptFileDir).toString('utf-8')
    command = `cd ${server.dir} && sh scripts/${typedScript}.sh`
  } else if (typeof program.method !== 'string') {
    console.log('You didn\'t specify a method to be called, please use the --method option for that')
    return
  // check if it is a python command
  } else if (program.lang === 'py') {
    command = `python ${app.pythonDir} ${program.method.replace(/[ ]*,[ ]*|[ ]+/g, ' ')}`
  } else {
    // then it is not a script or python command to process
    // it is a node method to call
    // we can return after
    this.consoleInfo(`Let\'s exec this method ${program.method}`)
    return this[program.method]()
  }
  // venv check
  if (app.venvDir) {
    command = `source ${app.venvDir}/bin/activate && ${command}`
  }
  if (program.shell !== 'concurrently') {
    this.consoleInfo('Let\'s exec this command !')
    this.consoleLog(command)
    if (scriptFile) {
      this.consoleInfo('Which corresponds to : ')
      this.consoleLog(scriptFile)
    }
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else if (script) {
    this.execAddConcurrently(script, command)
    // execute the whole if it is the end of the combination
    if (this.mapIndex === (this.mapLength -1)) {
      this.execConcurrentlyOrDirectly(script)
    }
  } else {
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}

export function execAddConcurrently (script, command) {
  const concurrentlyCommandsString = `${script}ConcurrentlyCommands`
  if (typeof this[concurrentlyCommandsString] === 'undefined') (
    this[concurrentlyCommandsString] = []
  )
  this[`${script}ConcurrentlyCommands`].push(command)
}

export function execConcurrentlyOrDirectly(script) {
  const { app, program } = this
  if (program.shell !== 'concurrently') {
    this.consoleInfo(`Let\'s ${script}`)
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
  else if (program.process === 'sync') {
    // NOTE: unfortunately, when we need to deploy several servers,
    // we cannot throw the heroku deploy commands
    // into parallel concurrently processed.
    // So we keep using concurrently to still display on the fly
    // the logs from the child processes but they are still sync.
    const command = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `${app.concurrentlyDir} \"${concurrentlyCommand}\"`)
      .join(' && ')
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else {
    const concurrentlyCommandsString = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `\"${concurrentlyCommand}\"`)
      .join(' ')
    const command = `${app.concurrentlyDir} --kill-others ${concurrentlyCommandsString}`
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}
<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp'),<% if (includeScss) { %>
    sass = require('gulp-ruby-sass'),<% } %>
    jshint = require('gulp-jshint'),
    open = require('gulp-open'),
    livereload = require('gulp-livereload'),
    growl = require('gulp-notify-growl'),
    notify = growl(),
    wiredep = require('wiredep').stream;<% if (includeScss) { %>

  gulp.task('styles', function () {
    return gulp.src('./public/styles/scss/*.scss')
      .pipe(sass())
      .pipe(gulp.dest('./public/styles/css'))
      .pipe(notify({
        title: 'Done.',
        message: 'Styles task complete'
      }));
  });<% } %>

gulp.task('scripts', function () {
  return gulp.src('./public/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(notify({
      title: 'Done.',
      message: 'Scripts task complete'
    }));
});

gulp.task('watch', function () {<% if (includeScss) { %>
  // Watch .scss files
  gulp.watch('./public/styles/**/*.scss', ['styles']);<% } %>
  // Watch .js files
  gulp.watch(['./public/scripts/**/*.js'], ['scripts']);

  // Create LiveReload server
  var server = livereload();

  // // Watch any files, reload on change
  gulp.watch(['./**', '!./node_modules/**', '!./public/lib/**']).on('change', function (file) {
    server.changed(file.path);
  });
});

gulp.task('serve', function () {
  var connect = require('connect'),
      directory = __dirname + '/public',
      port = 3000;

  connect()
    .use(connect.logger('dev'))
    .use(connect.static(directory))
    .listen(port);
});

gulp.task('open', ['scripts'<% if (includeScss) { %>, 'styles'<% } %>], function () {
  // A file must be specified as the src when running options.url or gulp will overlook the task.
  gulp.src('./public/index.html')
  .pipe(open('', {url: 'http://localhost:3000'}));
});

gulp.task('default', function () {
  gulp.start(<% if (includeScss) { %>'styles', <% } %>'scripts', 'serve', 'open', 'watch');
});

// inject bower components
gulp.task('wiredep', function () {
  gulp.src('public/*.html')
  .pipe(wiredep({
    directory: 'public/lib'
  }))
  .pipe(gulp.dest('public'));
});
<commit_msg>fix intendation in _gulpfile.js<commit_after>'use strict';

var gulp = require('gulp'),<% if (includeScss) { %>
    sass = require('gulp-ruby-sass'),<% } %>
    jshint = require('gulp-jshint'),
    open = require('gulp-open'),
    livereload = require('gulp-livereload'),
    growl = require('gulp-notify-growl'),
    notify = growl(),
    wiredep = require('wiredep').stream;<% if (includeScss) { %>

gulp.task('styles', function () {
  return gulp.src('./public/styles/scss/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('./public/styles/css'))
    .pipe(notify({
      title: 'Done.',
      message: 'Styles task complete'
    }));
});<% } %>

gulp.task('scripts', function () {
  return gulp.src('./public/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(notify({
      title: 'Done.',
      message: 'Scripts task complete'
    }));
});

gulp.task('watch', function () {<% if (includeScss) { %>
  // Watch .scss files
  gulp.watch('./public/styles/**/*.scss', ['styles']);<% } %>
  // Watch .js files
  gulp.watch(['./public/scripts/**/*.js'], ['scripts']);

  // Create LiveReload server
  var server = livereload();

  // // Watch any files, reload on change
  gulp.watch(['./**', '!./node_modules/**', '!./public/lib/**']).on('change', function (file) {
    server.changed(file.path);
  });
});

gulp.task('serve', function () {
  var connect = require('connect'),
      directory = __dirname + '/public',
      port = 3000;

  connect()
    .use(connect.logger('dev'))
    .use(connect.static(directory))
    .listen(port);
});

gulp.task('open', ['scripts'<% if (includeScss) { %>, 'styles'<% } %>], function () {
  // A file must be specified as the src when running options.url or gulp will overlook the task.
  gulp.src('./public/index.html')
  .pipe(open('', {url: 'http://localhost:3000'}));
});

gulp.task('default', function () {
  gulp.start(<% if (includeScss) { %>'styles', <% } %>'scripts', 'serve', 'open', 'watch');
});

// inject bower components
gulp.task('wiredep', function () {
  gulp.src('public/*.html')
  .pipe(wiredep({
    directory: 'public/lib'
  }))
  .pipe(gulp.dest('public'));
});
<|endoftext|>


<commit_before>var gulp = require('gulp');

var $ = require('gulp-load-plugins')();
var runSequence = require('run-sequence');

var env = 'dev';

gulp.task('clean:dev', function() {
  return gulp.src('.tmp', { read: false })
    .pipe($.rimraf({ force: true }));
});

gulp.task('clean:dist', function() {
  return gulp.src('dist', { read: false })
    .pipe($.rimraf({ force: true }));
});

gulp.task('scripts', function() {
  return gulp.src('app/scripts/app.js')
    .pipe($.plumber())
    .pipe($.browserify({
      transform: ['reactify'],
      extensions: ['.jsx'],
      debug: env == 'dev'
    }))
    .pipe(gulp.dest('.tmp/scripts'))
});
<% if (includeSass) { %>
gulp.task('compass', function() {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.plumber())
    .pipe($.compass({
      css: '.tmp/styles',
      sass: 'app/styles'
    }));
});<% } %>

gulp.task('imagemin', function() {
  return gulp.src('app/images/*')
    .pipe($.imagemin({
            progressive: true,
            svgoPlugins: [{removeViewBox: false}]
    }))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('copy', function() {
  return gulp.src(['app/*.txt', 'app/*.ico'])
    .pipe(gulp.dest('dist'));
})

gulp.task('bundle', function () {
  var assets = $.useref.assets({searchPath: '{.tmp,app}'});
  var jsFilter = $.filter(['**/*.js']);
  var cssFilter = $.filter(['**/*.css']);

  return gulp.src('app/*.html')
    .pipe(assets)
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe(jsFilter)
    .pipe($.uglify())
    .pipe(jsFilter.restore())
    .pipe(cssFilter)
    .pipe($.autoprefixer({
      browsers: ['last 5 versions']
    }))
    .pipe($.minifyCss())
    .pipe(cssFilter.restore())
    .pipe($.revAll({ ignore: [/^\/favicon.ico$/g, '.html'] }))
    .pipe($.revReplace())
    .pipe(gulp.dest('dist'))
    .pipe($.size());
});

gulp.task('html', function() {
  return gulp.src('app/*.html')
    .pipe($.useref())
    .pipe(gulp.dest('dist'))
    .pipe($.size());
});

gulp.task('webserver', function() {
  return gulp.src(['.tmp', 'app'])
    .pipe($.webserver({
      livereload: true,
      open: true
    }));
});

gulp.task('serve', function() {
  runSequence('clean:dev', ['scripts'<% if (includeSass) { %>, 'compass'<% } %>], 'webserver');

  gulp.watch('app/*.html');

  gulp.watch('app/scripts/**/*.js', ['scripts']);

  gulp.watch('app/scripts/**/*.jsx', ['scripts']);
  <% if (includeSass) { %>
  gulp.watch('app/styles/**/*.scss', ['compass']);<% } %>
});

gulp.task('build', function() {
  env = 'prod';

  runSequence(['clean:dev', 'clean:dist'],
              ['scripts',<% if (includeSass) { %> 'compass',<% } %> 'imagemin', 'copy'],
              'bundle');
});
<commit_msg>Removed unused gulp task<commit_after>var gulp = require('gulp');

var $ = require('gulp-load-plugins')();
var runSequence = require('run-sequence');

var env = 'dev';

gulp.task('clean:dev', function() {
  return gulp.src('.tmp', { read: false })
    .pipe($.rimraf({ force: true }));
});

gulp.task('clean:dist', function() {
  return gulp.src('dist', { read: false })
    .pipe($.rimraf({ force: true }));
});

gulp.task('scripts', function() {
  return gulp.src('app/scripts/app.js')
    .pipe($.plumber())
    .pipe($.browserify({
      transform: ['reactify'],
      extensions: ['.jsx'],
      debug: env == 'dev'
    }))
    .pipe(gulp.dest('.tmp/scripts'))
});
<% if (includeSass) { %>
gulp.task('compass', function() {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.plumber())
    .pipe($.compass({
      css: '.tmp/styles',
      sass: 'app/styles'
    }));
});<% } %>

gulp.task('imagemin', function() {
  return gulp.src('app/images/*')
    .pipe($.imagemin({
            progressive: true,
            svgoPlugins: [{removeViewBox: false}]
    }))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('copy', function() {
  return gulp.src(['app/*.txt', 'app/*.ico'])
    .pipe(gulp.dest('dist'));
})

gulp.task('bundle', function () {
  var assets = $.useref.assets({searchPath: '{.tmp,app}'});
  var jsFilter = $.filter(['**/*.js']);
  var cssFilter = $.filter(['**/*.css']);

  return gulp.src('app/*.html')
    .pipe(assets)
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe(jsFilter)
    .pipe($.uglify())
    .pipe(jsFilter.restore())
    .pipe(cssFilter)
    .pipe($.autoprefixer({
      browsers: ['last 5 versions']
    }))
    .pipe($.minifyCss())
    .pipe(cssFilter.restore())
    .pipe($.revAll({ ignore: [/^\/favicon.ico$/g, '.html'] }))
    .pipe($.revReplace())
    .pipe(gulp.dest('dist'))
    .pipe($.size());
});

gulp.task('webserver', function() {
  return gulp.src(['.tmp', 'app'])
    .pipe($.webserver({
      livereload: true,
      open: true
    }));
});

gulp.task('serve', function() {
  runSequence('clean:dev', ['scripts'<% if (includeSass) { %>, 'compass'<% } %>], 'webserver');

  gulp.watch('app/*.html');

  gulp.watch('app/scripts/**/*.js', ['scripts']);

  gulp.watch('app/scripts/**/*.jsx', ['scripts']);
  <% if (includeSass) { %>
  gulp.watch('app/styles/**/*.scss', ['compass']);<% } %>
});

gulp.task('build', function() {
  env = 'prod';

  runSequence(['clean:dev', 'clean:dist'],
              ['scripts',<% if (includeSass) { %> 'compass',<% } %> 'imagemin', 'copy'],
              'bundle');
});
<|endoftext|>