create-packkit 3.0.0 → 3.1.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.
@@ -1,3 +1,4 @@
1
+ import { V } from '../versions.js';
1
2
  // HTTP service target. Supports Hono (default), Fastify and Express. Splits the
2
3
  // app (testable) from the server entry (which listens), adds start/dev scripts,
3
4
  // and a production Dockerfile.
@@ -24,7 +25,7 @@ export default {
24
25
  },
25
26
  dependencies: deps(fw),
26
27
  devDependencies: {
27
- ...(cfg.isTs ? { tsx: '^4.0.0' } : {}),
28
+ ...(cfg.isTs ? { tsx: V.tsx } : {}),
28
29
  ...(cfg.isTs ? typeDeps(fw) : {}),
29
30
  },
30
31
  },
@@ -33,13 +34,13 @@ export default {
33
34
  };
34
35
 
35
36
  function deps(fw) {
36
- if (fw === 'fastify') return { fastify: '^5.0.0' };
37
- if (fw === 'express') return { express: '^5.0.0' };
38
- return { hono: '^4.5.0', '@hono/node-server': '^2.0.0' };
37
+ if (fw === 'fastify') return { fastify: V.fastify };
38
+ if (fw === 'express') return { express: V.express };
39
+ return { hono: V.hono, '@hono/node-server': V['@hono/node-server'] };
39
40
  }
40
41
 
41
42
  function typeDeps(fw) {
42
- if (fw === 'express') return { '@types/express': '^5.0.0' };
43
+ if (fw === 'express') return { '@types/express': V['@types/express'] };
43
44
  return {};
44
45
  }
45
46
 
@@ -2,6 +2,7 @@
2
2
  // brotli-compressed size of the built entry and fails when it exceeds the
3
3
  // limit — cheap insurance against a dependency silently bloating the package.
4
4
  import { toJson } from '../render.js';
5
+ import { V } from '../versions.js';
5
6
 
6
7
  export default {
7
8
  id: 'sizelimit',
@@ -14,8 +15,8 @@ export default {
14
15
  pkg: {
15
16
  scripts: { size: 'size-limit' },
16
17
  devDependencies: {
17
- 'size-limit': '^11.0.0',
18
- '@size-limit/preset-small-lib': '^11.0.0',
18
+ 'size-limit': V['size-limit'],
19
+ '@size-limit/preset-small-lib': V['@size-limit/preset-small-lib'],
19
20
  },
20
21
  },
21
22
  };
@@ -1,3 +1,4 @@
1
+ import { V } from '../versions.js';
1
2
  // Storybook for component libraries (React / Vue / Svelte), on the Vite builder.
2
3
 
3
4
  const FW = {
@@ -37,10 +38,10 @@ export default {
37
38
  'build-storybook': 'storybook build',
38
39
  },
39
40
  devDependencies: {
40
- storybook: '^10.0.0',
41
- [fw.builder]: '^10.0.0',
42
- [fw.renderer]: '^10.0.0',
43
- vite: '^8.0.0',
41
+ storybook: V.storybook,
42
+ [fw.builder]: V[fw.builder],
43
+ [fw.renderer]: V[fw.renderer],
44
+ vite: V.vite,
44
45
  },
45
46
  },
46
47
  };
@@ -1,4 +1,4 @@
1
- import { toJson } from '../render.js';
1
+ import { V } from '../versions.js';
2
2
 
3
3
  export default {
4
4
  id: 'test',
@@ -38,40 +38,40 @@ export default {
38
38
  ].filter((l) => l !== null).join('\n');
39
39
  pkg.scripts.test = 'vitest run';
40
40
  pkg.scripts['test:watch'] = 'vitest';
41
- pkg.devDependencies.vitest = '^4.0.0';
41
+ pkg.devDependencies.vitest = V.vitest;
42
42
  if (cfg.hasFramework) {
43
- pkg.devDependencies.jsdom = '^29.0.0';
44
- pkg.devDependencies['@testing-library/dom'] = '^10.0.0';
45
- if (cfg.isReact) pkg.devDependencies['@testing-library/react'] = '^16.0.0';
46
- if (cfg.isVue) pkg.devDependencies['@testing-library/vue'] = '^8.1.0';
47
- if (cfg.isSvelte) pkg.devDependencies['@testing-library/svelte'] = '^5.2.0';
43
+ pkg.devDependencies.jsdom = V.jsdom;
44
+ pkg.devDependencies['@testing-library/dom'] = V['@testing-library/dom'];
45
+ if (cfg.isReact) pkg.devDependencies['@testing-library/react'] = V['@testing-library/react'];
46
+ if (cfg.isVue) pkg.devDependencies['@testing-library/vue'] = V['@testing-library/vue'];
47
+ if (cfg.isSvelte) pkg.devDependencies['@testing-library/svelte'] = V['@testing-library/svelte'];
48
48
  }
49
49
  if (cfg.coverage) {
50
50
  pkg.scripts.coverage = 'vitest run --coverage';
51
- pkg.devDependencies['@vitest/coverage-v8'] = '^4.0.0';
51
+ pkg.devDependencies['@vitest/coverage-v8'] = V['@vitest/coverage-v8'];
52
52
  }
53
53
  files[`src/index.test.${testExt}`] = exampleTest('vitest', cfg);
54
54
  } else if (cfg.test === 'jest') {
55
55
  files['jest.config.js'] = jestConfig(cfg);
56
56
  pkg.scripts.test = 'jest';
57
57
  pkg.scripts['test:watch'] = 'jest --watch';
58
- pkg.devDependencies.jest = '^30.0.0';
58
+ pkg.devDependencies.jest = V.jest;
59
59
  if (cfg.isTs) {
60
- pkg.devDependencies['ts-jest'] = '^29.0.0';
61
- pkg.devDependencies['@types/jest'] = '^30.0.0';
60
+ pkg.devDependencies['ts-jest'] = V['ts-jest'];
61
+ pkg.devDependencies['@types/jest'] = V['@types/jest'];
62
62
  }
63
63
  if (cfg.coverage) pkg.scripts.coverage = 'jest --coverage';
64
64
  files[`src/index.test.${testExt}`] = exampleTest('jest', cfg);
65
65
  } else if (cfg.test === 'node') {
66
66
  pkg.scripts.test = cfg.isTs ? 'node --import tsx --test "src/**/*.test.ts"' : 'node --test';
67
- if (cfg.isTs) pkg.devDependencies.tsx = '^4.0.0';
67
+ if (cfg.isTs) pkg.devDependencies.tsx = V.tsx;
68
68
  files[`src/index.test.${testExt}`] = exampleTest('node', cfg);
69
69
  }
70
70
 
71
71
  // Express services test through supertest (no built-in inject).
72
72
  if (cfg.hasService && cfg.serviceFramework === 'express') {
73
- pkg.devDependencies.supertest = '^7.0.0';
74
- if (cfg.isTs) pkg.devDependencies['@types/supertest'] = '^6.0.0';
73
+ pkg.devDependencies.supertest = V.supertest;
74
+ if (cfg.isTs) pkg.devDependencies['@types/supertest'] = V['@types/supertest'];
75
75
  }
76
76
 
77
77
  return { files, pkg };
@@ -159,7 +159,6 @@ function exampleTest(runner, cfg) {
159
159
  ].join('\n');
160
160
  }
161
161
  const api = runner === 'jest' ? `` : `import { describe, it, expect } from 'vitest';\n`;
162
- const expectApi = runner === 'jest' ? '' : '';
163
162
  return [
164
163
  api + `import { greet } from '${imp}';`,
165
164
  ``,
@@ -1,4 +1,5 @@
1
1
  import { toJson } from '../render.js';
2
+ import { V } from '../versions.js';
2
3
 
3
4
  export default {
4
5
  id: 'typescript',
@@ -45,7 +46,7 @@ export default {
45
46
  },
46
47
  pkg: {
47
48
  devDependencies: {
48
- typescript: '^5.9.3',
49
+ typescript: V.typescript,
49
50
  '@types/node': `^${cfg.nodeVersion}.0.0`,
50
51
  },
51
52
  },
@@ -1,12 +1,13 @@
1
+ import { V } from '../versions.js';
1
2
  // Vite build path: powers Vite SPA apps (React/Vue/Svelte) and Vue component
2
3
  // libraries (SFCs need a real compiler). Owns the vite config, build scripts,
3
4
  // and — for libraries — the package entry points. index.html + source come
4
5
  // from frameworks.js. Svelte libraries ship source and don't come through here.
5
6
 
6
7
  const PLUGIN = {
7
- react: { import: `import react from '@vitejs/plugin-react';`, call: 'react()', dep: { '@vitejs/plugin-react': '^6.0.0' } },
8
- vue: { import: `import vue from '@vitejs/plugin-vue';`, call: 'vue()', dep: { '@vitejs/plugin-vue': '^6.0.0' } },
9
- svelte: { import: `import { svelte } from '@sveltejs/vite-plugin-svelte';`, call: 'svelte()', dep: { '@sveltejs/vite-plugin-svelte': '^7.0.0' } },
8
+ react: { import: `import react from '@vitejs/plugin-react';`, call: 'react()', dep: { '@vitejs/plugin-react': V['@vitejs/plugin-react'] } },
9
+ vue: { import: `import vue from '@vitejs/plugin-vue';`, call: 'vue()', dep: { '@vitejs/plugin-vue': V['@vitejs/plugin-vue'] } },
10
+ svelte: { import: `import { svelte } from '@sveltejs/vite-plugin-svelte';`, call: 'svelte()', dep: { '@sveltejs/vite-plugin-svelte': V['@sveltejs/vite-plugin-svelte'] } },
10
11
  };
11
12
 
12
13
  export default {
@@ -14,10 +15,10 @@ export default {
14
15
  active: (cfg) => cfg.viteBuild,
15
16
  apply(cfg) {
16
17
  const files = {};
17
- const pkg = { scripts: {}, devDependencies: { vite: '^8.0.0' } };
18
+ const pkg = { scripts: {}, devDependencies: { vite: V.vite } };
18
19
  const p = PLUGIN[cfg.framework];
19
20
  Object.assign(pkg.devDependencies, p.dep);
20
- if (cfg.isVue && cfg.isTs) pkg.devDependencies['vue-tsc'] = '^3.0.0';
21
+ if (cfg.isVue && cfg.isTs) pkg.devDependencies['vue-tsc'] = V['vue-tsc'];
21
22
 
22
23
  if (cfg.hasApp) {
23
24
  // Front-end SPA — not a published package.
@@ -48,8 +49,8 @@ export default {
48
49
  ].join('\n');
49
50
  pkg.scripts.build = 'vite build';
50
51
  pkg.scripts.dev = 'vite build --watch';
51
- pkg.devDependencies['vite-plugin-dts'] = '^5.0.0';
52
- if (cfg.isVue) pkg.devDependencies['vue-tsc'] = '^3.0.0';
52
+ pkg.devDependencies['vite-plugin-dts'] = V['vite-plugin-dts'];
53
+ if (cfg.isVue) pkg.devDependencies['vue-tsc'] = V['vue-tsc'];
53
54
  // entry points
54
55
  pkg.files = ['dist'];
55
56
  pkg.type = 'module';
@@ -60,7 +61,7 @@ export default {
60
61
  }
61
62
 
62
63
  pkg.scripts.clean = 'rimraf dist';
63
- pkg.devDependencies.rimraf = '^6.0.0';
64
+ pkg.devDependencies.rimraf = V.rimraf;
64
65
  return { files, pkg };
65
66
  },
66
67
  };
@@ -7,6 +7,7 @@ import community from './features/community.js';
7
7
  import agents from './features/agents.js';
8
8
  import gitfiles from './features/gitfiles.js';
9
9
  import { provenance } from './provenance.js';
10
+ import { V } from './versions.js';
10
11
 
11
12
  export function buildMonorepo(cfg) {
12
13
  // Two genuinely different shapes: a set of publishable libraries, or a
@@ -22,10 +23,7 @@ export function buildMonorepo(cfg) {
22
23
  const utils = `@${scope}/utils`;
23
24
  const wsProto = pm === 'pnpm' ? 'workspace:*' : '*';
24
25
 
25
- // Reuse the package-agnostic root files (LICENSE, community, AGENTS.md, gitignore).
26
- for (const feat of [community, agents, gitfiles]) {
27
- if (feat.active(cfg)) Object.assign(files, feat.apply(cfg).files);
28
- }
26
+ Object.assign(files, workspaceScaffold(cfg, { workspaceGlobs: ['packages/*'] }));
29
27
 
30
28
  // ---- root ----
31
29
  const rootPkg = {
@@ -46,22 +44,20 @@ export function buildMonorepo(cfg) {
46
44
  release: 'turbo build && changeset publish',
47
45
  },
48
46
  devDependencies: {
49
- turbo: '^2.0.0',
50
- typescript: '^5.9.3',
51
- tsup: '^8.0.0',
52
- vitest: '^4.0.0',
53
- eslint: '^10.0.0',
54
- '@eslint/js': '^10.0.0',
55
- 'typescript-eslint': '^8.0.0',
56
- prettier: '^3.3.0',
57
- '@changesets/cli': '^2.27.0',
47
+ turbo: V.turbo,
48
+ typescript: V.typescript,
49
+ tsup: V.tsup,
50
+ vitest: V.vitest,
51
+ eslint: V.eslint,
52
+ '@eslint/js': V['@eslint/js'],
53
+ 'typescript-eslint': V['typescript-eslint'],
54
+ prettier: V.prettier,
55
+ '@changesets/cli': V['@changesets/cli'],
58
56
  '@types/node': `^${cfg.nodeVersion}.0.0`,
59
57
  },
60
58
  };
61
59
  files['package.json'] = toJson(rootPkg);
62
60
 
63
- if (pm === 'pnpm') files['pnpm-workspace.yaml'] = 'packages:\n - "packages/*"\n';
64
-
65
61
  files['turbo.json'] = toJson({
66
62
  $schema: 'https://turbo.build/schema.json',
67
63
  tasks: {
@@ -73,21 +69,6 @@ export function buildMonorepo(cfg) {
73
69
  },
74
70
  });
75
71
 
76
- files['tsconfig.base.json'] = toJson({
77
- $schema: 'https://json.schemastore.org/tsconfig',
78
- compilerOptions: {
79
- target: 'ES2022',
80
- module: 'ESNext',
81
- moduleResolution: 'Bundler',
82
- lib: ['ES2022'],
83
- strict: true,
84
- esModuleInterop: true,
85
- skipLibCheck: true,
86
- declaration: true,
87
- noEmit: true,
88
- },
89
- });
90
-
91
72
  files['.changeset/config.json'] = toJson({
92
73
  $schema: 'https://unpkg.com/@changesets/config@3.0.0/schema.json',
93
74
  changelog: '@changesets/cli/changelog',
@@ -97,22 +78,7 @@ export function buildMonorepo(cfg) {
97
78
  });
98
79
  files['.changeset/README.md'] = '# Changesets\n\nRun `npx changeset` to record a version bump for your next release.\n';
99
80
 
100
- files['eslint.config.js'] = [
101
- `import js from '@eslint/js';`,
102
- `import tseslint from 'typescript-eslint';`,
103
- ``,
104
- `export default tseslint.config(`,
105
- `\tjs.configs.recommended,`,
106
- `\t...tseslint.configs.recommended,`,
107
- `\t{ ignores: ['**/dist'] },`,
108
- `);`,
109
- ``,
110
- ].join('\n');
111
- files['.prettierrc.json'] = toJson({ useTabs: true, singleQuote: true, semi: true, printWidth: 100, trailingComma: 'all' });
112
-
113
81
  files['README.md'] = rootReadme(cfg, pm, core, utils);
114
- files['packkit.json'] = provenance(cfg);
115
- files['.github/workflows/ci.yml'] = ciWorkflow(cfg, pm);
116
82
 
117
83
  // ---- packages ----
118
84
  addPackage(files, {
@@ -145,7 +111,6 @@ export function buildMonorepo(cfg) {
145
111
  deps: { [core]: wsProto },
146
112
  });
147
113
 
148
- const install = pm === 'npm' ? 'npm install' : `${pm} install`;
149
114
  return {
150
115
  config: cfg,
151
116
  files,
@@ -173,11 +138,9 @@ function buildFullstack(cfg) {
173
138
  const scope = cfg.name.replace(/^@/, '').split('/')[0];
174
139
  const shared = `@${scope}/shared`;
175
140
  const wsProto = pm === 'pnpm' ? 'workspace:*' : '*';
176
- const run = (s) => (pm === 'npm' ? `npm run ${s}` : `${pm} ${s}`);
177
141
 
178
- for (const feat of [community, agents, gitfiles]) {
179
- if (feat.active(cfg)) Object.assign(files, feat.apply(cfg).files);
180
- }
142
+ // apps/* + packages/*, and the DOM lib the web app's JSX needs.
143
+ Object.assign(files, workspaceScaffold(cfg, { workspaceGlobs: ['apps/*', 'packages/*'], tsconfigLib: ['ES2022', 'DOM'] }));
181
144
 
182
145
  files['package.json'] = toJson({
183
146
  name: cfg.name,
@@ -198,21 +161,17 @@ function buildFullstack(cfg) {
198
161
  typecheck: 'turbo typecheck',
199
162
  },
200
163
  devDependencies: {
201
- turbo: '^2.0.0',
202
- typescript: '^5.9.3',
203
- vitest: '^4.0.0',
204
- eslint: '^10.0.0',
205
- '@eslint/js': '^10.0.0',
206
- 'typescript-eslint': '^8.0.0',
207
- prettier: '^3.3.0',
164
+ turbo: V.turbo,
165
+ typescript: V.typescript,
166
+ vitest: V.vitest,
167
+ eslint: V.eslint,
168
+ '@eslint/js': V['@eslint/js'],
169
+ 'typescript-eslint': V['typescript-eslint'],
170
+ prettier: V.prettier,
208
171
  '@types/node': `^${cfg.nodeVersion}.0.0`,
209
172
  },
210
173
  });
211
174
 
212
- if (pm === 'pnpm') {
213
- files['pnpm-workspace.yaml'] = 'packages:\n - "apps/*"\n - "packages/*"\n';
214
- }
215
-
216
175
  files['turbo.json'] = toJson({
217
176
  $schema: 'https://turbo.build/schema.json',
218
177
  tasks: {
@@ -226,36 +185,7 @@ function buildFullstack(cfg) {
226
185
  },
227
186
  });
228
187
 
229
- files['tsconfig.base.json'] = toJson({
230
- $schema: 'https://json.schemastore.org/tsconfig',
231
- compilerOptions: {
232
- target: 'ES2022',
233
- module: 'ESNext',
234
- moduleResolution: 'Bundler',
235
- lib: ['ES2022', 'DOM'],
236
- strict: true,
237
- esModuleInterop: true,
238
- skipLibCheck: true,
239
- declaration: true,
240
- noEmit: true,
241
- },
242
- });
243
-
244
- files['eslint.config.js'] = [
245
- `import js from '@eslint/js';`,
246
- `import tseslint from 'typescript-eslint';`,
247
- ``,
248
- `export default tseslint.config(`,
249
- `\tjs.configs.recommended,`,
250
- `\t...tseslint.configs.recommended,`,
251
- `\t{ ignores: ['**/dist'] },`,
252
- `);`,
253
- ``,
254
- ].join('\n');
255
- files['.prettierrc.json'] = toJson({ useTabs: true, singleQuote: true, semi: true, printWidth: 100, trailingComma: 'all' });
256
- files['.github/workflows/ci.yml'] = ciWorkflow(cfg, pm);
257
188
  files['README.md'] = fullstackReadme(cfg, pm, shared);
258
- files['packkit.json'] = provenance(cfg);
259
189
 
260
190
  // ---- packages/shared: the contract both sides compile against ----
261
191
  files['packages/shared/package.json'] = toJson({
@@ -272,7 +202,7 @@ function buildFullstack(cfg) {
272
202
  typecheck: 'tsc --noEmit',
273
203
  lint: 'eslint .',
274
204
  },
275
- devDependencies: { tsup: '^8.0.0' },
205
+ devDependencies: { tsup: V.tsup },
276
206
  });
277
207
  files['packages/shared/tsconfig.json'] = toJson({ extends: '../../tsconfig.base.json', include: ['src'] });
278
208
  files['packages/shared/src/index.ts'] = [
@@ -307,8 +237,8 @@ function buildFullstack(cfg) {
307
237
  typecheck: 'tsc --noEmit',
308
238
  lint: 'eslint .',
309
239
  },
310
- dependencies: { hono: '^4.5.0', '@hono/node-server': '^2.0.0', [shared]: wsProto },
311
- devDependencies: { tsx: '^4.0.0', tsup: '^8.0.0' },
240
+ dependencies: { hono: V.hono, '@hono/node-server': V['@hono/node-server'], [shared]: wsProto },
241
+ devDependencies: { tsx: V.tsx, tsup: V.tsup },
312
242
  });
313
243
  files['apps/server/tsconfig.json'] = toJson({ extends: '../../tsconfig.base.json', include: ['src'] });
314
244
  files['apps/server/src/app.ts'] = [
@@ -368,17 +298,17 @@ function buildFullstack(cfg) {
368
298
  typecheck: 'tsc --noEmit',
369
299
  lint: 'eslint .',
370
300
  },
371
- dependencies: { react: '^19.0.0', 'react-dom': '^19.0.0', [shared]: wsProto },
301
+ dependencies: { react: V.react, 'react-dom': V['react-dom'], [shared]: wsProto },
372
302
  devDependencies: {
373
- vite: '^8.0.0',
374
- '@vitejs/plugin-react': '^6.0.0',
303
+ vite: V.vite,
304
+ '@vitejs/plugin-react': V['@vitejs/plugin-react'],
375
305
  // Without the React types, `turbo typecheck` fails on the very first run
376
306
  // with TS7016/TS7026 on every JSX element.
377
- '@types/react': '^19.0.0',
378
- '@types/react-dom': '^19.0.0',
379
- '@testing-library/react': '^16.0.0',
380
- '@testing-library/dom': '^10.0.0',
381
- jsdom: '^29.0.0',
307
+ '@types/react': V['@types/react'],
308
+ '@types/react-dom': V['@types/react-dom'],
309
+ '@testing-library/react': V['@testing-library/react'],
310
+ '@testing-library/dom': V['@testing-library/dom'],
311
+ jsdom: V.jsdom,
382
312
  },
383
313
  });
384
314
  files['apps/web/tsconfig.json'] = toJson({
@@ -515,6 +445,50 @@ function fullstackReadme(cfg, pm, shared) {
515
445
  ].join('\n');
516
446
  }
517
447
 
448
+ // Root-level scaffolding shared by both monorepo layouts: workspace globs, the
449
+ // TypeScript base config, lint/format config, community files, CI, and
450
+ // provenance. Each layout adds its own root package.json, turbo tasks, and
451
+ // packages on top. Extracted so these aren't maintained twice.
452
+ function workspaceScaffold(cfg, { workspaceGlobs, tsconfigLib = ['ES2022'] }) {
453
+ const files = {};
454
+ // Reuse the package-agnostic root files (LICENSE, community, AGENTS.md, gitignore).
455
+ for (const feat of [community, agents, gitfiles]) {
456
+ if (feat.active(cfg)) Object.assign(files, feat.apply(cfg).files);
457
+ }
458
+ if (cfg.packageManager === 'pnpm') {
459
+ files['pnpm-workspace.yaml'] = 'packages:\n' + workspaceGlobs.map((g) => ` - "${g}"\n`).join('');
460
+ }
461
+ files['tsconfig.base.json'] = toJson({
462
+ $schema: 'https://json.schemastore.org/tsconfig',
463
+ compilerOptions: {
464
+ target: 'ES2022',
465
+ module: 'ESNext',
466
+ moduleResolution: 'Bundler',
467
+ lib: tsconfigLib,
468
+ strict: true,
469
+ esModuleInterop: true,
470
+ skipLibCheck: true,
471
+ declaration: true,
472
+ noEmit: true,
473
+ },
474
+ });
475
+ files['eslint.config.js'] = [
476
+ `import js from '@eslint/js';`,
477
+ `import tseslint from 'typescript-eslint';`,
478
+ ``,
479
+ `export default tseslint.config(`,
480
+ `\tjs.configs.recommended,`,
481
+ `\t...tseslint.configs.recommended,`,
482
+ `\t{ ignores: ['**/dist'] },`,
483
+ `);`,
484
+ ``,
485
+ ].join('\n');
486
+ files['.prettierrc.json'] = toJson({ useTabs: true, singleQuote: true, semi: true, printWidth: 100, trailingComma: 'all' });
487
+ files['.github/workflows/ci.yml'] = ciWorkflow(cfg, cfg.packageManager);
488
+ files['packkit.json'] = provenance(cfg);
489
+ return files;
490
+ }
491
+
518
492
  function addPackage(files, { name, dir, src, test, deps }) {
519
493
  const pkg = {
520
494
  name,
@@ -353,5 +353,28 @@ export function normalizeConfig(input = {}, diagnostics = null) {
353
353
  if (cfg.release !== 'changesets') coerce('canary', false, 'CANARY_REQUIRES_CHANGESETS', 'Canary releases were disabled because they require the Changesets release flow.');
354
354
  // JSR is TypeScript-first, ESM, and for plain (non-framework) libraries.
355
355
  if (!(cfg.isTs && cfg.hasLibrary && !cfg.hasFramework && !cfg.hasApp)) coerce('jsr', false, 'JSR_REQUIRES_PLAIN_TS_LIBRARY', 'JSR publishing was disabled because it applies only to a plain TypeScript library.');
356
+
357
+ cfg.resolved = resolvedView(cfg);
356
358
  return cfg;
357
359
  }
360
+
361
+ /**
362
+ * A grouped, read-only view of the derived state — the same booleans the flat
363
+ * `cfg.has*`/`is*` flags expose, organized by concern. Consumers that prefer
364
+ * structured access (`cfg.resolved.targets.app`) can use it instead of
365
+ * re-interpreting raw selections; the flat flags remain for existing code.
366
+ *
367
+ * It is derived purely from the flags computed above, and test/invariants.test.js
368
+ * asserts the two never disagree and that the state is internally consistent —
369
+ * so the derived model can't silently contradict itself.
370
+ */
371
+ function resolvedView(cfg) {
372
+ return {
373
+ targets: { library: cfg.hasLibrary, cli: cfg.hasCli, service: cfg.hasService, app: cfg.hasApp },
374
+ language: { typescript: cfg.isTs, ext: cfg.ext, srcExt: cfg.srcExt },
375
+ framework: { name: cfg.framework, react: cfg.isReact, vue: cfg.isVue, svelte: cfg.isSvelte, any: cfg.hasFramework },
376
+ build: { vite: cfg.viteBuild, custom: cfg.customBuild, usesVite: cfg.usesVite, has: cfg.hasBuild },
377
+ module: { format: cfg.moduleFormat, esm: cfg.hasEsm, cjs: cfg.hasCjs },
378
+ package: { publishable: cfg.publishable, monorepo: cfg.monorepo },
379
+ };
380
+ }
@@ -0,0 +1,121 @@
1
+ // The single source of truth for every dependency version Packkit writes into
2
+ // generated projects. Feature modules reference these by name instead of
3
+ // hard-coding a spec, so a bump happens in one place and the freshness workflow
4
+ // (scripts/check-template-deps.mjs) has one authoritative list to check.
5
+ //
6
+ // A conformance test (test/versions.test.js) asserts that every version in the
7
+ // generated output comes from this catalog, so nothing can drift back into a
8
+ // feature module unnoticed.
9
+ //
10
+ // When you bump anything here, update LAST_REVIEWED.
11
+
12
+ export const LAST_REVIEWED = '2026-07-26';
13
+
14
+ // name → npm spec. Deps that ship at one version everywhere.
15
+ export const V = {
16
+ // toolchain
17
+ typescript: '^5.9.3',
18
+ 'typescript-eslint': '^8.0.0',
19
+ eslint: '^10.0.0',
20
+ '@eslint/js': '^10.0.0',
21
+ prettier: '^3.3.0',
22
+ '@biomejs/biome': '^2.0.0',
23
+ oxlint: '^1.0.0',
24
+ turbo: '^2.0.0',
25
+ rimraf: '^6.0.0',
26
+ '@types/node': '^24.0.0',
27
+
28
+ // build
29
+ tsup: '^8.0.0',
30
+ tsdown: '^0.6.0',
31
+ unbuild: '^3.0.0',
32
+ rollup: '^4.0.0',
33
+ '@rollup/plugin-typescript': '^12.0.0',
34
+ '@rollup/plugin-terser': '^1.0.0',
35
+ tslib: '^2.6.0',
36
+ tsx: '^4.0.0',
37
+ vite: '^8.0.0',
38
+ 'vite-plugin-dts': '^5.0.0',
39
+
40
+ // test
41
+ vitest: '^4.0.0',
42
+ '@vitest/coverage-v8': '^4.0.0',
43
+ jsdom: '^29.0.0',
44
+ jest: '^30.0.0',
45
+ '@types/jest': '^30.0.0',
46
+ 'ts-jest': '^29.0.0',
47
+ supertest: '^7.0.0',
48
+ '@types/supertest': '^6.0.0',
49
+ '@playwright/test': '^1.50.0',
50
+
51
+ // release / package checks
52
+ '@changesets/cli': '^2.27.0',
53
+ 'release-it': '^21.0.0',
54
+ np: '^12.0.0',
55
+ publint: '^0.3.0',
56
+ '@arethetypeswrong/cli': '^0.18.0',
57
+ knip: '^5.0.0',
58
+ 'size-limit': '^11.0.0',
59
+ '@size-limit/preset-small-lib': '^11.0.0',
60
+
61
+ // git hooks
62
+ 'simple-git-hooks': '^2.11.0',
63
+ husky: '^9.1.0',
64
+ lefthook: '^2.0.0',
65
+ 'lint-staged': '^16.2.0',
66
+
67
+ // react
68
+ react: '^19.0.0',
69
+ 'react-dom': '^19.0.0',
70
+ '@types/react': '^19.0.0',
71
+ '@types/react-dom': '^19.0.0',
72
+ '@vitejs/plugin-react': '^6.0.0',
73
+ '@testing-library/react': '^16.0.0',
74
+ '@testing-library/dom': '^10.0.0',
75
+
76
+ // vue
77
+ vue: '^3.4.0',
78
+ 'vue-tsc': '^3.0.0',
79
+ '@vitejs/plugin-vue': '^6.0.0',
80
+ '@testing-library/vue': '^8.1.0',
81
+
82
+ // svelte
83
+ svelte: '^5.0.0',
84
+ 'svelte-check': '^4.0.0',
85
+ '@sveltejs/vite-plugin-svelte': '^7.0.0',
86
+ '@testing-library/svelte': '^5.2.0',
87
+
88
+ // storybook
89
+ storybook: '^10.0.0',
90
+ '@storybook/react': '^10.0.0',
91
+ '@storybook/react-vite': '^10.0.0',
92
+ '@storybook/vue3': '^10.0.0',
93
+ '@storybook/vue3-vite': '^10.0.0',
94
+ '@storybook/svelte': '^10.0.0',
95
+ '@storybook/svelte-vite': '^10.0.0',
96
+
97
+ // services
98
+ hono: '^4.5.0',
99
+ '@hono/node-server': '^2.0.0',
100
+ fastify: '^5.0.0',
101
+ express: '^5.0.0',
102
+ '@types/express': '^5.0.0',
103
+ zod: '^4.0.0',
104
+ };
105
+
106
+ // Peer-dependency ranges — deliberately looser than the direct-dependency spec,
107
+ // so a component library declares broad compatibility rather than a hard pin.
108
+ export const PEER = {
109
+ react: '>=18',
110
+ 'react-dom': '>=18',
111
+ vue: '>=3',
112
+ svelte: '>=5',
113
+ };
114
+
115
+ // Deps intentionally held below their latest major, with the reason. The
116
+ // freshness check reads this so a held-back dep is reported, not failed.
117
+ export const HELD = {
118
+ typescript: 'typescript-eslint peers typescript <6.1.0 (no TS 7 support yet)',
119
+ knip: 'v6 crashes on the oxc-parser native binding',
120
+ 'lint-staged': 'v17 requires Node >=22.22.1, above our Node 22 engines floor; hold at 16 to keep the Maintenance-LTS line working',
121
+ };