UNPKG

templates-mo

Version:

Templates is a scaffolding framework that makes code generation simple, dynamic, and reusable. Generate files, parts of your app, or whole project structures—without the repetitive copy-pasting

174 lines (150 loc) 4.05 kB
/** * Inspired from hey-api/openapi-ts * https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/index.ts */ import { sync } from 'cross-spawn'; import type { SettingsFilePrompt } from '@tps/types/settings'; import path from 'path'; import Templates from '@tps/templates'; import { findUp } from '@tps/utilities/fileSystem'; const NONE = 'none'; type ExcludeNone<T extends string> = Exclude<T, typeof NONE>; export type OutputProcessor = { args: (paths: string[]) => ReadonlyArray<string>; command: string; name: string; }; type OutputProcessorMap<T extends string> = Record< ExcludeNone<T>, OutputProcessor >; function isErrnoException(error: unknown): error is NodeJS.ErrnoException { return (error as NodeJS.ErrnoException).code !== undefined; } export const FORMATTER_PROMPT = { name: 'formatter', aliases: ['format'], description: 'Type of formatter you would like to use to format the component', message: 'What type of formatter do you want to use to format your code', type: 'list', tpsType: 'data', hidden: true, choices: [NONE, 'prettier', 'biome'], default: NONE, } as const satisfies SettingsFilePrompt; export type formatters = (typeof FORMATTER_PROMPT.choices)[number]; /** * Map of supported formatters */ const formattersMap: OutputProcessorMap<formatters> = { biome: { args: (paths) => ['format', '--write', ...paths], command: 'biome', name: 'Biome (Format)', }, prettier: { args: (paths) => [ '--ignore-unknown', ...paths, '--write', '--ignore-path', './.prettierignore', ], command: 'prettier', name: 'Prettier', }, }; export const runFormatter = async ( formatter: formatters, cwd: string, paths: string[], tps?: Templates, ): Promise<void> => { if (formatter === NONE) { return; } const formatterObj = formattersMap[formatter] ?? null; if (formatterObj) { runCommand(formatterObj, cwd, paths, tps); } }; /** * Map of supported linters */ export const linters = { biome: { args: (paths) => ['lint', '--apply', ...paths], command: 'biome', name: 'Biome (Lint)', }, eslint: { args: (paths) => [...paths, '--fix'], command: 'eslint', name: 'ESLint', }, } as const satisfies Record<string, OutputProcessor>; export const runCommand = ( module: OutputProcessor, cwd: string, paths: string[], tps: Templates = null, ): void => { console.log(`✨ Running ${module.name}`); const templatesNodeModulesBin = path.resolve( __dirname, '../../', 'node_modules', '.bin', ); // TODO: Use find up // const destNodeModulesBin = path.resolve(cwd, 'node_modules', '.bin'); const parentNodeModules = findUp('node_modules', cwd); const destNodeModulesBin = parentNodeModules ? [path.resolve(parentNodeModules, '.bin')] : []; const templateNodeModuleBin = tps ? [path.resolve(tps.src, 'node_modules', '.bin')] : []; const bins = [ // Bins from main template directory which satisfies default templates templatesNodeModulesBin, // Bins from Modules from the `cwd` or in other words `dest` ...destNodeModulesBin, // Bins from template modules ...templateNodeModuleBin, process.env.PATH, ]; const envPath = bins.join(':'); const result = sync(module.command, module.args(paths), { env: { ...process.env, PATH: envPath }, }); if (result?.error) { const { error } = result; if (isErrnoException(error)) { if (error.code === 'ENOENT') { console.log(`❌ Command not found: ${module.command}`); return; } } console.log(`❌ ${module.name} failed!`); console.log(error); } else if (result?.status > 0) { // command error console.log(`❌ ${module.name} failed!`); console.log(result.stdout.toString()); console.log(result.stderr.toString()); } }; export const packageManagers = { npm: { args: (paths) => ['install', '--prefix', ...paths], command: 'npm', name: 'Npm Install', }, yarn: { args: (paths) => ['install', '--cwd', ...paths], command: 'yarn', name: 'Yarn Install', }, } as const satisfies Record<string, OutputProcessor>;