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
JavaScript
#!/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));
}