stonyx 0.2.3-alpha.61 → 0.2.3-alpha.63

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
@@ -27,7 +27,19 @@ stonyx test # Bootstrap + run tests
27
27
  stonyx help # Show all available commands
28
28
  ```
29
29
 
30
- Stonyx reads `config/environment.ts` (preferred) or `config/environment.js`, loads the `@stonyx/*` modules declared in your `dependencies` or `devDependencies` (initializing the async ones), and runs your application.
30
+ Stonyx reads `config/environment.ts` (preferred) or `config/environment.js`, initializes all `@stonyx/*` modules from your `devDependencies`, and runs your application.
31
+
32
+ ## Exports
33
+
34
+ | Import path | What it exports |
35
+ |-------------|-----------------|
36
+ | `stonyx` | `Stonyx` class (default), `waitForModule(name)` |
37
+ | `stonyx/config` | Live reference to the Stonyx config (default) |
38
+ | `stonyx/log` | Live reference to the Chronicle logger (default) |
39
+ | `stonyx/lifecycle` | `runStartupHooks(modules)`, `runShutdownHooks(modules)` |
40
+ | `stonyx/test-helpers` | `setupIntegrationTests(hooks)` |
41
+
42
+ `stonyx/config` and `stonyx/log` throw if read before Stonyx is initialized. See the [API Reference](docs/api.md) for details.
31
43
 
32
44
  ## Documentation
33
45
 
package/dist/cli/new.js CHANGED
@@ -167,12 +167,8 @@ export function generatePackageJson(name, selectedModules, coreVersion = readCor
167
167
  tsx: '^4.21.0',
168
168
  typescript: '^5.8.3'
169
169
  };
170
- // The scaffolder writes modules to `devDependencies` because that remains
171
- // the conventional placement for an application — NOT because it is the only
172
- // one discovery sees. Since abofs/stonyx#106 rule 3 the loader scans the
173
- // de-duplicated union of `dependencies` and `devDependencies`, so either
174
- // placement is discovered (see docs/modules.md). Requested on the core's own
175
- // release line rather than at `latest`.
170
+ // Modules are discovered from devDependencies (see docs/modules.md), and are
171
+ // requested on the core's own release line rather than at `latest`.
176
172
  for (const mod of selectedModules) {
177
173
  devDependencies[mod.package] = moduleTag;
178
174
  }
package/dist/modules.d.ts CHANGED
@@ -2,29 +2,4 @@ import type { StoynxModule } from './lifecycle.js';
2
2
  import type Chronicle from '@stonyx/logs';
3
3
  export type StoynxConfig = Record<string, Record<string, unknown> | unknown>;
4
4
  export default function loadModules(config: StoynxConfig, rootPath: string, chronicle: Chronicle): Promise<StoynxModule[]>;
5
- /**
6
- * Blocks until `@stonyx/<moduleName>` has finished initializing.
7
- *
8
- * TWO failure sentences, because one sentence stopped being able to carry both
9
- * facts. Until PR #120 fix round 1 `modulePromises` held every DECLARED
10
- * `@stonyx/*` name, so "absent from the map" and "absent from the manifest"
11
- * were the same fact and the single inherited sentence was true. Registration
12
- * is now scoped to the DISCOVERED set — which is what removes the boot hang,
13
- * see the registration loop in `loadModules` — so absence from the map has two
14
- * causes, and the inherited sentence is FALSE for one of them: it tells the
15
- * operator the package is not in their dependencies while it is sitting in
16
- * their manifest.
17
- *
18
- * Not hypothetical, and not a shape this repo has to imagine: `@stonyx/logs`
19
- * ships without the `stonyx-module` keyword and is an ordinary `dependencies`
20
- * entry of THIS package.json. An app in the same shape calling
21
- * `waitForModule('logs')` would be sent to grep a manifest that already
22
- * contains the answer. Naming the wrong cause is the defect abofs/stonyx#108
23
- * was filed over, and it is not made acceptable by being one line long.
24
- *
25
- * The declared branch does not say WHICH of the two causes applies, because
26
- * this function does not know. Discovery does, and it already `console.warn`s
27
- * the specific reason at load time, so the message points at that warning
28
- * rather than guessing between them.
29
- */
30
5
  export declare function waitForModule(moduleName: string): Promise<void>;
package/dist/modules.js CHANGED
@@ -8,38 +8,6 @@ import { mergeObject } from '@stonyx/utils/object';
8
8
  import { importConfig, CONFIG_NOT_FOUND_PREFIX, CONFIG_NOT_LOADABLE_PREFIX } from './util/import-config.js';
9
9
  import { findForeignCores, duplicateCoreMessage } from './util/duplicate-core.js';
10
10
  const modulePromises = {};
11
- // The `@stonyx/*` names this app DECLARED — which since PR #120 fix round 1 is
12
- // a strictly WIDER set than the keys of `modulePromises`. Registration is now
13
- // scoped to what discovery admitted, so a declared name whose manifest is
14
- // missing, or whose manifest carries no `stonyx-module` keyword, is in this set
15
- // and not in that map. `waitForModule`'s failure branch is the only consumer,
16
- // and this is the only question it needs answered: "is this name in the
17
- // manifest?" is exactly what `modulePromises` stopped being able to answer.
18
- //
19
- // Reassigned per `loadModules` call rather than accumulated, because only the
20
- // current manifest can answer for the current boot — a name declared by an
21
- // earlier load must not answer for this one.
22
- //
23
- // It is NOT symmetric with `modulePromises`, and the asymmetry is worth
24
- // stating because the obvious reading of the line above is that it is.
25
- // `modulePromises` has no reset: no `delete`, no reassignment, no clear, at
26
- // any commit on `dev`. It accumulates the union of every `loadModules` call in
27
- // the process while this set is replaced per call. Measured over three loads
28
- // in one process at c87826b: load A declares and installs
29
- // `@stonyx/probe-life`; load B declares it with the package absent, so
30
- // discovery warns and `continue`s and registers nothing — and
31
- // `waitForModule('probe-life')` still RESOLVES, off load A's stale entry.
32
- // That is the "reports success for a module never loaded, silently" outcome
33
- // the registration loop below calls worse than the hang; T23 forecloses that
34
- // remedy WITHIN one call and no test sees across calls.
35
- //
36
- // Inherited, not introduced here — `modulePromises` was never reset at base
37
- // 5693744 either — and deliberately not closed here, because giving it a
38
- // per-call lifetime has a blast radius of its own and is filed separately.
39
- // Production reachability is nil: one `loadModules` call site, guarded by the
40
- // `Stonyx.instance` early return. The suite is held safe by the
41
- // unique-module-name-per-test rule in this file's test header, not by this.
42
- let declaredModuleNames = new Set();
43
11
  // Configure module-specific logging
44
12
  function configureLog(chronicle, module, config) {
45
13
  const { logColor, logMethod, logTimestamp } = config;
@@ -50,38 +18,8 @@ function configureLog(chronicle, module, config) {
50
18
  function initializeModule(moduleName, moduleClass, modules, initPromises) {
51
19
  const moduleInstance = new moduleClass();
52
20
  modules.push(moduleInstance);
53
- // A module class with no `init()` has nothing to initialise, so it is ready
54
- // the moment it is instantiated. Without this its registered promise dangles
55
- // — the only resolve on this path lives inside the `init()` wrapper below,
56
- // which is never created. Third of the three never-resolved paths named in
57
- // #120, and the one that scoping registration to `discovered` does NOT close,
58
- // because such a module IS discovered — measured, not reasoned: with only
59
- // the registration fix applied the suite read 180 pass / 1 fail at f575a3c
60
- // with T24 the sole failure. The SHA is the point; see the aggregate
61
- // convention noted at the `declaredDependencies` union below. Resolving here is readiness BY DEFINITION — there is no `init()`,
62
- // so there is nothing to wait for — and that is what separates it from
63
- // resolving on a discovery `continue`, where there is no module to be ready
64
- // at all.
65
- //
66
- // It is deliberately NOT the stronger claim that the module initialised.
67
- // `waitForModule` is documented as blocking until a module "finishes
68
- // initializing", and a `static async init()` typo — which TypeScript cannot
69
- // catch, since `init?()` is optional in lifecycle.ts — presents to the loader
70
- // as a class with no `init()`. This resolves it, and the waiter proceeds
71
- // against a module whose own initialisation never ran; measured, the waiter
72
- // observes the module's ready flag still false. The loader cannot tell that
73
- // typo from a legitimate no-`init()` module, and a no-`init()` module is a
74
- // supported shape, so resolving is the only non-hanging answer available and
75
- // availability over a silent hang is the right trade. The claim is what is
76
- // narrowed here, not the behaviour.
77
- //
78
- // `?.` for the reason the resolve below carries it: `initializeModule` is
79
- // also reached from the standalone path as `initializeModule(projectName, …)`,
80
- // and `projectName` is never a key in `modulePromises`.
81
- if (!moduleInstance.init) {
82
- modulePromises[moduleName]?.resolve();
21
+ if (!moduleInstance.init)
83
22
  return;
84
- }
85
23
  initPromises.push((async () => {
86
24
  await moduleInstance.init();
87
25
  modulePromises[moduleName]?.resolve();
@@ -140,55 +78,21 @@ export default async function loadModules(config, rootPath, chronicle) {
140
78
  const modules = [];
141
79
  const initPromises = [];
142
80
  const rootPackage = await readFile(`${rootPath}/package.json`, { json: true });
143
- // DISCOVERY SOURCE (abofs/stonyx#106 rule 3).
144
- //
145
- // This local used to be named `dependencies` while holding `devDependencies`
146
- // and nothing else. That name is the ruled root cause of #106: every reader
147
- // of this function — including its own docs — believed discovery scanned
148
- // both maps, because the identifier said so. It did not. An installed stonyx
149
- // module declared in `dependencies` was invisible, which is precisely how the
150
- // published fleet pins the core, so the loader could not see its own
151
- // siblings.
152
- //
153
- // Rule 3 widens the source to the DE-DUPLICATED union of both maps. Object
154
- // spread IS the de-duplication and is load-bearing: a module declared in both
155
- // maps collapses to one key, so it is registered once and — for an async
156
- // module — instantiated once. (A SYNC module is discovered once and
157
- // instantiated zero times; the loader never imports its entry point. Measured
158
- // on a dual-declared sync fixture: `modules.length === 0`.)
159
- //
160
- // A bare `[ ...Object.keys(deps), ...Object.keys(devDeps) ]` concat instantiates
161
- // a dual-declared module twice and runs its `init()` twice. That WOULD be
162
- // invisible to the suite aggregate were T22 not armed; T22's instance-count
163
- // assertion is what makes it visible. With the concat seeded, T22 is the SOLE
164
- // failure and both of its counts read actual 2 against expected 1 — measured
165
- // 180 pass / 1 fail at f575a3c, out of a 181-test suite.
166
- //
167
- // The SHA is not decoration. This sentence replaced one that round 1 found
168
- // false, and its replacement carried a figure measured at the PREVIOUS head
169
- // under the words "at this head": 178/1, which cannot reconcile against a
170
- // 181-test suite at all. Four reviewers caught it independently. A bare
171
- // aggregate has no author and no date, so it rots silently on the next commit
172
- // that adds a test; an aggregate with the SHA it was taken at stays true
173
- // forever and tells the next reader exactly what to re-run.
174
- const declaredDependencies = {
175
- ...(rootPackage.dependencies || {}),
176
- ...(rootPackage.devDependencies || {}),
177
- };
81
+ const dependencies = (rootPackage.devDependencies || {});
178
82
  const projectName = typeof rootPackage.name === 'string' ? rootPackage.name : '';
179
83
  // Expose rootPath to public configuration
180
84
  config.rootPath = rootPath;
181
- // The `@stonyx/` prefix filter STAYS (#106 keeps it explicitly). Widening it
182
- // to `@` — or dropping it — makes the loader warn on every non-Stonyx
183
- // dependency and leaves their `modulePromises` entries permanently
184
- // unresolved. T3 owns that kill. What is a module is decided below, by the
185
- // `stonyx-module` keyword; this is only a name test that bounds the scan.
186
- const moduleDependencies = Object.keys(declaredDependencies).filter((moduleName) => moduleName.startsWith('@stonyx/'));
187
- declaredModuleNames = new Set(moduleDependencies);
85
+ const moduleDependencies = Object.keys(dependencies).filter((moduleName) => moduleName.startsWith('@stonyx/'));
86
+ // Setup module promises prior to initialization
87
+ for (const moduleName of moduleDependencies) {
88
+ const promise = {};
89
+ modulePromises[moduleName] = promise;
90
+ promise.ready = new Promise(resolve => promise.resolve = resolve);
91
+ }
188
92
  // DISCOVERY, hoisted above the pre-flight.
189
93
  //
190
94
  // The pre-flight must check exactly what the loader loads, and no more.
191
- // `@stonyx/*` is a NAME test, not a membership test: a scoped dependency
95
+ // `@stonyx/*` is a NAME test, not a membership test: a scoped devDependency
192
96
  // without the `stonyx-module` keyword is warned about and skipped below —
193
97
  // never imported, never configured, incapable of registering anything on any
194
98
  // singleton — and refusing to boot over its nested copy prescribed the MODULE
@@ -215,86 +119,6 @@ export default async function loadModules(config, rootPath, chronicle) {
215
119
  keywords,
216
120
  });
217
121
  }
218
- // Setup module promises prior to initialization — over `discovered`, NOT
219
- // over `moduleDependencies`.
220
- //
221
- // This loop used to run above discovery and consume the declared list. That
222
- // was already the shape at base, but rule 3 widened what the declared list
223
- // contains, and the widening turned a fast named throw into an unbounded
224
- // boot hang. A name registered here but never resolved leaves
225
- // `waitForModule` pending forever; discovery `continue`s past two kinds of
226
- // name without reaching ANY of this file's three resolve sites — a manifest
227
- // that is not there, and a package without the `stonyx-module` keyword.
228
- // Three, not the two this sentence claimed when it was written: the
229
- // no-`init()` early return in `initializeModule` is a resolve site and the
230
- // commit that wrote this sentence is the commit that added it. The sites are
231
- // named rather than numbered by line, because the comment above
232
- // `initializeModule` reasons from this inventory and a stale count sends the
233
- // next dangling-promise audit one site short — they are the no-`init()`
234
- // early return, the `init()` wrapper, and the sync-module `continue` in the
235
- // load loop.
236
- // `@stonyx/logs` ships without that keyword and is an ordinary `dependencies`
237
- // entry, so this is a shape the fleet actually has.
238
- //
239
- // name in `dependencies`, not installed, another module awaits it
240
- // base 5693744 throws "…was not registered in project dependencies", 3 ms
241
- // a57045e one console.warn, then loadModules never settles
242
- //
243
- // Registering only what discovery admitted restores the fail-fast for both
244
- // of those DISCOVERY FAILURES, and for `devDependencies`-declared names too
245
- // — that half is INHERITED, not introduced here, and it is a real behaviour
246
- // change: hang becomes throw — and the throw names the right cause, because
247
- // `modulePromises` is no longer the app's declared list and `waitForModule`
248
- // below branches on that difference rather than asserting the old one. T13
249
- // pins the hang-becomes-throw change and the named cause; the PR body names
250
- // them. ("Halves" is avoided here because T13 uses that word for its own
251
- // input-domain split — the `devDependencies` half it covers and the
252
- // `dependencies` half it hands to T23.)
253
- //
254
- // SCOPED DELIBERATELY, because the sentence above would otherwise read as a
255
- // closure statement for the whole never-settling-promise family and it is
256
- // not one. What it closes is names that FAIL discovery. A name that PASSES
257
- // discovery is registered, and where its class HAS an `init()` its promise
258
- // is resolved only once that `init()` has settled — the qualifier matters,
259
- // because two of the three resolve sites named above settle without any
260
- // `init()` running. So two discovered modules whose `init()`s await
261
- // each other through `waitForModule` are both registered, reach no resolve
262
- // site, and leave `await Promise.all(initPromises)` at the end of this
263
- // function pending forever. Measured on a cross-map mutual wait
264
- // (`@stonyx/c1-a` in `dependencies`, `@stonyx/c1-b` in `devDependencies`,
265
- // each `init()` awaiting the other), 3000 ms cap:
266
- // base 5693744 threw in 3 ms — the `dependencies` half was never
267
- // discovered, so it failed fast for the wrong reason
268
- // a57045e HUNG, 3001 ms
269
- // f575a3c HUNG, 3003 ms — scoping registration does not touch it
270
- // c87826b HUNG, 3002 ms — re-measured at the head this ships from,
271
- // because the rows above stop before three commits that
272
- // touch this file and a reader at head cannot tell from a
273
- // truncated table whether the row still applies
274
- // Rule 3 moves that shape from fail-fast to deadlock and nothing in this
275
- // file closes it.
276
- //
277
- // NO DETECTION IS ATTEMPTED, and that is a decision rather than an
278
- // oversight. `waitForModule` is told which module is being waited FOR and
279
- // never which module is waiting, so a cycle is not observable without
280
- // caller attribution that does not exist here, and this framework sets no
281
- // boot timeout to hang a fallback off. A wait cycle is an authoring error in
282
- // the modules; docs/modules.md's `waitForModule` section records the hazard
283
- // so it is documented rather than merely known.
284
- //
285
- // The alternative — resolving in each `continue` branch — also removes the
286
- // hang and is worse: `waitForModule` would report success for a module that
287
- // was never loaded, silently. T23 asserts the named rejection precisely so
288
- // that remedy cannot pass.
289
- //
290
- // Ordering is safe: nothing between here and the old site touches
291
- // `modulePromises`, discovery only reads manifests, and registration still
292
- // completes before the first `init()` can run.
293
- for (const { name: moduleName } of discovered) {
294
- const promise = {};
295
- modulePromises[moduleName] = promise;
296
- promise.ready = new Promise(resolve => promise.resolve = resolve);
297
- }
298
122
  // Pre-flight: invariant I1, "one core". Before ANY module entry point is
299
123
  // imported, because the point of the check is the module that would NOT
300
124
  // throw — it would load, initialise, and register its hooks on a second
@@ -353,41 +177,10 @@ export default async function loadModules(config, rootPath, chronicle) {
353
177
  await Promise.all(initPromises);
354
178
  return modules;
355
179
  }
356
- /**
357
- * Blocks until `@stonyx/<moduleName>` has finished initializing.
358
- *
359
- * TWO failure sentences, because one sentence stopped being able to carry both
360
- * facts. Until PR #120 fix round 1 `modulePromises` held every DECLARED
361
- * `@stonyx/*` name, so "absent from the map" and "absent from the manifest"
362
- * were the same fact and the single inherited sentence was true. Registration
363
- * is now scoped to the DISCOVERED set — which is what removes the boot hang,
364
- * see the registration loop in `loadModules` — so absence from the map has two
365
- * causes, and the inherited sentence is FALSE for one of them: it tells the
366
- * operator the package is not in their dependencies while it is sitting in
367
- * their manifest.
368
- *
369
- * Not hypothetical, and not a shape this repo has to imagine: `@stonyx/logs`
370
- * ships without the `stonyx-module` keyword and is an ordinary `dependencies`
371
- * entry of THIS package.json. An app in the same shape calling
372
- * `waitForModule('logs')` would be sent to grep a manifest that already
373
- * contains the answer. Naming the wrong cause is the defect abofs/stonyx#108
374
- * was filed over, and it is not made acceptable by being one line long.
375
- *
376
- * The declared branch does not say WHICH of the two causes applies, because
377
- * this function does not know. Discovery does, and it already `console.warn`s
378
- * the specific reason at load time, so the message points at that warning
379
- * rather than guessing between them.
380
- */
381
180
  export async function waitForModule(moduleName) {
382
181
  const fullName = `@stonyx/${moduleName}`;
383
182
  const modulePromise = modulePromises[fullName];
384
- if (!modulePromise) {
385
- throw new Error(declaredModuleNames.has(fullName)
386
- ? `Could wait for module: ${fullName}. It IS declared in this project's dependencies or ` +
387
- 'devDependencies, but the loader did not load it: either it is not installed under ' +
388
- 'node_modules, or its package.json does not carry the "stonyx-module" keyword. ' +
389
- 'loadModules warned which one at load time.'
390
- : `Could wait for module: ${fullName}. Module was not registered in project dependencies`);
391
- }
392
- await modulePromise.ready;
183
+ if (!modulePromise)
184
+ throw new Error(`Could wait for module: ${fullName}. Module was not registered in project dependencies`);
185
+ await modulePromises[fullName].ready;
393
186
  }
@@ -265,8 +265,7 @@ export function findForeignCores(modules, core = undefined, report = IGNORE) {
265
265
  * NOTHING OFF DISK IS TRUSTED FOR DISPLAY — all three interpolated fields.
266
266
  *
267
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 `dependencies` or
269
- * `devDependencies` (abofs/stonyx#106 rule 3: the loader scans the union),
268
+ * `moduleName` is narrower — it is a key from the app's own `devDependencies`,
270
269
  * so forging it is an app author forging their own diagnostic — but it is
271
270
  * rendered through the same `padEnd` table, and it caused the same two
272
271
  * symptoms: seeded with ANSI escapes and embedded newlines it FORGED A
@@ -298,7 +297,7 @@ export function findForeignCores(modules, core = undefined, report = IGNORE) {
298
297
  * nothing but its own wrap. The module name and the version are both inside
299
298
  * PADDED columns whose widths are the max over every row, so one long value
300
299
  * there pushes every other row's remaining columns off screen. Those two take
301
- * the tight default; a scoped `@stonyx/*` dependency key is far short of it,
300
+ * the tight default; a scoped `@stonyx/*` devDependency key is far short of it,
302
301
  * so a real name is sanitised and never shortened either.
303
302
  */
304
303
  const PATH_LIMIT = 1024;
@@ -369,46 +368,10 @@ export function duplicateCoreMessage(foreign) {
369
368
  'peerDependencies range, never as an exact dependency — @stonyx/discord is the reference shape. ' +
370
369
  consumerRemedy,
371
370
  '',
372
- // THIS PARAGRAPH IS A CLAIM ABOUT WHAT THE LOADER SCANS, printed at the
373
- // moment boot is refused. abofs/stonyx#106 rule 3 widened that scan to the
374
- // de-duplicated union of `dependencies` and `devDependencies`, and until
375
- // PR #120 fix round 1 this sentence still said `devDependencies` only —
376
- // so a refusal could name `seen by "@stonyx/x"` for a package declared
377
- // ONLY in `dependencies` and then, a few lines later, tell the operator
378
- // that such a declaration is not counted. Reproduced verbatim before the
379
- // fix. That sends them to search `devDependencies`, where there is nothing.
380
- // If the scan source ever changes again, this string changes with it;
381
- // `duplicate-core-test.ts` D4 asserts the claim, not just the header.
382
- //
383
- // FIX ROUND 2 corrects the closing sentence for the same reason. It used to
384
- // exclude "a copy dragged in by ... a package without the keyword", which
385
- // is false for the commonest shape there is: `npm install -g stonyx` plus
386
- // `stonyx new` writes `stonyx` into the app's own `dependencies`
387
- // (`src/cli/new.ts`), `stonyx/package.json` carries no `keywords` field at
388
- // all, and every discovered module's ESM walk reaches
389
- // `<app>/node_modules/stonyx` — so that copy is counted and PRINTED in the
390
- // `seen by` row this sentence sits under. It is the exact case the
391
- // duplicate-INSTALL remedy branch above was written for. The sentence now
392
- // names what is ENUMERATED — the resolution walk — instead of closing a set
393
- // of packages, because the enumeration is a property the code has and the
394
- // set is not. D20 pins it against a fixture that counts such a copy.
395
- //
396
- // "FIRST copy that walk finds" is not padding, and it is the round-2
397
- // rewrite's own near-miss: `coreSeenBy` returns on the first `asCore` hit,
398
- // so a second copy further up the same walk is SHADOWED and never counted.
399
- // Written as "a copy owned by any package on that walk is counted" this
400
- // sentence would have been false in the same shape as the one it replaces —
401
- // over-closing in the other direction. The shadowing case is stated because
402
- // it is the reason the fail-open note below matters.
403
371
  'Scope of this check: it compares physical package ROOTS only. It does not check that the ' +
404
- 'single surviving copy is a compatible version, and the modules it starts from are the ' +
405
- '@stonyx/* packages declared in this app\'s dependencies or devDependencies that carry the ' +
406
- '"stonyx-module" keyword — nothing else. Either placement counts: the loader scans the ' +
407
- 'de-duplicated union of both maps. What it then reports is not those packages\' own copies but ' +
408
- 'the copy each of them would IMPORT: for every one it follows Node\'s ESM resolution walk up ' +
409
- 'from that module\'s own directory and takes the FIRST copy that walk finds, whoever owns it — ' +
410
- 'including this app\'s own node_modules/stonyx, which no module need declare. A copy no ' +
411
- 'module\'s ' +
412
- 'walk reaches first is not counted, and one further up a walk is hidden by a nearer one.',
372
+ 'single surviving copy is a compatible version, and it looks at @stonyx/* packages declared in ' +
373
+ 'this app\'s devDependencies that carry the "stonyx-module" keyword — nothing else. A copy ' +
374
+ 'dragged in by anything outside that set, including an @stonyx/* package declared in ' +
375
+ 'dependencies rather than devDependencies, is not counted.',
413
376
  ].join('\n');
414
377
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonyx",
3
- "version": "0.2.3-alpha.61",
3
+ "version": "0.2.3-alpha.63",
4
4
  "description": "Base stonyx framework module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",