@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 CHANGED
@@ -9,7 +9,7 @@ JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.
9
9
  [![Coverage](https://codecov.io/gh/rtorcato/js-tooling/branch/main/graph/badge.svg)](https://codecov.io/gh/rtorcato/js-tooling)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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, and GitHub Actions CI — all wired together. The interactive `setup` wizard scaffolds everything in one shot; `doctor` checks an existing project for drift.
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.
@@ -124,9 +124,10 @@ const FIXERS = [
124
124
  },
125
125
  {
126
126
  target: 'husky',
127
- description: 'Set up Husky + lint-staged (deep-merges existing lint-staged field)',
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 (never overwrites)',
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
- console.log(chalk.cyan(` [dry-run] would write: ${fixer.outputs.join(', ')}`));
275
- return { applied: true, written: [] };
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 { applied: true, written: filesWritten };
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 isDrift = result.status === 'drift';
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: !isDrift },
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
- console.log(chalk.cyan(`\n🔧 ${fixer.target} — ${chalk.bold(result.check)} is ${result.status}\n`));
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
- console.log(chalk.cyan(`\n🔧 ${fixable.length} item(s) to address\n`));
332
- let applied = 0;
333
- let skipped = 0;
334
- let unsupported = 0;
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
- console.log(chalk.gray(` — ${result.check}: no fixer registered`));
339
- unsupported++;
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
- console.log(` ${chalk.bold(result.check)} (${result.status}) → ${fixer.target}`);
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
- console.log(chalk.gray(' skipped'));
346
- skipped++;
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(`${applied} applied`)}, ${chalk.gray(`${skipped} skipped`)}, ${chalk.yellow(`${unsupported} unsupported`)}\n`);
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
- console.log(chalk.dim('\n💡 All configuration files have been generated in your project directory.'));
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
- let viteConfig = `import { defineConfig } from 'vite'
59
- `;
60
- if (config.projectType === 'react-app') {
61
- viteConfig += `import react from '@vitejs/plugin-react'
62
- `;
63
- }
64
- viteConfig += `
65
- export default defineConfig({
66
- plugins: [${config.projectType === 'react-app' ? 'react()' : ''}],
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 = `import { defineConfig } from '@playwright/test'
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 "js-tooling setup" to configure your project\n'));
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.4.0",
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 (strict)
24
- "header-max-length": [2, "always", 50],
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,4 @@
1
+ import type { PlaywrightTestConfig } from '@playwright/test'
2
+
3
+ declare const config: PlaywrightTestConfig
4
+ export default config
@@ -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,4 @@
1
+ import type { UserConfig } from 'vite'
2
+
3
+ declare const config: UserConfig
4
+ export default config
@@ -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
+ })
@@ -1,3 +0,0 @@
1
- # `@turbo/eslint-config`
2
-
3
- Collection of internal eslint configurations.