UNPKG

schafott-cli

Version:

Scaffold CLI is a command line tool to generate scaffolds for your projects. It takes away the pain of creating the same files and folders over and over again for every new library project you start. Also it comes with ready-to-use configurations for Type

613 lines (561 loc) 16.4 kB
#!/usr/bin/env node import { select, Separator, input, checkbox } from '@inquirer/prompts'; import fs from 'fs/promises'; import chalk from 'chalk'; import { exec } from 'child_process'; import ora from 'ora'; import fs$1 from 'fs'; import path from 'path'; var name$1 = "libname"; var version$2 = "0.0.1"; var main$1 = "dist/index.js"; var module$1 = "dist/index.esm.js"; var types$1 = "./dist/types/index.d.ts"; var files$1 = [ "dist" ]; var scripts$1 = { build: "rollup -c", dev: "rollup -c -w" }; var keywords$1 = [ ]; var author$2 = ""; var license$1 = "ISC"; var description$2 = ""; var dependencies$1 = { }; var devDependencies$1 = { "@babel/cli": "^7.24.8", "@babel/core": "^7.24.9", "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "^7.24.7", "@eslint/js": "^9.7.0", "@rollup/plugin-terser": "0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/node": "^20.14.11", eslint: "^9.7.0", globals: "^15.8.0", prettier: "^3.3.3", "prettier-eslint": "^16.3.0", rollup: "^4.19.0", "rollup-plugin-typescript2": "^0.36.0", "ts-node": "^10.9.2", tslib: "^2.6.3", typescript: "^5.5.4" }; var pkgJson$1 = { name: name$1, version: version$2, main: main$1, module: module$1, types: types$1, files: files$1, scripts: scripts$1, keywords: keywords$1, author: author$2, license: license$1, description: description$2, dependencies: dependencies$1, devDependencies: devDependencies$1 }; var compilerOptions$1 = { target: "ES2015", module: "ES2015", moduleResolution: "node", declaration: true, outDir: "./dist", strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, removeComments: false }; var include$1 = [ "src/**/*" ]; var exclude$1 = [ "node_modules", "dist" ]; var tsConfigJson$1 = { compilerOptions: compilerOptions$1, include: include$1, exclude: exclude$1 }; const log = console.log; const validatePath = async (projectPath) => { if (await fs .access(projectPath) .then(() => true) .catch(() => false)) { const overwriteProjectFolder = await select({ message: 'Directory already exists, overwrite?', choices: [ { name: 'No', value: 'no' }, { name: 'Yes', value: 'yes' }, ], }); if (overwriteProjectFolder === 'no') { log(chalk.red('Aborted')); process.exit(0); } if (overwriteProjectFolder === 'yes') { await fs.rm(projectPath, { recursive: true }); await fs.mkdir(projectPath, { recursive: true }); await process.chdir(projectPath); } } await fs.mkdir(projectPath, { recursive: true }); await process.chdir(projectPath); }; const installDeps = async (deps) => { const hasChangesets = deps['@changesets/cli']; const installDeps = await select({ message: 'Install dependencies using npm?', choices: [ { name: 'No', value: 'no' }, { name: 'Yes', value: 'yes' }, ], default: 'yes', }); if (installDeps === 'yes') { const spinner = ora('Installing dependencies').start(); exec('npm install', (error) => { if (error) { console.error(`exec error: ${error}`); spinner.fail('Failed to install dependencies'); return; } spinner.succeed(`Dependencies installed. ${hasChangesets ? 'Run `changeset init` to setup changesets' : ''}`); }); } else { log(chalk.green('Done, run `npm install` to install dependencies')); } }; const writeFile = async (path, data) => { await fs .writeFile(path, data) .then(() => { log(chalk.green('CREATED') + ` ${path}`); }) .catch((err) => { log(chalk.red(err)); process.exit(1); }); }; const createDirectory = async (path) => { await fs .mkdir(path, { recursive: true }) .then(() => { log(chalk.green('CREATED DIR') + ` ${path}`); }) .catch((err) => { log(chalk.red(err)); process.exit(1); }); }; const rollupConfigLib$1 = `// rollup.config.js import typescript from '@rollup/plugin-typescript' import terser from '@rollup/plugin-terser' import pkg from './package.json' assert { type: 'json' } export default { input: 'src/index.ts', output: [ { file: pkg.module, format: 'es', sourcemap: true, }, { file: pkg.main, format: 'cjs', sourcemap: true, }, ], plugins: [ typescript(), terser({ format: { comments: (node, comment) => { const text = comment.value const type = comment.type if (type === 'comment2') { // multiline comment return /@preserve|@license|@cc_on/i.test(text) } }, }, }), ], } `; const createLibFiles = async (tsConfigScaffold, pkgJson) => { await writeFile('src/index.ts', ''); await writeFile('package.json', JSON.stringify(pkgJson, null, 2)); await writeFile('rollup.config.mjs', rollupConfigLib$1); fs$1.writeFileSync('tsconfig.json', JSON.stringify(tsConfigScaffold, null, 2)); }; const generateLibFiles = async (options) => { const { projectName, licence, projectFeatures, version, author, description } = options; const pkg = pkgJson$1; pkg.name = projectName; pkg.dependencies = {}; pkg.version = version; pkg.license = licence; pkg.author = author; pkg.description = description; pkg.scripts = Object.assign(Object.assign({}, pkg.scripts), (projectFeatures.includes('jest') && { test: 'jest' })); pkg.devDependencies = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, pkg.devDependencies), (projectFeatures.includes('changesets') && { '@changesets/cli': '^2.27.7' })), (projectFeatures.includes('prettier') && { prettier: '^3.3.3' })), (projectFeatures.includes('jest') && { jest: '^29.7.0' })), (projectFeatures.includes('jest') && { '@types/jest': '^29.5.12' })), (projectFeatures.includes('jest') && { 'ts-jest': '^29.2.5' })); const deps = Object.assign(Object.assign({}, pkg.devDependencies), pkg.dependencies); await installDeps(deps); await createLibFiles(tsConfigJson$1, pkgJson$1); }; const prompts = async () => { const selected = await select({ message: 'Package Type', choices: [ { name: 'JS-Library', value: 'lib' }, { name: 'React-Component-Library', value: 'react-lib' }, new Separator(), { name: 'cancel', value: 'cancel' }, ], }); if (selected === 'cancel') { process.exit(0); } const targetDirectory = await input({ message: 'Where do you want to create the project?', default: '.', }); const projectName = await input({ message: 'Project-Folder Name', }); const version = await input({ message: 'Version', default: '0.0.1', }); const description = await input({ message: 'Description', default: 'Your Description', }); const author = await input({ message: 'Author', default: 'Your Name', }); const licence = await input({ message: 'Licence', default: 'MIT', }); if (!projectName) { log(chalk.red('Project-Folder Name is required')); process.exit(1); } const projectFeatures = await checkbox({ message: 'Select Features', choices: [ { name: 'Prettier', value: 'prettier' }, { name: 'Changesets', value: 'changesets' }, { name: 'Jest', value: 'jest' }, ], }); return { selected, targetDirectory, projectName, projectFeatures, licence, version, description, author, }; }; const prettierConfig = { semi: true, singleQuote: true, trailingComma: 'all', printWidth: 100, bracketSpacing: true, endOfLine: 'lf', tabWidth: 2, useTabs: false, parser: 'typescript', }; const lintConfig = ` import globals from 'globals' import pluginJs from '@eslint/js' import tseslint from 'typescript-eslint' export default [ { files: ['**/*.{js,mjs,cjs,ts}'] }, { languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended, ...tseslint.configs.recommended, ] `; const gitignore = ` # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) dist/ # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next # temp folder temp/ # Rollup .rts2_cache_cjs .rts2_cache_es .rts2_cache_um`; const jestConfig = `module.exports = { preset: 'ts-jest', testEnvironment: 'node', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], }`; const buttonTestCode = ` /** * @jest-environment jsdom */ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { Button } from './components/Button'; import React from 'react'; test('loads and displays Hello', async () => { // ARRANGE render(<Button label="Hello" />); // ACT await screen.findByRole('button'); // ASSERT expect(screen.getByRole('button')).toHaveTextContent('Hello'); }); `; const writeBaseFiles = async (options) => { const { projectFeatures } = options; await createDirectory('src'); await writeFile('README.md', '# Project Title\n\nProject Description'); await writeFile('eslint.config.js', lintConfig); await writeFile('.gitignore', gitignore); if (projectFeatures.includes('prettier')) { await writeFile('.prettierrc', JSON.stringify(prettierConfig, null, 2)); } if (projectFeatures.includes('jest')) { await writeFile('jest.config.js', jestConfig); await writeFile('src/index.test.tsx', buttonTestCode); } }; var name = "test5"; var version$1 = "0.0.1"; var main = "dist/index.js"; var module = "dist/index.esm.js"; var types = "./dist/types/index.d.ts"; var files = [ "dist" ]; var scripts = { build: "rollup -c", dev: "rollup -c -w" }; var keywords = [ ]; var author$1 = "Your Name"; var license = "MIT"; var description$1 = "Your Description"; var dependencies = { }; var peerDependencies = { react: "^18.0.0", "react-dom": "^18.0.0" }; var devDependencies = { "@babel/cli": "^7.24.8", "@babel/core": "^7.24.9", "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "^7.24.7", "@babel/preset-react": "^7.24.7", "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-node-resolve": "15.2.3", "rollup-plugin-peer-deps-external": "2.2.4", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/node": "^20.14.11", "@types/react": "^18.3.9", eslint: "^9.7.0", globals: "^15.8.0", prettier: "^3.3.3", "prettier-eslint": "^16.3.0", rollup: "^4.19.0", "rollup-plugin-typescript2": "^0.36.0", "ts-node": "^10.9.2", tslib: "^2.6.3", typescript: "^5.5.4" }; var pkgJson = { name: name, version: version$1, main: main, module: module, types: types, files: files, scripts: scripts, keywords: keywords, author: author$1, license: license, description: description$1, dependencies: dependencies, peerDependencies: peerDependencies, devDependencies: devDependencies }; var compilerOptions = { target: "es5", module: "esnext", jsx: "react", sourceMap: true, outDir: "dist", strict: true, moduleResolution: "node", allowSyntheticDefaultImports: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }; var include = [ "src/**/*" ]; var exclude = [ "node_modules", "dist" ]; var tsConfigJson = { compilerOptions: compilerOptions, include: include, exclude: exclude }; const rollupConfigLib = `// rollup.config.mjs import babel from '@rollup/plugin-babel'; import resolve from '@rollup/plugin-node-resolve'; import external from 'rollup-plugin-peer-deps-external'; import commonjs from '@rollup/plugin-commonjs'; import typescript from '@rollup/plugin-typescript'; import terser from "@rollup/plugin-terser"; export default [ { input: './src/index.ts', output: [ { file: 'dist/index.js', format: 'cjs', sourcemap: true, }, { file: 'dist/index.es.js', format: 'es', exports: 'named', sourcemap: true, }, ], plugins: [ external(), resolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }), commonjs(), typescript(), babel({ babelHelpers: 'bundled', exclude: 'node_modules/**', extensions: ['.js', '.jsx', '.ts', '.tsx'], }), terser(), ], }, ]; `; const buttonExampleComponent = `//This is a simple Button Component. Use this as a starting point for your own components. import React from 'react'; interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { label: string; } export const Button = ({ label }: ButtonProps) => { return <button>{label}</button>; }; `; const reactIndexExport = `export { Button } from './components/Button';`; const createReactFiles = async (tsConfigScaffold, pkgJson) => { await writeFile('src/index.ts', reactIndexExport); await createDirectory('src/components'); await writeFile('src/components/Button.tsx', buttonExampleComponent); await writeFile('package.json', JSON.stringify(pkgJson, null, 2)); await writeFile('rollup.config.mjs', rollupConfigLib); fs$1.writeFileSync('tsconfig.json', JSON.stringify(tsConfigScaffold, null, 2)); }; const generateReactFiles = async (options) => { const { projectName, licence, projectFeatures, version, author, description } = options; const pkg = pkgJson; pkg.name = projectName; pkg.dependencies = {}; pkg.version = version; pkg.license = licence; pkg.author = author; pkg.description = description; pkg.scripts = Object.assign(Object.assign({}, pkg.scripts), (projectFeatures.includes('jest') && { test: 'jest' })); pkg.devDependencies = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, pkg.devDependencies), (projectFeatures.includes('jest') && { jest: '^29.7.0' })), (projectFeatures.includes('jest') && { '@types/jest': '^29.5.12' })), (projectFeatures.includes('jest') && { 'ts-jest': '^29.2.5' })), (projectFeatures.includes('jest') && { '@testing-library/react': '^15.0.7' })), (projectFeatures.includes('jest') && { '@testing-library/jest-dom': '^6.5.0' })), (projectFeatures.includes('jest') && { '@testing-library/dom': '^10.4.0' })), (projectFeatures.includes('jest') && { 'jest-environment-jsdom': '^29.7.0' })); const deps = Object.assign(Object.assign({}, pkg.devDependencies), pkg.dependencies); await createReactFiles(tsConfigJson, pkgJson); await installDeps(deps); }; const { selected, targetDirectory, projectName, projectFeatures, licence, description, author, version, } = await prompts(); const promptValues = { selected, targetDirectory, projectName, projectFeatures, licence, description, author, version, }; const projectPath = path.join(targetDirectory, projectName); await validatePath(path.join(process.cwd(), projectPath)); await writeBaseFiles(Object.assign({}, promptValues)); if (selected === 'lib') { await generateLibFiles(Object.assign({}, promptValues)); } if (selected === 'react-lib') { await generateReactFiles(Object.assign({}, promptValues)); }