@rtorcato/js-tooling 2.5.0 → 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.
- package/dist/cli/commands/fix.js +24 -7
- package/dist/cli/commands/setup-presets.js +193 -0
- package/dist/cli/commands/setup.js +46 -6
- package/dist/cli/generators/build.js +10 -25
- package/dist/cli/generators/testing.js +2 -34
- package/dist/cli/index.js +148 -30
- package/package.json +13 -1
- package/tooling/commitlint/commitlint.mjs +3 -2
- package/tooling/playwright/playwright.config.d.mts +4 -0
- package/tooling/playwright/playwright.config.mjs +19 -0
- package/tooling/vite/vite.config.d.mts +4 -0
- package/tooling/vite/vite.config.mjs +18 -0
package/dist/cli/commands/fix.js
CHANGED
|
@@ -124,9 +124,10 @@ const FIXERS = [
|
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
target: 'husky',
|
|
127
|
-
description: 'Set up Husky + lint-staged
|
|
127
|
+
description: 'Set up Husky + lint-staged',
|
|
128
128
|
appliesTo: ['Husky', 'lint-staged'],
|
|
129
129
|
outputs: ['.husky/pre-commit', 'package.json (lint-staged field)'],
|
|
130
|
+
riskLevel: 'safe-merge',
|
|
130
131
|
canFixDrift: true,
|
|
131
132
|
async run({ targetDir, pkg }) {
|
|
132
133
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
@@ -209,9 +210,10 @@ const FIXERS = [
|
|
|
209
210
|
},
|
|
210
211
|
{
|
|
211
212
|
target: 'engines',
|
|
212
|
-
description: 'Add engines.node to package.json
|
|
213
|
+
description: 'Add engines.node to package.json',
|
|
213
214
|
appliesTo: ['engines.node'],
|
|
214
215
|
outputs: ['package.json (engines.node field)'],
|
|
216
|
+
riskLevel: 'safe-merge',
|
|
215
217
|
canFixDrift: true,
|
|
216
218
|
async run({ targetDir }) {
|
|
217
219
|
const result = await ensureEnginesNode(targetDir);
|
|
@@ -234,6 +236,7 @@ const FIXERS = [
|
|
|
234
236
|
description: 'Add @rtorcato/js-tooling to devDependencies',
|
|
235
237
|
appliesTo: ['package.json'],
|
|
236
238
|
outputs: ['package.json (devDependencies)'],
|
|
239
|
+
riskLevel: 'safe-merge',
|
|
237
240
|
canFixDrift: true,
|
|
238
241
|
async run({ targetDir, pkg }) {
|
|
239
242
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
@@ -282,15 +285,29 @@ async function applyFixer(fixer, result, targetDir, pkg, dryRun, silent) {
|
|
|
282
285
|
}
|
|
283
286
|
return { filesWritten, dryRun: false };
|
|
284
287
|
}
|
|
288
|
+
function promptMessageFor(fixer, result) {
|
|
289
|
+
const risk = fixer.riskLevel ?? 'destructive';
|
|
290
|
+
if (risk === 'safe-merge') {
|
|
291
|
+
return { message: `${fixer.description} (existing fields preserved)?`, default: true };
|
|
292
|
+
}
|
|
293
|
+
if (risk === 'safe-add') {
|
|
294
|
+
return { message: `${fixer.description}?`, default: true };
|
|
295
|
+
}
|
|
296
|
+
// destructive
|
|
297
|
+
if (result.status === 'drift') {
|
|
298
|
+
return {
|
|
299
|
+
message: `⚠️ ${fixer.description} — overwrite existing file? user customizations will be lost`,
|
|
300
|
+
default: false,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return { message: `Apply ${fixer.description}?`, default: true };
|
|
304
|
+
}
|
|
285
305
|
async function confirmApply(fixer, result, assumeYes) {
|
|
286
306
|
if (assumeYes)
|
|
287
307
|
return true;
|
|
288
|
-
const
|
|
289
|
-
const message = isDrift
|
|
290
|
-
? `⚠️ ${fixer.description} — overwrite existing file? user customizations will be lost`
|
|
291
|
-
: `Apply ${fixer.description}?`;
|
|
308
|
+
const { message, default: defaultValue } = promptMessageFor(fixer, result);
|
|
292
309
|
const { confirm } = await inquirer.prompt([
|
|
293
|
-
{ type: 'confirm', name: 'confirm', message, default:
|
|
310
|
+
{ type: 'confirm', name: 'confirm', message, default: defaultValue },
|
|
294
311
|
]);
|
|
295
312
|
return confirm === true;
|
|
296
313
|
}
|
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
|
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) {
|
|
@@ -53,32 +53,17 @@ console.log('Build completed!')
|
|
|
53
53
|
`;
|
|
54
54
|
await fs.writeFile(esbuildConfigPath, esbuildConfig);
|
|
55
55
|
}
|
|
56
|
-
async function generateViteConfig(config, targetDir) {
|
|
56
|
+
export async function generateViteConfig(config, targetDir) {
|
|
57
57
|
const viteConfigPath = path.join(targetDir, 'vite.config.ts');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resolve: {
|
|
68
|
-
alias: {
|
|
69
|
-
'@': '/src',
|
|
70
|
-
'~': '/src'
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
build: {
|
|
74
|
-
outDir: 'dist',
|
|
75
|
-
sourcemap: true,
|
|
76
|
-
},
|
|
77
|
-
server: {
|
|
78
|
-
port: 3000,
|
|
79
|
-
open: true
|
|
80
|
-
}
|
|
81
|
-
})
|
|
58
|
+
// React apps need the plugin; we layer it on top of the shipped preset.
|
|
59
|
+
const viteConfig = config.projectType === 'react-app'
|
|
60
|
+
? `import preset from '@rtorcato/js-tooling/vite'
|
|
61
|
+
import react from '@vitejs/plugin-react'
|
|
62
|
+
import { defineConfig, mergeConfig } from 'vite'
|
|
63
|
+
|
|
64
|
+
export default mergeConfig(preset, defineConfig({ plugins: [react()] }))
|
|
65
|
+
`
|
|
66
|
+
: `export { default } from '@rtorcato/js-tooling/vite'
|
|
82
67
|
`;
|
|
83
68
|
await fs.writeFile(viteConfigPath, viteConfig);
|
|
84
69
|
}
|
|
@@ -38,41 +38,9 @@ async function generateJestConfig(config, targetDir) {
|
|
|
38
38
|
`;
|
|
39
39
|
await fs.writeFile(jestConfigPath, jestConfig);
|
|
40
40
|
}
|
|
41
|
-
async function generatePlaywrightConfig(targetDir) {
|
|
41
|
+
export async function generatePlaywrightConfig(targetDir) {
|
|
42
42
|
const playwrightConfigPath = path.join(targetDir, 'playwright.config.ts');
|
|
43
|
-
const playwrightConfig = `
|
|
44
|
-
|
|
45
|
-
export default defineConfig({
|
|
46
|
-
testDir: './tests/e2e',
|
|
47
|
-
fullyParallel: true,
|
|
48
|
-
forbidOnly: !!process.env.CI,
|
|
49
|
-
retries: process.env.CI ? 2 : 0,
|
|
50
|
-
workers: process.env.CI ? 1 : undefined,
|
|
51
|
-
reporter: 'html',
|
|
52
|
-
use: {
|
|
53
|
-
baseURL: 'http://localhost:3000',
|
|
54
|
-
trace: 'on-first-retry',
|
|
55
|
-
},
|
|
56
|
-
projects: [
|
|
57
|
-
{
|
|
58
|
-
name: 'chromium',
|
|
59
|
-
use: { ...devices['Desktop Chrome'] },
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'firefox',
|
|
63
|
-
use: { ...devices['Desktop Firefox'] },
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'webkit',
|
|
67
|
-
use: { ...devices['Desktop Safari'] },
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
webServer: {
|
|
71
|
-
command: 'npm run dev',
|
|
72
|
-
url: 'http://localhost:3000',
|
|
73
|
-
reuseExistingServer: !process.env.CI,
|
|
74
|
-
},
|
|
75
|
-
})
|
|
43
|
+
const playwrightConfig = `export { default } from '@rtorcato/js-tooling/playwright'
|
|
76
44
|
`;
|
|
77
45
|
await fs.writeFile(playwrightConfigPath, playwrightConfig);
|
|
78
46
|
}
|
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
|
-
.
|
|
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
|
|
62
|
-
{
|
|
63
|
-
|
|
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.
|
|
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": [
|
|
@@ -47,10 +47,14 @@
|
|
|
47
47
|
"tooling/eslint/*.d.mts",
|
|
48
48
|
"tooling/eslint/types.d.ts",
|
|
49
49
|
"tooling/jest-presets/**",
|
|
50
|
+
"tooling/playwright/playwright.config.mjs",
|
|
51
|
+
"tooling/playwright/playwright.config.d.mts",
|
|
50
52
|
"tooling/prettier/index.mjs",
|
|
51
53
|
"tooling/prettier/index.d.mts",
|
|
52
54
|
"tooling/typescript/*.json",
|
|
53
55
|
"tooling/typescript/reset.d.ts",
|
|
56
|
+
"tooling/vite/vite.config.mjs",
|
|
57
|
+
"tooling/vite/vite.config.d.mts",
|
|
54
58
|
"tooling/vitest/vitest.config.mjs",
|
|
55
59
|
"tooling/vitest/vitest.config.d.mts",
|
|
56
60
|
"tooling/vitest/vitest.config.react.mjs",
|
|
@@ -88,6 +92,10 @@
|
|
|
88
92
|
"types": "./tooling/jest-presets/node/jest-preset.d.mts",
|
|
89
93
|
"import": "./tooling/jest-presets/node/jest-preset.mjs"
|
|
90
94
|
},
|
|
95
|
+
"./playwright": {
|
|
96
|
+
"types": "./tooling/playwright/playwright.config.d.mts",
|
|
97
|
+
"import": "./tooling/playwright/playwright.config.mjs"
|
|
98
|
+
},
|
|
91
99
|
"./prettier": {
|
|
92
100
|
"types": "./tooling/prettier/index.d.mts",
|
|
93
101
|
"import": "./tooling/prettier/index.mjs"
|
|
@@ -99,6 +107,10 @@
|
|
|
99
107
|
"./typescript/react": "./tooling/typescript/tsconfig.react.json",
|
|
100
108
|
"./typescript/test": "./tooling/typescript/tsconfig.test.json",
|
|
101
109
|
"./typescript/reset": "./tooling/typescript/reset.d.ts",
|
|
110
|
+
"./vite": {
|
|
111
|
+
"types": "./tooling/vite/vite.config.d.mts",
|
|
112
|
+
"import": "./tooling/vite/vite.config.mjs"
|
|
113
|
+
},
|
|
102
114
|
"./vitest/config": {
|
|
103
115
|
"types": "./tooling/vitest/vitest.config.d.mts",
|
|
104
116
|
"import": "./tooling/vitest/vitest.config.mjs"
|
|
@@ -20,8 +20,9 @@ export default {
|
|
|
20
20
|
"test",
|
|
21
21
|
],
|
|
22
22
|
],
|
|
23
|
-
// Enforce length limits
|
|
24
|
-
|
|
23
|
+
// Enforce length limits: 72 chars matches the conventional-commits
|
|
24
|
+
// recommendation and git's default email format
|
|
25
|
+
"header-max-length": [2, "always", 72],
|
|
25
26
|
"body-max-line-length": [2, "always", 72],
|
|
26
27
|
"footer-max-line-length": [2, "always", 72],
|
|
27
28
|
// Enforce case rules (allow common patterns)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: './tests/e2e',
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
forbidOnly: !!process.env.CI,
|
|
7
|
+
retries: process.env.CI ? 2 : 0,
|
|
8
|
+
workers: process.env.CI ? 1 : undefined,
|
|
9
|
+
reporter: 'html',
|
|
10
|
+
use: {
|
|
11
|
+
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000',
|
|
12
|
+
trace: 'on-first-retry',
|
|
13
|
+
},
|
|
14
|
+
projects: [
|
|
15
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
16
|
+
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
|
17
|
+
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
|
18
|
+
],
|
|
19
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
resolve: {
|
|
5
|
+
alias: {
|
|
6
|
+
'@': '/src',
|
|
7
|
+
'~': '/src',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
build: {
|
|
11
|
+
outDir: 'dist',
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
},
|
|
14
|
+
server: {
|
|
15
|
+
port: 3000,
|
|
16
|
+
open: true,
|
|
17
|
+
},
|
|
18
|
+
})
|