create-packkit 2.0.1 → 2.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-packkit",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
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": {
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 { nodeFloor, 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 = nodeFloor(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
  },
@@ -1,10 +1,6 @@
1
1
  // Always-on: base package.json descriptive fields + the source entry + README.
2
2
 
3
- // The real minimum patch for each major, driven by our template deps (eslint 10,
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 { nodeFloor } from '../node.js';
8
4
 
9
5
  export default {
10
6
  id: 'meta',
@@ -119,6 +115,13 @@ function readme(cfg) {
119
115
  const badges = makeBadges(cfg);
120
116
  if (badges) lines.push(badges, '');
121
117
 
118
+ lines.push(
119
+ '## Requirements',
120
+ '',
121
+ `Node.js >= ${nodeFloor(cfg.nodeVersion)} (\`.nvmrc\` pins it; run \`nvm use\`). Enforced via \`engine-strict\`, so installs fail fast on an unsupported version.`,
122
+ '',
123
+ );
124
+
122
125
  lines.push('## Install', '', '```sh', install, '```', '');
123
126
 
124
127
  if (cfg.hasApp) {
@@ -0,0 +1,22 @@
1
+ // Shared Node-version floor logic. Browser-safe (no node builtins) so both the
2
+ // generator and the CLI preflight agree on exactly one source of truth.
3
+
4
+ // The real minimum patch for each supported major, driven by our template deps
5
+ // (eslint 10, jsdom 29, vite 8 need ^20.19; vite/others need ^22.12). Writing a
6
+ // bare ">=20" would be a lie — a user on 20.17 hits EBADENGINE and transitive
7
+ // syntax errors. Keep this honest.
8
+ export const NODE_FLOOR = { 18: '18.18.0', 20: '20.19.0', 22: '22.12.0', 24: '24.0.0' };
9
+
10
+ export const nodeFloor = (v) => NODE_FLOOR[v] || `${v}.0.0`;
11
+
12
+ const parts = (s) =>
13
+ String(s).replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
14
+
15
+ /** True if `current` (e.g. "v20.17.0" or process.version) is >= `floor` ("20.19.0"). */
16
+ export function meetsNodeFloor(current, floor) {
17
+ const [a, b, c] = parts(current);
18
+ const [x, y, z] = parts(floor);
19
+ if (a !== x) return a > x;
20
+ if (b !== y) return b > y;
21
+ return c >= z;
22
+ }