diff --git a/README.md b/README.md index 6459612..b269607 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,17 @@ Gulp based project skeleton with modular tasks. - browser-sync (dev server) - rsync (deploy) +## creating project +``` +yarn global add create-gulpset-skeleton +create-gulpset-skeleton my-app +``` + ## starting development -1. download this repo from [releases](https://github.com/fourdigit/gulpset/releases) -2. `yarn` -3. `yarn start` +``` +yarn start +``` ## want to generate production build(minified) diff --git a/bootstrap/assets/README.md b/bootstrap/assets/README.md new file mode 100644 index 0000000..d003d12 --- /dev/null +++ b/bootstrap/assets/README.md @@ -0,0 +1,59 @@ +This project was bootstrapped with [@fourdigit/gulpset](https://github.com/fourdigit/gulpset). + +## To generate production build(minified) + +1. `yarn build:prod` + +## each tasks + +### js settings + +- see `/webpack.config.js`, `/webpack.config.prod.js` & `/gulpset/tasks/scripts/index.js` + +#### When you use ES5 + +1. `/.eslintrc`: modify `@fourdigit/eslint-config-fourdigit/esnext` => `@fourdigit/eslint-config-fourdigit/base` +2. modify `copy` task (add `js` ext) +3. remove `scripts` task from `gulpfile.js` + +#### development & production + +Gulpset has 2 webpack settings & corresponding 2 gulp tasks. + +- webpack-watch + + - ./webpacck.config.js + - `mode: development` + - `process.env.NODE_ENV === 'development'` + +- webpack + - webpack.config.prod.js + - `mode: production` => webpack 4 automatically optimize your code for production. + - `process.env.NODE_ENV === 'production'` => you can use this environmental variable for environmental settings e.g. API key, endpoints. + +### scss settings + +- see `/gulpset/tasks/scss/index.js` +- some utility scss libs are loaded on `/src/assets/css/app.scss` + +### ejs settings + +- see `/gulpset/tasks/ejs/index.js` +- some utility functions are included here `/src/_utils.ejs` + +### deployrsync settings + +1. Open `/gulpset/tasks/deployrsync/index.js` +2. Set target user:hostname to `gulpset.confs.deployrsync.options.hostname` +3. Add private key of target server. e.g. `ssh-add ~/.ssh/xxxxxxxx_rsa` +4. Run `gulp deployrsync` + +#### Deploy via bitbucket-pipelines + +1. Create private key for target server. +2. Encode it into base 64. e.g `$ base64 gulpset_rsa| pbcopy` +3. On bitbucket web screen, enter repository settings +4. Go Pipelines settings > Enable Pipelines +5. Environment variables > make and set `PRIVATE_KEY` (make sure the to enable checkbox of "encrypt") and `TARGET_HOST` +6. Built files by newly pushed `develop` branch will be deployed to `TARGET_HOST` +7. if you want to use other branches, rewrite `branches` section on `bitbucket-pipelines.yml` diff --git a/bootstrap/index.js b/bootstrap/index.js new file mode 100755 index 0000000..0f796e7 --- /dev/null +++ b/bootstrap/index.js @@ -0,0 +1,219 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ +const commander = require('commander'); +const crossSpawn = require('cross-spawn'); +const path = require('path'); +const colors = require('ansi-colors'); +const fs = require('fs-extra'); +const os = require('os'); +const validateProjectName = require('validate-npm-package-name'); + +const cwd = process.cwd(); +var newPrjRootPath; // Root path of the new project +const pkgRootPath = path.resolve(__dirname, '..'); +let projectName; + +const packageJson = require(path.join(pkgRootPath, 'package.json')); + +// Parse command +const program = new commander.Command(packageJson.name) + .version(packageJson.version) + .arguments('') + .usage(`${colors.green('')}`) + .action(name => { + projectName = name; + }) + .parse(process.argv); + +const handleExit = () => { + console.log('Exiting without error.'); + process.exit(); +}; + +const handleError = e => { + console.error(colors.red('ERROR! An error was encountered while executing')); + console.error(e); + console.log(colors.red('Exiting with error.')); + process.exit(1); +}; + +process.on('SIGINT', handleExit); +process.on('uncaughtException', handleError); + +/** + * Validate command arguments + * + */ +function validateArgs() { + if (typeof projectName === 'undefined') { + console.error('Please specify the project directory:'); + console.log(` ${colors.cyan(program.name())} ${colors.green('')}`); + console.log(); + console.log('For example:'); + console.log(` ${colors.cyan(program.name())} ${colors.green('my-gulpset-project')}`); + console.log(); + process.exit(1); + } +} + +/** + * Print warnings and errors to console + * + * @param {Array} results array of warning and error messages + */ +function printValidationResults(results) { + if (typeof results !== 'undefined') { + results.forEach(error => { + console.error(colors.red(` * ${error}`)); + }); + } +} + +/** + * Validate project name + * Exit with code 1 if name is invalid + * + * @param {string} name name of the project + * @param {Array} dependencies list of dependencies of `create-gulpset-skeleton` + */ +function checkProjectName(name, dependencies) { + // Validate project name against NPM naming restriction + // https://github.com/npm/cli/blob/latest/doc/files/package.json.md#name + const validationResult = validateProjectName(name); + if (!validationResult.validForNewPackages) { + console.error(`Could not create a project called ${colors.red(`"${name}"`)} because of npm naming restrictions:`); + printValidationResults(validationResult.errors); + printValidationResults(validationResult.warnings); + process.exit(1); + } + + // Check if project name conflicts with existing NPM packages + if (dependencies.indexOf(name) >= 0) { + console.error( + colors.red(`We cannot create a project called `) + + colors.green(name) + + colors.red( + ` because a dependency with the same name exists.\n` + + `Due to the way npm works, the following names are not allowed:\n\n` + ) + + colors.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) + + colors.red('\n\nPlease choose a different project name.') + ); + process.exit(1); + } +} + +/** + * Validate the path of the to-be-created-project + * + * @param {string} projectName + */ +function checkProjectPath(projectName) { + const pathOfNewPrj = path.join(cwd, projectName); + if (fs.existsSync(pathOfNewPrj) && fs.readdirSync(pathOfNewPrj).length > 0) { + console.error( + `ERROR! Directory ${projectName} already exist and it's not empty. Please remove it before proceeding.` + ); + process.exit(1); + } +} + +/** + * Generate the `package.json` file for the new project + * + * @param {string} prjName name of the new project + * @returns new package.json object + */ +function generatePackageJson(prjName) { + + let newPkgJson = {}; + const dependencies = [ + '@fourdigit/sanitize-4d.css', + '@fourdigit/scss-utilities', + ]; + + newPkgJson.name = prjName; + newPkgJson.version = '0.1.0'; + newPkgJson.description = ''; + newPkgJson.main = packageJson.main; + newPkgJson.scripts = packageJson.scripts; + newPkgJson.devDependencies = packageJson.devDependencies; + newPkgJson.dependencies = {}; + dependencies.forEach(depName => { + newPkgJson.dependencies[depName] = packageJson.dependencies[depName]; + }); + + return newPkgJson; +} + +/** + * Create new project `name` using `gulpset-skeleton` + * + * @param {string} name + */ +function createApp(name) { + const root = path.resolve(name); + const appName = path.basename(root); + + checkProjectName(appName, [...Object.keys(packageJson.dependencies), ...Object.keys(packageJson.devDependencies)]); + + checkProjectPath(name); + + newPrjRootPath = path.join(cwd, name); + + fs.ensureDirSync(name); + + console.log(`Creating a new gulpset project in ${colors.green(root)}.\n`); + + // Copy core files and templates + const filesToCopy = [ + '.editorconfig', + '.eslintignore', + '.eslintrc', + '.prettierignore', + '.prettierrc.js', + '.stylelintrc.js', + 'aigis_config.yml', + 'bitbucket-pipelines.yml', + 'gulpfile.js', + 'webpack.config.js', + 'webpack.config.prod.js', + ]; + const directoriesToCopy = ['gulpset', 'src']; + const bootstrapAssetsPath = 'bootstrap/assets'; + + for (let i = 0; i < directoriesToCopy.length; i++) { + const srcDirPath = path.join(pkgRootPath, directoriesToCopy[i]); + const dstDirPath = path.join(newPrjRootPath, directoriesToCopy[i]); + fs.copySync(srcDirPath, dstDirPath); + } + + // Copy files to root directory of created project + fs.readdirSync(path.join(pkgRootPath, bootstrapAssetsPath)).forEach(filename => { + const srcDirPath = path.join(pkgRootPath, bootstrapAssetsPath, filename); + const dstDirPath = path.join(newPrjRootPath, filename); + fs.copySync(srcDirPath, dstDirPath); + }); + + for (let i = 0; i < filesToCopy.length; i++) { + const srcFilePath = path.join(pkgRootPath, filesToCopy[i]); + const dstFilePath = path.join(newPrjRootPath, filesToCopy[i]); + fs.copyFileSync(srcFilePath, dstFilePath); + } + + // Create new `package.json` + const newPkgJson = generatePackageJson(name); + fs.writeFileSync( + path.join(newPrjRootPath, 'package.json'), + JSON.stringify(newPkgJson, null, 2) + os.EOL + ); + + crossSpawn.sync('yarn', ['install', '--cwd', newPrjRootPath], { + stdio: 'inherit', + }); + + console.log(`Success! Created ${appName} at ${path.join(root)}`); +} + +validateArgs(); +createApp(projectName); diff --git a/package.json b/package.json index 2baa9e8..cc0f1d3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "gulpset-skeleton", + "name": "create-gulpset-skeleton", "version": "3.1.0", "description": "Gulp based project skeleton with modular tasks.", "main": "gulpfile.js", @@ -58,7 +58,13 @@ }, "dependencies": { "@fourdigit/sanitize-4d.css": "^7.0.4", - "@fourdigit/scss-utilities": "^1.0.0" + "@fourdigit/scss-utilities": "^1.0.0", + "ansi-color": "^0.2.1", + "commander": "^2.19.0", + "cross-spawn": "^6.0.5", + "fs-extra": "^7.0.1", + "minimist": "^1.2.0", + "validate-npm-package-name": "^3.0.0" }, "directories": { "doc": "docs" @@ -69,6 +75,9 @@ "eslint:fix": "yarn eslint --fix", "deployrsync": "gulp deployrsync" }, + "bin": { + "create-gulpset-skeleton": "bootstrap/index.js" + }, "repository": { "type": "git", "url": "git+https://github.com/fourdigitdesign/gulpset.git" diff --git a/yarn.lock b/yarn.lock index 2e76a51..a0982c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -952,6 +952,11 @@ ansi-align@^2.0.0: dependencies: string-width "^2.0.0" +ansi-color@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a" + integrity sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o= + ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -1899,6 +1904,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -2447,6 +2457,11 @@ commander@2.8.x, commander@~2.8.1: dependencies: graceful-readlink ">= 1.0.0" +commander@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -4515,6 +4530,15 @@ fs-extra@^1.0.0: jsonfile "^2.1.0" klaw "^1.0.0" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -6429,6 +6453,13 @@ jsonfile@^3.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -11813,6 +11844,13 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + dependencies: + builtins "^1.0.3" + value-or-function@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"