stonyx 0.2.3-alpha.57 → 0.2.3-alpha.59
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 +1 -1
- package/dist/cli/new.js +6 -2
- package/dist/modules.js +64 -12
- package/dist/util/duplicate-core.js +17 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ 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`,
|
|
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.
|
|
31
31
|
|
|
32
32
|
## Documentation
|
|
33
33
|
|
package/dist/cli/new.js
CHANGED
|
@@ -167,8 +167,12 @@ export function generatePackageJson(name, selectedModules, coreVersion = readCor
|
|
|
167
167
|
tsx: '^4.21.0',
|
|
168
168
|
typescript: '^5.8.3'
|
|
169
169
|
};
|
|
170
|
-
//
|
|
171
|
-
//
|
|
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`.
|
|
172
176
|
for (const mod of selectedModules) {
|
|
173
177
|
devDependencies[mod.package] = moduleTag;
|
|
174
178
|
}
|
package/dist/modules.js
CHANGED
|
@@ -18,8 +18,24 @@ function configureLog(chronicle, module, config) {
|
|
|
18
18
|
function initializeModule(moduleName, moduleClass, modules, initPromises) {
|
|
19
19
|
const moduleInstance = new moduleClass();
|
|
20
20
|
modules.push(moduleInstance);
|
|
21
|
-
|
|
21
|
+
// A module class with no `init()` has nothing to initialise, so it is ready
|
|
22
|
+
// the moment it is instantiated. Without this its registered promise dangles
|
|
23
|
+
// — the only resolve on this path lives inside the `init()` wrapper below,
|
|
24
|
+
// which is never created. Third of the three never-resolved paths named in
|
|
25
|
+
// #120, and the one that scoping registration to `discovered` does NOT close,
|
|
26
|
+
// because such a module IS discovered — measured, not reasoned: with only
|
|
27
|
+
// the registration fix applied the suite read 180/1 with T24 the sole
|
|
28
|
+
// failure. Resolving here is truthful, because the module really did load,
|
|
29
|
+
// and that is what separates it from resolving on a discovery `continue`,
|
|
30
|
+
// where there is no module to be ready.
|
|
31
|
+
//
|
|
32
|
+
// `?.` for the reason the resolve below carries it: `initializeModule` is
|
|
33
|
+
// also reached from the standalone path as `initializeModule(projectName, …)`,
|
|
34
|
+
// and `projectName` is never a key in `modulePromises`.
|
|
35
|
+
if (!moduleInstance.init) {
|
|
36
|
+
modulePromises[moduleName]?.resolve();
|
|
22
37
|
return;
|
|
38
|
+
}
|
|
23
39
|
initPromises.push((async () => {
|
|
24
40
|
await moduleInstance.init();
|
|
25
41
|
modulePromises[moduleName]?.resolve();
|
|
@@ -90,10 +106,17 @@ export default async function loadModules(config, rootPath, chronicle) {
|
|
|
90
106
|
//
|
|
91
107
|
// Rule 3 widens the source to the DE-DUPLICATED union of both maps. Object
|
|
92
108
|
// spread IS the de-duplication and is load-bearing: a module declared in both
|
|
93
|
-
// maps collapses to one key, so it is registered once and
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
109
|
+
// maps collapses to one key, so it is registered once and — for an async
|
|
110
|
+
// module — instantiated once. (A SYNC module is discovered once and
|
|
111
|
+
// instantiated zero times; the loader never imports its entry point. Measured
|
|
112
|
+
// on a dual-declared sync fixture: `modules.length === 0`.)
|
|
113
|
+
//
|
|
114
|
+
// A bare `[ ...Object.keys(deps), ...Object.keys(devDeps) ]` concat instantiates
|
|
115
|
+
// a dual-declared module twice and runs its `init()` twice. That WOULD be
|
|
116
|
+
// invisible to the suite aggregate were T22 not armed; T22's instance-count
|
|
117
|
+
// assertion is what makes it visible. Measured at this head with the concat
|
|
118
|
+
// seeded: 178/1 with T22 the sole failure, both of its counts reading actual 2
|
|
119
|
+
// against expected 1.
|
|
97
120
|
const declaredDependencies = {
|
|
98
121
|
...(rootPackage.dependencies || {}),
|
|
99
122
|
...(rootPackage.devDependencies || {}),
|
|
@@ -107,16 +130,10 @@ export default async function loadModules(config, rootPath, chronicle) {
|
|
|
107
130
|
// unresolved. T3 owns that kill. What is a module is decided below, by the
|
|
108
131
|
// `stonyx-module` keyword; this is only a name test that bounds the scan.
|
|
109
132
|
const moduleDependencies = Object.keys(declaredDependencies).filter((moduleName) => moduleName.startsWith('@stonyx/'));
|
|
110
|
-
// Setup module promises prior to initialization
|
|
111
|
-
for (const moduleName of moduleDependencies) {
|
|
112
|
-
const promise = {};
|
|
113
|
-
modulePromises[moduleName] = promise;
|
|
114
|
-
promise.ready = new Promise(resolve => promise.resolve = resolve);
|
|
115
|
-
}
|
|
116
133
|
// DISCOVERY, hoisted above the pre-flight.
|
|
117
134
|
//
|
|
118
135
|
// The pre-flight must check exactly what the loader loads, and no more.
|
|
119
|
-
// `@stonyx/*` is a NAME test, not a membership test: a scoped
|
|
136
|
+
// `@stonyx/*` is a NAME test, not a membership test: a scoped dependency
|
|
120
137
|
// without the `stonyx-module` keyword is warned about and skipped below —
|
|
121
138
|
// never imported, never configured, incapable of registering anything on any
|
|
122
139
|
// singleton — and refusing to boot over its nested copy prescribed the MODULE
|
|
@@ -143,6 +160,41 @@ export default async function loadModules(config, rootPath, chronicle) {
|
|
|
143
160
|
keywords,
|
|
144
161
|
});
|
|
145
162
|
}
|
|
163
|
+
// Setup module promises prior to initialization — over `discovered`, NOT
|
|
164
|
+
// over `moduleDependencies`.
|
|
165
|
+
//
|
|
166
|
+
// This loop used to run above discovery and consume the declared list. That
|
|
167
|
+
// was already the shape at base, but rule 3 widened what the declared list
|
|
168
|
+
// contains, and the widening turned a fast named throw into an unbounded
|
|
169
|
+
// boot hang. A name registered here but never resolved leaves
|
|
170
|
+
// `waitForModule` pending forever; discovery `continue`s past two kinds of
|
|
171
|
+
// name without reaching either of this file's two resolve sites — a manifest
|
|
172
|
+
// that is not there, and a package without the `stonyx-module` keyword.
|
|
173
|
+
// `@stonyx/logs` ships without that keyword and is an ordinary `dependencies`
|
|
174
|
+
// entry, so this is a shape the fleet actually has.
|
|
175
|
+
//
|
|
176
|
+
// name in `dependencies`, not installed, another module awaits it
|
|
177
|
+
// base 5693744 throws "…was not registered in project dependencies", 3 ms
|
|
178
|
+
// a57045e one console.warn, then loadModules never settles
|
|
179
|
+
//
|
|
180
|
+
// Registering only what discovery admitted restores the fail-fast for both,
|
|
181
|
+
// and for `devDependencies`-declared names too — that half is INHERITED, not
|
|
182
|
+
// introduced here, and it is a real behaviour change: hang becomes throw.
|
|
183
|
+
// T13 pins it, and the PR body names it.
|
|
184
|
+
//
|
|
185
|
+
// The alternative — resolving in each `continue` branch — also removes the
|
|
186
|
+
// hang and is worse: `waitForModule` would report success for a module that
|
|
187
|
+
// was never loaded, silently. T23 asserts the named rejection precisely so
|
|
188
|
+
// that remedy cannot pass.
|
|
189
|
+
//
|
|
190
|
+
// Ordering is safe: nothing between here and the old site touches
|
|
191
|
+
// `modulePromises`, discovery only reads manifests, and registration still
|
|
192
|
+
// completes before the first `init()` can run.
|
|
193
|
+
for (const { name: moduleName } of discovered) {
|
|
194
|
+
const promise = {};
|
|
195
|
+
modulePromises[moduleName] = promise;
|
|
196
|
+
promise.ready = new Promise(resolve => promise.resolve = resolve);
|
|
197
|
+
}
|
|
146
198
|
// Pre-flight: invariant I1, "one core". Before ANY module entry point is
|
|
147
199
|
// imported, because the point of the check is the module that would NOT
|
|
148
200
|
// throw — it would load, initialise, and register its hooks on a second
|
|
@@ -265,7 +265,8 @@ 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 `
|
|
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),
|
|
269
270
|
* so forging it is an app author forging their own diagnostic — but it is
|
|
270
271
|
* rendered through the same `padEnd` table, and it caused the same two
|
|
271
272
|
* symptoms: seeded with ANSI escapes and embedded newlines it FORGED A
|
|
@@ -297,7 +298,7 @@ export function findForeignCores(modules, core = undefined, report = IGNORE) {
|
|
|
297
298
|
* nothing but its own wrap. The module name and the version are both inside
|
|
298
299
|
* PADDED columns whose widths are the max over every row, so one long value
|
|
299
300
|
* there pushes every other row's remaining columns off screen. Those two take
|
|
300
|
-
* the tight default; a scoped `@stonyx/*`
|
|
301
|
+
* the tight default; a scoped `@stonyx/*` dependency key is far short of it,
|
|
301
302
|
* so a real name is sanitised and never shortened either.
|
|
302
303
|
*/
|
|
303
304
|
const PATH_LIMIT = 1024;
|
|
@@ -368,10 +369,21 @@ export function duplicateCoreMessage(foreign) {
|
|
|
368
369
|
'peerDependencies range, never as an exact dependency — @stonyx/discord is the reference shape. ' +
|
|
369
370
|
consumerRemedy,
|
|
370
371
|
'',
|
|
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, seven 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.
|
|
371
382
|
'Scope of this check: it compares physical package ROOTS only. It does not check that the ' +
|
|
372
383
|
'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
|
|
374
|
-
'
|
|
375
|
-
'
|
|
384
|
+
'this app\'s dependencies or devDependencies that carry the "stonyx-module" keyword — nothing ' +
|
|
385
|
+
'else. Either placement counts: the loader scans the de-duplicated union of both maps. A copy ' +
|
|
386
|
+
'dragged in by anything outside that set — a transitive dependency, a package without the ' +
|
|
387
|
+
'keyword, or one not declared in this app\'s manifest at all — is not counted.',
|
|
376
388
|
].join('\n');
|
|
377
389
|
}
|