├── src ├── file │ ├── __tests__ │ │ ├── fixtures │ │ │ └── .gitkeep │ │ ├── fileExists.js │ │ ├── directoryExists.js │ │ └── isDirectroyEmpty.js │ ├── fileExists.js │ ├── directoryExists.js │ └── isDirectoryEmpty.js ├── presets │ ├── __tests__ │ │ ├── fixtures │ │ │ └── scaffold │ │ │ │ └── package.json │ │ ├── helpers │ │ │ └── options.js │ │ ├── writeFile.js │ │ ├── copyFiles.js │ │ ├── writeJson.js │ │ ├── updateFile.js │ │ ├── removeFiles.js │ │ ├── updateFiles.js │ │ └── updateJson.js │ ├── removeFiles.js │ ├── writeFile.js │ ├── updateFiles.js │ ├── writeJson.js │ ├── copyFiles.js │ ├── addScaffoldInfo.js │ ├── updateJson.js │ └── updateFile.js ├── git │ ├── getRemote.js │ ├── getCommitHash.js │ ├── __tests__ │ │ ├── getRemoteURL.js │ │ ├── getRemote.js │ │ ├── getClean.js │ │ ├── checkGitRepository.js │ │ └── getCommitHash.js │ ├── getClean.js │ ├── getRemoteURL.js │ └── checkGitRepository.js ├── config │ ├── config.js │ └── index.js ├── lib │ ├── ensureConfig.js │ ├── updateScaffoldStat.js │ ├── getInfoFromShell.js │ ├── logger.js │ ├── index.js │ └── configManager.js └── commands │ ├── config.js │ ├── __tests__ │ ├── init.js │ └── config.js │ ├── config-commands │ ├── list.js │ ├── add.js │ └── remove.js │ └── init.js ├── .npmrc ├── assets ├── psd │ └── gt.psd └── images │ ├── gt.png │ └── flowchart.png ├── .prettierignore ├── .travis.yml ├── index.js ├── .github └── FUNDING.yml ├── .gitignore ├── scripts ├── build.js ├── build │ ├── index.js │ └── babel.js └── config.js ├── .babelrc ├── .npmignore ├── CONTRIBUTING.md ├── .editorconfig ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /src/file/__tests__/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry="https://registry.npmjs.org/" 2 | -------------------------------------------------------------------------------- /assets/psd/gt.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/granturismo/HEAD/assets/psd/gt.psd -------------------------------------------------------------------------------- /assets/images/gt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/granturismo/HEAD/assets/images/gt.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | README.md 3 | package.json 4 | **/__test__/**/fixtures/** 5 | assets 6 | -------------------------------------------------------------------------------- /assets/images/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/granturismo/HEAD/assets/images/flowchart.png -------------------------------------------------------------------------------- /src/presets/__tests__/fixtures/scaffold/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scaffold", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.0" 4 | - "6.0" 5 | after_success: 6 | - bash <(curl -s https://codecov.io/bash) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * @since 2016-11-16 10:21 5 | * @author vivaxy 6 | */ 7 | 8 | require('./build/lib/index'); 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: vivaxy 2 | open_collective: vivaxy_personal 3 | custom: ['https://gist.github.com/vivaxy/58eed1803a2eddda05c90aed99430de2'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_Store 4 | npm-debug.log 5 | /build 6 | .nyc_output 7 | coverage 8 | coverage.lcov 9 | yarn-error.log* 10 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-18 10:55:28 3 | * @author vivaxy 4 | */ 5 | 6 | require('babel-polyfill'); 7 | require('babel-register')(); 8 | require('./build/index'); 9 | -------------------------------------------------------------------------------- /scripts/build/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-18 11:04:07 3 | * @author vivaxy 4 | */ 5 | 6 | import babel from './babel'; 7 | 8 | babel().catch((ex) => { 9 | throw ex; 10 | }); 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "env": { 7 | "test": { 8 | "sourceMaps": "inline" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /assets 2 | /.idea 3 | node_modules 4 | .DS_Store 5 | npm-debug.log 6 | /scripts 7 | /src 8 | /test 9 | /.babelrc 10 | /.editorconfig 11 | /.eslintrc 12 | .nyc_output 13 | coverage 14 | /coverage.lcov 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Commit 2 | 3 | Commit with [Conventional Commits](https://conventionalcommits.org/). 4 | 5 | # Release 6 | 7 | Make sure you have commit and push all your changes. 8 | 9 | Run `npm run release`. 10 | -------------------------------------------------------------------------------- /src/git/getRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-28 20:56 3 | * @author vivaxy 4 | */ 5 | 6 | import getInfoFromShell from '../lib/getInfoFromShell'; 7 | 8 | export default async() => { 9 | return await getInfoFromShell('git', ['remote']); 10 | }; 11 | -------------------------------------------------------------------------------- /src/git/getCommitHash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180109 14:56 3 | * @author vivaxy 4 | */ 5 | 6 | import getInfoFromShell from '../lib/getInfoFromShell'; 7 | 8 | export default async() => { 9 | return await getInfoFromShell('git', ['rev-parse', 'HEAD']); 10 | }; 11 | -------------------------------------------------------------------------------- /src/git/__tests__/getRemoteURL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:25:47 3 | * @author vivaxy 4 | */ 5 | 6 | 7 | import test from 'ava'; 8 | 9 | test('should get remote url', async(t) => { 10 | // travis ci is using git@v1.8 11 | // so we pass it 12 | t.pass(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 10:17 3 | * @author vivaxy 4 | */ 5 | 6 | export default { 7 | scaffold: { 8 | 'vivaxy/gt-react-scaffold': { 9 | repo: 'https://github.com/vivaxy/gt-react-scaffold.git', 10 | stat: 0, 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/git/getClean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-27 13:49 3 | * @author vivaxy 4 | */ 5 | 6 | import getInfoFromShell from '../lib/getInfoFromShell'; 7 | 8 | export default async() => { 9 | const statusOutput = await getInfoFromShell('git', ['status', '-s']); 10 | return statusOutput === ''; 11 | }; 12 | -------------------------------------------------------------------------------- /src/git/__tests__/getRemote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:23:47 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | 8 | import getRemote from '../getRemote'; 9 | 10 | test('should get remote', async(t) => { 11 | const result = await getRemote(); 12 | t.is(typeof result, 'string'); 13 | }); 14 | -------------------------------------------------------------------------------- /src/git/__tests__/getClean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:17:07 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | 8 | import getClean from '../getClean'; 9 | 10 | test('should tell if git repository is clean', async(t) => { 11 | const result = await getClean(); 12 | t.is(typeof result, 'boolean'); 13 | }); 14 | -------------------------------------------------------------------------------- /src/lib/ensureConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 21:31 3 | * @author vivaxy 4 | */ 5 | 6 | import * as configManager from './configManager'; 7 | import config from '../config/config'; 8 | 9 | export default async() => { 10 | if (!await configManager.exist()) { 11 | await configManager.write(config); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/presets/__tests__/helpers/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 11:48:53 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | 8 | export default { 9 | scaffold: { folder: path.join(__dirname, '..', 'fixtures', 'scaffold') }, 10 | project: { folder: path.join(__dirname, '..', 'fixtures', 'project') }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/updateScaffoldStat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 11:40 3 | * @author vivaxy 4 | */ 5 | 6 | import * as configManager from './configManager'; 7 | 8 | export default async(scaffoldName) => { 9 | const userConfig = configManager.read(); 10 | userConfig.scaffold[scaffoldName].stat++; 11 | await configManager.write(userConfig); 12 | }; 13 | -------------------------------------------------------------------------------- /src/commands/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 16:45 3 | * @author vivaxy 4 | */ 5 | 6 | import yargs from 'yargs'; 7 | 8 | export const command = 'config ...'; 9 | export const describe = 'Show or edit configs'; 10 | export const builder = () => { 11 | return yargs.commandDir('./config-commands'); 12 | }; 13 | export const handler = () => {}; 14 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 10:17 3 | * @author vivaxy 4 | */ 5 | 6 | export const GT_HOME = `${process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE}/.gt`; 7 | export const CONFIG_FILE_NAME = 'config.json'; 8 | export const PROJECT_GT_FILE = 'scripts/gt.js'; 9 | export const MODULES_FOLDER = 'node_modules'; 10 | export const GIT_FOLDER = '.git'; 11 | -------------------------------------------------------------------------------- /src/git/__tests__/checkGitRepository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 16:53:16 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | 8 | import checkGitRepository from '../checkGitRepository'; 9 | 10 | test('should tell if this is a git base folder', async(t) => { 11 | // in git folder 12 | const result1 = await checkGitRepository(); 13 | t.true(result1); 14 | }); 15 | -------------------------------------------------------------------------------- /src/lib/getInfoFromShell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-22 16:04 3 | * @author vivaxy 4 | */ 5 | 6 | import execa from 'execa'; 7 | 8 | export default async(file, args) => { 9 | // here `...args` makes things worse, IDKW 10 | const { code, stdout } = await execa(file, args); 11 | if (code === 0) { 12 | return stdout.split('\n')[0]; 13 | } 14 | return null; 15 | }; 16 | -------------------------------------------------------------------------------- /src/presets/removeFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-02-20 18:54 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project: { folder } }) => { 10 | return async(files) => { 11 | await Promise.all(files.map((file) => { 12 | return fse.remove(path.join(folder, file)); 13 | })); 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/git/__tests__/getCommitHash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180109 14:57 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | import getCommitHash from '../getCommitHash'; 8 | 9 | test('should get commit hash', async(t) => { 10 | const result = await getCommitHash(); 11 | t.is(typeof result, 'string'); 12 | t.is(result.includes('\n'), false); 13 | t.is(result.length, 40); 14 | }); 15 | -------------------------------------------------------------------------------- /src/commands/__tests__/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-18 12:02:48 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | import * as init from '../init'; 8 | 9 | test('init should has correct exports', (t) => { 10 | t.true(typeof init.command === 'string'); 11 | t.true(typeof init.describe === 'string'); 12 | t.deepEqual(init.builder, {}); 13 | t.true(typeof init.handler === 'function'); 14 | }); 15 | -------------------------------------------------------------------------------- /src/git/getRemoteURL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-31 13:54:02 3 | * @author vivaxy 4 | */ 5 | 6 | import getInfoFromShell from '../lib/getInfoFromShell'; 7 | import getGitRemote from './getRemote'; 8 | 9 | export default async() => { 10 | const remote = await getGitRemote(); 11 | if (remote) { 12 | return await getInfoFromShell('git', ['remote', 'get-url', remote]); 13 | } 14 | return undefined; 15 | }; 16 | -------------------------------------------------------------------------------- /src/file/fileExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-27 15:36 3 | * @author vivaxy 4 | */ 5 | 6 | import fs from 'fs'; 7 | 8 | export default async(filename) => { 9 | return await new Promise((resolve) => { 10 | fs.access(filename, (err) => { 11 | if (err) { 12 | resolve(false); 13 | } else { 14 | resolve(true); 15 | } 16 | }); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/presets/writeFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 20:41 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project }) => { 10 | return async(filename, data) => { 11 | const distFolder = project.folder; 12 | const distFilename = path.join(distFolder, filename); 13 | 14 | await fse.outputFile(distFilename, data); 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | curly_bracket_next_line = false 11 | spaces_around_operators = true 12 | indent_brace_style = 1tbs 13 | 14 | [*.js] 15 | quote_type = single 16 | 17 | [*.{html,less,css,json}] 18 | quote_type = double 19 | 20 | [package.json] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /src/presets/updateFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 20:44 3 | * @author vivaxy 4 | */ 5 | 6 | import getUpdateFile from './updateFile'; 7 | 8 | export default ({ project, scaffold }) => { 9 | const updateFile = getUpdateFile({ project, scaffold }); 10 | 11 | return async(files, filter) => { 12 | await Promise.all(files.map((file) => { 13 | return updateFile(file, filter); 14 | })); 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/commands/__tests__/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-18 11:45:25 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | import * as config from '../config'; 8 | 9 | test('config should has correct exports', (t) => { 10 | t.true(typeof config.command === 'string'); 11 | t.true(typeof config.describe === 'string'); 12 | t.true(typeof config.builder === 'function'); 13 | t.true(typeof config.handler === 'function'); 14 | }); 15 | -------------------------------------------------------------------------------- /src/file/directoryExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-27 15:57 3 | * @author vivaxy 4 | */ 5 | 6 | import fs from 'fs'; 7 | 8 | export default async(filename) => { 9 | return await new Promise((resolve) => { 10 | fs.stat(filename, (err, stats) => { 11 | if (err) { 12 | resolve(false); 13 | } else { 14 | resolve(stats.isDirectory()); 15 | } 16 | }); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/presets/writeJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 21:07 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project }) => { 10 | return async(filename, data) => { 11 | const distFolder = project.folder; 12 | const distFilename = path.join(distFolder, filename); 13 | 14 | await fse.outputFile(distFilename, JSON.stringify(data, null, 2)); 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/file/isDirectoryEmpty.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export default async(dirname, excepts = []) => { 4 | return await new Promise((resolve, reject) => { 5 | fs.readdir(dirname, (err, files) => { 6 | if (err) { 7 | reject(err); 8 | } else { 9 | resolve(files.filter((filename) => { 10 | return !excepts.includes(filename); 11 | }).length === 0); 12 | } 13 | }); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/presets/copyFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 20:34 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project, scaffold }) => { 10 | return async(files) => { 11 | const sourceFolder = scaffold.folder; 12 | const distFolder = project.folder; 13 | 14 | await Promise.all(files.map((file) => { 15 | return fse.copy(path.join(sourceFolder, file), path.join(distFolder, file)); 16 | })); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/lib/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180302 12:42 3 | * @author vivaxy 4 | */ 5 | 6 | import chalk from 'chalk'; 7 | import logSymbols from 'log-symbols'; 8 | 9 | export const native = (message) => { 10 | console.log(message); 11 | }; 12 | 13 | export const info = (message) => { 14 | console.log(chalk.blue(logSymbols.info), message); 15 | }; 16 | 17 | export const success = (message) => { 18 | console.log(chalk.green(logSymbols.success), message); 19 | }; 20 | 21 | export const error = (message) => { 22 | console.log(chalk.red(logSymbols.error), message); 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-12-04 16:28 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | 8 | // file => absolute 9 | // filename => relative 10 | // folder => absolute 11 | // folderName => relative 12 | 13 | export const SOURCE_FOLDER_NAME = 'src'; 14 | export const BUILD_FOLDER_NAME = 'build'; 15 | export const LOG_FILENAME = 'build.log'; 16 | 17 | export const PROJECT_BASE_FOLDER = path.join(__dirname, '..'); 18 | export const SOURCE_PATH = path.join(PROJECT_BASE_FOLDER, SOURCE_FOLDER_NAME); 19 | export const BUILD_PATH = path.join(PROJECT_BASE_FOLDER, BUILD_FOLDER_NAME); 20 | -------------------------------------------------------------------------------- /src/git/checkGitRepository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-22 16:05 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | 8 | import directoryExists from '../file/directoryExists'; 9 | import getInfoFromShell from '../lib/getInfoFromShell'; 10 | 11 | const cwd = process.cwd(); 12 | 13 | export default async() => { 14 | const gitExists = await directoryExists(path.join(cwd, '.git')); 15 | if (gitExists) { 16 | const isInWorkTree = await getInfoFromShell('git', ['rev-parse', '--is-inside-work-tree']); 17 | if (isInWorkTree === 'true') { 18 | return true; 19 | } 20 | } 21 | return false; 22 | }; 23 | -------------------------------------------------------------------------------- /src/presets/addScaffoldInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180109 15:56 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project: { folder: distFolder }, scaffold: { folder: sourceFolder, git: { headHash } } }) => { 10 | return async({ scaffoldCommitHash = 'scaffoldCommitHash', scaffoldVersion = 'scaffoldVersion' }) => { 11 | const distPackageJson = require(path.join(distFolder, 'package.json')); 12 | const { version } = require(path.join(sourceFolder, 'package.json')); 13 | await fse.outputFile({ ...distPackageJson, [scaffoldCommitHash]: headHash, [scaffoldVersion]: version }); 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/presets/__tests__/writeFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:42:30 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import options from './helpers/options'; 11 | import createWriteFile from '../writeFile'; 12 | 13 | test('should write file', async(t) => { 14 | const filename = 'test-filename'; 15 | const fileContent = 'test-file-content'; 16 | const writeFile = createWriteFile(options); 17 | await writeFile(filename, fileContent); 18 | const expectedFileContent = await fse.readFile(path.join(options.project.folder, filename), 'utf8'); 19 | t.is(fileContent, expectedFileContent); 20 | }); 21 | -------------------------------------------------------------------------------- /src/commands/config-commands/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180302 12:34 3 | * @author vivaxy 4 | */ 5 | 6 | import columnify from 'columnify'; 7 | import * as configManager from '../../lib/configManager'; 8 | 9 | export const command = 'list'; 10 | export const describe = 'Show all configs'; 11 | export const builder = {}; 12 | export const handler = () => { 13 | const userConfig = configManager.read().scaffold; 14 | const scaffoldList = configManager.readScaffoldListByStatOrder(); 15 | const data = scaffoldList.map((scaffold) => { 16 | return { stat: userConfig[scaffold].stat, name: scaffold, repo: userConfig[scaffold].repo }; 17 | }); 18 | console.log(columnify(data)); 19 | }; 20 | -------------------------------------------------------------------------------- /src/presets/updateJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 20:44 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | export default ({ project, scaffold }) => { 10 | return async(filename, filter) => { 11 | const sourceFolder = scaffold.folder; 12 | const distFolder = project.folder; 13 | 14 | const sourceFilename = path.join(sourceFolder, filename); 15 | const distFilename = path.join(distFolder, filename); 16 | const sourceData = await fse.readJson(sourceFilename); 17 | const distData = filter(sourceData); 18 | await fse.outputFile(distFilename, JSON.stringify(distData, null, 2)); 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 10:22 3 | * @author vivaxy 4 | */ 5 | 6 | import 'babel-polyfill'; 7 | import yargs from 'yargs'; 8 | import updateNotifier from 'update-notifier'; 9 | import pkg from '../../package.json'; 10 | 11 | import ensureConfig from './ensureConfig'; 12 | 13 | const configureYargs = () => { 14 | return yargs 15 | .commandDir('../commands') 16 | .demandCommand() 17 | .help() 18 | .version() 19 | .argv._; 20 | }; 21 | 22 | const main = async() => { 23 | updateNotifier({ pkg }).notify(); 24 | 25 | await ensureConfig(); 26 | 27 | configureYargs(); 28 | }; 29 | 30 | main().catch((ex) => { 31 | throw ex; 32 | }); 33 | -------------------------------------------------------------------------------- /src/presets/updateFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-15 20:44 3 | * @author vivaxy 4 | */ 5 | 6 | import fs from 'fs'; 7 | import path from 'path'; 8 | import fse from 'fs-extra'; 9 | 10 | export default ({ project, scaffold }) => { 11 | return async(filename, filter) => { 12 | const sourceFolder = scaffold.folder; 13 | const distFolder = project.folder; 14 | 15 | const sourceFilename = path.join(sourceFolder, filename); 16 | const distFilename = path.join(distFolder, filename); 17 | const sourceData = fs.readFileSync(sourceFilename, 'utf8'); 18 | const distData = filter(sourceData); 19 | await fse.outputFile(distFilename, distData); 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/file/__tests__/fileExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 16:44:41 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | 9 | import fileExists from '../fileExists'; 10 | 11 | test('should tell if a file exists or not', async(t) => { 12 | // not exists 13 | const result1 = await fileExists(path.join(__dirname, 'fixtures', 'non-exists-folder')); 14 | t.is(result1, false); 15 | 16 | // exists as a file 17 | const result2 = await fileExists(path.join(__dirname, 'fixtures', '.gitkeep')); 18 | t.is(result2, true); 19 | 20 | // exists as a folder 21 | const result3 = await fileExists(path.join(__dirname, 'fixtures')); 22 | t.is(result3, true); 23 | }); 24 | -------------------------------------------------------------------------------- /src/file/__tests__/directoryExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 16:37:12 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | 9 | import directoryExists from '../directoryExists'; 10 | 11 | test('should tell if a directory exists or not', async(t) => { 12 | // not exists 13 | const result1 = await directoryExists(path.join(__dirname, 'fixtures', 'non-exists-directory')); 14 | t.is(result1, false); 15 | 16 | // exists as a file 17 | const result2 = await directoryExists(path.join(__dirname, 'fixtures', '.gitkeep')); 18 | t.is(result2, false); 19 | 20 | // exists as a folder 21 | const result3 = await directoryExists(path.join(__dirname, 'fixtures')); 22 | t.is(result3, true); 23 | }); 24 | -------------------------------------------------------------------------------- /src/presets/__tests__/copyFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:28:03 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import options from './helpers/options'; 11 | import createCopyFiles from '../copyFiles'; 12 | import fileExists from '../../file/fileExists'; 13 | 14 | test.afterEach.always('clear project folder', async() => { 15 | await fse.remove(options.project.folder); 16 | }); 17 | 18 | test('should copy files', async(t) => { 19 | const files = ['package.json']; 20 | const copyFiles = createCopyFiles(options); 21 | await copyFiles(files); 22 | const results = await Promise.all(files.map((filename) => { 23 | return fileExists(path.join(options.project.folder, filename)); 24 | })); 25 | t.true(results.every((value) => { 26 | return value; 27 | })); 28 | }); 29 | -------------------------------------------------------------------------------- /src/presets/__tests__/writeJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 13:30:35 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import createWriteJson from '../writeJson'; 11 | import options from './helpers/options'; 12 | 13 | test.afterEach.always('clear project folder', async() => { 14 | await fse.remove(options.project.folder); 15 | }); 16 | 17 | test('should write a new `package.json` file with appropriate format', async(t) => { 18 | const json = { 19 | newAttribute: 'newValue', 20 | version: '1.0.0', 21 | }; 22 | const writeJson = createWriteJson(options); 23 | const filename = 'package.json'; 24 | await writeJson(filename, json); 25 | const newJSONText = await fse.readFile(path.join(options.project.folder, filename), 'utf8'); 26 | t.is(newJSONText, JSON.stringify(json, null, 2)); 27 | }); 28 | -------------------------------------------------------------------------------- /src/presets/__tests__/updateFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:36:41 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import options from './helpers/options'; 11 | import createUpdateFile from '../updateFile'; 12 | 13 | test.afterEach.always('clear project folder', async() => { 14 | await fse.remove(options.project.folder); 15 | }); 16 | 17 | test('should update file', async(t) => { 18 | const file = 'package.json'; 19 | const addedContent = ' // new content'; 20 | const updateFile = createUpdateFile(options); 21 | await updateFile(file, (content) => { 22 | return content + addedContent; 23 | }); 24 | const content = await fse.readFile(path.join(options.scaffold.folder, file), 'utf8'); 25 | const newContent = await fse.readFile(path.join(options.project.folder, file), 'utf8'); 26 | t.is(newContent, content + addedContent); 27 | }); 28 | -------------------------------------------------------------------------------- /src/presets/__tests__/removeFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:32:17 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import options from './helpers/options'; 11 | import createCopyFiles from '../copyFiles'; 12 | import createRemoveFiles from '../removeFiles'; 13 | import fileExists from '../../file/fileExists'; 14 | 15 | test.afterEach.always('clear project folder', async() => { 16 | await fse.remove(options.project.folder); 17 | }); 18 | 19 | test('should remove files', async(t) => { 20 | const files = ['package.json']; 21 | const copyFiles = createCopyFiles(options); 22 | const removeFiles = createRemoveFiles(options); 23 | await copyFiles(files); 24 | await removeFiles(files); 25 | const results = await Promise.all(files.map((filename) => { 26 | return fileExists(path.join(options.project.folder, filename)); 27 | })); 28 | t.false(results.every((value) => { 29 | return value; 30 | })); 31 | }); 32 | -------------------------------------------------------------------------------- /src/commands/config-commands/add.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180302 12:35 3 | * @author vivaxy 4 | */ 5 | import * as logger from '../../lib/logger'; 6 | import { editConfig } from '../../lib/configManager'; 7 | 8 | export const command = 'add [name] [repo]'; 9 | export const describe = 'Add a config'; 10 | export const builder = { 11 | name: { 12 | demandOption: true, 13 | describe: 'Scaffold name', 14 | type: 'string', 15 | }, 16 | repo: { 17 | demandOption: true, 18 | describe: 'Repository URL', 19 | type: 'string', 20 | }, 21 | }; 22 | export const handler = async({ name: scaffoldName, repo }) => { 23 | if (repo) { 24 | await editConfig((userConfig) => { 25 | const { stat = 0 } = userConfig.scaffold[scaffoldName] || {}; 26 | const newConfig = Object.assign({}, userConfig); 27 | newConfig.scaffold[scaffoldName] = { repo, stat }; 28 | return newConfig; 29 | }); 30 | logger.success(`Scaffold \`${scaffoldName}\` at \`${repo}\` added.`); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 vivaxy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/file/__tests__/isDirectroyEmpty.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import test from 'ava'; 3 | import fse from 'fs-extra'; 4 | 5 | import isDirectoryEmpty from '../isDirectoryEmpty'; 6 | 7 | test('should tell if a directory is empty or not', async(t) => { 8 | // not empty 9 | const result1 = await isDirectoryEmpty(path.join(__dirname, 'fixtures')); 10 | t.is(result1, false); 11 | 12 | // not empty 13 | const result2 = await isDirectoryEmpty(path.join(__dirname, 'fixtures'), ['empty-dir']); 14 | t.is(result2, false); 15 | 16 | // is empty 17 | const result3 = await isDirectoryEmpty(path.join(__dirname, 'fixtures'), ['.gitkeep']); 18 | t.is(result3, true); 19 | 20 | // is empty 21 | const testDirectory4 = path.join(__dirname, 'fixtures', 'empty-dir'); 22 | await fse.ensureDir(testDirectory4); 23 | const result4 = await isDirectoryEmpty(testDirectory4); 24 | t.is(result4, true); 25 | await fse.remove(testDirectory4); 26 | 27 | // is not a dir 28 | t.throws(isDirectoryEmpty(path.join(__dirname, 'fixtures', '.gitkeep'))); 29 | t.throws(isDirectoryEmpty(path.join(__dirname, 'fixtures', 'empty-dir'))); 30 | }); 31 | -------------------------------------------------------------------------------- /src/presets/__tests__/updateFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 17:36:41 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import options from './helpers/options'; 11 | import createUpdateFiles from '../updateFiles'; 12 | 13 | test.afterEach.always('clear project folder', async() => { 14 | await fse.remove(options.project.folder); 15 | }); 16 | 17 | test('should update files', async(t) => { 18 | const files = ['package.json']; 19 | const addedContent = ' // new content'; 20 | const updateFiles = createUpdateFiles(options); 21 | await updateFiles(files, (content) => { 22 | return content + addedContent; 23 | }); 24 | const contents = await Promise.all(files.map((file) => { 25 | return fse.readFile(path.join(options.scaffold.folder, file), 'utf8'); 26 | })); 27 | const newContents = await Promise.all(files.map((file) => { 28 | return fse.readFile(path.join(options.project.folder, file), 'utf8'); 29 | })); 30 | t.true(newContents.every((newContent, index) => { 31 | return newContent === contents[index] + addedContent; 32 | })); 33 | }); 34 | -------------------------------------------------------------------------------- /src/lib/configManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-17 11:36 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import fse from 'fs-extra'; 8 | 9 | import fileExists from '../file/fileExists'; 10 | import { GT_HOME, CONFIG_FILE_NAME } from '../config'; 11 | 12 | const userConfigFile = path.join(GT_HOME, CONFIG_FILE_NAME); 13 | 14 | export const read = () => { 15 | return require(userConfigFile); 16 | }; 17 | 18 | export const write = async(json) => { 19 | return await fse.outputJson(userConfigFile, json, { spaces: 2 }); 20 | }; 21 | 22 | export const exist = async() => { 23 | return await fileExists(userConfigFile); 24 | }; 25 | 26 | export const readScaffoldListByStatOrder = () => { 27 | const userConfig = read(); 28 | const scaffoldConfig = userConfig.scaffold; 29 | const scaffoldNameList = Object.keys(scaffoldConfig); 30 | 31 | scaffoldNameList.sort((prev, next) => { 32 | return scaffoldConfig[next].stat - scaffoldConfig[prev].stat; 33 | }); 34 | return scaffoldNameList; 35 | }; 36 | 37 | export const editConfig = async(filter) => { 38 | const config = read(); 39 | const updatedUserConfig = await filter(config); 40 | await write(updatedUserConfig); 41 | }; 42 | -------------------------------------------------------------------------------- /src/commands/config-commands/remove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 20180302 12:40 3 | * @author vivaxy 4 | */ 5 | import path from 'path'; 6 | import fse from 'fs-extra'; 7 | 8 | import { editConfig } from '../../lib/configManager'; 9 | import * as logger from '../../lib/logger'; 10 | import { GT_HOME } from '../../config'; 11 | 12 | export const command = 'remove [name]'; 13 | export const describe = 'Remove a config'; 14 | export const builder = { 15 | name: { 16 | demandOption: true, 17 | describe: 'Scaffold name', 18 | type: 'string', 19 | }, 20 | }; 21 | export const handler = async({ name: scaffoldName }) => { 22 | let repo = null; 23 | await editConfig(async(userConfig) => { 24 | const newConfig = Object.assign({}, userConfig); 25 | if (!newConfig.scaffold[scaffoldName]) { 26 | logger.info(`Scaffold \`${scaffoldName}\` not exists.`); 27 | return userConfig; 28 | } 29 | repo = newConfig.scaffold[scaffoldName].repo; 30 | delete newConfig.scaffold[scaffoldName]; 31 | await fse.remove(path.join(GT_HOME, scaffoldName)); 32 | logger.success(`Scaffold \`${scaffoldName}\` at \`${repo}\` removed.`); 33 | return newConfig; 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /src/presets/__tests__/updateJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-06-13 11:41:48 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import test from 'ava'; 8 | import fse from 'fs-extra'; 9 | 10 | import createUpdateJson from '../updateJson'; 11 | import options from './helpers/options'; 12 | 13 | test.afterEach.always('clear project folder', async() => { 14 | await fse.remove(options.project.folder); 15 | }); 16 | 17 | test('should update a new `package.json` file with appropriate format', async(t) => { 18 | const newJSONAttributes = { 19 | newAttribute: 'newValue', 20 | version: '1.0.0', 21 | }; 22 | const updateJson = createUpdateJson(options); 23 | const filename = 'package.json'; 24 | const originalJSON = await fse.readJson(path.join(options.scaffold.folder, filename)); 25 | await updateJson(filename, (json) => { 26 | return { 27 | ...json, 28 | ...newJSONAttributes, 29 | }; 30 | }); 31 | const newJSONText = await fse.readFile(path.join(options.project.folder, filename), 'utf8'); 32 | const newJSON = JSON.parse(newJSONText); 33 | t.deepEqual({ 34 | ...originalJSON, 35 | ...newJSONAttributes, 36 | }, newJSON); 37 | t.is(newJSONText, JSON.stringify(newJSON, null, 2)); 38 | }); 39 | -------------------------------------------------------------------------------- /scripts/build/babel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-18 10:56:54 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | 8 | import glob from 'glob-promise'; 9 | import fse from 'fs-extra'; 10 | import { transformFile } from 'babel-core'; 11 | 12 | import { SOURCE_PATH, BUILD_PATH } from '../config'; 13 | 14 | export default async() => { 15 | const sourceFiles = path.join(SOURCE_PATH, '**', '*.js'); 16 | const files = await glob(sourceFiles); 17 | 18 | const transformJobs = files.map((file) => { 19 | return new Promise((resolve, reject) => { 20 | transformFile(file, (err, result) => { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | const relativeFilename = path.relative(SOURCE_PATH, file); 25 | resolve({ 26 | relativeFilename, 27 | ...result, 28 | }); 29 | } 30 | }); 31 | }); 32 | }); 33 | 34 | const results = await Promise.all(transformJobs); 35 | 36 | const writeJobs = results.map(({ relativeFilename, code }) => { 37 | const outputFilename = path.join(BUILD_PATH, relativeFilename); 38 | return fse.outputFile(outputFilename, code); 39 | }); 40 | 41 | return await Promise.all(writeJobs); 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "granturismo", 3 | "version": "1.14.0", 4 | "description": "generator tool", 5 | "bin": { 6 | "gt": "./index.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/vivaxy/granturismo.git" 11 | }, 12 | "keywords": [ 13 | "granturismo", 14 | "gt", 15 | "generator", 16 | "yo" 17 | ], 18 | "author": "vivaxy", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/vivaxy/granturismo/issues" 22 | }, 23 | "homepage": "https://github.com/vivaxy/granturismo#readme", 24 | "dependencies": { 25 | "babel-polyfill": "^6.16.0", 26 | "chalk": "^2.3.1", 27 | "columnify": "^1.5.4", 28 | "execa": "^0.5.0", 29 | "fs-extra": "^3.0.1", 30 | "git-user-name": "^1.2.0", 31 | "inquirer": "^1.2.3", 32 | "listr": "^0.8.0", 33 | "log-symbols": "^2.2.0", 34 | "update-notifier": "^2.1.0", 35 | "yargs": "^11.0.0" 36 | }, 37 | "devDependencies": { 38 | "ava": "^0.18.2", 39 | "babel-core": "^6.24.0", 40 | "babel-preset-es2015": "^6.18.0", 41 | "babel-preset-stage-0": "^6.16.0", 42 | "babel-register": "^6.18.0", 43 | "cross-env": "^3.2.4", 44 | "glob": "^7.1.1", 45 | "glob-promise": "^3.1.0", 46 | "husky": "^0.14.3", 47 | "lint-staged": "^7.0.0", 48 | "nyc": "^10.1.2", 49 | "prettier": "^1.11.1", 50 | "standard-version": "^3.0.0" 51 | }, 52 | "scripts": { 53 | "beta": "npm run test && npm run build && npm publish --tag beta", 54 | "release": "npm run test && npm run build && standard-version && git push --follow-tags && npm publish --registry=https://registry.npmjs.org/", 55 | "test": "cross-env NODE_ENV=test nyc ava && nyc report --reporter=html && nyc report --reporter=lcov > coverage.lcov", 56 | "build": "cross-env NODE_ENV=production node ./scripts/build.js", 57 | "precommit": "lint-staged" 58 | }, 59 | "ava": { 60 | "files": [ 61 | "src/**/__tests__/**/*.js" 62 | ], 63 | "source": [ 64 | "src/**/*.js", 65 | "!src/**/__tests__/**/*.js" 66 | ], 67 | "require": [ 68 | "babel-polyfill", 69 | "babel-register" 70 | ], 71 | "babel": "inherit", 72 | "concurrency": 1, 73 | "verbose": true 74 | }, 75 | "lint-staged": { 76 | "**/**.{js,json,pcss,md}": [ 77 | "prettier --write", 78 | "git add" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Granturismo 2 | 3 | ![GT](./assets/images/gt.png) 4 | 5 | [![Build Status][travis-image]][travis-url] 6 | [![NPM Version][npm-version-image]][npm-url] 7 | [![NPM Downloads][npm-downloads-image]][npm-url] 8 | [![MIT License][license-image]][license-url] 9 | [![Conventional Commits][conventional-commits-image]][conventional-commits-url] 10 | [![Codecov][codecov-image]][codecov-url] 11 | 12 | Generator Tool. 13 | 14 | Workflow tool for scaffolding projects. 15 | 16 | The streaming scaffold system. 17 | 18 | It is easy to learn and easy to use, more efficient. 19 | 20 | If you want to use a scaffold, the scaffold should be adapted to gt, but it is much more simpler than [yeoman](http://yeoman.io/). 21 | 22 | ## Scaffolds 23 | 24 | - [vivaxy/gt-react-scaffold](https://github.com/vivaxy/gt-react-scaffold) webpack, babel, react, redux, router... 25 | - [vivaxy/gt-front-end-scaffold](https://github.com/vivaxy/gt-front-end-scaffold) webpack, babel, eslint... 26 | - [vivaxy/gt-node-server](https://github.com/vivaxy/gt-node-server) nodejs server 27 | - [vivaxy/gt-npm-package](https://github.com/vivaxy/gt-npm-package) npm package 28 | 29 | ## Installation 30 | 31 | Make sure your git version >= 2.7.0 32 | 33 | Make sure you have installed nodejs 34 | 35 | `npm i -g granturismo` 36 | 37 | ## Usage 38 | 39 | `gt` 40 | 41 | `gt help` 42 | 43 | `gt init` 44 | 45 | `gt config list` 46 | 47 | `gt config add scaffold-name git-repo` 48 | 49 | `gt config remove scaffold-name` 50 | 51 | ## How to Scaffold Using GT? 52 | 53 | Implement `scripts/gt.js`, adding project info into user config. 54 | 55 | If `scripts/gt.js`, all files will be copied by default. 56 | 57 | See [Scaffolds](#scaffolds) for examples. 58 | 59 | ### `gt.js` 60 | 61 | If you want to use es6 in `gt.js`, please use `babel-register` or babel-built js. 62 | 63 | ```js 64 | // using `babel-register` 65 | if (!global._babelPolyfill) { 66 | require('babel-polyfill'); 67 | } 68 | require('babel-register'); 69 | module.exports = require('./gt/index'); 70 | ``` 71 | 72 | GT cli invokes methods in `scaffold/scripts/gt.js`, and passing options into `init`. 73 | 74 | ```js 75 | // gt.js 76 | /** 77 | * `ask` will be invoked first 78 | * prompt questions 79 | * `config` returned will be passed into `init` and `after` by `options.config` 80 | */, 81 | export const ask = async(options) => { 82 | return config; 83 | }; 84 | export const init = async(options) => { 85 | 86 | }; 87 | export const after = async(options) => { 88 | 89 | }; 90 | ``` 91 | 92 | ```js 93 | // options 94 | { 95 | project: { 96 | folder: '/absolute/path/to/project/folder', 97 | name: 'project-name', // same as project folder name 98 | git: { 99 | repositoryURL: 'git://git-url', // mainly used for package.json repository.url 100 | username: 'vivaxy', // git configured username 101 | }, 102 | }, 103 | scaffold: { 104 | folder: '/absolute/path/to/scaffold/folder', // mostly ~/.gt/scaffold-name 105 | name: 'scaffold-name', 106 | git: { 107 | headHash: '23c5742ac306e561554d1cfa56b1618d30d16157', 108 | }, 109 | }, 110 | presets: { 111 | copyFiles: async() => {}, 112 | writeFile: async() => {}, 113 | updateFile: async() => {}, 114 | writeJson: async() => {}, 115 | updateJson: async() => {}, 116 | removeFiles: async() => {}, 117 | addScaffoldInfo: async() => {}, 118 | }, 119 | } 120 | ``` 121 | 122 | ```js 123 | /** 124 | * listr context 125 | * do not modify existing attributes 126 | * if you want to passing variables in listr context, add a new attribute 127 | */ 128 | { 129 | selectedScaffoldName, 130 | selectedScaffoldRepo, 131 | selectedScaffoldFolder, 132 | projectGT: {}, // js object required from `./scripts/gt.js` 133 | GTInfo: {}, // options 134 | } 135 | ``` 136 | 137 | #### Presets 138 | 139 | ##### `copyFiles(fileList)` 140 | 141 | - `fileList Array[String]` is an array containing filename your want to copy. 142 | 143 | eg. 144 | 145 | ``` 146 | const copyFiles = async() => { 147 | const { presets } = options; 148 | 149 | const files = [ 150 | `docs`, 151 | `mock-server`, 152 | `source`, 153 | `.babelrc`, 154 | `.editorconfig`, 155 | `.gitignore`, 156 | `LICENSE`, 157 | `webpack.config.js`, 158 | ]; 159 | 160 | await presets.copyFiles(files); 161 | }; 162 | ``` 163 | 164 | ##### `writeFile(file, content)` 165 | 166 | - `file {String}` 167 | - `content {String}` 168 | 169 | Write string into file under project folder. 170 | 171 | ##### `updateFile(file, filter)` 172 | 173 | - `file {String}` 174 | - `filter {Function} filter(input) => output` 175 | - `input {String}` 176 | - `output {String}` 177 | 178 | Read file from scaffold, passing into `filter`, write filter result into file under project folder. 179 | 180 | ##### `updateFiles(files, filter)` 181 | 182 | - `files {Array[String]}` 183 | - `filter {Function} filter(input) => output` 184 | - `input {String}` 185 | - `output {String}` 186 | 187 | Read file from scaffold, passing into `filter`, write filter result into file under project folder. 188 | 189 | ##### `writeJson(file, json)` 190 | 191 | - `file {String}` 192 | - `json {Object}` 193 | 194 | Same as `writeFile`, but passing json object into second parameter. 195 | 196 | ##### `updateJson(file, filter)` 197 | 198 | - `file {String}` 199 | - `filter {Function} filter(input) => output` 200 | - `input {Object}` 201 | - `output {Object}` 202 | 203 | Same as `updateFile`, but passing json object into `filter`. 204 | 205 | ##### `removeFiles(fileList)` 206 | 207 | - `fileList Array[String]` is an array containing filename your want to copy. 208 | 209 | Same as `copyFiles`, but remove files in project folder. 210 | 211 | ##### `addScaffoldInfo({ scaffoldCommitHash, scaffoldVersion })` 212 | 213 | - `scaffoldCommitHash {String}` default: `'scaffoldCommitHash'` 214 | - `scaffoldVersion {String}` default: `'scaffoldVersion'` 215 | 216 | Update the project `package.json` file, add `scaffoldCommitHash` and `scaffoldVersion`. 217 | Use `scaffoldCommitHash` and `scaffoldVersion` as `package.json` key. 218 | If `scaffoldCommitHash` or `scaffoldVersion` is falsy`, it will not add this key. 219 | 220 | ### How to test a scaffold project? 221 | 222 | - Checkout a new branch, update your `gt.js`. 223 | - Use `gt config add test-scaffold-name git-repo#new-branch-name` to set a test registry. 224 | - `gt init` and select `test-scaffold-name` to run `gt.js` in your new branch to test. 225 | 226 | ## Change Log 227 | 228 | [Change Log](CHANGELOG.md) 229 | 230 | ## Contributing 231 | 232 | [Contributing](CONTRIBUTING.md) 233 | 234 | ## Prior Art 235 | 236 | - [yeoman](http://yeoman.io/) 237 | - [node-scaffold-generator](https://github.com/kaelzhang/node-scaffold-generator) 238 | - [generate](https://github.com/generate/generate) 239 | 240 | [travis-image]: https://img.shields.io/travis/vivaxy/granturismo.svg?style=flat-square 241 | [travis-url]: https://travis-ci.org/vivaxy/granturismo 242 | [npm-version-image]: http://img.shields.io/npm/v/granturismo.svg?style=flat-square 243 | [npm-url]: https://www.npmjs.com/package/granturismo 244 | [npm-downloads-image]: https://img.shields.io/npm/dt/granturismo.svg?style=flat-square 245 | [license-image]: https://img.shields.io/npm/l/granturismo.svg?style=flat-square 246 | [license-url]: LICENSE 247 | [conventional-commits-image]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=flat-square 248 | [conventional-commits-url]: https://conventionalcommits.org 249 | [codecov-image]: https://img.shields.io/codecov/c/github/vivaxy/granturismo.svg?style=flat-square 250 | [codecov-url]: https://codecov.io/gh/vivaxy/granturismo 251 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [1.14.0](https://github.com/vivaxy/granturismo/compare/v1.13.4...v1.14.0) (2018-03-02) 7 | 8 | 9 | ### Features 10 | 11 | * **help:** :sparkles:Improve help display ([a2dd809](https://github.com/vivaxy/granturismo/commit/a2dd809)) 12 | 13 | 14 | 15 | 16 | ## [1.13.4](https://github.com/vivaxy/granturismo/compare/v1.13.3...v1.13.4) (2018-01-14) 17 | 18 | 19 | 20 | 21 | ## [1.13.3](https://github.com/vivaxy/granturismo/compare/v1.13.2...v1.13.3) (2018-01-14) 22 | 23 | 24 | 25 | 26 | ## [1.13.2](https://github.com/vivaxy/granturismo/compare/v1.13.1...v1.13.2) (2018-01-14) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **init:** :bug:Fix prompt for installation of `yarn` ([b283fb7](https://github.com/vivaxy/granturismo/commit/b283fb7)) 32 | 33 | 34 | 35 | 36 | ## [1.13.1](https://github.com/vivaxy/granturismo/compare/v1.13.1-1...v1.13.1) (2018-01-14) 37 | 38 | 39 | 40 | 41 | # [1.13.0](https://github.com/vivaxy/granturismo/compare/v1.12.2...v1.13.0) (2018-01-09) 42 | 43 | 44 | ### Features 45 | 46 | * **init:** :sparkles:Add `addScaffoldInfo` preset ([4c9f2b0](https://github.com/vivaxy/granturismo/commit/4c9f2b0)) 47 | 48 | 49 | 50 | 51 | ## [1.12.2](https://github.com/vivaxy/granturismo/compare/v1.12.1...v1.12.2) (2018-01-09) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **init:** :bug:Fix commit hash ([9e56bae](https://github.com/vivaxy/granturismo/commit/9e56bae)) 57 | 58 | 59 | 60 | 61 | ## [1.12.1](https://github.com/vivaxy/granturismo/compare/v1.12.0...v1.12.1) (2018-01-09) 62 | 63 | 64 | 65 | 66 | # [1.12.0](https://github.com/vivaxy/granturismo/compare/v1.11.1...v1.12.0) (2018-01-09) 67 | 68 | 69 | ### Features 70 | 71 | * **scaffold:** :sparkles:Add scaffold project head commit hash ([07512c5](https://github.com/vivaxy/granturismo/commit/07512c5)) 72 | 73 | 74 | 75 | 76 | ## [1.11.1](https://github.com/vivaxy/granturismo/compare/v1.11.1-t01...v1.11.1) (2018-01-03) 77 | 78 | 79 | 80 | 81 | # [1.11.0](https://github.com/vivaxy/granturismo/compare/v1.10.0...v1.11.0) (2017-08-25) 82 | 83 | 84 | ### Features 85 | 86 | * **init:** :sparkles:Prompt for confirm if directory is not empty. ([771a61e](https://github.com/vivaxy/granturismo/commit/771a61e)) 87 | 88 | 89 | 90 | 91 | # [1.10.0](https://github.com/vivaxy/granturismo/compare/v1.9.1...v1.10.0) (2017-08-25) 92 | 93 | 94 | ### Features 95 | 96 | * **git:** :sparkles:Add git username ([10d7a97](https://github.com/vivaxy/granturismo/commit/10d7a97)) 97 | 98 | 99 | 100 | 101 | ## [1.9.1](https://github.com/vivaxy/granturismo/compare/v1.9.0...v1.9.1) (2017-06-13) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **presets:** :bug:Fix `updateJson` and `writeJson` with no json format ([6cfc6ec](https://github.com/vivaxy/granturismo/commit/6cfc6ec)) 107 | 108 | 109 | 110 | 111 | # [1.9.0](https://github.com/vivaxy/granturismo/compare/v1.8.0...v1.9.0) (2017-06-13) 112 | 113 | 114 | ### Features 115 | 116 | * **bin:** :sparkles:Show help when command not met ([78b0a1c](https://github.com/vivaxy/granturismo/commit/78b0a1c)) 117 | 118 | 119 | 120 | 121 | # [1.8.0](https://github.com/vivaxy/granturismo/compare/v1.7.1...v1.8.0) (2017-06-12) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * :bug:fix eslint error ([b66c9ed](https://github.com/vivaxy/granturismo/commit/b66c9ed)) 127 | 128 | 129 | ### Features 130 | 131 | * :sparkles:add feature only copying project files when cannot find gt.js in scaffold project ([a59bc99](https://github.com/vivaxy/granturismo/commit/a59bc99)) 132 | 133 | 134 | 135 | 136 | ## [1.7.1](https://github.com/vivaxy/granturismo/compare/v1.7.0...v1.7.1) (2017-05-27) 137 | 138 | 139 | 140 | 141 | # [1.7.0](https://github.com/vivaxy/granturismo/compare/v1.6.2...v1.7.0) (2017-05-09) 142 | 143 | 144 | ### Features 145 | 146 | * **log:** :sparkles:Add console.log for listr errors ([fe756da](https://github.com/vivaxy/granturismo/commit/fe756da)) 147 | 148 | 149 | 150 | 151 | ## [1.6.2](https://github.com/vivaxy/granturismo/compare/v1.6.1...v1.6.2) (2017-03-21) 152 | 153 | 154 | 155 | 156 | ## [1.6.1](https://github.com/vivaxy/granturismo/compare/v1.6.0-0...v1.6.1) (2017-03-18) 157 | 158 | 159 | 160 | 161 | # [1.6.0](https://github.com/vivaxy/granturismo/compare/v1.6.0-0...v1.6.0) (2017-03-18) 162 | 163 | 164 | 165 | 166 | # [1.6.0](https://github.com/vivaxy/granturismo/compare/v1.5.1...v1.6.0) (2017-03-17) 167 | 168 | 169 | ### Features 170 | 171 | * **update-notifier:** :sparkles:Add update-notifier ([1319652](https://github.com/vivaxy/granturismo/commit/1319652)) 172 | 173 | 174 | 175 | 176 | ## [1.5.1](https://github.com/vivaxy/granturismo/compare/v1.5.0...v1.5.1) (2017-03-10) 177 | 178 | 179 | ### Bug Fixes 180 | 181 | * **presets:** :bug:Export updateFiles ([6024ca5](https://github.com/vivaxy/granturismo/commit/6024ca5)) 182 | 183 | 184 | 185 | 186 | # [1.5.0](https://github.com/vivaxy/granturismo/compare/v1.4.2...v1.5.0) (2017-03-10) 187 | 188 | 189 | ### Features 190 | 191 | * **presets:** :sparkles:updateFiles ([47e4dd4](https://github.com/vivaxy/granturismo/commit/47e4dd4)) 192 | 193 | 194 | 195 | 196 | ## [1.4.2](https://github.com/vivaxy/granturismo/compare/v1.4.1...v1.4.2) (2017-02-20) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * **git:** :bug:Fix git pull command path ([4013c55](https://github.com/vivaxy/granturismo/commit/4013c55)) 202 | 203 | 204 | 205 | 206 | ## [1.4.1](https://github.com/vivaxy/granturismo/compare/v1.4.0...v1.4.1) (2017-02-20) 207 | 208 | 209 | ### Bug Fixes 210 | 211 | * **checkout:** :bug:Fix git checkout ([49ede56](https://github.com/vivaxy/granturismo/commit/49ede56)) 212 | 213 | 214 | 215 | 216 | # [1.4.0](https://github.com/vivaxy/granturismo/compare/v1.3.0...v1.4.0) (2017-02-20) 217 | 218 | 219 | ### Features 220 | 221 | * **commit-ish:** :sparkles:Support commit-ish from selectedScaffoldRepo ([0b125b6](https://github.com/vivaxy/granturismo/commit/0b125b6)) 222 | 223 | 224 | 225 | 226 | # [1.3.0](https://github.com/vivaxy/granturismo/compare/v1.2.1...v1.3.0) (2017-02-20) 227 | 228 | 229 | ### Features 230 | 231 | * **removeFiles:** :sparkles:Add removeFiles preset ([8a9c1c1](https://github.com/vivaxy/granturismo/commit/8a9c1c1)) 232 | 233 | 234 | 235 | 236 | ## [1.2.1](https://github.com/vivaxy/granturismo/compare/v1.2.0...v1.2.1) (2017-02-20) 237 | 238 | 239 | ### Bug Fixes 240 | 241 | * **init:** :bug:Fix undefined projectGT ([4c77f5a](https://github.com/vivaxy/granturismo/commit/4c77f5a)) 242 | 243 | 244 | 245 | 246 | # [1.2.0](https://github.com/vivaxy/granturismo/compare/v1.1.0...v1.2.0) (2017-02-20) 247 | 248 | 249 | ### Features 250 | 251 | * **ask:** :sparkles:Add ask step ([402f715](https://github.com/vivaxy/granturismo/commit/402f715)) 252 | 253 | 254 | 255 | 256 | # [1.1.0](https://github.com/vivaxy/granturismo/compare/v1.0.5...v1.1.0) (2017-02-20) 257 | 258 | 259 | ### Features 260 | 261 | * **npmInstall:** :sparkles:Using yarn if `yarn.lock` exists within scaffold ([4932250](https://github.com/vivaxy/granturismo/commit/4932250)) 262 | 263 | 264 | 265 | 266 | ## [1.0.5](https://github.com/vivaxy/granturismo/compare/v1.0.4...v1.0.5) (2017-02-05) 267 | 268 | 269 | ### Bug Fixes 270 | 271 | * **config:** :bug:Update config.json ([b2b00cf](https://github.com/vivaxy/granturismo/commit/b2b00cf)) 272 | * **configManager:** :bug:Fix scaffold order by stat ([3144d2b](https://github.com/vivaxy/granturismo/commit/3144d2b)) 273 | 274 | 275 | 276 | 277 | ## [1.0.4](https://github.com/vivaxy/granturismo/compare/v1.0.3...v1.0.4) (2017-01-16) 278 | 279 | 280 | ### Bug Fixes 281 | 282 | * **ensure config:** :bug:Fix `~/.gt/config.json` not exists, an error thrown ([551d47b](https://github.com/vivaxy/granturismo/commit/551d47b)) 283 | 284 | 285 | 286 | 287 | ## [1.0.3](https://github.com/vivaxy/granturismo/compare/v1.0.2...v1.0.3) (2016-12-21) 288 | 289 | 290 | ### Bug Fixes 291 | 292 | * **package.json:** :bug:fix missing dependece `minimatch` ([d86434c](https://github.com/vivaxy/granturismo/commit/d86434c)) 293 | 294 | 295 | 296 | 297 | ## [1.0.2](https://github.com/vivaxy/granturismo/compare/v1.0.1...v1.0.2) (2016-11-29) 298 | 299 | 300 | 301 | # 1.0.1 302 | 303 | several improvements 304 | 305 | - added `gt config list` 306 | - removed scaffold folder when removing config from gt 307 | - silent output when getting git repo url 308 | 309 | # 1.0.0 310 | 311 | new command 312 | 313 | - added config command 314 | 315 | # 0.0.0 316 | 317 | first version 318 | -------------------------------------------------------------------------------- /src/commands/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 10:20 3 | * @author vivaxy 4 | */ 5 | 6 | import path from 'path'; 7 | import execa from 'execa'; 8 | import Listr from 'listr'; 9 | import fse from 'fs-extra'; 10 | import inquirer from 'inquirer'; 11 | import gitUsername from 'git-user-name'; 12 | 13 | import * as logger from '../lib/logger'; 14 | import fileExists from '../file/fileExists'; 15 | import directoryExists from '../file/directoryExists'; 16 | import isDirectoryEmpty from '../file/isDirectoryEmpty'; 17 | import * as configManager from '../lib/configManager'; 18 | import updateScaffoldStat from '../lib/updateScaffoldStat'; 19 | import checkGitRepository from '../git/checkGitRepository'; 20 | import checkGitClean from '../git/getClean'; 21 | import getGitRemoteURL from '../git/getRemoteURL'; 22 | import getCommitHash from '../git/getCommitHash'; 23 | import { GT_HOME, MODULES_FOLDER, GIT_FOLDER, PROJECT_GT_FILE } from '../config'; 24 | 25 | import getCopyFiles from '../presets/copyFiles'; 26 | import getWriteFile from '../presets/writeFile'; 27 | import getUpdateFile from '../presets/updateFile'; 28 | import getUpdateFiles from '../presets/updateFiles'; 29 | import getWriteJson from '../presets/writeJson'; 30 | import getUpdateJson from '../presets/updateJson'; 31 | import getRemoveFiles from '../presets/removeFiles'; 32 | import getAddScaffoldInfo from '../presets/addScaffoldInfo'; 33 | 34 | const cwd = process.cwd(); 35 | 36 | const gitCloneTask = async({ selectedScaffoldRepo, selectedScaffoldFolder }) => { 37 | const [repoURL, commitIsh] = selectedScaffoldRepo.split('#'); 38 | const clone = await execa('git', ['clone', repoURL, selectedScaffoldFolder]); 39 | if (clone.code !== 0) { 40 | throw new Error(`clone error: 41 | selectedScaffoldRepo: ${selectedScaffoldRepo} 42 | repoURL: ${repoURL} 43 | ${clone.stderr}`); 44 | } 45 | process.chdir(selectedScaffoldFolder); 46 | if (commitIsh) { 47 | await execa('git', ['checkout', commitIsh]); 48 | if (clone.code !== 0) { 49 | throw new Error(`checkout error: 50 | selectedScaffoldRepo: ${selectedScaffoldRepo} 51 | commitIsh: ${commitIsh} 52 | ${clone.stderr}`); 53 | } 54 | } 55 | process.chdir(cwd); 56 | }; 57 | 58 | const gitPullTask = async({ selectedScaffoldFolder }) => { 59 | process.chdir(selectedScaffoldFolder); 60 | const gitClean = await checkGitClean(); 61 | if (!gitClean) { 62 | await execa('git', ['checkout', '.']); 63 | } 64 | await execa('git', ['pull']); 65 | process.chdir(cwd); 66 | }; 67 | 68 | const npmInstallTask = async({ selectedScaffoldFolder }) => { 69 | process.chdir(selectedScaffoldFolder); 70 | const yarnLockExists = await fileExists(path.join(selectedScaffoldFolder, 'yarn.lock')); 71 | if (yarnLockExists) { 72 | await execa('yarn', ['install']); 73 | } else { 74 | await execa('npm', ['install']); 75 | } 76 | process.chdir(cwd); 77 | }; 78 | 79 | const prepareForCopyProjectFiles = async(ctx) => { 80 | const { selectedScaffoldFolder } = ctx; 81 | const copyInfo = { 82 | project: { folder: cwd }, 83 | scaffold: { folder: selectedScaffoldFolder }, 84 | }; 85 | const copyFiles = getCopyFiles(copyInfo); 86 | const files = await fse.readdir(selectedScaffoldFolder); 87 | const excludeGitFiles = files.filter((file) => { 88 | return file !== GIT_FOLDER && file !== MODULES_FOLDER; 89 | }); 90 | copyFiles(excludeGitFiles); 91 | }; 92 | 93 | const prepareForScaffoldGT = async(ctx) => { 94 | const { selectedScaffoldName, selectedScaffoldFolder, projectGTFilePath } = ctx; 95 | 96 | const projectGT = require(projectGTFilePath); 97 | let projectGit = null; 98 | 99 | const isGitRepository = await checkGitRepository(); 100 | if (isGitRepository) { 101 | const repositoryURL = await getGitRemoteURL(); 102 | projectGit = { repositoryURL, username: gitUsername() }; 103 | } 104 | 105 | process.chdir(selectedScaffoldFolder); 106 | const headHash = await getCommitHash(); 107 | process.chdir(cwd); 108 | 109 | const GTInfo = { 110 | project: { folder: cwd, name: cwd.split(path.sep).pop(), git: projectGit }, 111 | scaffold: { folder: selectedScaffoldFolder, name: selectedScaffoldName, git: { headHash } }, 112 | }; 113 | 114 | GTInfo.presets = { 115 | copyFiles: getCopyFiles(GTInfo), 116 | writeFile: getWriteFile(GTInfo), 117 | updateFile: getUpdateFile(GTInfo), 118 | writeJson: getWriteJson(GTInfo), 119 | updateJson: getUpdateJson(GTInfo), 120 | removeFiles: getRemoveFiles(GTInfo), 121 | updateFiles: getUpdateFiles(GTInfo), 122 | addScaffoldInfo: getAddScaffoldInfo(GTInfo), 123 | }; 124 | 125 | ctx.projectGT = projectGT; 126 | ctx.GTInfo = GTInfo; 127 | }; 128 | 129 | const runScaffoldGT = async({ projectGT, GTInfo }) => { 130 | return await projectGT.init(GTInfo); 131 | }; 132 | 133 | const updateStat = async({ selectedScaffoldName }) => { 134 | await updateScaffoldStat(selectedScaffoldName); 135 | }; 136 | 137 | const getFriendlyInformation = (ex) => { 138 | if (ex.message === 'spawn yarn ENOENT') { 139 | return `Please install \`yarn\`. 140 | See [docs](https://yarnpkg.com/en/docs/install) for details.`; 141 | } 142 | return 'Please file an issue on [Github](https://github.com/vivaxy/granturismo/issues) with error details.'; 143 | }; 144 | 145 | let projectGTFileExists = false; 146 | 147 | export const command = 'init'; 148 | export const describe = 'Choose a scaffold to init your new project'; 149 | export const builder = {}; 150 | export const handler = async() => { 151 | // check dir empty? 152 | const isCurrentDirectoryEmpty = await isDirectoryEmpty(cwd, ['.git']); 153 | if (!isCurrentDirectoryEmpty) { 154 | const continueResult = await inquirer.prompt([ 155 | { 156 | type: 'confirm', 157 | name: 'continue', 158 | message: 'Current directory is not empty, files will be overridden. Continue?', 159 | default: true, 160 | }, 161 | ]); 162 | if (!continueResult.continue) { 163 | return; 164 | } 165 | } 166 | 167 | const userConfig = configManager.read(); 168 | const scaffoldConfig = userConfig.scaffold; 169 | const scaffoldNameList = configManager.readScaffoldListByStatOrder(); 170 | 171 | const answer = await inquirer.prompt([ 172 | { 173 | type: 'list', 174 | name: 'scaffold', 175 | message: 'Choose a scaffold...', 176 | choices: scaffoldNameList, 177 | }, 178 | ]); 179 | 180 | const selectedScaffoldName = answer.scaffold; 181 | const selectedScaffoldRepo = scaffoldConfig[selectedScaffoldName].repo; 182 | const selectedScaffoldFolder = path.join(GT_HOME, selectedScaffoldName); 183 | 184 | const projectGTFilePath = path.join(GT_HOME, selectedScaffoldName, PROJECT_GT_FILE); 185 | 186 | const gitTasks = [ 187 | { 188 | title: 'Clone scaffold.', 189 | task: gitCloneTask, 190 | skip: async() => { 191 | const selectedScaffoldFolderExists = await directoryExists(selectedScaffoldFolder); 192 | if (selectedScaffoldFolderExists) { 193 | return 'scaffold exists'; 194 | } 195 | }, 196 | }, 197 | { title: 'Pull scaffold.', task: gitPullTask }, 198 | ]; 199 | 200 | const preTasks = []; 201 | const postTasks = [{ title: 'Finishing.', task: updateStat }]; 202 | 203 | const gitListr = new Listr(gitTasks); 204 | 205 | let listrContext = { selectedScaffoldName, selectedScaffoldRepo, selectedScaffoldFolder, projectGTFilePath }; 206 | 207 | try { 208 | listrContext = await gitListr.run(listrContext); 209 | 210 | projectGTFileExists = await fileExists(projectGTFilePath); 211 | 212 | if (projectGTFileExists) { 213 | preTasks.push( 214 | { title: 'Install scaffold npm packages.', task: npmInstallTask }, 215 | { title: 'Prepare for scaffold GT.', task: prepareForScaffoldGT }, 216 | ); 217 | postTasks.unshift({ title: 'Run scaffold GT.', task: runScaffoldGT }); 218 | } else { 219 | preTasks.push({ title: 'Prepare for copy project files.', task: prepareForCopyProjectFiles }); 220 | } 221 | 222 | const preListr = new Listr(preTasks); 223 | const postListr = new Listr(postTasks); 224 | 225 | listrContext = await preListr.run(listrContext); 226 | if (listrContext.GTInfo) { 227 | listrContext.GTInfo.config = {}; 228 | } 229 | 230 | if (listrContext.projectGT && listrContext.projectGT.ask) { 231 | listrContext.GTInfo.config = await listrContext.projectGT.ask(listrContext.GTInfo); 232 | } 233 | 234 | listrContext = await postListr.run(listrContext); 235 | 236 | if (listrContext.projectGT && listrContext.projectGT.after) { 237 | await listrContext.projectGT.after(listrContext.GTInfo); 238 | } 239 | } catch (ex) { 240 | logger.native(ex); 241 | logger.native(); 242 | logger.error(getFriendlyInformation(ex)); 243 | } 244 | }; 245 | --------------------------------------------------------------------------------