├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── create-modern-node ├── LICENSE ├── README.md ├── cli.js ├── index.js ├── package.json └── yarn.lock ├── package.json ├── src ├── cli.js ├── config │ └── lint-staged.json ├── hook ├── init.js ├── install.js ├── paths.js ├── precommit.js ├── test.js ├── uninstall.js ├── utils.js └── utils │ └── createJestConfig.js ├── template-typescript ├── .travis.yml ├── LICENSE ├── README.md ├── gitignore ├── src │ └── index.ts └── test │ └── index.test.js ├── template ├── .prettierignore ├── .travis.yml ├── LICENSE ├── README.md ├── gitignore ├── src │ └── index.js └── test │ └── index.test.js ├── test └── cli.test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | yarn-error.log 4 | dist 5 | package-lock.json 6 | test/sandbox 7 | test/sandbox-ts 8 | *.tgz 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | assets 13 | 14 | # examples 15 | example 16 | examples 17 | 18 | # code coverage directories 19 | coverage 20 | .nyc_output 21 | 22 | # build scripts 23 | Makefile 24 | Gulpfile.js 25 | Gruntfile.js 26 | 27 | # configs 28 | appveyor.yml 29 | circle.yml 30 | codeship-services.yml 31 | codeship-steps.yml 32 | wercker.yml 33 | .tern-project 34 | .gitattributes 35 | .editorconfig 36 | .*ignore 37 | .eslintrc 38 | .jshintrc 39 | .flowconfig 40 | .documentup.json 41 | .yarn-metadata.json 42 | .travis.yml 43 | 44 | # misc 45 | *.md 46 | *.ts 47 | *.yml 48 | *.yaml 49 | *.txt 50 | *.map 51 | *.log 52 | *.html 53 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | template 2 | template-typescript 3 | test/sandbox 4 | test/sandbox-ts 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | env: 4 | global: 5 | - FORCE_POSTINSTALL=true 6 | 7 | node_js: 8 | - '14' 9 | - '12' 10 | - '10' 11 | 12 | before_install: 13 | - node --version 14 | - yarn --version 15 | 16 | install: 17 | - npm install 18 | 19 | os: 20 | - linux 21 | - osx 22 | - windows 23 | 24 | matrix: 25 | fast_finish: true 26 | allow_failures: 27 | - os: osx 28 | - os: windows 29 | 30 | script: 31 | - npm test 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | COPY . /modern-node 4 | 5 | RUN git config --global user.name "John Smith" 6 | 7 | RUN git config --global user.email "email@example.com" 8 | 9 | RUN /modern-node/create-modern-node/cli.js app --modern-version file:/modern-node 10 | 11 | WORKDIR /app 12 | 13 | RUN git status 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Adam Stankiewicz 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Modern Node](http://i.imgur.com/PqQAqwO.png) [![Unix CI](https://img.shields.io/travis/sheerun/modern-node/master.svg)](https://travis-ci.org/sheerun/modern-node) [![Modern Node](https://img.shields.io/badge/modern-node-9BB48F.svg)](https://github.com/sheerun/modern-node) 2 | 3 | > All-in-one development toolkit for creating node modules with Jest, Prettier, ESLint, and Standard 4 | 5 | - 🃏 Testing with [Jest](https://facebook.github.io/jest/) 6 | - 💅 Formatting with [prettier](https://prettier.io/) 7 | - 🌟 Linting with [eslint](https://eslint.org/) configured on [standard](https://standardjs.com/) rules 8 | - 🐶 Automatically runs `precommit` script from `package.json` from when committing code 9 | 10 | ## Installation (new projects) 11 | 12 | ``` 13 | yarn create modern-node my-module 14 | ``` 15 | 16 | > If you're using [npm](https://www.npmjs.com/): `npm init modern-node my-module`. 17 | 18 | ## Installation (existing projects) 19 | 20 | ``` 21 | yarn add --dev modern-node 22 | ``` 23 | 24 | > If you're using [npm](https://www.npmjs.com/): `npm install --save-dev modern-node`. 25 | 26 | Now you add appropriate scripts to your `package.json`: 27 | 28 | ``` 29 | { 30 | "scripts": { 31 | "test": "modern test", 32 | "format": "modern format", 33 | "lint": "modern lint", 34 | "precommit": "modern precommit" 35 | } 36 | } 37 | ``` 38 | 39 | ## Usage 40 | 41 | Test your project with Jest (watch mode, unless running on CI server): 42 | 43 | ``` 44 | modern test 45 | ``` 46 | 47 | Format all files in the project with [prettier-standard](https://github.com/sheerun/prettier-standard) (add `--help` for more options): 48 | 49 | ``` 50 | modern format # format all files 51 | modern format --changed # format only changed files 52 | modern format '**/*.js' # format only selected files 53 | ``` 54 | 55 | Format and files in the project (add `--help` for more options): 56 | 57 | ``` 58 | modern lint # lint all files 59 | modern lint --changed # lint only changed files 60 | modern lint '**/*.js' # lint only selected files 61 | ``` 62 | 63 | Format and lint staged changes (useful to put into `precommit` script): 64 | 65 | ``` 66 | modern precommit 67 | ``` 68 | 69 | For now linted extensions can be configured with `lint-staged` option in `package.json`. 70 | 71 | ## License 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /create-modern-node/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Adam Stankiewicz 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 | -------------------------------------------------------------------------------- /create-modern-node/README.md: -------------------------------------------------------------------------------- 1 | # create-modern-node 2 | 3 | Quickly create modern node module with [modern-node](https://github.com/sheerun/modern-node) 4 | 5 | ## Usage 6 | 7 | ``` 8 | npm init modern-node 9 | ``` 10 | 11 | or 12 | 13 | ``` 14 | yarn create modern-node 15 | ``` 16 | 17 | ## License 18 | 19 | MIT 20 | -------------------------------------------------------------------------------- /create-modern-node/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var currentNodeVersion = process.versions.node 4 | var semver = currentNodeVersion.split('.') 5 | var major = semver[0] 6 | 7 | if (major < 8) { 8 | console.error( 9 | 'You are running Node ' + 10 | currentNodeVersion + 11 | '.\n' + 12 | 'Create Modern Node requires Node 8 or higher. \n' + 13 | 'Please update your version of Node.' 14 | ) 15 | process.exit(1) 16 | } 17 | 18 | require('./index') 19 | -------------------------------------------------------------------------------- /create-modern-node/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const minimist = require('minimist') 3 | const dns = require('dns') 4 | const execSync = require('child_process').execSync 5 | const fs = require('fs-extra') 6 | const hyperquest = require('hyperquest') 7 | const os = require('os') 8 | const path = require('path') 9 | const semver = require('semver') 10 | const spawn = require('cross-spawn') 11 | const tmp = require('tmp') 12 | const unpack = require('tar-pack').unpack 13 | const url = require('url') 14 | const validateProjectName = require('validate-npm-package-name') 15 | 16 | // These files should be allowed to remain on a failed install, 17 | // but then silently removed during the next create. 18 | const errorLogFilePatterns = [ 19 | 'npm-debug.log', 20 | 'yarn-error.log', 21 | 'yarn-debug.log' 22 | ] 23 | 24 | var argv = minimist(process.argv.slice(2), { 25 | string: ['version', 'internal-testing-template'], 26 | boolean: ['typescript'] 27 | }) 28 | 29 | if (argv.help || argv._.length === 0 || argv.version === true) { 30 | console.log(`Usage: create-modern-node ${chalk.green( 31 | '' 32 | )} [options] 33 | 34 | Options: 35 | --version use a non-standard version of modern-node (see below) 36 | --typescript 37 | 38 | Only ${chalk.green('')} is required. 39 | A custom ${chalk.cyan('--version')} can be one of: 40 | - a specific npm version: ${chalk.green('0.8.2')} 41 | - a specific npm tag: ${chalk.green('@next')} 42 | - a custom fork published on npm: ${chalk.green('my-modern-node')} 43 | - a local path relative to the current working directory: ${chalk.green( 44 | 'file:../my-modern-node' 45 | )} 46 | - a .tgz archive: ${chalk.green( 47 | 'https://mysite.com/my-modern-node-0.8.2.tgz' 48 | )} 49 | - a .tar.gz archive: ${chalk.green( 50 | 'https://mysite.com/my-modern-node-0.8.2.tar.gz' 51 | )} 52 | 53 | If you have any problems, do not hesitate to file an issue: 54 | ${chalk.cyan('https://github.com/sheerun/modern-node/issues/new')} 55 | `) 56 | process.exit(!argv.help) 57 | } 58 | 59 | function printValidationResults (results) { 60 | if (typeof results !== 'undefined') { 61 | results.forEach(error => { 62 | console.error(chalk.red(` * ${error}`)) 63 | }) 64 | } 65 | } 66 | 67 | const projectName = argv._[0] 68 | 69 | createApp( 70 | projectName, 71 | argv.verbose, 72 | argv.version, 73 | argv.typescript, 74 | argv['internal-testing-template'] 75 | ) 76 | 77 | function createApp (name, verbose, version, useTypescript, template) { 78 | const root = path.resolve(name) 79 | const appName = path.basename(root) 80 | 81 | checkAppName(appName) 82 | fs.ensureDirSync(name) 83 | if (!isSafeToCreateProjectIn(root, name)) { 84 | process.exit(1) 85 | } 86 | 87 | console.log(`Creating a new Node module in ${chalk.green(root)}.`) 88 | 89 | const packageJson = { 90 | name: appName, 91 | version: '0.0.0', 92 | license: 'MIT' 93 | } 94 | fs.writeFileSync( 95 | path.join(root, 'package.json'), 96 | JSON.stringify(packageJson, null, 2) + os.EOL 97 | ) 98 | 99 | const useYarn = shouldUseYarn() 100 | const originalDirectory = process.cwd() 101 | process.chdir(root) 102 | if (!useYarn && !checkThatNpmCanReadCwd()) { 103 | process.exit(1) 104 | } 105 | 106 | run( 107 | root, 108 | appName, 109 | version, 110 | verbose, 111 | originalDirectory, 112 | template, 113 | useYarn, 114 | useTypescript 115 | ).catch(reason => { 116 | console.log() 117 | console.log('Aborting installation.') 118 | if (reason.command) { 119 | console.log(` ${chalk.cyan(reason.command)} has failed.`) 120 | } else { 121 | console.log(chalk.red('Unexpected error. Please report it as a bug:')) 122 | console.log(reason) 123 | } 124 | console.log() 125 | 126 | // On 'exit' we will delete these files from target directory. 127 | const knownGeneratedFiles = ['package.json', 'yarn.lock', 'node_modules'] 128 | const currentFiles = fs.readdirSync(path.join(root)) 129 | currentFiles.forEach(file => { 130 | knownGeneratedFiles.forEach(fileToMatch => { 131 | // This removes all knownGeneratedFiles. 132 | if (file === fileToMatch) { 133 | console.log(`Deleting generated file... ${chalk.cyan(file)}`) 134 | fs.removeSync(path.join(root, file)) 135 | } 136 | }) 137 | }) 138 | const remainingFiles = fs.readdirSync(path.join(root)) 139 | if (!remainingFiles.length) { 140 | // Delete target folder if empty 141 | console.log( 142 | `Deleting ${chalk.cyan(`${appName}/`)} from ${chalk.cyan( 143 | path.resolve(root, '..') 144 | )}` 145 | ) 146 | process.chdir(path.resolve(root, '..')) 147 | fs.removeSync(path.join(root)) 148 | } 149 | console.log('Done.') 150 | process.exit(1) 151 | }) 152 | } 153 | 154 | function shouldUseYarn () { 155 | try { 156 | execSync('yarnpkg --version', { stdio: 'ignore' }) 157 | return true 158 | } catch (e) { 159 | return false 160 | } 161 | } 162 | 163 | function install (root, useYarn, dependencies, verbose, isOnline) { 164 | return new Promise((resolve, reject) => { 165 | let command 166 | let args 167 | if (useYarn) { 168 | command = 'yarnpkg' 169 | args = ['add', '--exact', '--dev'] 170 | if (!isOnline) { 171 | args.push('--offline') 172 | } 173 | ;[].push.apply(args, dependencies) 174 | 175 | // Explicitly set cwd() to work around issues 176 | // Unfortunately we can only do this for Yarn because npm support for 177 | // equivalent --prefix flag doesn't help with this issue. 178 | // This is why for npm, we run checkThatNpmCanReadCwd() early instead. 179 | args.push('--cwd') 180 | args.push(root) 181 | 182 | if (!isOnline) { 183 | console.log(chalk.yellow('You appear to be offline.')) 184 | console.log(chalk.yellow('Falling back to the local Yarn cache.')) 185 | console.log() 186 | } 187 | } else { 188 | command = 'npm' 189 | args = [ 190 | 'install', 191 | '--save', 192 | '--save-exact', 193 | '--loglevel', 194 | '--dev', 195 | 'error' 196 | ].concat(dependencies) 197 | } 198 | 199 | if (verbose) { 200 | args.push('--verbose') 201 | } 202 | 203 | const child = spawn(command, args, { stdio: 'inherit' }) 204 | child.on('close', code => { 205 | if (code !== 0) { 206 | // eslint-disable-next-line 207 | reject({ 208 | command: `${command} ${args.join(' ')}` 209 | }) 210 | return 211 | } 212 | resolve() 213 | }) 214 | }) 215 | } 216 | 217 | function tryGitInit (appPath) { 218 | let didInit = false 219 | try { 220 | const hasGit = fs.existsSync(path.join(appPath, '.git')) 221 | const hasHg = fs.existsSync(path.join(appPath, '.hg')) 222 | 223 | if (hasGit || hasHg) { 224 | return false 225 | } 226 | 227 | execSync('git init', { stdio: 'ignore' }) 228 | didInit = true 229 | 230 | return true 231 | } catch (e) { 232 | if (didInit) { 233 | // If we successfully initialized but couldn't commit, 234 | // maybe the commit author config is not set. 235 | // In the future, we might supply our own committer 236 | // like Ember CLI does, but for now, let's just 237 | // remove the Git files to avoid a half-done state. 238 | try { 239 | // unlinkSync() doesn't work on directories. 240 | fs.removeSync(path.join(appPath, '.git')) 241 | } catch (removeErr) { 242 | // Ignore. 243 | } 244 | } 245 | return false 246 | } 247 | } 248 | 249 | async function run ( 250 | root, 251 | appName, 252 | version, 253 | verbose, 254 | originalDirectory, 255 | template, 256 | useYarn, 257 | useTypescript 258 | ) { 259 | if (tryGitInit(root)) { 260 | console.log('Initialized a git repository.') 261 | } 262 | 263 | const packageToInstall = await getInstallPackage(version, originalDirectory) 264 | const allDependencies = [packageToInstall] 265 | if (useTypescript) { 266 | allDependencies.push('typescript') 267 | } 268 | 269 | const packageName = await getPackageName(packageToInstall) 270 | const isOnline = await checkIfOnline(useYarn) 271 | 272 | console.log(`Installing ${chalk.cyan(packageName)}...`) 273 | 274 | await install(root, useYarn, allDependencies, verbose, isOnline) 275 | checkNodeVersion(packageName) 276 | setCaretRangeForRuntimeDeps(packageName) 277 | 278 | const pnpPath = path.resolve(process.cwd(), '.pnp.js') 279 | 280 | const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [] 281 | 282 | await executeNodeScript( 283 | { 284 | cwd: process.cwd(), 285 | args: nodeArgs 286 | }, 287 | [root, appName, verbose, originalDirectory, template], 288 | ` 289 | var init = require('${packageName}/src/init.js'); 290 | init.apply(null, JSON.parse(process.argv[1])); 291 | ` 292 | ) 293 | } 294 | 295 | function getInstallPackage (version, originalDirectory) { 296 | let packageToInstall = 'modern-node' 297 | const validSemver = semver.valid(version) 298 | if (validSemver) { 299 | packageToInstall += `@${validSemver}` 300 | } else if (version) { 301 | if (version[0] === '@' && version.indexOf('/') === -1) { 302 | packageToInstall += version 303 | } else if (version.match(/^file:/)) { 304 | packageToInstall = `file:${path.resolve( 305 | originalDirectory, 306 | version.match(/^file:(.*)?$/)[1] 307 | )}` 308 | } else { 309 | // for tar.gz or alternative paths 310 | packageToInstall = version 311 | } 312 | } 313 | 314 | return Promise.resolve(packageToInstall) 315 | } 316 | 317 | function getTemporaryDirectory () { 318 | return new Promise((resolve, reject) => { 319 | // Unsafe cleanup lets us recursively delete the directory if it contains 320 | // contents; by default it only allows removal if it's empty 321 | tmp.dir({ unsafeCleanup: true }, (err, tmpdir, callback) => { 322 | if (err) { 323 | reject(err) 324 | } else { 325 | resolve({ 326 | tmpdir: tmpdir, 327 | cleanup: () => { 328 | try { 329 | callback() 330 | } catch (ignored) { 331 | // Callback might throw and fail, since it's a temp directory the 332 | // OS will clean it up eventually... 333 | } 334 | } 335 | }) 336 | } 337 | }) 338 | }) 339 | } 340 | 341 | function extractStream (stream, dest) { 342 | return new Promise((resolve, reject) => { 343 | stream.pipe( 344 | unpack(dest, err => { 345 | if (err) { 346 | reject(err) 347 | } else { 348 | resolve(dest) 349 | } 350 | }) 351 | ) 352 | }) 353 | } 354 | 355 | // Extract package name from tarball url or path. 356 | function getPackageName (installPackage) { 357 | if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) { 358 | return getTemporaryDirectory() 359 | .then(obj => { 360 | let stream 361 | if (/^http/.test(installPackage)) { 362 | stream = hyperquest(installPackage) 363 | } else { 364 | stream = fs.createReadStream(installPackage) 365 | } 366 | return extractStream(stream, obj.tmpdir).then(() => obj) 367 | }) 368 | .then(obj => { 369 | const packageName = require(path.join(obj.tmpdir, 'package.json')).name 370 | obj.cleanup() 371 | return packageName 372 | }) 373 | .catch(err => { 374 | // The package name could be with or without semver version, e.g. modern-node-0.2.0-alpha.1.tgz 375 | // However, this function returns package name only without semver version. 376 | console.log( 377 | `Could not extract the package name from the archive: ${err.message}` 378 | ) 379 | const assumedProjectName = installPackage.match( 380 | /^.+\/(.+?)(?:-\d+.+)?\.(tgz|tar\.gz)$/ 381 | )[1] 382 | console.log( 383 | `Based on the filename, assuming it is "${chalk.cyan( 384 | assumedProjectName 385 | )}"` 386 | ) 387 | return Promise.resolve(assumedProjectName) 388 | }) 389 | } else if (installPackage.indexOf('git+') === 0) { 390 | // Pull package name out of git urls e.g: 391 | // git+https://github.com/mycompany/modern-node.git 392 | // git+ssh://github.com/mycompany/modern-node.git#v1.2.3 393 | return Promise.resolve(installPackage.match(/([^/]+)\.git(#.*)?$/)[1]) 394 | } else if (installPackage.match(/.+@/)) { 395 | // Do not match @scope/ when stripping off @version or @tag 396 | return Promise.resolve( 397 | installPackage.charAt(0) + installPackage.substr(1).split('@')[0] 398 | ) 399 | } else if (installPackage.match(/^file:/)) { 400 | const installPackagePath = installPackage.match(/^file:(.*)?$/)[1] 401 | const installPackageJson = require(path.join( 402 | installPackagePath, 403 | 'package.json' 404 | )) 405 | return Promise.resolve(installPackageJson.name) 406 | } 407 | return Promise.resolve(installPackage) 408 | } 409 | 410 | function checkNodeVersion (packageName) { 411 | const packageJsonPath = path.resolve( 412 | process.cwd(), 413 | 'node_modules', 414 | packageName, 415 | 'package.json' 416 | ) 417 | 418 | if (!fs.existsSync(packageJsonPath)) { 419 | return 420 | } 421 | 422 | const packageJson = require(packageJsonPath) 423 | if (!packageJson.engines || !packageJson.engines.node) { 424 | return 425 | } 426 | 427 | if (!semver.satisfies(process.version, packageJson.engines.node)) { 428 | console.error( 429 | chalk.red( 430 | 'You are running Node %s.\n' + 431 | 'Create Modern Node requires Node %s or higher. \n' + 432 | 'Please update your version of Node.' 433 | ), 434 | process.version, 435 | packageJson.engines.node 436 | ) 437 | process.exit(1) 438 | } 439 | } 440 | 441 | function checkAppName (appName) { 442 | const validationResult = validateProjectName(appName) 443 | if (!validationResult.validForNewPackages) { 444 | console.error( 445 | `Could not create a project called ${chalk.red( 446 | `"${appName}"` 447 | )} because of npm naming restrictions:` 448 | ) 449 | printValidationResults(validationResult.errors) 450 | printValidationResults(validationResult.warnings) 451 | process.exit(1) 452 | } 453 | 454 | // TODO: there should be a single place that holds the dependencies 455 | const dependencies = ['modern-node'].sort() 456 | if (dependencies.indexOf(appName) >= 0) { 457 | console.error( 458 | chalk.red( 459 | `We cannot create a project called ${chalk.green( 460 | appName 461 | )} because a dependency with the same name exists.\n` + 462 | `Due to the way npm works, the following names are not allowed:\n\n` 463 | ) + 464 | chalk.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) + 465 | chalk.red('\n\nPlease choose a different project name.') 466 | ) 467 | process.exit(1) 468 | } 469 | } 470 | 471 | function setCaretRangeForRuntimeDeps (packageName) { 472 | const packagePath = path.join(process.cwd(), 'package.json') 473 | const packageJson = require(packagePath) 474 | 475 | if (typeof packageJson.devDependencies === 'undefined') { 476 | console.error(chalk.red('Missing dependencies in package.json')) 477 | process.exit(1) 478 | } 479 | 480 | const packageVersion = packageJson.devDependencies[packageName] 481 | if (typeof packageVersion === 'undefined') { 482 | console.error(chalk.red(`Unable to find ${packageName} in package.json`)) 483 | process.exit(1) 484 | } 485 | 486 | fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL) 487 | } 488 | 489 | // If project only contains files generated by GH, it’s safe. 490 | // Also, if project contains remnant error logs from a previous 491 | // installation, lets remove them now. 492 | function isSafeToCreateProjectIn (root, name) { 493 | const validFiles = [ 494 | '.DS_Store', 495 | 'Thumbs.db', 496 | '.git', 497 | '.gitignore', 498 | '.idea', 499 | 'README.md', 500 | 'LICENSE', 501 | '.hg', 502 | '.hgignore', 503 | '.hgcheck', 504 | '.npmignore', 505 | 'mkdocs.yml', 506 | 'docs', 507 | '.travis.yml', 508 | '.gitlab-ci.yml', 509 | '.gitattributes' 510 | ] 511 | 512 | const conflicts = fs 513 | .readdirSync(root) 514 | .filter(file => !validFiles.includes(file)) 515 | // IntelliJ IDEA creates module files before CRA is launched 516 | .filter(file => !/\.iml$/.test(file)) 517 | // Don't treat log files from previous installation as conflicts 518 | .filter( 519 | file => !errorLogFilePatterns.some(pattern => file.indexOf(pattern) === 0) 520 | ) 521 | 522 | if (conflicts.length > 0) { 523 | console.log( 524 | `The directory ${chalk.green(name)} contains files that could conflict:` 525 | ) 526 | for (const file of conflicts) { 527 | console.log(` ${file}`) 528 | } 529 | console.log( 530 | 'Either try using a new directory name, or remove the files listed above.' 531 | ) 532 | 533 | return false 534 | } 535 | 536 | // Remove any remnant files from a previous installation 537 | const currentFiles = fs.readdirSync(path.join(root)) 538 | currentFiles.forEach(file => { 539 | errorLogFilePatterns.forEach(errorLogFilePattern => { 540 | // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files 541 | if (file.indexOf(errorLogFilePattern) === 0) { 542 | fs.removeSync(path.join(root, file)) 543 | } 544 | }) 545 | }) 546 | return true 547 | } 548 | 549 | function getProxy () { 550 | if (process.env.https_proxy) { 551 | return process.env.https_proxy 552 | } else { 553 | try { 554 | // Trying to read https-proxy from .npmrc 555 | const httpsProxy = execSync('npm config get https-proxy') 556 | .toString() 557 | .trim() 558 | return httpsProxy !== 'null' ? httpsProxy : undefined 559 | } catch (e) {} 560 | } 561 | } 562 | function checkThatNpmCanReadCwd () { 563 | const cwd = process.cwd() 564 | let childOutput = null 565 | try { 566 | // Note: intentionally using spawn over exec since 567 | // the problem doesn't reproduce otherwise. 568 | // `npm config list` is the only reliable way I could find 569 | // to reproduce the wrong path. Just printing process.cwd() 570 | // in a Node process was not enough. 571 | childOutput = spawn.sync('npm', ['config', 'list']).output.join('') 572 | } catch (err) { 573 | // Something went wrong spawning node. 574 | // Not great, but it means we can't do this check. 575 | // We might fail later on, but let's continue. 576 | return true 577 | } 578 | if (typeof childOutput !== 'string') { 579 | return true 580 | } 581 | const lines = childOutput.split('\n') 582 | // `npm config list` output includes the following line: 583 | // "; cwd = C:\path\to\current\dir" (unquoted) 584 | // I couldn't find an easier way to get it. 585 | const prefix = '; cwd = ' 586 | const line = lines.find(line => line.indexOf(prefix) === 0) 587 | if (typeof line !== 'string') { 588 | // Fail gracefully. They could remove it. 589 | return true 590 | } 591 | const npmCWD = line.substring(prefix.length) 592 | if (npmCWD === cwd) { 593 | return true 594 | } 595 | console.error( 596 | chalk.red( 597 | `Could not start an npm process in the right directory.\n\n` + 598 | `The current directory is: ${chalk.bold(cwd)}\n` + 599 | `However, a newly started npm process runs in: ${chalk.bold( 600 | npmCWD 601 | )}\n\n` + 602 | `This is probably caused by a misconfigured system terminal shell.` 603 | ) 604 | ) 605 | if (process.platform === 'win32') { 606 | console.error( 607 | chalk.red(`On Windows, this can usually be fixed by running:\n\n`) + 608 | ` ${chalk.cyan( 609 | 'reg' 610 | )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` + 611 | ` ${chalk.cyan( 612 | 'reg' 613 | )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` + 614 | chalk.red(`Try to run the above two lines in the terminal.\n`) + 615 | chalk.red( 616 | `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/` 617 | ) 618 | ) 619 | } 620 | return false 621 | } 622 | 623 | function checkIfOnline (useYarn) { 624 | if (!useYarn) { 625 | // Don't ping the Yarn registry. 626 | // We'll just assume the best case. 627 | return Promise.resolve(true) 628 | } 629 | 630 | return new Promise(resolve => { 631 | dns.lookup('registry.yarnpkg.com', err => { 632 | let proxy 633 | if (err != null && (proxy = getProxy())) { 634 | // If a proxy is defined, we likely can't resolve external hostnames. 635 | // Try to resolve the proxy name as an indication of a connection. 636 | // eslint-disable-next-line 637 | dns.lookup(url.parse(proxy).hostname, proxyErr => { 638 | resolve(proxyErr == null) 639 | }) 640 | } else { 641 | resolve(err == null) 642 | } 643 | }) 644 | }) 645 | } 646 | 647 | function executeNodeScript ({ cwd, args }, data, source) { 648 | return new Promise((resolve, reject) => { 649 | const child = spawn( 650 | process.execPath, 651 | [...args, '-e', source, '--', JSON.stringify(data)], 652 | { cwd, stdio: 'inherit' } 653 | ) 654 | 655 | child.on('close', code => { 656 | if (code !== 0) { 657 | // eslint-disable-next-line 658 | reject({ 659 | command: `node ${args.join(' ')}` 660 | }) 661 | return 662 | } 663 | resolve() 664 | }) 665 | }) 666 | } 667 | -------------------------------------------------------------------------------- /create-modern-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-modern-node", 3 | "version": "1.2.0", 4 | "description": "Initialize an app with modern-node", 5 | "bin": "cli.js", 6 | "files": [ 7 | "cli.js", 8 | "index.js" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/sheerun/modern-node.git", 13 | "directory": "create-modern-node" 14 | }, 15 | "author": "Adam Stankiewicz ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "chalk": "4.1.0", 19 | "cross-spawn": "7.0.3", 20 | "envinfo": "7.7.2", 21 | "fs-extra": "9.0.1", 22 | "hyperquest": "2.1.3", 23 | "minimist": "^1.2.5", 24 | "semver": "7.3.2", 25 | "tar-pack": "3.4.1", 26 | "tmp": "0.2.1", 27 | "validate-npm-package-name": "3.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /create-modern-node/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/color-name@^1.1.1": 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" 8 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== 9 | 10 | ansi-styles@^4.1.0: 11 | version "4.2.1" 12 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" 13 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== 14 | dependencies: 15 | "@types/color-name" "^1.1.1" 16 | color-convert "^2.0.1" 17 | 18 | at-least-node@^1.0.0: 19 | version "1.0.0" 20 | resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" 21 | integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== 22 | 23 | balanced-match@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 26 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 27 | 28 | block-stream@*: 29 | version "0.0.9" 30 | resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" 31 | integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= 32 | dependencies: 33 | inherits "~2.0.0" 34 | 35 | brace-expansion@^1.1.7: 36 | version "1.1.11" 37 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 38 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 39 | dependencies: 40 | balanced-match "^1.0.0" 41 | concat-map "0.0.1" 42 | 43 | buffer-from@^0.1.1: 44 | version "0.1.2" 45 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0" 46 | integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg== 47 | 48 | builtins@^1.0.3: 49 | version "1.0.3" 50 | resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" 51 | integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= 52 | 53 | chalk@4.1.0: 54 | version "4.1.0" 55 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" 56 | integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== 57 | dependencies: 58 | ansi-styles "^4.1.0" 59 | supports-color "^7.1.0" 60 | 61 | color-convert@^2.0.1: 62 | version "2.0.1" 63 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 64 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 65 | dependencies: 66 | color-name "~1.1.4" 67 | 68 | color-name@~1.1.4: 69 | version "1.1.4" 70 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 71 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 72 | 73 | concat-map@0.0.1: 74 | version "0.0.1" 75 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 76 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 77 | 78 | core-util-is@~1.0.0: 79 | version "1.0.2" 80 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 81 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 82 | 83 | cross-spawn@7.0.3: 84 | version "7.0.3" 85 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 86 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 87 | dependencies: 88 | path-key "^3.1.0" 89 | shebang-command "^2.0.0" 90 | which "^2.0.1" 91 | 92 | debug@^2.2.0: 93 | version "2.6.9" 94 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 95 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 96 | dependencies: 97 | ms "2.0.0" 98 | 99 | duplexer2@~0.0.2: 100 | version "0.0.2" 101 | resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" 102 | integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= 103 | dependencies: 104 | readable-stream "~1.1.9" 105 | 106 | envinfo@7.7.2: 107 | version "7.7.2" 108 | resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.2.tgz#098f97a0e902f8141f9150553c92dbb282c4cabe" 109 | integrity sha512-k3Eh5bKuQnZjm49/L7H4cHzs2FlL5QjbTB3JrPxoTI8aJG7hVMe4uKyJxSYH4ahseby2waUwk5OaKX/nAsaYgg== 110 | 111 | fs-extra@9.0.1: 112 | version "9.0.1" 113 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" 114 | integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== 115 | dependencies: 116 | at-least-node "^1.0.0" 117 | graceful-fs "^4.2.0" 118 | jsonfile "^6.0.1" 119 | universalify "^1.0.0" 120 | 121 | fs.realpath@^1.0.0: 122 | version "1.0.0" 123 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 124 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 125 | 126 | fstream-ignore@^1.0.5: 127 | version "1.0.5" 128 | resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" 129 | integrity sha1-nDHa40dnAY/h0kmyTa2mfQktoQU= 130 | dependencies: 131 | fstream "^1.0.0" 132 | inherits "2" 133 | minimatch "^3.0.0" 134 | 135 | fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.12: 136 | version "1.0.12" 137 | resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" 138 | integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== 139 | dependencies: 140 | graceful-fs "^4.1.2" 141 | inherits "~2.0.0" 142 | mkdirp ">=0.5 0" 143 | rimraf "2" 144 | 145 | glob@^7.1.3: 146 | version "7.1.6" 147 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 148 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 149 | dependencies: 150 | fs.realpath "^1.0.0" 151 | inflight "^1.0.4" 152 | inherits "2" 153 | minimatch "^3.0.4" 154 | once "^1.3.0" 155 | path-is-absolute "^1.0.0" 156 | 157 | graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: 158 | version "4.2.3" 159 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 160 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 161 | 162 | has-flag@^4.0.0: 163 | version "4.0.0" 164 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 165 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 166 | 167 | hyperquest@2.1.3: 168 | version "2.1.3" 169 | resolved "https://registry.yarnpkg.com/hyperquest/-/hyperquest-2.1.3.tgz#523127d7a343181b40bf324e231d2576edf52633" 170 | integrity sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw== 171 | dependencies: 172 | buffer-from "^0.1.1" 173 | duplexer2 "~0.0.2" 174 | through2 "~0.6.3" 175 | 176 | inflight@^1.0.4: 177 | version "1.0.6" 178 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 179 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 180 | dependencies: 181 | once "^1.3.0" 182 | wrappy "1" 183 | 184 | inherits@2, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: 185 | version "2.0.4" 186 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 187 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 188 | 189 | isarray@0.0.1: 190 | version "0.0.1" 191 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 192 | integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= 193 | 194 | isarray@~1.0.0: 195 | version "1.0.0" 196 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 197 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 198 | 199 | isexe@^2.0.0: 200 | version "2.0.0" 201 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 202 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 203 | 204 | jsonfile@^6.0.1: 205 | version "6.0.1" 206 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" 207 | integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== 208 | dependencies: 209 | universalify "^1.0.0" 210 | optionalDependencies: 211 | graceful-fs "^4.1.6" 212 | 213 | minimatch@^3.0.0, minimatch@^3.0.4: 214 | version "3.0.4" 215 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 216 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 217 | dependencies: 218 | brace-expansion "^1.1.7" 219 | 220 | minimist@^1.2.5: 221 | version "1.2.5" 222 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 223 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 224 | 225 | "mkdirp@>=0.5 0": 226 | version "0.5.5" 227 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 228 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 229 | dependencies: 230 | minimist "^1.2.5" 231 | 232 | ms@2.0.0: 233 | version "2.0.0" 234 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 235 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 236 | 237 | once@^1.3.0, once@^1.3.3: 238 | version "1.4.0" 239 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 240 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 241 | dependencies: 242 | wrappy "1" 243 | 244 | path-is-absolute@^1.0.0: 245 | version "1.0.1" 246 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 247 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 248 | 249 | path-key@^3.1.0: 250 | version "3.1.1" 251 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 252 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 253 | 254 | process-nextick-args@~2.0.0: 255 | version "2.0.1" 256 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 257 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 258 | 259 | "readable-stream@>=1.0.33-1 <1.1.0-0": 260 | version "1.0.34" 261 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" 262 | integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= 263 | dependencies: 264 | core-util-is "~1.0.0" 265 | inherits "~2.0.1" 266 | isarray "0.0.1" 267 | string_decoder "~0.10.x" 268 | 269 | readable-stream@^2.1.4: 270 | version "2.3.7" 271 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 272 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 273 | dependencies: 274 | core-util-is "~1.0.0" 275 | inherits "~2.0.3" 276 | isarray "~1.0.0" 277 | process-nextick-args "~2.0.0" 278 | safe-buffer "~5.1.1" 279 | string_decoder "~1.1.1" 280 | util-deprecate "~1.0.1" 281 | 282 | readable-stream@~1.1.9: 283 | version "1.1.14" 284 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 285 | integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= 286 | dependencies: 287 | core-util-is "~1.0.0" 288 | inherits "~2.0.1" 289 | isarray "0.0.1" 290 | string_decoder "~0.10.x" 291 | 292 | rimraf@2, rimraf@^2.5.1: 293 | version "2.7.1" 294 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 295 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 296 | dependencies: 297 | glob "^7.1.3" 298 | 299 | rimraf@^3.0.0: 300 | version "3.0.2" 301 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 302 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 303 | dependencies: 304 | glob "^7.1.3" 305 | 306 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 307 | version "5.1.2" 308 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 309 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 310 | 311 | semver@7.3.2: 312 | version "7.3.2" 313 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" 314 | integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== 315 | 316 | shebang-command@^2.0.0: 317 | version "2.0.0" 318 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 319 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 320 | dependencies: 321 | shebang-regex "^3.0.0" 322 | 323 | shebang-regex@^3.0.0: 324 | version "3.0.0" 325 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 326 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 327 | 328 | string_decoder@~0.10.x: 329 | version "0.10.31" 330 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 331 | integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= 332 | 333 | string_decoder@~1.1.1: 334 | version "1.1.1" 335 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 336 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 337 | dependencies: 338 | safe-buffer "~5.1.0" 339 | 340 | supports-color@^7.1.0: 341 | version "7.1.0" 342 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" 343 | integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== 344 | dependencies: 345 | has-flag "^4.0.0" 346 | 347 | tar-pack@3.4.1: 348 | version "3.4.1" 349 | resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" 350 | integrity sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg== 351 | dependencies: 352 | debug "^2.2.0" 353 | fstream "^1.0.10" 354 | fstream-ignore "^1.0.5" 355 | once "^1.3.3" 356 | readable-stream "^2.1.4" 357 | rimraf "^2.5.1" 358 | tar "^2.2.1" 359 | uid-number "^0.0.6" 360 | 361 | tar@^2.2.1: 362 | version "2.2.2" 363 | resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" 364 | integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== 365 | dependencies: 366 | block-stream "*" 367 | fstream "^1.0.12" 368 | inherits "2" 369 | 370 | through2@~0.6.3: 371 | version "0.6.5" 372 | resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" 373 | integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= 374 | dependencies: 375 | readable-stream ">=1.0.33-1 <1.1.0-0" 376 | xtend ">=4.0.0 <4.1.0-0" 377 | 378 | tmp@0.2.1: 379 | version "0.2.1" 380 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" 381 | integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== 382 | dependencies: 383 | rimraf "^3.0.0" 384 | 385 | uid-number@^0.0.6: 386 | version "0.0.6" 387 | resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" 388 | integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= 389 | 390 | universalify@^1.0.0: 391 | version "1.0.0" 392 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" 393 | integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== 394 | 395 | util-deprecate@~1.0.1: 396 | version "1.0.2" 397 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 398 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 399 | 400 | validate-npm-package-name@3.0.0: 401 | version "3.0.0" 402 | resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" 403 | integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= 404 | dependencies: 405 | builtins "^1.0.3" 406 | 407 | which@^2.0.1: 408 | version "2.0.2" 409 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 410 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 411 | dependencies: 412 | isexe "^2.0.0" 413 | 414 | wrappy@1: 415 | version "1.0.2" 416 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 417 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 418 | 419 | "xtend@>=4.0.0 <4.1.0-0": 420 | version "4.0.2" 421 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 422 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 423 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modern-node", 3 | "version": "2.9.1", 4 | "dependencies": { 5 | "camelcase": "^6.0.0", 6 | "chalk": "^4.1.0", 7 | "execa": "^4.0.3", 8 | "fast-glob": "^3.2.4", 9 | "find-up": "^4.1.0", 10 | "fs-extra": "^9.0.1", 11 | "jest": "^26.2.2", 12 | "jest-watch-typeahead": "^0.6.0", 13 | "prettier-standard": "^16.4.1", 14 | "read-pkg-up": "^7.0.1", 15 | "username": "^5.1.0" 16 | }, 17 | "bin": { 18 | "modern": "./src/cli.js" 19 | }, 20 | "scripts": { 21 | "install": "node src/install.js", 22 | "uninstall": "node src/uninstall.js", 23 | "lint": "./src/cli.js lint", 24 | "format": "./src/cli.js format", 25 | "test": "node ./src/cli.js test", 26 | "precommit": "./src/cli.js precommit" 27 | }, 28 | "engines": { 29 | "node": ">= 8" 30 | }, 31 | "files": [ 32 | "src", 33 | "template", 34 | "template-typescript" 35 | ], 36 | "license": "MIT", 37 | "devDependencies": { 38 | "tmp": "^0.2.1", 39 | "typescript": "^3.9.7" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Makes the script crash on unhandled rejections instead of silently 4 | // ignoring them. In the future, promise rejections that are not handled will 5 | // terminate the Node.js process with a non-zero exit code. 6 | process.on('unhandledRejection', err => { 7 | throw err 8 | }) 9 | 10 | const execa = require('execa') 11 | 12 | const help = ` 13 | Usage 14 | $ modern 15 | 16 | Commands: 17 | $ test - jest 18 | $ format - prettier-standard 19 | $ lint - prettier-standard --lint 20 | $ precommit - prettier-standard --lint --staged 21 | ` 22 | 23 | function terminate (message) { 24 | message = typeof message === 'string' ? message : message.join('\n') 25 | if (message.length > 0) { 26 | message = `${message}\n` 27 | } 28 | process.stderr.write(message) 29 | process.exit(1) 30 | } 31 | 32 | function tryExec (command, args, options) { 33 | options = options || {} 34 | 35 | options.stdio = 'inherit' 36 | options.preferLocal = true 37 | 38 | try { 39 | return execa.sync(command, args, options) 40 | } catch (e) { 41 | process.exit(e.exitCode || 1) 42 | } 43 | } 44 | 45 | async function main () { 46 | var argv = process.argv.slice(2) 47 | 48 | if (argv[0] === 'test') { 49 | require('./test') 50 | return 51 | } 52 | 53 | if (argv[0] === 'precommit') { 54 | await tryExec( 55 | 'prettier-standard', 56 | ['--lint', '--staged'].concat(argv.slice(1)) 57 | ) 58 | return 59 | } 60 | 61 | if (argv[0] === 'format') { 62 | await tryExec('prettier-standard', [].concat(argv.slice(1))) 63 | return 64 | } 65 | 66 | if (argv[0] === 'lint') { 67 | await tryExec('prettier-standard', ['--lint'].concat(argv.slice(1))) 68 | return 69 | } 70 | 71 | if (argv[0] === '--help') { 72 | console.log(help) 73 | return 74 | } 75 | 76 | terminate(help) 77 | } 78 | 79 | main() 80 | -------------------------------------------------------------------------------- /src/config/lint-staged.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,json,ts,tsx,md,mdx,css,html,yml,yaml,scss,sass}": [ 3 | "modern format", 4 | "git add" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | HAS_NODE=`which node 2> /dev/null || which nodejs 2> /dev/null || which iojs 2> /dev/null` 4 | 5 | # 6 | # There are some issues with Source Tree because paths are not set correctly for 7 | # the given environment. Sourcing the bash_profile seems to resolve this for bash users, 8 | # sourcing the zshrc for zshell users. 9 | # 10 | # https://answers.atlassian.com/questions/140339/sourcetree-hook-failing-because-paths-don-t-seem-to-be-set-correctly 11 | # 12 | function source_home_file { 13 | file="$HOME/$1" 14 | [[ -f "${file}" ]] && source "${file}" 15 | } 16 | 17 | if [[ -z "$HAS_NODE" ]]; then 18 | source_home_file ".bash_profile" || source_home_file ".zshrc" || source_home_file ".bashrc" || true 19 | fi 20 | 21 | NODE=`which node 2> /dev/null` 22 | NODEJS=`which nodejs 2> /dev/null` 23 | IOJS=`which iojs 2> /dev/null` 24 | LOCAL="/usr/local/bin/node" 25 | BINARY= 26 | 27 | # 28 | # Figure out which binary we need to use for our script execution. 29 | # 30 | if [[ -n "$NODE" ]]; then 31 | BINARY="$NODE" 32 | elif [[ -n "$NODEJS" ]]; then 33 | BINARY="$NODEJS" 34 | elif [[ -n "$IOJS" ]]; then 35 | BINARY="$IOJS" 36 | elif [[ -x "$LOCAL" ]]; then 37 | BINARY="$LOCAL" 38 | fi 39 | 40 | "$BINARY" "$@" 41 | -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | // Makes the script crash on unhandled rejections instead of silently 2 | // ignoring them. In the future, promise rejections that are not handled will 3 | // terminate the Node.js process with a non-zero exit code. 4 | process.on('unhandledRejection', err => { 5 | throw err 6 | }) 7 | 8 | const fs = require('fs-extra') 9 | const path = require('path') 10 | const chalk = require('chalk') 11 | const execSync = require('child_process').execSync 12 | const os = require('os') 13 | const glob = require('fast-glob') 14 | const camelcase = require('camelcase') 15 | const username = require('username') 16 | const execa = require('execa') 17 | 18 | function fetchGitConfig () { 19 | try { 20 | const data = {} 21 | const out = execa.sync('git', ['config', '--get-regexp', 'user.']) 22 | out.stdout 23 | .trim() 24 | .split('\n') 25 | .forEach(line => { 26 | const key = line.split(' ')[0] 27 | const value = line.slice(key.length + 1) 28 | data[key] = value 29 | }) 30 | return data 31 | } catch (e) { 32 | return {} 33 | } 34 | } 35 | 36 | function tryGitInitAndCommit (appPath) { 37 | let hasGit = fs.existsSync(path.join(appPath, '.git')) 38 | const hasHg = fs.existsSync(path.join(appPath, '.hg')) 39 | 40 | if (!hasGit && !hasHg) { 41 | try { 42 | execSync('git init', { stdio: 'ignore', cwd: appPath }) 43 | hasGit = true 44 | } catch (e) { 45 | console.error('Failed initialize git...') 46 | console.error(e.message) 47 | return 48 | } 49 | } 50 | 51 | if (hasGit) { 52 | try { 53 | execSync('git add -A', { stdio: 'ignore', cwd: appPath }) 54 | execSync('git commit -m "Initial commit on master..."', { 55 | stdio: 'ignore', 56 | cwd: appPath 57 | }) 58 | return true 59 | } catch (e) { 60 | console.error('Failed commit, probably git user is not configurd...') 61 | return false 62 | } 63 | } 64 | } 65 | 66 | function replaceInFile (filename, process) { 67 | const data = fs.readFileSync(filename, 'utf8') 68 | const result = process(data) 69 | fs.writeFileSync(filename, result, 'utf8') 70 | } 71 | 72 | module.exports = function ( 73 | appPath, 74 | appName, 75 | verbose, 76 | originalDirectory, 77 | template 78 | ) { 79 | const ownPath = path.dirname( 80 | require.resolve(path.join(__dirname, '..', 'package.json')) 81 | ) 82 | const appPackage = require(path.join(appPath, 'package.json')) 83 | const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock')) 84 | 85 | // Copy over some of the devDependencies 86 | const dependencies = appPackage.devDependencies || {} 87 | 88 | const useTypeScript = dependencies.typescript != null 89 | 90 | const user = username.sync() 91 | 92 | const gitConfig = fetchGitConfig() 93 | 94 | const devDependencies = appPackage.devDependencies 95 | delete appPackage.devDependencies 96 | 97 | appPackage.version = '0.0.0' 98 | 99 | if (gitConfig['user.name'] && gitConfig['user.email']) { 100 | appPackage.author = `${gitConfig['user.name']} <${gitConfig['user.email']}>` 101 | } 102 | 103 | if (user) { 104 | appPackage.repository = `${user}/${appPackage.name}` 105 | } 106 | 107 | appPackage.license = 'MIT' 108 | 109 | if (useTypeScript) { 110 | appPackage.main = 'lib/index.js' 111 | appPackage.files = ['lib'] 112 | } else { 113 | appPackage.main = 'src/index.js' 114 | appPackage.files = ['src'] 115 | } 116 | 117 | // Setup the script rules 118 | appPackage.scripts = { 119 | test: 'modern test', 120 | format: 'modern format', 121 | lint: 'modern lint', 122 | precommit: 'modern precommit' 123 | } 124 | 125 | // Move devDependencies to the end 126 | appPackage.devDependencies = devDependencies 127 | 128 | fs.writeFileSync( 129 | path.join(appPath, 'package.json'), 130 | JSON.stringify(appPackage, null, 2) + os.EOL 131 | ) 132 | 133 | const readmeExists = fs.existsSync(path.join(appPath, 'README.md')) 134 | if (readmeExists) { 135 | fs.renameSync( 136 | path.join(appPath, 'README.md'), 137 | path.join(appPath, 'README.old.md') 138 | ) 139 | } 140 | 141 | // Copy the files for the user 142 | const templatePath = template 143 | ? path.resolve(originalDirectory, template) 144 | : path.join(ownPath, useTypeScript ? 'template-typescript' : 'template') 145 | if (fs.existsSync(templatePath)) { 146 | fs.copySync(templatePath, appPath) 147 | } else { 148 | console.error( 149 | `Could not locate supplied template: ${chalk.green(templatePath)}` 150 | ) 151 | return 152 | } 153 | 154 | glob 155 | .sync(['**/*.{md,js,ts}', 'LICENSE'], { cwd: appPath }) 156 | .forEach(filename => { 157 | replaceInFile(path.join(appPath, filename), data => 158 | data 159 | .replace(/{{jsName}}/g, camelcase(appPackage.name)) 160 | .replace(/{{name}}/g, appPackage.name) 161 | .replace(/{{username}}/g, user) 162 | .replace(/{{fullname}}/g, gitConfig['user.name']) 163 | ) 164 | }) 165 | 166 | // Rename gitignore after the fact to prevent npm from renaming it to .npmignore 167 | // See: https://github.com/npm/npm/issues/1862 168 | try { 169 | fs.moveSync( 170 | path.join(appPath, 'gitignore'), 171 | path.join(appPath, '.gitignore'), 172 | [] 173 | ) 174 | } catch (err) { 175 | // Append if there's already a `.gitignore` file there 176 | if (err.code === 'EEXIST') { 177 | const data = fs.readFileSync(path.join(appPath, 'gitignore')) 178 | fs.appendFileSync(path.join(appPath, '.gitignore'), data) 179 | fs.unlinkSync(path.join(appPath, 'gitignore')) 180 | } else { 181 | throw err 182 | } 183 | } 184 | 185 | if (tryGitInitAndCommit(appPath)) { 186 | console.log() 187 | console.log('Initialized a git repository.') 188 | } 189 | 190 | require('./install') 191 | 192 | // Display the most elegant way to cd. 193 | // This needs to handle an undefined originalDirectory for 194 | // backward compatibility with old global-cli's. 195 | let cdpath 196 | if (originalDirectory && path.join(originalDirectory, appName) === appPath) { 197 | cdpath = appName 198 | } else { 199 | cdpath = appPath 200 | } 201 | 202 | // Change displayed command to yarn instead of yarnpkg 203 | const displayedCommand = useYarn ? 'yarn' : 'npm' 204 | 205 | console.log() 206 | console.log(`Success! Created ${appName} at ${appPath}`) 207 | console.log('Inside that directory, you can run several commands:') 208 | console.log() 209 | console.log(chalk.cyan(` ${displayedCommand} format`)) 210 | console.log(' Formats all files in repository.') 211 | console.log() 212 | console.log(chalk.cyan(` ${displayedCommand} lint`)) 213 | console.log(' Formats and lints all files in repository') 214 | console.log() 215 | console.log(chalk.cyan(` ${displayedCommand} test`)) 216 | console.log(' Runs all tests with Jest.') 217 | console.log() 218 | console.log() 219 | console.log('We suggest that you begin by typing:') 220 | console.log() 221 | console.log(chalk.cyan(' cd'), cdpath) 222 | console.log(` ${chalk.cyan(`${displayedCommand} test`)}`) 223 | if (readmeExists) { 224 | console.log() 225 | console.log( 226 | chalk.yellow( 227 | 'You had a `README.md` file, we renamed it to `README.old.md`' 228 | ) 229 | ) 230 | } 231 | console.log() 232 | console.log('Happy hacking!') 233 | console.log() 234 | console.log('btw. We are open to suggestions how to improve modern-node!') 235 | console.log( 236 | 'Please submit an issue on https://github.com/sheerun/modern-node' 237 | ) 238 | } 239 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const os = require('os') 4 | const readPkgUp = require('read-pkg-up') 5 | 6 | const { getGitFolderPath } = require('./utils') 7 | 8 | const env = process.env 9 | const isCi = 10 | env.CI || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari 11 | env.CONTINUOUS_INTEGRATION || // Travis CI, Cirrus CI 12 | env.BUILD_NUMBER || // Jenkins, TeamCity 13 | env.RUN_ID 14 | 15 | if (isCi && !env.FORCE_POSTINSTALL) { 16 | return 17 | } 18 | 19 | const root = path.resolve(__dirname, '..') 20 | const git = getGitFolderPath(root) 21 | 22 | // Bail out if we don't have an `.git` directory as the hooks will not get 23 | // triggered. If we do have directory create a hooks folder if it doesn't exist. 24 | if (!git) { 25 | console.warn('Not found any .git folder for installing pre-commit hook') 26 | console.warn('Please install modern-node again after initializing repository') 27 | return 28 | } 29 | 30 | let pkg 31 | try { 32 | const result = readPkgUp.sync({ cwd: path.dirname(git), normalize: false }) 33 | if (result) { 34 | pkg = result.packageJson 35 | } 36 | } catch (err) { 37 | if (err.code !== 'ENOENT') { 38 | throw err 39 | } 40 | } 41 | 42 | if (!pkg) { 43 | return 44 | } 45 | 46 | ;['pre-commit'].forEach(function (dep) { 47 | if (pkg.dependencies && pkg.dependencies[dep]) { 48 | console.warn( 49 | 'package.json: Please remove ' + dep + ' from your dependencies' 50 | ) 51 | } 52 | 53 | if (pkg.devDependencies && pkg.devDependencies[dep]) { 54 | console.warn( 55 | 'package.json: Please remove ' + dep + ' from your devDependencies' 56 | ) 57 | } 58 | }) 59 | 60 | if (pkg['pre-commit']) { 61 | console.warn( 62 | 'package.json: Please move pre-commit field to scripts.precommit' 63 | ) 64 | } 65 | 66 | if (pkg.precommit) { 67 | console.warn('package.json: Please move precommit field to scripts.precommit') 68 | } 69 | 70 | if (pkg.husky && pkg.husky.hooks && pkg.husky['pre-commit']) { 71 | console.warn( 72 | 'package.json: Please move husky.hooks.pre-commit to scripts.precommit' 73 | ) 74 | } 75 | 76 | const hooks = path.join(git, 'hooks') 77 | if (!fs.existsSync(hooks)) fs.mkdirSync(hooks) 78 | 79 | // If there's an existing `pre-commit` hook we want to back it up instead of 80 | // overriding it and losing it completely as it might contain something 81 | // important. 82 | const precommit = path.join(hooks, 'pre-commit') 83 | const precommitPath = path.join(__dirname, 'precommit.js') 84 | const precommitRelativeUnixPath = path.relative( 85 | path.join(git, '..'), 86 | precommitPath 87 | ) 88 | if (fs.existsSync(precommit) && !fs.lstatSync(precommit).isSymbolicLink()) { 89 | const body = fs.readFileSync(precommit) 90 | 91 | if (body.indexOf(precommitRelativeUnixPath) >= 0) { 92 | return 93 | } else { 94 | fs.writeFileSync(precommit + '.old', fs.readFileSync(precommit)) 95 | } 96 | } 97 | 98 | // We cannot create a symlink over an existing file so make sure it's gone and 99 | // finish the installation process. 100 | try { 101 | fs.unlinkSync(precommit) 102 | } catch (e) {} 103 | 104 | const hook = path.join(__dirname, 'hook') 105 | const hookRelativeUnixPath = path.relative(path.join(git, '..'), hook) 106 | 107 | const precommitContent = `#!/usr/bin/env bash 108 | 109 | set -e 110 | 111 | if [[ -f "${hookRelativeUnixPath}" && -f "${precommitRelativeUnixPath}" ]]; then 112 | "${hookRelativeUnixPath}" "${precommitRelativeUnixPath}" 113 | fi 114 | `.replace('\n', os.EOL) 115 | 116 | // It could be that we do not have rights to this folder which could cause the 117 | // installation of this module to completely fail. We should just output the 118 | // error instead destroying the whole npm install process. 119 | try { 120 | fs.writeFileSync(precommit, precommitContent) 121 | } catch (e) { 122 | console.error('Failed to create the hook file in your .git/hooks folder:') 123 | console.error(e.message) 124 | } 125 | 126 | try { 127 | fs.chmodSync(precommit, '774') 128 | } catch (e) { 129 | console.error('Failed to chmod 0774 .git/hooks/pre-commit:') 130 | console.error(e.message) 131 | } 132 | -------------------------------------------------------------------------------- /src/paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | const appDirectory = fs.realpathSync(process.cwd()) 6 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath) 7 | 8 | const moduleFileExtensions = [ 9 | 'web.mjs', 10 | 'mjs', 11 | 'web.js', 12 | 'js', 13 | 'web.ts', 14 | 'ts', 15 | 'web.tsx', 16 | 'tsx', 17 | 'json', 18 | 'web.jsx', 19 | 'jsx' 20 | ] 21 | 22 | // Resolve file paths in the same order as webpack 23 | const resolveModule = (resolveFn, filePath) => { 24 | const extension = moduleFileExtensions.find(extension => 25 | fs.existsSync(resolveFn(`${filePath}.${extension}`)) 26 | ) 27 | 28 | if (extension) { 29 | return resolveFn(`${filePath}.${extension}`) 30 | } 31 | 32 | return resolveFn(`${filePath}.js`) 33 | } 34 | 35 | // config after eject: we're in ./config/ 36 | module.exports = { 37 | dotenv: resolveApp('.env'), 38 | appPath: resolveApp('.'), 39 | appBuild: resolveApp('build'), 40 | appIndexJs: resolveModule(resolveApp, 'src/index'), 41 | appPackageJson: resolveApp('package.json'), 42 | appSrc: resolveApp('src'), 43 | appTsConfig: resolveApp('tsconfig.json'), 44 | appJsConfig: resolveApp('jsconfig.json'), 45 | yarnLockFile: resolveApp('yarn.lock'), 46 | testsSetup: resolveModule(resolveApp, 'src/setupTests'), 47 | appNodeModules: resolveApp('node_modules') 48 | } 49 | 50 | // @remove-on-eject-begin 51 | const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath) 52 | 53 | module.exports = { 54 | dotenv: resolveApp('.env'), 55 | appPath: resolveApp('.'), 56 | appBuild: resolveApp('build'), 57 | appIndexJs: resolveModule(resolveApp, 'src/index'), 58 | appPackageJson: resolveApp('package.json'), 59 | appSrc: resolveApp('src'), 60 | appTsConfig: resolveApp('tsconfig.json'), 61 | appJsConfig: resolveApp('jsconfig.json'), 62 | yarnLockFile: resolveApp('yarn.lock'), 63 | testsSetup: resolveModule(resolveApp, 'src/setupTests'), 64 | appNodeModules: resolveApp('node_modules'), 65 | // These properties only exist before ejecting: 66 | ownPath: resolveOwn('.'), 67 | ownNodeModules: resolveOwn('node_modules') // This is empty on npm 3 68 | } 69 | 70 | const ownPackageJson = require('../package.json') 71 | const modernNodePath = resolveApp(`node_modules/${ownPackageJson.name}`) 72 | const modernNodeLinked = 73 | fs.existsSync(modernNodePath) && fs.lstatSync(modernNodePath).isSymbolicLink() 74 | 75 | if ( 76 | !modernNodeLinked && 77 | __dirname.indexOf(path.join('packages', 'modern-node', 'src')) !== -1 78 | ) { 79 | module.exports = { 80 | dotenv: resolveOwn('template/.env'), 81 | appPath: resolveApp('.'), 82 | appBuild: resolveOwn('../../build'), 83 | appIndexJs: resolveModule(resolveOwn, 'template/src/index'), 84 | appPackageJson: resolveOwn('package.json'), 85 | appSrc: resolveOwn('template/src'), 86 | appTsConfig: resolveOwn('template/tsconfig.json'), 87 | appJsConfig: resolveOwn('template/jsconfig.json'), 88 | yarnLockFile: resolveOwn('template/yarn.lock'), 89 | testsSetup: resolveModule(resolveOwn, 'template/src/setupTests'), 90 | appNodeModules: resolveOwn('node_modules'), 91 | // These properties only exist before ejecting: 92 | ownPath: resolveOwn('.'), 93 | ownNodeModules: resolveOwn('node_modules'), 94 | appTypeDeclarations: resolveOwn('template/src/modern-node-env.d.ts'), 95 | ownTypeDeclarations: resolveOwn('src/modern-node.d.ts') 96 | } 97 | } 98 | 99 | module.exports.moduleFileExtensions = moduleFileExtensions 100 | -------------------------------------------------------------------------------- /src/precommit.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa') 2 | const readPkgUp = require('read-pkg-up') 3 | const path = require('path') 4 | 5 | let pkg 6 | try { 7 | pkg = readPkgUp.sync({ normalize: false }) 8 | } catch (err) { 9 | if (err.code !== 'ENOENT') { 10 | throw err 11 | } 12 | } 13 | 14 | if ( 15 | pkg && 16 | pkg.packageJson && 17 | pkg.packageJson.scripts && 18 | pkg.packageJson.scripts.precommit 19 | ) { 20 | const script = pkg.packageJson.scripts.precommit 21 | 22 | try { 23 | execa.sync(script, { 24 | cwd: path.dirname(pkg.path), 25 | env: {}, 26 | stdio: 'inherit', 27 | preferLocal: true, 28 | shell: true 29 | }) 30 | } catch (e) { 31 | if (e.exitCode && e.exitCode > 0) { 32 | process.exit(e.exitCode) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const jest = require('jest') 3 | 4 | // Do this as the first thing so that any code reading it knows the right env. 5 | process.env.BABEL_ENV = 'test' 6 | process.env.NODE_ENV = 'test' 7 | 8 | // This is not necessary after eject because we embed config into package.json. 9 | const createJestConfig = require('./utils/createJestConfig') 10 | const paths = require('./paths') 11 | 12 | let argv = process.argv.slice(2) 13 | 14 | argv.push( 15 | '--config', 16 | JSON.stringify( 17 | createJestConfig( 18 | relativePath => path.resolve(__dirname, '..', relativePath), 19 | path.resolve(paths.appSrc, '..'), 20 | false 21 | ) 22 | ) 23 | ) 24 | 25 | // This is a very dirty workaround for https://github.com/facebook/jest/issues/5913. 26 | // We're trying to resolve the environment ourselves because Jest does it incorrectly. 27 | // TODO: remove this as soon as it's fixed in Jest. 28 | const resolve = require('resolve') 29 | function resolveJestDefaultEnvironment (name) { 30 | const jestDir = path.dirname( 31 | resolve.sync('jest', { 32 | basedir: __dirname 33 | }) 34 | ) 35 | const jestCLIDir = path.dirname( 36 | resolve.sync('jest-cli', { 37 | basedir: jestDir 38 | }) 39 | ) 40 | const jestConfigDir = path.dirname( 41 | resolve.sync('jest-config', { 42 | basedir: jestCLIDir 43 | }) 44 | ) 45 | return resolve.sync(name, { 46 | basedir: jestConfigDir 47 | }) 48 | } 49 | const cleanArgv = [] 50 | let env = 'node' 51 | let next 52 | do { 53 | next = argv.shift() 54 | if (next === '--env') { 55 | env = argv.shift() 56 | } else if (next.indexOf('--env=') === 0) { 57 | env = next.substring('--env='.length) 58 | } else { 59 | cleanArgv.push(next) 60 | } 61 | } while (argv.length > 0) 62 | argv = cleanArgv 63 | let resolvedEnv 64 | try { 65 | resolvedEnv = resolveJestDefaultEnvironment(`jest-environment-${env}`) 66 | } catch (e) { 67 | // ignore 68 | } 69 | if (!resolvedEnv) { 70 | try { 71 | resolvedEnv = resolveJestDefaultEnvironment(env) 72 | } catch (e) { 73 | // ignore 74 | } 75 | } 76 | const testEnvironment = resolvedEnv || env 77 | argv.push('--env', testEnvironment) 78 | jest.run(argv) 79 | -------------------------------------------------------------------------------- /src/uninstall.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const { getGitFolderPath } = require('./utils') 5 | 6 | const root = path.resolve(__dirname, '..') 7 | const git = getGitFolderPath(root) 8 | 9 | // Location of pre-commit hook, if it exists 10 | var precommit = path.resolve(git, 'hooks', 'pre-commit') 11 | 12 | // Bail out if we don't have pre-commit file, it might be removed manually. 13 | if (!fs.existsSync(precommit)) return 14 | 15 | // If we don't have an old file, we should just remove the pre-commit hook. But 16 | // if we do have an old precommit file we want to restore that. 17 | if (!fs.existsSync(precommit + '.old')) { 18 | fs.unlinkSync(precommit) 19 | } else { 20 | fs.writeFileSync(precommit, fs.readFileSync(precommit + '.old')) 21 | fs.chmodSync(precommit, '755') 22 | fs.unlinkSync(precommit + '.old') 23 | } 24 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const findUp = require('find-up') 4 | 5 | function getGitFolderPath (root) { 6 | root = path.normalize(root.split('node_modules')[0]) 7 | let git = findUp.sync('.git', { type: 'directory', cwd: root }) 8 | 9 | // Resolve git directory for submodules 10 | if (fs.existsSync(git) && fs.lstatSync(git).isFile()) { 11 | const gitinfo = fs.readFileSync(git).toString() 12 | const gitdirmatch = /gitdir: (.+)/.exec(gitinfo) 13 | const gitdir = gitdirmatch.length === 2 ? gitdirmatch[1] : null 14 | 15 | if (gitdir !== null) { 16 | git = path.join(root, gitdir) 17 | } 18 | } 19 | 20 | return git 21 | } 22 | 23 | module.exports = { 24 | getGitFolderPath 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/createJestConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const chalk = require('chalk') 3 | const paths = require('../paths') 4 | 5 | module.exports = (resolve, rootDir, isEjecting) => { 6 | // Use this instead of `paths.testsSetup` to avoid putting 7 | // an absolute filename into configuration after ejecting. 8 | const setupTestsMatches = paths.testsSetup.match(/test[/\\]setupTests\.(.+)/) 9 | const setupTestsFileExtension = 10 | (setupTestsMatches && setupTestsMatches[1]) || 'js' 11 | const setupTestsFile = fs.existsSync(paths.testsSetup) 12 | ? `/test/setupTests.${setupTestsFileExtension}` 13 | : undefined 14 | 15 | const config = { 16 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'], 17 | 18 | setupFilesAfterEnv: setupTestsFile ? [setupTestsFile] : [], 19 | testMatch: [ 20 | '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', 21 | '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', 22 | '/test/**/__tests__/**/*.{js,jsx,ts,tsx}', 23 | '/test/**/*.{spec,test}.{js,jsx,ts,tsx}' 24 | ], 25 | testEnvironment: 'jest-environment-node', 26 | transformIgnorePatterns: [ 27 | '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$' 28 | ], 29 | moduleFileExtensions: [...paths.moduleFileExtensions, 'node'].filter( 30 | ext => !ext.includes('mjs') 31 | ), 32 | watchPlugins: [ 33 | 'jest-watch-typeahead/filename', 34 | 'jest-watch-typeahead/testname' 35 | ] 36 | } 37 | if (rootDir) { 38 | config.rootDir = rootDir 39 | } 40 | const overrides = Object.assign({}, require(paths.appPackageJson).jest) 41 | const supportedKeys = [ 42 | 'collectCoverageFrom', 43 | 'coverageReporters', 44 | 'coverageThreshold', 45 | 'coveragePathIgnorePatterns', 46 | 'extraGlobals', 47 | 'globalSetup', 48 | 'globalTeardown', 49 | 'moduleNameMapper', 50 | 'resetMocks', 51 | 'resetModules', 52 | 'snapshotSerializers', 53 | 'transform', 54 | 'transformIgnorePatterns', 55 | 'watchPathIgnorePatterns' 56 | ] 57 | if (overrides) { 58 | supportedKeys.forEach(key => { 59 | if (Object.prototype.hasOwnProperty.call(overrides, key)) { 60 | if (Array.isArray(config[key]) || typeof config[key] !== 'object') { 61 | // for arrays or primitive types, directly override the config key 62 | config[key] = overrides[key] 63 | } else { 64 | // for object types, extend gracefully 65 | config[key] = Object.assign({}, config[key], overrides[key]) 66 | } 67 | 68 | delete overrides[key] 69 | } 70 | }) 71 | const unsupportedKeys = Object.keys(overrides) 72 | if (unsupportedKeys.length) { 73 | const isOverridingSetupFile = 74 | unsupportedKeys.indexOf('setupFilesAfterEnv') > -1 75 | 76 | if (isOverridingSetupFile) { 77 | console.error( 78 | chalk.red( 79 | 'We detected ' + 80 | chalk.bold('setupFilesAfterEnv') + 81 | ' in your package.json.\n\n' + 82 | 'Remove it from Jest configuration, and put the initialization code in ' + 83 | chalk.bold('test/setupTests.js') + 84 | '.\nThis file will be loaded automatically.\n' 85 | ) 86 | ) 87 | } else { 88 | console.error( 89 | chalk.red( 90 | '\nOut of the box, Create Modern Node only supports overriding ' + 91 | 'these Jest options:\n\n' + 92 | supportedKeys 93 | .map(key => chalk.bold(' \u2022 ' + key)) 94 | .join('\n') + 95 | '.\n\n' + 96 | 'These options in your package.json Jest configuration ' + 97 | 'are not currently supported by Create Modern Node:\n\n' + 98 | unsupportedKeys 99 | .map(key => chalk.bold(' \u2022 ' + key)) 100 | .join('\n') + 101 | '\n\nIf you wish to override other Jest options, you need to ' + 102 | 'eject from the default setup. You can do so by running ' + 103 | chalk.bold('npm run eject') + 104 | ' but remember that this is a one-way operation. ' + 105 | 'You may also file an issue with Create Modern Node to discuss ' + 106 | 'supporting more options out of the box.\n' 107 | ) 108 | ) 109 | } 110 | 111 | process.exit(1) 112 | } 113 | } 114 | return config 115 | } 116 | -------------------------------------------------------------------------------- /template-typescript/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '12' 5 | - '10' 6 | - '8' 7 | 8 | before_install: 9 | - node --version 10 | - yarn --version 11 | 12 | install: 13 | - npm install 14 | 15 | os: 16 | - linux 17 | - osx 18 | 19 | matrix: 20 | fast_finish: true 21 | allow_failures: 22 | - os: osx 23 | 24 | script: 25 | - npm test 26 | -------------------------------------------------------------------------------- /template-typescript/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) {{fullname}} 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 | -------------------------------------------------------------------------------- /template-typescript/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | [![Build Status](https://travis-ci.org/{{username}}/{{name}}.svg?branch=master)](https://travis-ci.org/{{username}}/{{name}}) 4 | [![Modern Node](https://img.shields.io/badge/modern-node-9BB48F.svg)](https://github.com/sheerun/modern-node) 5 | 6 | > Short package description 7 | 8 | ## Installation 9 | 10 | ``` 11 | yarn add {{name}} 12 | ``` 13 | 14 | > If you're using [npm](https://www.npmjs.com/) you can use: `npm install {{name}}`. 15 | 16 | ## Usage 17 | 18 | Section describing how to use this package 19 | 20 | ## License 21 | 22 | MIT 23 | -------------------------------------------------------------------------------- /template-typescript/gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /template-typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | function {{jsName}}() { 2 | return true 3 | } 4 | 5 | export default {{jsName}} 6 | -------------------------------------------------------------------------------- /template-typescript/test/index.test.js: -------------------------------------------------------------------------------- 1 | const {{jsName}} = require('../') 2 | 3 | it('works', () => { 4 | expect({{jsName}}()).toBe(true) 5 | }) 6 | -------------------------------------------------------------------------------- /template/.prettierignore: -------------------------------------------------------------------------------- 1 | test/fixtures 2 | -------------------------------------------------------------------------------- /template/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '12' 5 | - '10' 6 | - '8' 7 | 8 | before_install: 9 | - node --version 10 | - yarn --version 11 | 12 | install: 13 | - npm install 14 | 15 | os: 16 | - linux 17 | - osx 18 | 19 | matrix: 20 | fast_finish: true 21 | allow_failures: 22 | - os: osx 23 | 24 | script: 25 | - npm test 26 | -------------------------------------------------------------------------------- /template/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) {{fullname}} 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 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | [![Build Status](https://travis-ci.org/{{username}}/{{name}}.svg?branch=master)](https://travis-ci.org/{{username}}/{{name}}) 4 | [![Modern Node](https://img.shields.io/badge/modern-node-9BB48F.svg)](https://github.com/sheerun/modern-node) 5 | 6 | > Short package description 7 | 8 | ## Installation 9 | 10 | ``` 11 | yarn add {{name}} 12 | ``` 13 | 14 | > If you're using [npm](https://www.npmjs.com/) you can use: `npm install {{name}}`. 15 | 16 | ## Usage 17 | 18 | Section describing how to use this package 19 | 20 | ## License 21 | 22 | MIT 23 | -------------------------------------------------------------------------------- /template/gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /template/src/index.js: -------------------------------------------------------------------------------- 1 | function {{jsName}} () { 2 | return true 3 | } 4 | 5 | module.exports = {{jsName}} 6 | -------------------------------------------------------------------------------- /template/test/index.test.js: -------------------------------------------------------------------------------- 1 | const {{jsName}} = require('../') 2 | 3 | it('works', () => { 4 | expect({{jsName}}()).toBe(true) 5 | }) 6 | -------------------------------------------------------------------------------- /test/cli.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const cp = require('child_process') 3 | const tmp = require('tmp') 4 | const path = require('path') 5 | const os = require('os') 6 | 7 | tmp.setGracefulCleanup() 8 | var tmpdir = tmp.dirSync().name 9 | 10 | function execSync (command, args) { 11 | return cp.execSync(command, { 12 | maxBuffer: 20 * 1024 * 1024, 13 | stdio: 'inherit', 14 | ...args 15 | }) 16 | } 17 | 18 | it('works for js repository', () => { 19 | const cwd = tmpdir + '/sandbox' 20 | 21 | fs.removeSync(cwd) 22 | execSync( 23 | `npm init modern-node sandbox --modern-version file:${process.cwd()}`, 24 | { 25 | cwd: tmpdir 26 | } 27 | ) 28 | execSync('npm run format', { cwd }) 29 | execSync('ls -lah', { cwd }) 30 | execSync('cat .gitignore', { cwd }) 31 | execSync('npm run lint', { cwd }) 32 | execSync('echo "console.log( 12);" > index.js', { cwd }) 33 | execSync('git add -A', { cwd }) 34 | execSync('git commit -m test', { cwd }) 35 | 36 | execSync('ls -lah 1>&2', { cwd }) 37 | execSync('ls -lah 1>&2', { cwd: path.join(cwd, '.git/hooks') }) 38 | execSync('cat pre-commit 1>&2', { cwd: path.join(cwd, '.git/hooks') }) 39 | 40 | const contents = fs.readFileSync(cwd + '/index.js', 'utf-8') 41 | expect(contents).toEqual('console.log(12)' + os.EOL) 42 | }) 43 | --------------------------------------------------------------------------------