create-packkit 2.0.1 → 2.0.3
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/package.json +3 -2
- package/src/cli/index.js +32 -0
- package/src/core/features/gitfiles.js +7 -1
- package/src/core/features/githooks.js +2 -2
- package/src/core/features/meta.js +12 -8
- package/src/core/node-versions.js +12 -0
- package/src/core/node.js +34 -0
- package/src/core/options.js +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-packkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Highly configurable scaffolder for modern npm packages and CLIs — pick your stack (TS/JS, bundler, tests, linter, CI, releases) from a CLI or a web configurator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"test": "node --test",
|
|
27
27
|
"check:deps": "node scripts/check-template-deps.mjs",
|
|
28
28
|
"integration": "node scripts/integration.mjs",
|
|
29
|
-
"build:web": "esbuild src/core/index.js --bundle --format=esm --outfile=docs/packkit-core.js"
|
|
29
|
+
"build:web": "esbuild src/core/index.js --bundle --format=esm --outfile=docs/packkit-core.js",
|
|
30
|
+
"update:node": "node scripts/update-node-versions.mjs"
|
|
30
31
|
},
|
|
31
32
|
"repository": {
|
|
32
33
|
"type": "git",
|
package/src/cli/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import { generate, fromPreset, normalizeConfig, PRESET_NAMES, OPTIONS, PRESET_INFO, PRESET_ALIASES } from '../core/index.js';
|
|
6
|
+
import { engineFloor, meetsNodeFloor } from '../core/node.js';
|
|
6
7
|
import { parseCliArgs } from './args.js';
|
|
7
8
|
import { runWizard } from './wizard.js';
|
|
8
9
|
import { writeProject, dirIsEmptyOrMissing, gitInit, installDeps } from './write.js';
|
|
@@ -87,6 +88,25 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
87
88
|
config.gitInit = args.git;
|
|
88
89
|
config.install = args.install;
|
|
89
90
|
|
|
91
|
+
// Node preflight: the generated project's tools (eslint, vite, vitest) hard-
|
|
92
|
+
// require this floor. npm only *warns* on engines, so catch it here — clearly,
|
|
93
|
+
// once — instead of letting a doomed install spew EBADENGINE and leave a broken
|
|
94
|
+
// project. This is the signal both humans and agents need up front.
|
|
95
|
+
const floor = engineFloor(config.nodeVersion);
|
|
96
|
+
const nodeOk = meetsNodeFloor(process.version, floor);
|
|
97
|
+
if (!nodeOk) {
|
|
98
|
+
const lines = [
|
|
99
|
+
`Node ${process.version} is below this project's required Node >= ${floor}.`,
|
|
100
|
+
`Tools like eslint, vite and vitest will not run on it.`,
|
|
101
|
+
`Fix: nvm install ${config.nodeVersion} (or install any Node >= ${floor})`,
|
|
102
|
+
];
|
|
103
|
+
if (config.install) {
|
|
104
|
+
lines.push(`Skipping the dependency install until Node is upgraded.`);
|
|
105
|
+
config.install = false;
|
|
106
|
+
}
|
|
107
|
+
if (interactive) p.log.warn(lines.join('\n')); else console.error('\n⚠ ' + lines.join('\n '));
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
// Resolve the target directory.
|
|
91
111
|
const targetDir = args.here ? process.cwd() : resolve(process.cwd(), config.name);
|
|
92
112
|
if (!args.here && !config.name) {
|
|
@@ -119,13 +139,25 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
119
139
|
config.test !== 'none' ? `${runWord(config)} test` : null,
|
|
120
140
|
].filter(Boolean);
|
|
121
141
|
|
|
142
|
+
// Clarify things that surprise people: a framework *library* has no dev
|
|
143
|
+
// server (its `dev` just rebuilds), and the Node floor this project needs.
|
|
144
|
+
const componentLib = config.hasFramework && config.hasLibrary && !config.hasApp;
|
|
145
|
+
const hints = [
|
|
146
|
+
componentLib
|
|
147
|
+
? `This is a ${config.framework} component library — \`${runWord(config)} dev\` rebuilds on change (there's no dev server). For a runnable app, scaffold the "${config.framework}-app" preset instead.`
|
|
148
|
+
: null,
|
|
149
|
+
`Requires Node >= ${floor}${nodeOk ? '' : ` — you're on ${process.version}, upgrade first`}.`,
|
|
150
|
+
].filter(Boolean);
|
|
151
|
+
|
|
122
152
|
const done = `Created ${summary.name} — ${summary.fileCount} files · ${summary.stack.join(' · ')}`;
|
|
123
153
|
if (interactive) {
|
|
124
154
|
p.note(next.join('\n') || 'You are all set.', 'Next steps');
|
|
155
|
+
if (hints.length) p.log.info(hints.join('\n'));
|
|
125
156
|
p.outro(done);
|
|
126
157
|
} else {
|
|
127
158
|
console.log(done);
|
|
128
159
|
if (next.length) console.log('\nNext steps:\n ' + next.join('\n '));
|
|
160
|
+
if (hints.length) console.log('\n' + hints.map((h) => '• ' + h).join('\n'));
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
const latest = await checkForUpdate(pkgVersion());
|
|
@@ -39,11 +39,17 @@ indent_style = space
|
|
|
39
39
|
indent_size = 2
|
|
40
40
|
`;
|
|
41
41
|
|
|
42
|
+
// engine-strict turns npm/pnpm's `engines` warning into a hard install error, so
|
|
43
|
+
// a contributor, CI job, or agent on an unsupported Node fails immediately with a
|
|
44
|
+
// clear message instead of a broken install they scroll past.
|
|
45
|
+
const NPMRC = `engine-strict=true
|
|
46
|
+
`;
|
|
47
|
+
|
|
42
48
|
export default {
|
|
43
49
|
id: 'gitfiles',
|
|
44
50
|
active: () => true,
|
|
45
51
|
apply(cfg) {
|
|
46
|
-
const files = { '.gitignore': GITIGNORE };
|
|
52
|
+
const files = { '.gitignore': GITIGNORE, '.npmrc': NPMRC };
|
|
47
53
|
if (cfg.editorconfig) files['.editorconfig'] = EDITORCONFIG;
|
|
48
54
|
return { files, pkg: {} };
|
|
49
55
|
},
|
|
@@ -34,8 +34,8 @@ export default {
|
|
|
34
34
|
|
|
35
35
|
if (needsLintStaged) {
|
|
36
36
|
pkg['lint-staged'] = staged;
|
|
37
|
-
// Held at 16: v17 requires Node >=22.22.1,
|
|
38
|
-
//
|
|
37
|
+
// Held at 16: v17 requires Node >=22.22.1, above our Node 22 engines
|
|
38
|
+
// floor (22.13) — it would break the Maintenance-LTS line. See ENGINE_MIN.
|
|
39
39
|
pkg.devDependencies['lint-staged'] = '^16.2.0';
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// Always-on: base package.json descriptive fields + the source entry + README.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
// jsdom 29, vite 8 need ^20.19; vite/others need ^22.12). `>=20` would be a lie —
|
|
5
|
-
// a user on 20.17 hits EBADENGINE and transitive syntax errors. Keep this honest.
|
|
6
|
-
export const NODE_FLOOR = { 18: '18.18.0', 20: '20.19.0', 22: '22.12.0', 24: '24.0.0' };
|
|
7
|
-
const nodeFloor = (v) => NODE_FLOOR[v] || `${v}.0.0`;
|
|
3
|
+
import { engineFloor, nodePin } from '../node.js';
|
|
8
4
|
|
|
9
5
|
export default {
|
|
10
6
|
id: 'meta',
|
|
@@ -16,7 +12,7 @@ export default {
|
|
|
16
12
|
version: '0.0.0',
|
|
17
13
|
description: cfg.description || '',
|
|
18
14
|
type: cfg.moduleFormat === 'cjs' ? 'commonjs' : 'module',
|
|
19
|
-
engines: { node: `>=${
|
|
15
|
+
engines: { node: `>=${engineFloor(cfg.nodeVersion)}` },
|
|
20
16
|
scripts: {},
|
|
21
17
|
};
|
|
22
18
|
|
|
@@ -39,8 +35,9 @@ export default {
|
|
|
39
35
|
// README
|
|
40
36
|
files['README.md'] = readme(cfg);
|
|
41
37
|
|
|
42
|
-
// Node version pin —
|
|
43
|
-
|
|
38
|
+
// Node version pin — that line's newest patch (from Node's own data), so
|
|
39
|
+
// `nvm use` lands on a current, real release.
|
|
40
|
+
files['.nvmrc'] = `${nodePin(cfg.nodeVersion)}\n`;
|
|
44
41
|
|
|
45
42
|
// A typecheck script for TS projects — framework-aware (plain tsc can't
|
|
46
43
|
// resolve .vue/.svelte modules).
|
|
@@ -119,6 +116,13 @@ function readme(cfg) {
|
|
|
119
116
|
const badges = makeBadges(cfg);
|
|
120
117
|
if (badges) lines.push(badges, '');
|
|
121
118
|
|
|
119
|
+
lines.push(
|
|
120
|
+
'## Requirements',
|
|
121
|
+
'',
|
|
122
|
+
`Node.js >= ${engineFloor(cfg.nodeVersion)} (\`.nvmrc\` pins ${nodePin(cfg.nodeVersion)}; run \`nvm use\`). Enforced via \`engine-strict\`, so installs fail fast on an unsupported version.`,
|
|
123
|
+
'',
|
|
124
|
+
);
|
|
125
|
+
|
|
122
126
|
lines.push('## Install', '', '```sh', install, '```', '');
|
|
123
127
|
|
|
124
128
|
if (cfg.hasApp) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/update-node-versions.mjs — do not edit by hand.
|
|
2
|
+
// Source: nodejs.org/dist + nodejs/Release schedule, refreshed 2026-07-14.
|
|
3
|
+
// Offered lines track the Node release schedule; each version is that line's
|
|
4
|
+
// newest published patch. Run `node scripts/update-node-versions.mjs` to refresh.
|
|
5
|
+
|
|
6
|
+
export const NODE_LINES = {
|
|
7
|
+
'24': { version: '24.18.0', status: 'active-lts', codename: 'Krypton', label: '24 (Active LTS, "Krypton")' },
|
|
8
|
+
'22': { version: '22.23.1', status: 'maintenance', codename: 'Jod', label: '22 (Maintenance LTS, "Jod")' },
|
|
9
|
+
'26': { version: '26.5.0', status: 'current', codename: null, label: '26 (Current)' },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_NODE = '24';
|
package/src/core/node.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Shared Node-version logic. Browser-safe (no node builtins) so the generator
|
|
2
|
+
// and the CLI preflight agree on exactly one source of truth.
|
|
3
|
+
//
|
|
4
|
+
// Two concerns, two sources:
|
|
5
|
+
// - which majors we offer + the exact patch to pin -> node-versions.js,
|
|
6
|
+
// auto-derived from Node's release schedule + dist index (never guessed).
|
|
7
|
+
// - the minimum patch *within a major* our template deps need -> ENGINE_MIN,
|
|
8
|
+
// below, driven by our deps (eslint/vite), governed by the template-deps
|
|
9
|
+
// freshness workflow. These change rarely and independently of Node.
|
|
10
|
+
|
|
11
|
+
import { NODE_LINES, DEFAULT_NODE } from './node-versions.js';
|
|
12
|
+
|
|
13
|
+
export { NODE_LINES, DEFAULT_NODE };
|
|
14
|
+
|
|
15
|
+
// eslint 10 needs ^20.19/^22.13; vite 8 needs ^20.19/^22.12 — take the max.
|
|
16
|
+
export const ENGINE_MIN = { 18: '18.18.0', 20: '20.19.0', 22: '22.13.0' };
|
|
17
|
+
|
|
18
|
+
/** The `engines` minimum for a target major (a floor, not a pin). */
|
|
19
|
+
export const engineFloor = (v) => ENGINE_MIN[v] || `${v}.0.0`;
|
|
20
|
+
|
|
21
|
+
/** The exact version to pin in `.nvmrc` — that line's newest patch, or the floor. */
|
|
22
|
+
export const nodePin = (v) => NODE_LINES[v]?.version || engineFloor(v);
|
|
23
|
+
|
|
24
|
+
const parts = (s) =>
|
|
25
|
+
String(s).replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
26
|
+
|
|
27
|
+
/** True if `current` (e.g. "v22.5.0" or process.version) is >= `floor` ("22.13.0"). */
|
|
28
|
+
export function meetsNodeFloor(current, floor) {
|
|
29
|
+
const [a, b, c] = parts(current);
|
|
30
|
+
const [x, y, z] = parts(floor);
|
|
31
|
+
if (a !== x) return a > x;
|
|
32
|
+
if (b !== y) return b > y;
|
|
33
|
+
return c >= z;
|
|
34
|
+
}
|
package/src/core/options.js
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
// Both the CLI wizard and the web configurator render from this schema, and
|
|
3
3
|
// `normalizeConfig` turns partial/user input into a complete, valid config.
|
|
4
4
|
|
|
5
|
+
import { NODE_LINES, DEFAULT_NODE } from './node.js';
|
|
6
|
+
|
|
5
7
|
/** @typedef {'select'|'multiselect'|'boolean'|'text'} OptionType */
|
|
6
8
|
|
|
9
|
+
// Node choices are derived from Node's own release schedule (see node-versions.js)
|
|
10
|
+
// so the LTS/Current lines and their patches stay current without hand-editing.
|
|
11
|
+
const NODE_CHOICES = Object.entries(NODE_LINES).map(([value, info]) => ({ value, label: info.label }));
|
|
12
|
+
|
|
7
13
|
export const OPTIONS = {
|
|
8
14
|
// ---- package metadata ----
|
|
9
15
|
name: { group: 'meta', type: 'text', label: 'Package name', default: 'my-package' },
|
|
@@ -59,12 +65,8 @@ export const OPTIONS = {
|
|
|
59
65
|
],
|
|
60
66
|
},
|
|
61
67
|
nodeVersion: {
|
|
62
|
-
group: 'core', type: 'select', label: 'Node version', default:
|
|
63
|
-
choices:
|
|
64
|
-
{ value: '20', label: '20 (LTS, ≥20.19)' },
|
|
65
|
-
{ value: '22', label: '22 (LTS, ≥22.12)' },
|
|
66
|
-
{ value: '24', label: '24 (Current)' },
|
|
67
|
-
],
|
|
68
|
+
group: 'core', type: 'select', label: 'Node version', default: DEFAULT_NODE,
|
|
69
|
+
choices: NODE_CHOICES,
|
|
68
70
|
},
|
|
69
71
|
|
|
70
72
|
// ---- build ----
|