├── templates ├── javascript │ ├── src │ │ ├── main.js │ │ └── index.js │ └── package.json └── typescript │ ├── README.md │ ├── src │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── bin └── create-project ├── README.md ├── package.json ├── LICENSE ├── .gitignore └── src ├── cli.js └── main.js /templates/javascript/src/main.js: -------------------------------------------------------------------------------- 1 | console.log('Hello'); 2 | -------------------------------------------------------------------------------- /templates/typescript/README.md: -------------------------------------------------------------------------------- 1 | # `typescript-template` 2 | -------------------------------------------------------------------------------- /templates/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | console.log('Hello World'); 2 | -------------------------------------------------------------------------------- /bin/create-project: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require = require('esm')(module /*, options*/); 4 | require('../src/cli.js').cli(process.argv); 5 | -------------------------------------------------------------------------------- /templates/javascript/src/index.js: -------------------------------------------------------------------------------- 1 | // Set options as a parameter, environment variable, or rc file. 2 | require = require('esm')(module /*, options*/); 3 | module.exports = require('./main.js'); 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@dkundel/create-project` 2 | 3 | 🏗 Personal CLI to bootstrap new projects 4 | 5 | ## Installation & Usage 6 | 7 | ```bash 8 | npm init @dkundel/project 9 | # or 10 | npx @dkundel/create-project 11 | # or 12 | npm install -g @dkundel/create-project 13 | create-project 14 | ``` 15 | 16 | ## License 17 | 18 | MIT 19 | 20 | ## Collaborators 21 | 22 | - Dominik Kundel 23 | -------------------------------------------------------------------------------- /templates/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Dominik Kundel (https://dkundel.com)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "esm": "^3.2.18" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /templates/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "dev": "tsc --watch", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "Dominik Kundel (https://dkundel.com)", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "typescript": "^3.3.3333" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dkundel/create-project", 3 | "version": "1.0.0", 4 | "description": "A CLI to bootstrap my new projects", 5 | "main": "src/index.js", 6 | "bin": "bin/create-project", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "cli", 15 | "create-project" 16 | ], 17 | "author": "Dominik Kundel (https://dkundel.com)", 18 | "license": "MIT", 19 | "dependencies": { 20 | "arg": "^4.1.0", 21 | "chalk": "^2.4.2", 22 | "covgen": "^3.1.0", 23 | "esm": "^3.2.18", 24 | "execa": "^1.0.0", 25 | "gitignore": "^0.6.0", 26 | "inquirer": "^6.2.2", 27 | "listr": "^0.14.3", 28 | "ncp": "^2.0.0", 29 | "pkg-install": "^0.2.0", 30 | "spdx-license-list": "^5.0.0" 31 | }, 32 | "files": [ 33 | "bin/", 34 | "src/", 35 | "templates/" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dominik Kundel 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import arg from 'arg'; 2 | import inquirer from 'inquirer'; 3 | import { createProject } from './main'; 4 | 5 | function parseArgumentsIntoOptions(rawArgs) { 6 | const args = arg( 7 | { 8 | '--git': Boolean, 9 | '--yes': Boolean, 10 | '--install': Boolean, 11 | '-g': '--git', 12 | '-y': '--yes', 13 | '-i': '--install', 14 | }, 15 | { 16 | argv: rawArgs.slice(2), 17 | } 18 | ); 19 | return { 20 | skipPrompts: args['--yes'] || false, 21 | git: args['--git'] || false, 22 | template: args._[0], 23 | runInstall: args['--install'] || false, 24 | }; 25 | } 26 | 27 | async function promptForMissingOptions(options) { 28 | const defaultTemplate = 'javascript'; 29 | if (options.skipPrompts) { 30 | return { 31 | ...options, 32 | template: options.template || defaultTemplate, 33 | }; 34 | } 35 | 36 | const questions = []; 37 | if (!options.template) { 38 | questions.push({ 39 | type: 'list', 40 | name: 'template', 41 | message: 'Please choose which project template to use', 42 | choices: ['javascript', 'typescript'], 43 | default: defaultTemplate, 44 | }); 45 | } 46 | 47 | if (!options.git) { 48 | questions.push({ 49 | type: 'confirm', 50 | name: 'git', 51 | message: 'Should a git be initialized?', 52 | default: false, 53 | }); 54 | } 55 | 56 | const answers = await inquirer.prompt(questions); 57 | return { 58 | ...options, 59 | template: options.template || answers.template, 60 | git: options.git || answers.git, 61 | }; 62 | } 63 | 64 | export async function cli(args) { 65 | let options = parseArgumentsIntoOptions(args); 66 | options = await promptForMissingOptions(options); 67 | await createProject(options); 68 | } 69 | 70 | // ... 71 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import execa from 'execa'; 3 | import fs from 'fs'; 4 | import gitignore from 'gitignore'; 5 | import Listr from 'listr'; 6 | import ncp from 'ncp'; 7 | import path from 'path'; 8 | import { projectInstall } from 'pkg-install'; 9 | import license from 'spdx-license-list/licenses/MIT'; 10 | import { promisify } from 'util'; 11 | 12 | const access = promisify(fs.access); 13 | const writeFile = promisify(fs.writeFile); 14 | const copy = promisify(ncp); 15 | const writeGitignore = promisify(gitignore.writeFile); 16 | 17 | async function copyTemplateFiles(options) { 18 | return copy(options.templateDirectory, options.targetDirectory, { 19 | clobber: false, 20 | }); 21 | } 22 | 23 | async function createGitignore(options) { 24 | const file = fs.createWriteStream( 25 | path.join(options.targetDirectory, '.gitignore'), 26 | { flags: 'a' } 27 | ); 28 | return writeGitignore({ 29 | type: 'Node', 30 | file: file, 31 | }); 32 | } 33 | 34 | async function createLicense(options) { 35 | const targetPath = path.join(options.targetDirectory, 'LICENSE'); 36 | const licenseContent = license.licenseText 37 | .replace('', new Date().getFullYear()) 38 | .replace('', `${options.name} (${options.email})`); 39 | return writeFile(targetPath, licenseContent, 'utf8'); 40 | } 41 | 42 | async function initGit(options) { 43 | const result = await execa('git', ['init'], { 44 | cwd: options.targetDirectory, 45 | }); 46 | if (result.failed) { 47 | return Promise.reject(new Error('Failed to initialize git')); 48 | } 49 | return; 50 | } 51 | 52 | export async function createProject(options) { 53 | options = { 54 | ...options, 55 | targetDirectory: options.targetDirectory || process.cwd(), 56 | email: 'hi@dominik.dev', 57 | name: 'Dominik Kundel', 58 | }; 59 | 60 | const fullPathName = new URL(import.meta.url).pathname; 61 | const templateDir = path.resolve( 62 | fullPathName.substr(fullPathName.indexOf('/')), 63 | '../../templates', 64 | options.template.toLowerCase() 65 | ); 66 | options.templateDirectory = templateDir; 67 | 68 | try { 69 | await access(templateDir, fs.constants.R_OK); 70 | } catch (err) { 71 | console.error('%s Invalid template name', chalk.red.bold('ERROR')); 72 | process.exit(1); 73 | } 74 | 75 | const tasks = new Listr( 76 | [ 77 | { 78 | title: 'Copy project files', 79 | task: () => copyTemplateFiles(options), 80 | }, 81 | { 82 | title: 'Create gitignore', 83 | task: () => createGitignore(options), 84 | }, 85 | { 86 | title: 'Create License', 87 | task: () => createLicense(options), 88 | }, 89 | { 90 | title: 'Initialize git', 91 | task: () => initGit(options), 92 | enabled: () => options.git, 93 | }, 94 | { 95 | title: 'Install dependencies', 96 | task: () => 97 | projectInstall({ 98 | cwd: options.targetDirectory, 99 | }), 100 | skip: () => 101 | !options.runInstall 102 | ? 'Pass --install to automatically install dependencies' 103 | : undefined, 104 | }, 105 | ], 106 | { 107 | exitOnError: false, 108 | } 109 | ); 110 | 111 | await tasks.run(); 112 | console.log('%s Project ready', chalk.green.bold('DONE')); 113 | return true; 114 | } 115 | -------------------------------------------------------------------------------- /templates/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true /* Generates corresponding '.map' file. */, 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist" /* Redirect output structure to the directory. */, 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } 61 | --------------------------------------------------------------------------------