@rtorcato/js-tooling 2.4.0 → 2.5.1
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/README.md +3 -1
- package/dist/cli/commands/fix.js +90 -28
- package/dist/cli/commands/setup.js +31 -1
- package/dist/cli/generators/build.js +10 -25
- package/dist/cli/generators/testing.js +2 -34
- package/dist/cli/index.js +10 -1
- 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/tooling/eslint/README.md +0 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.
|
|
|
9
9
|
[](https://codecov.io/gh/rtorcato/js-tooling)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
Most tooling libraries give you one piece — just TypeScript configs, or just an ESLint preset. **js-tooling** covers the entire lifecycle: TypeScript, Biome/ESLint, Vitest/Jest, Commitlint, Husky, Semantic Release,
|
|
12
|
+
Most tooling libraries give you one piece — just TypeScript configs, or just an ESLint preset. **js-tooling** covers the entire lifecycle: TypeScript, Biome/ESLint, Vitest/Jest, Commitlint, Husky, Semantic Release, GitHub Actions CI, and supply-chain security (Dependabot + CodeQL) — all wired together. The interactive `setup` wizard scaffolds everything in one shot; `doctor` checks an existing project for drift; `fix` applies the missing pieces incrementally.
|
|
13
13
|
|
|
14
14
|
**[Full documentation →](https://rtorcato.github.io/js-tooling/)**
|
|
15
15
|
|
|
@@ -23,6 +23,8 @@ npx @rtorcato/js-tooling setup
|
|
|
23
23
|
|
|
24
24
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
25
25
|
|
|
26
|
+
**v2.4.0** — New `fix` command applies scaffolders for items `doctor` flags, with `--yes` and `--dry-run` flags. Drift never auto-overwrites — every existing file you'd lose is confirmed first. Doctor grew checks for `engines.node`, `.editorconfig`, `.nvmrc`, Husky, `lint-staged`, semantic-release, knip, GitHub Actions, GitLab CI, Dependabot, and CodeQL — plus a `Next steps:` footer that names the exact `fix` command to run for each finding. Setup wizard adds a "Include security automation?" prompt for Dependabot + CodeQL.
|
|
27
|
+
|
|
26
28
|
**v2.0.0** — All 39 tool packages moved from `dependencies` to `peerDependencies`. Add them to your own `devDependencies`. Also ships: `doctor` subcommand, generator unit tests, Dependabot, CI matrix (Node 22 + 24).
|
|
27
29
|
|
|
28
30
|
**v1.1.0** — Stricter commitlint limits, fix for CLI path resolution when copying configs.
|
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');
|
|
@@ -269,38 +272,74 @@ function logTargets() {
|
|
|
269
272
|
console.log(` ${chalk.green('●')} ${chalk.bold(f.target)}: ${chalk.gray(f.description)}`);
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
|
-
async function applyFixer(fixer, result, targetDir, pkg, dryRun) {
|
|
275
|
+
async function applyFixer(fixer, result, targetDir, pkg, dryRun, silent) {
|
|
273
276
|
if (dryRun) {
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
if (!silent) {
|
|
278
|
+
console.log(chalk.cyan(` [dry-run] would write: ${fixer.outputs.join(', ')}`));
|
|
279
|
+
}
|
|
280
|
+
return { filesWritten: [], dryRun: true };
|
|
276
281
|
}
|
|
277
282
|
const { filesWritten } = await fixer.run({ targetDir, pkg, result });
|
|
278
|
-
if (filesWritten.length > 0) {
|
|
283
|
+
if (!silent && filesWritten.length > 0) {
|
|
279
284
|
console.log(chalk.green(` ✅ wrote ${filesWritten.join(', ')}`));
|
|
280
285
|
}
|
|
281
|
-
return {
|
|
286
|
+
return { filesWritten, dryRun: false };
|
|
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 };
|
|
282
304
|
}
|
|
283
305
|
async function confirmApply(fixer, result, assumeYes) {
|
|
284
306
|
if (assumeYes)
|
|
285
307
|
return true;
|
|
286
|
-
const
|
|
287
|
-
const message = isDrift
|
|
288
|
-
? `⚠️ ${fixer.description} — overwrite existing file? user customizations will be lost`
|
|
289
|
-
: `Apply ${fixer.description}?`;
|
|
308
|
+
const { message, default: defaultValue } = promptMessageFor(fixer, result);
|
|
290
309
|
const { confirm } = await inquirer.prompt([
|
|
291
|
-
{ type: 'confirm', name: 'confirm', message, default:
|
|
310
|
+
{ type: 'confirm', name: 'confirm', message, default: defaultValue },
|
|
292
311
|
]);
|
|
293
312
|
return confirm === true;
|
|
294
313
|
}
|
|
314
|
+
function recordFor(target, check, doctorStatus, status, filesWritten) {
|
|
315
|
+
return { target, check, status, doctorStatus, filesWritten };
|
|
316
|
+
}
|
|
295
317
|
export async function fixCommand(target, options = {}) {
|
|
296
318
|
const targetDir = path.resolve(options.directory ?? process.cwd());
|
|
297
|
-
const assumeYes = options.yes === true;
|
|
298
319
|
const dryRun = options.dryRun === true;
|
|
320
|
+
const json = options.json === true;
|
|
321
|
+
// JSON mode implies --yes so prompts don't corrupt the output stream.
|
|
322
|
+
const assumeYes = options.yes === true || json;
|
|
323
|
+
const silent = json;
|
|
299
324
|
const pkg = await readPackageJson(targetDir);
|
|
300
325
|
const results = await runDoctor(targetDir);
|
|
326
|
+
const actions = [];
|
|
327
|
+
const emitJson = (resolvedTarget) => {
|
|
328
|
+
const payload = { directory: targetDir, target: resolvedTarget, actions };
|
|
329
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
330
|
+
};
|
|
301
331
|
if (target) {
|
|
302
332
|
const fixer = findFixer(target);
|
|
303
333
|
if (!fixer) {
|
|
334
|
+
if (json) {
|
|
335
|
+
console.log(JSON.stringify({
|
|
336
|
+
directory: targetDir,
|
|
337
|
+
error: 'unknown-target',
|
|
338
|
+
target,
|
|
339
|
+
available: FIXERS.map((f) => f.target),
|
|
340
|
+
}, null, 2));
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
304
343
|
console.error(chalk.red(`\n❌ Unknown fix target: ${target}\n`));
|
|
305
344
|
logTargets();
|
|
306
345
|
console.log();
|
|
@@ -309,46 +348,69 @@ export async function fixCommand(target, options = {}) {
|
|
|
309
348
|
const result = results.find((r) => fixer.appliesTo.includes(r.check)) ??
|
|
310
349
|
{ check: fixer.appliesTo[0] ?? fixer.target, status: 'missing', detail: '' };
|
|
311
350
|
if (result.status === 'ok') {
|
|
351
|
+
actions.push(recordFor(fixer.target, result.check, 'ok', 'already-ok', []));
|
|
352
|
+
if (json)
|
|
353
|
+
return emitJson(fixer.target);
|
|
312
354
|
console.log(chalk.green(`\n✅ ${result.check} is already configured\n`));
|
|
313
355
|
return;
|
|
314
356
|
}
|
|
315
|
-
|
|
357
|
+
if (!silent) {
|
|
358
|
+
console.log(chalk.cyan(`\n🔧 ${fixer.target} — ${chalk.bold(result.check)} is ${result.status}\n`));
|
|
359
|
+
}
|
|
316
360
|
const ok = await confirmApply(fixer, result, assumeYes);
|
|
317
361
|
if (!ok) {
|
|
362
|
+
actions.push(recordFor(fixer.target, result.check, result.status, 'skipped', []));
|
|
363
|
+
if (json)
|
|
364
|
+
return emitJson(fixer.target);
|
|
318
365
|
console.log(chalk.gray(' skipped\n'));
|
|
319
366
|
return;
|
|
320
367
|
}
|
|
321
|
-
await applyFixer(fixer, result, targetDir, pkg, dryRun);
|
|
368
|
+
const outcome = await applyFixer(fixer, result, targetDir, pkg, dryRun, silent);
|
|
369
|
+
actions.push(recordFor(fixer.target, result.check, result.status, outcome.dryRun ? 'dry-run' : 'applied', outcome.filesWritten));
|
|
370
|
+
if (json)
|
|
371
|
+
return emitJson(fixer.target);
|
|
322
372
|
console.log();
|
|
323
373
|
return;
|
|
324
374
|
}
|
|
325
|
-
// No target — walk all non-ok results
|
|
326
375
|
const fixable = results.filter((r) => r.status !== 'ok');
|
|
327
376
|
if (fixable.length === 0) {
|
|
377
|
+
if (json)
|
|
378
|
+
return emitJson(null);
|
|
328
379
|
console.log(chalk.green('\n✅ All checks pass — nothing to fix\n'));
|
|
329
380
|
return;
|
|
330
381
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
let
|
|
382
|
+
if (!silent) {
|
|
383
|
+
console.log(chalk.cyan(`\n🔧 ${fixable.length} item(s) to address\n`));
|
|
384
|
+
}
|
|
385
|
+
let appliedCount = 0;
|
|
386
|
+
let skippedCount = 0;
|
|
387
|
+
let unsupportedCount = 0;
|
|
335
388
|
for (const result of fixable) {
|
|
336
389
|
const fixer = findFixerForCheck(result.check);
|
|
337
390
|
if (!fixer) {
|
|
338
|
-
|
|
339
|
-
|
|
391
|
+
actions.push(recordFor(null, result.check, result.status, 'unsupported', []));
|
|
392
|
+
if (!silent)
|
|
393
|
+
console.log(chalk.gray(` — ${result.check}: no fixer registered`));
|
|
394
|
+
unsupportedCount++;
|
|
340
395
|
continue;
|
|
341
396
|
}
|
|
342
|
-
|
|
397
|
+
if (!silent) {
|
|
398
|
+
console.log(` ${chalk.bold(result.check)} (${result.status}) → ${fixer.target}`);
|
|
399
|
+
}
|
|
343
400
|
const ok = await confirmApply(fixer, result, assumeYes);
|
|
344
401
|
if (!ok) {
|
|
345
|
-
|
|
346
|
-
|
|
402
|
+
actions.push(recordFor(fixer.target, result.check, result.status, 'skipped', []));
|
|
403
|
+
if (!silent)
|
|
404
|
+
console.log(chalk.gray(' skipped'));
|
|
405
|
+
skippedCount++;
|
|
347
406
|
continue;
|
|
348
407
|
}
|
|
349
|
-
await applyFixer(fixer, result, targetDir, pkg, dryRun);
|
|
350
|
-
applied
|
|
408
|
+
const outcome = await applyFixer(fixer, result, targetDir, pkg, dryRun, silent);
|
|
409
|
+
actions.push(recordFor(fixer.target, result.check, result.status, outcome.dryRun ? 'dry-run' : 'applied', outcome.filesWritten));
|
|
410
|
+
appliedCount++;
|
|
351
411
|
}
|
|
412
|
+
if (json)
|
|
413
|
+
return emitJson(null);
|
|
352
414
|
console.log();
|
|
353
|
-
console.log(` Summary: ${chalk.green(`${
|
|
415
|
+
console.log(` Summary: ${chalk.green(`${appliedCount} applied`)}, ${chalk.gray(`${skippedCount} skipped`)}, ${chalk.yellow(`${unsupportedCount} unsupported`)}\n`);
|
|
354
416
|
}
|
|
@@ -220,6 +220,36 @@ function showNextSteps(config, _targetDir) {
|
|
|
220
220
|
steps.forEach((step, index) => {
|
|
221
221
|
console.log(` ${index + 1}. ${step}`);
|
|
222
222
|
});
|
|
223
|
-
|
|
223
|
+
const skipped = collectSkippedFixSuggestions(config);
|
|
224
|
+
if (skipped.length > 0) {
|
|
225
|
+
console.log(chalk.bold('\n💡 Want to add something you skipped?\n'));
|
|
226
|
+
for (const s of skipped) {
|
|
227
|
+
console.log(` ${chalk.gray('-')} ${s}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
console.log(chalk.dim('\n📁 All configuration files have been generated in your project directory.'));
|
|
224
231
|
console.log(chalk.dim(' You can modify them to suit your specific needs.\n'));
|
|
225
232
|
}
|
|
233
|
+
function collectSkippedFixSuggestions(config) {
|
|
234
|
+
const suggestions = [];
|
|
235
|
+
if (!config.gitHooks) {
|
|
236
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix husky` to add git hooks later');
|
|
237
|
+
}
|
|
238
|
+
if (!config.commitLint) {
|
|
239
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix commitlint` to add conventional-commit linting');
|
|
240
|
+
}
|
|
241
|
+
if (!config.semanticRelease && config.projectType === 'library') {
|
|
242
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix semantic-release` to add automated releases');
|
|
243
|
+
}
|
|
244
|
+
if (!config.securityAutomation) {
|
|
245
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix dependabot` and `fix codeql` for security automation');
|
|
246
|
+
}
|
|
247
|
+
if (config.linting.tool === 'none') {
|
|
248
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix biome` or `fix eslint` to add linting');
|
|
249
|
+
}
|
|
250
|
+
if (config.testing.framework === 'none') {
|
|
251
|
+
suggestions.push('Run `npx @rtorcato/js-tooling fix vitest` to add a test runner');
|
|
252
|
+
}
|
|
253
|
+
suggestions.push('Run `npx @rtorcato/js-tooling doctor` any time to audit drift');
|
|
254
|
+
return suggestions;
|
|
255
|
+
}
|
|
@@ -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
|
@@ -74,14 +74,21 @@ program
|
|
|
74
74
|
{ name: 'Playwright', desc: 'End-to-end testing configuration' },
|
|
75
75
|
{ name: 'Commitlint', desc: 'Conventional commit linting' },
|
|
76
76
|
{ name: 'Husky', desc: 'Git hooks for pre-commit validation' },
|
|
77
|
+
{ name: 'lint-staged', desc: 'Run linters on staged files (pairs with Husky)' },
|
|
77
78
|
{ name: 'Semantic Release', desc: 'Automated versioning and publishing' },
|
|
78
79
|
{ name: 'tsup', desc: 'TypeScript bundler configuration' },
|
|
79
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' },
|
|
80
86
|
];
|
|
81
87
|
configs.forEach(({ name, desc }) => {
|
|
82
88
|
console.log(` ${chalk.green('●')} ${chalk.bold(name)}: ${chalk.gray(desc)}`);
|
|
83
89
|
});
|
|
84
|
-
console.log(chalk.dim('\n💡 Run
|
|
90
|
+
console.log(chalk.dim('\n💡 Run `js-tooling setup` for a new project'));
|
|
91
|
+
console.log(chalk.dim(' or `js-tooling fix` to apply missing pieces to an existing one\n'));
|
|
85
92
|
});
|
|
86
93
|
program
|
|
87
94
|
.command('doctor')
|
|
@@ -95,10 +102,12 @@ program
|
|
|
95
102
|
.option('-d, --directory <path>', 'Target directory', process.cwd())
|
|
96
103
|
.option('--yes', 'Assume yes to all prompts (including drift overwrites)')
|
|
97
104
|
.option('--dry-run', 'Print what would change without writing files')
|
|
105
|
+
.option('--json', 'Emit machine-readable JSON output (implies --yes)')
|
|
98
106
|
.action((target, options) => fixCommand(target, {
|
|
99
107
|
directory: options.directory,
|
|
100
108
|
yes: options.yes,
|
|
101
109
|
dryRun: options.dryRun,
|
|
110
|
+
json: options.json,
|
|
102
111
|
}));
|
|
103
112
|
program.hook('preAction', async (_, actionCommand) => {
|
|
104
113
|
const name = actionCommand.name();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rtorcato/js-tooling",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
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
|
+
})
|
package/tooling/eslint/README.md
DELETED