@rtorcato/js-tooling 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ export const PRESET_NAMES = [
2
+ 'library',
3
+ 'web-app',
4
+ 'node-api',
5
+ 'nextjs-app',
6
+ 'react-app',
7
+ ];
8
+ const BASE = {
9
+ linting: { tool: 'biome' },
10
+ formatting: { tool: 'biome' },
11
+ testing: { framework: 'vitest', environment: 'node' },
12
+ gitHooks: true,
13
+ commitLint: true,
14
+ semanticRelease: false,
15
+ securityAutomation: true,
16
+ };
17
+ export function buildPresetConfig(name, projectName) {
18
+ switch (name) {
19
+ case 'library':
20
+ return {
21
+ ...BASE,
22
+ projectName,
23
+ projectType: 'library',
24
+ typescript: { enabled: true, config: 'base' },
25
+ semanticRelease: true,
26
+ bundler: 'tsup',
27
+ };
28
+ case 'web-app':
29
+ return {
30
+ ...BASE,
31
+ projectName,
32
+ projectType: 'web-app',
33
+ typescript: { enabled: true, config: 'base' },
34
+ testing: { framework: 'vitest', environment: 'browser' },
35
+ bundler: 'vite',
36
+ };
37
+ case 'node-api':
38
+ return {
39
+ ...BASE,
40
+ projectName,
41
+ projectType: 'node-api',
42
+ typescript: { enabled: true, config: 'node' },
43
+ bundler: 'esbuild',
44
+ };
45
+ case 'nextjs-app':
46
+ return {
47
+ ...BASE,
48
+ projectName,
49
+ projectType: 'nextjs-app',
50
+ typescript: { enabled: true, config: 'next' },
51
+ linting: { tool: 'eslint', eslintConfig: 'nextjs' },
52
+ formatting: { tool: 'prettier' },
53
+ bundler: 'none',
54
+ };
55
+ case 'react-app':
56
+ return {
57
+ ...BASE,
58
+ projectName,
59
+ projectType: 'react-app',
60
+ typescript: { enabled: true, config: 'react' },
61
+ testing: { framework: 'vitest', environment: 'browser' },
62
+ bundler: 'vite',
63
+ };
64
+ }
65
+ }
66
+ export const CONFIG_SCHEMA = {
67
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
68
+ $id: 'https://rtorcato.github.io/js-tooling/schemas/project-config.json',
69
+ title: 'ProjectConfig',
70
+ description: '@rtorcato/js-tooling setup configuration',
71
+ type: 'object',
72
+ additionalProperties: false,
73
+ required: [
74
+ 'projectName',
75
+ 'projectType',
76
+ 'typescript',
77
+ 'linting',
78
+ 'formatting',
79
+ 'testing',
80
+ 'gitHooks',
81
+ 'commitLint',
82
+ 'semanticRelease',
83
+ 'securityAutomation',
84
+ 'bundler',
85
+ ],
86
+ properties: {
87
+ projectName: { type: 'string', minLength: 1 },
88
+ projectType: {
89
+ type: 'string',
90
+ enum: ['library', 'web-app', 'node-api', 'nextjs-app', 'react-app'],
91
+ },
92
+ typescript: {
93
+ type: 'object',
94
+ additionalProperties: false,
95
+ required: ['enabled', 'config'],
96
+ properties: {
97
+ enabled: { type: 'boolean' },
98
+ config: { type: 'string', enum: ['base', 'react', 'next', 'node', 'express'] },
99
+ },
100
+ },
101
+ linting: {
102
+ type: 'object',
103
+ additionalProperties: false,
104
+ required: ['tool'],
105
+ properties: {
106
+ tool: { type: 'string', enum: ['biome', 'eslint', 'both', 'none'] },
107
+ eslintConfig: { type: 'string', enum: ['base', 'nextjs'] },
108
+ },
109
+ },
110
+ formatting: {
111
+ type: 'object',
112
+ additionalProperties: false,
113
+ required: ['tool'],
114
+ properties: { tool: { type: 'string', enum: ['biome', 'prettier', 'none'] } },
115
+ },
116
+ testing: {
117
+ type: 'object',
118
+ additionalProperties: false,
119
+ required: ['framework'],
120
+ properties: {
121
+ framework: { type: 'string', enum: ['vitest', 'jest', 'playwright', 'none'] },
122
+ environment: { type: 'string', enum: ['node', 'browser', 'both'] },
123
+ },
124
+ },
125
+ gitHooks: { type: 'boolean' },
126
+ commitLint: { type: 'boolean' },
127
+ semanticRelease: { type: 'boolean' },
128
+ securityAutomation: { type: 'boolean' },
129
+ bundler: { type: 'string', enum: ['tsup', 'esbuild', 'vite', 'none'] },
130
+ },
131
+ };
132
+ const ALLOWED_KEYS = new Set(CONFIG_SCHEMA.required);
133
+ export function validateProjectConfig(input) {
134
+ const errors = [];
135
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
136
+ return { valid: false, errors: ['Config must be a JSON object'] };
137
+ }
138
+ const obj = input;
139
+ for (const key of Object.keys(obj)) {
140
+ if (!ALLOWED_KEYS.has(key))
141
+ errors.push(`Unknown field: ${key}`);
142
+ }
143
+ for (const required of CONFIG_SCHEMA.required) {
144
+ if (!(required in obj))
145
+ errors.push(`Missing required field: ${required}`);
146
+ }
147
+ return { valid: errors.length === 0, errors };
148
+ }
149
+ export function computeFileList(config) {
150
+ const files = ['package.json'];
151
+ files.push('.editorconfig', '.nvmrc', 'knip.json');
152
+ if (config.typescript.enabled) {
153
+ files.push('tsconfig.json', 'reset.d.ts');
154
+ }
155
+ if (config.linting.tool === 'biome' || config.linting.tool === 'both') {
156
+ files.push('biome.jsonc');
157
+ }
158
+ if (config.linting.tool === 'eslint' || config.linting.tool === 'both') {
159
+ files.push('eslint.config.mjs');
160
+ }
161
+ if (config.linting.tool === 'eslint') {
162
+ files.push('prettier.config.mjs');
163
+ }
164
+ if (config.testing.framework === 'vitest') {
165
+ files.push('vitest.config.ts', 'vitest.setup.ts');
166
+ }
167
+ if (config.testing.framework === 'jest') {
168
+ files.push('jest.config.mjs');
169
+ }
170
+ if (config.testing.framework === 'playwright') {
171
+ files.push('playwright.config.ts');
172
+ }
173
+ if (config.gitHooks) {
174
+ files.push('.husky/pre-commit', '.gitignore');
175
+ }
176
+ if (config.commitLint) {
177
+ files.push('.husky/commit-msg', 'commitlint.config.mjs');
178
+ }
179
+ files.push('.github/workflows/ci.yml');
180
+ if (config.securityAutomation) {
181
+ files.push('.github/dependabot.yml', '.github/workflows/codeql.yml');
182
+ }
183
+ if (config.bundler === 'tsup')
184
+ files.push('tsup.config.ts');
185
+ else if (config.bundler === 'esbuild')
186
+ files.push('build.mjs');
187
+ else if (config.bundler === 'vite')
188
+ files.push('vite.config.ts');
189
+ if (config.semanticRelease)
190
+ files.push('release.config.mjs');
191
+ files.push('README.md');
192
+ return files;
193
+ }
@@ -1,17 +1,58 @@
1
+ import path from 'node:path';
1
2
  import chalk from 'chalk';
2
3
  import fs from 'fs-extra';
3
4
  import inquirer from 'inquirer';
4
- import path from 'node:path';
5
5
  import { generateConfigs } from '../generators/index.js';
6
6
  import { installDependencies } from '../utils/install.js';
7
+ import { buildPresetConfig, computeFileList, CONFIG_SCHEMA, PRESET_NAMES, validateProjectConfig, } from './setup-presets.js';
8
+ async function resolveConfig(options) {
9
+ if (options.config && options.preset) {
10
+ console.warn(chalk.yellow('āš ļø Both --config and --preset given; --config wins.\n'));
11
+ }
12
+ if (options.config) {
13
+ const configPath = path.resolve(options.config);
14
+ if (!(await fs.pathExists(configPath))) {
15
+ throw new Error(`Config file not found: ${configPath}`);
16
+ }
17
+ const raw = await fs.readJson(configPath);
18
+ const { valid, errors } = validateProjectConfig(raw);
19
+ if (!valid) {
20
+ throw new Error(`Invalid config:\n - ${errors.join('\n - ')}`);
21
+ }
22
+ return raw;
23
+ }
24
+ if (options.preset) {
25
+ if (!PRESET_NAMES.includes(options.preset)) {
26
+ throw new Error(`Unknown preset: ${options.preset}. Available: ${PRESET_NAMES.join(', ')}`);
27
+ }
28
+ const projectName = path.basename(path.resolve(options.directory));
29
+ return buildPresetConfig(options.preset, projectName);
30
+ }
31
+ return promptForConfig();
32
+ }
7
33
  export async function setupProject(options) {
34
+ if (options.configSchema) {
35
+ console.log(JSON.stringify(CONFIG_SCHEMA, null, 2));
36
+ return;
37
+ }
8
38
  const targetDir = path.resolve(options.directory);
9
- console.log(chalk.cyan('\nšŸ› ļø Welcome to JS Tooling Setup!\n'));
10
- console.log(chalk.gray(`Setting up tooling in: ${targetDir}\n`));
39
+ const interactive = !options.config && !options.preset;
40
+ const dryRun = options.dryRun === true;
41
+ if (interactive && !dryRun) {
42
+ console.log(chalk.cyan('\nšŸ› ļø Welcome to JS Tooling Setup!\n'));
43
+ console.log(chalk.gray(`Setting up tooling in: ${targetDir}\n`));
44
+ }
11
45
  try {
12
- // Check if directory exists and is writable
13
46
  await fs.ensureDir(targetDir);
14
- const config = await promptForConfig();
47
+ const config = await resolveConfig(options);
48
+ if (dryRun) {
49
+ const files = computeFileList(config);
50
+ console.log(JSON.stringify({ directory: targetDir, config, files }, null, 2));
51
+ return;
52
+ }
53
+ if (!interactive) {
54
+ console.log(chalk.cyan(`\nšŸ› ļø Scaffolding ${config.projectType} in ${targetDir}\n`));
55
+ }
15
56
  console.log(chalk.cyan('\nšŸ“ Generating configuration files...\n'));
16
57
  await generateConfigs(config, targetDir);
17
58
  if (!options.skipInstall) {
@@ -19,7 +60,6 @@ export async function setupProject(options) {
19
60
  await installDependencies(config, targetDir);
20
61
  }
21
62
  console.log(chalk.green('\nāœ… Setup completed successfully!\n'));
22
- // Show next steps
23
63
  showNextSteps(config, targetDir);
24
64
  }
25
65
  catch (error) {
package/dist/cli/index.js CHANGED
@@ -28,6 +28,10 @@ program
28
28
  .description('šŸš€ Setup tooling for a new or existing project')
29
29
  .option('-d, --directory <path>', 'Target directory for setup', process.cwd())
30
30
  .option('--skip-install', 'Skip installing dependencies')
31
+ .option('--preset <name>', 'Skip prompts; use defaults for a project type')
32
+ .option('--config <path>', 'Skip prompts; read a JSON ProjectConfig from <path>')
33
+ .option('--dry-run', 'Print the resolved config and file list, write nothing')
34
+ .option('--config-schema', 'Print the JSON Schema for ProjectConfig and exit')
31
35
  .action(setupProject);
32
36
  program
33
37
  .command('copy <config>')
@@ -52,41 +56,155 @@ program
52
56
  console.error(chalk.red(`\nāŒ Error copying configuration: ${error}\n`));
53
57
  }
54
58
  });
59
+ const TOOL_CATALOG = [
60
+ {
61
+ name: 'TypeScript',
62
+ description: 'Base, React, Next.js, Node.js, Express tsconfig presets',
63
+ exports: [
64
+ '@rtorcato/js-tooling/typescript/base',
65
+ '@rtorcato/js-tooling/typescript/react',
66
+ '@rtorcato/js-tooling/typescript/next',
67
+ '@rtorcato/js-tooling/typescript/node',
68
+ '@rtorcato/js-tooling/typescript/express',
69
+ '@rtorcato/js-tooling/typescript/test',
70
+ '@rtorcato/js-tooling/typescript/reset',
71
+ ],
72
+ fixTarget: 'tsconfig',
73
+ },
74
+ {
75
+ name: 'ESLint',
76
+ description: 'Base and Next.js ESLint configurations',
77
+ exports: ['@rtorcato/js-tooling/eslint/base', '@rtorcato/js-tooling/eslint/nextjs'],
78
+ fixTarget: 'eslint',
79
+ },
80
+ {
81
+ name: 'Biome',
82
+ description: 'Fast formatter and linter configuration',
83
+ exports: ['@rtorcato/js-tooling/biome'],
84
+ fixTarget: 'biome',
85
+ },
86
+ {
87
+ name: 'Prettier',
88
+ description: 'Code formatter configuration',
89
+ exports: ['@rtorcato/js-tooling/prettier'],
90
+ fixTarget: 'prettier',
91
+ },
92
+ {
93
+ name: 'Vitest',
94
+ description: 'Testing framework configuration',
95
+ exports: [
96
+ '@rtorcato/js-tooling/vitest/config',
97
+ '@rtorcato/js-tooling/vitest/react',
98
+ '@rtorcato/js-tooling/vitest/setup',
99
+ ],
100
+ fixTarget: 'vitest',
101
+ },
102
+ {
103
+ name: 'Jest',
104
+ description: 'Testing framework presets for browser and Node.js',
105
+ exports: [
106
+ '@rtorcato/js-tooling/jest-presets/browser/jest-preset',
107
+ '@rtorcato/js-tooling/jest-presets/node/jest-preset',
108
+ ],
109
+ fixTarget: null,
110
+ },
111
+ {
112
+ name: 'Playwright',
113
+ description: 'End-to-end testing configuration',
114
+ exports: ['@rtorcato/js-tooling/playwright'],
115
+ fixTarget: null,
116
+ },
117
+ {
118
+ name: 'Commitlint',
119
+ description: 'Conventional commit linting',
120
+ exports: ['@rtorcato/js-tooling/commitlint/config'],
121
+ fixTarget: 'commitlint',
122
+ },
123
+ {
124
+ name: 'Husky',
125
+ description: 'Git hooks for pre-commit validation',
126
+ exports: [],
127
+ fixTarget: 'husky',
128
+ },
129
+ {
130
+ name: 'lint-staged',
131
+ description: 'Run linters on staged files (pairs with Husky)',
132
+ exports: [],
133
+ fixTarget: 'husky',
134
+ },
135
+ {
136
+ name: 'Semantic Release',
137
+ description: 'Automated versioning and publishing',
138
+ exports: [
139
+ '@rtorcato/js-tooling/semantic-release',
140
+ '@rtorcato/js-tooling/semantic-release/github',
141
+ '@rtorcato/js-tooling/semantic-release/docker',
142
+ ],
143
+ fixTarget: 'semantic-release',
144
+ },
145
+ {
146
+ name: 'tsup',
147
+ description: 'TypeScript bundler configuration',
148
+ exports: ['@rtorcato/js-tooling/tsup'],
149
+ fixTarget: null,
150
+ },
151
+ {
152
+ name: 'esbuild',
153
+ description: 'Fast JavaScript bundler configuration',
154
+ exports: ['@rtorcato/js-tooling/esbuild'],
155
+ fixTarget: null,
156
+ },
157
+ {
158
+ name: 'Vite',
159
+ description: 'Modern web app build tool configuration',
160
+ exports: ['@rtorcato/js-tooling/vite'],
161
+ fixTarget: null,
162
+ },
163
+ {
164
+ name: 'EditorConfig',
165
+ description: 'Cross-editor formatting consistency (.editorconfig)',
166
+ exports: [],
167
+ fixTarget: 'editorconfig',
168
+ },
169
+ {
170
+ name: '.nvmrc',
171
+ description: 'Pin Node version per repository',
172
+ exports: [],
173
+ fixTarget: 'nvmrc',
174
+ },
175
+ {
176
+ name: 'knip',
177
+ description: 'Find unused files, exports, and dependencies',
178
+ exports: [],
179
+ fixTarget: 'knip',
180
+ },
181
+ {
182
+ name: 'Dependabot',
183
+ description: 'Weekly automated dependency updates',
184
+ exports: [],
185
+ fixTarget: 'dependabot',
186
+ },
187
+ {
188
+ name: 'CodeQL',
189
+ description: 'GitHub security scanning workflow',
190
+ exports: [],
191
+ fixTarget: 'codeql',
192
+ },
193
+ ];
55
194
  program
56
195
  .command('list')
57
196
  .alias('ls')
58
197
  .description('šŸ“‹ List all available tooling configurations')
59
- .action(() => {
198
+ .option('--json', 'Emit machine-readable JSON output')
199
+ .action((options) => {
200
+ if (options.json) {
201
+ console.log(JSON.stringify({ tools: TOOL_CATALOG }, null, 2));
202
+ return;
203
+ }
60
204
  console.log(chalk.cyan('\nšŸ› ļø Available tooling configurations:\n'));
61
- const configs = [
62
- {
63
- name: 'TypeScript',
64
- desc: 'Base, React, Next.js, Node.js, Express configurations',
65
- },
66
- { name: 'ESLint', desc: 'Base and Next.js ESLint configurations' },
67
- { name: 'Biome', desc: 'Fast formatter and linter configuration' },
68
- { name: 'Prettier', desc: 'Code formatter configuration' },
69
- { name: 'Vitest', desc: 'Testing framework configuration' },
70
- {
71
- name: 'Jest',
72
- desc: 'Testing framework presets for browser and Node.js',
73
- },
74
- { name: 'Playwright', desc: 'End-to-end testing configuration' },
75
- { name: 'Commitlint', desc: 'Conventional commit linting' },
76
- { name: 'Husky', desc: 'Git hooks for pre-commit validation' },
77
- { name: 'lint-staged', desc: 'Run linters on staged files (pairs with Husky)' },
78
- { name: 'Semantic Release', desc: 'Automated versioning and publishing' },
79
- { name: 'tsup', desc: 'TypeScript bundler configuration' },
80
- { name: 'esbuild', desc: 'Fast JavaScript bundler configuration' },
81
- { name: 'EditorConfig', desc: 'Cross-editor formatting consistency (.editorconfig)' },
82
- { name: '.nvmrc', desc: 'Pin Node version per repository' },
83
- { name: 'knip', desc: 'Find unused files, exports, and dependencies' },
84
- { name: 'Dependabot', desc: 'Weekly automated dependency updates' },
85
- { name: 'CodeQL', desc: 'GitHub security scanning workflow' },
86
- ];
87
- configs.forEach(({ name, desc }) => {
88
- console.log(` ${chalk.green('ā—')} ${chalk.bold(name)}: ${chalk.gray(desc)}`);
89
- });
205
+ for (const { name, description } of TOOL_CATALOG) {
206
+ console.log(` ${chalk.green('ā—')} ${chalk.bold(name)}: ${chalk.gray(description)}`);
207
+ }
90
208
  console.log(chalk.dim('\nšŸ’” Run `js-tooling setup` for a new project'));
91
209
  console.log(chalk.dim(' or `js-tooling fix` to apply missing pieces to an existing one\n'));
92
210
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/js-tooling",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.",
5
5
  "type": "module",
6
6
  "keywords": [