stonyx 0.2.3-alpha.46 → 0.2.3-alpha.47
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/main.js +7 -3
- package/dist/util/import-config.d.ts +20 -0
- package/dist/util/import-config.js +86 -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.js`, initializes all `@stonyx/*` modules from your `devDependencies`, 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
31
|
|
|
32
32
|
## Documentation
|
|
33
33
|
|
package/dist/main.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import Chronicle from '@stonyx/logs';
|
|
17
17
|
import loadModules from './modules.js';
|
|
18
18
|
import { mergeObject } from '@stonyx/utils/object';
|
|
19
|
-
import { importConfig } from './util/import-config.js';
|
|
19
|
+
import { importConfig, CONFIG_NOT_FOUND_PREFIX } from './util/import-config.js';
|
|
20
20
|
import { resolveModuleName } from './util/resolve-module-name.js';
|
|
21
21
|
export default class Stonyx {
|
|
22
22
|
static initialized = false;
|
|
@@ -60,8 +60,12 @@ export default class Stonyx {
|
|
|
60
60
|
Object.assign(config, merged);
|
|
61
61
|
}
|
|
62
62
|
catch (err) {
|
|
63
|
-
//
|
|
64
|
-
|
|
63
|
+
// A test override that is genuinely ABSENT is non-fatal. Everything else
|
|
64
|
+
// re-throws — including `CONFIG_NOT_LOADABLE_PREFIX`, which is the whole
|
|
65
|
+
// point of that prefix existing (abofs/stonyx#105, invariant I2). A
|
|
66
|
+
// config the consumer wrote and this loader declined must never reach
|
|
67
|
+
// the caller as "you have no test overrides".
|
|
68
|
+
if (!(err instanceof Error) || !err.message.startsWith(CONFIG_NOT_FOUND_PREFIX))
|
|
65
69
|
throw err;
|
|
66
70
|
}
|
|
67
71
|
}
|
|
@@ -1 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "There is no config here." Callers are allowed to treat this as benign —
|
|
3
|
+
* `src/main.ts` does, for the optional `test/config/environment.*` override.
|
|
4
|
+
*
|
|
5
|
+
* `main.ts` matches on this exact string, so it is exported rather than
|
|
6
|
+
* duplicated: the swallow and the thrower must not be able to drift apart.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CONFIG_NOT_FOUND_PREFIX = "Config not found:";
|
|
9
|
+
/**
|
|
10
|
+
* "There is a config here and I declined to load it." Deliberately NOT prefixed
|
|
11
|
+
* with `CONFIG_NOT_FOUND_PREFIX`, because `main.ts`'s `NODE_ENV=test` catch
|
|
12
|
+
* swallows that prefix and must not swallow this.
|
|
13
|
+
*
|
|
14
|
+
* This is invariant I2 of the Sprint 93 install-and-boot cluster — "no silent
|
|
15
|
+
* decline". Before #105 the two states were literally indistinguishable:
|
|
16
|
+
* measured against `stonyx@0.2.3-beta.95`, a consumer with `config/environment.ts`
|
|
17
|
+
* and the same consumer after `mv config/environment.js config/environment.xyz`
|
|
18
|
+
* both produced the byte-identical `Config not found: .../config/environment.js`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CONFIG_NOT_LOADABLE_PREFIX = "Config present but not loadable:";
|
|
1
21
|
export declare function importConfig<T = unknown>(basePath: string): Promise<T>;
|
|
@@ -1,10 +1,91 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { pathToFileURL } from 'url';
|
|
3
|
+
/**
|
|
4
|
+
* Extensions `importConfig` will load, in preference order.
|
|
5
|
+
*
|
|
6
|
+
* `.ts` first. `stonyx@0.2.3-beta.62` shipped exactly this list; some release in
|
|
7
|
+
* `(beta.62, beta.82]` reduced it to a hard-coded `.js`, which is the regression
|
|
8
|
+
* abofs/stonyx#105 reports. Three places in this repo disagreed with that
|
|
9
|
+
* reduction and none were changed with it:
|
|
10
|
+
* - `docs/configuration.md:7,24` documents `.ts` as preferred and describes
|
|
11
|
+
* this exact algorithm verbatim;
|
|
12
|
+
* - `scripts/postinstall.js` writes `config/environment.ts` whenever the
|
|
13
|
+
* consumer has a `tsconfig.json` — the framework wrote the file its own
|
|
14
|
+
* loader then refused to read, which is how every TypeScript consumer (the
|
|
15
|
+
* shape `docs/cli.md:29` calls the default) failed to boot at all;
|
|
16
|
+
* - `src/util/resolve-entry-point.ts` — the sibling resolver, for `app.{ts,js}`
|
|
17
|
+
* — still prefers `.ts` today.
|
|
18
|
+
*/
|
|
19
|
+
const LOADABLE_EXTENSIONS = ['ts', 'js'];
|
|
20
|
+
/**
|
|
21
|
+
* Extensions a consumer plausibly writes a config as, that this loader will not
|
|
22
|
+
* read. Their only purpose is to be DETECTED: see `CONFIG_NOT_LOADABLE_PREFIX`.
|
|
23
|
+
*/
|
|
24
|
+
const UNREADABLE_EXTENSIONS = ['mts', 'cts', 'tsx', 'jsx', 'mjs', 'cjs', 'json'];
|
|
25
|
+
/**
|
|
26
|
+
* "There is no config here." Callers are allowed to treat this as benign —
|
|
27
|
+
* `src/main.ts` does, for the optional `test/config/environment.*` override.
|
|
28
|
+
*
|
|
29
|
+
* `main.ts` matches on this exact string, so it is exported rather than
|
|
30
|
+
* duplicated: the swallow and the thrower must not be able to drift apart.
|
|
31
|
+
*/
|
|
32
|
+
export const CONFIG_NOT_FOUND_PREFIX = 'Config not found:';
|
|
33
|
+
/**
|
|
34
|
+
* "There is a config here and I declined to load it." Deliberately NOT prefixed
|
|
35
|
+
* with `CONFIG_NOT_FOUND_PREFIX`, because `main.ts`'s `NODE_ENV=test` catch
|
|
36
|
+
* swallows that prefix and must not swallow this.
|
|
37
|
+
*
|
|
38
|
+
* This is invariant I2 of the Sprint 93 install-and-boot cluster — "no silent
|
|
39
|
+
* decline". Before #105 the two states were literally indistinguishable:
|
|
40
|
+
* measured against `stonyx@0.2.3-beta.95`, a consumer with `config/environment.ts`
|
|
41
|
+
* and the same consumer after `mv config/environment.js config/environment.xyz`
|
|
42
|
+
* both produced the byte-identical `Config not found: .../config/environment.js`.
|
|
43
|
+
*/
|
|
44
|
+
export const CONFIG_NOT_LOADABLE_PREFIX = 'Config present but not loadable:';
|
|
45
|
+
/**
|
|
46
|
+
* Node error codes that mean "I can see this file and I refuse to load it",
|
|
47
|
+
* as distinct from "your config threw" or "your config imports something
|
|
48
|
+
* missing". Only these are re-framed; everything else propagates untouched so a
|
|
49
|
+
* genuine syntax error in a consumer's config still reads as one.
|
|
50
|
+
*
|
|
51
|
+
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` is the load-bearing member:
|
|
52
|
+
* Node refuses to type-strip anything under `node_modules`, so a `@stonyx/*`
|
|
53
|
+
* module that ships `config/environment.ts` crashes every consumer. That rule
|
|
54
|
+
* is unchanged by this fix and is why `docs/conventions/framework-modules.md`
|
|
55
|
+
* still requires modules to ship `.js` — but the failure is now named.
|
|
56
|
+
*/
|
|
57
|
+
const FILE_TYPE_REFUSAL_CODES = new Set([
|
|
58
|
+
'ERR_UNKNOWN_FILE_EXTENSION',
|
|
59
|
+
'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
|
|
60
|
+
]);
|
|
3
61
|
export async function importConfig(basePath) {
|
|
4
|
-
const
|
|
5
|
-
if (
|
|
6
|
-
|
|
62
|
+
const matches = LOADABLE_EXTENSIONS.filter(ext => existsSync(`${basePath}.${ext}`));
|
|
63
|
+
if (matches.length === 0) {
|
|
64
|
+
const declined = UNREADABLE_EXTENSIONS.filter(ext => existsSync(`${basePath}.${ext}`));
|
|
65
|
+
if (declined.length > 0) {
|
|
66
|
+
throw new Error(`${CONFIG_NOT_LOADABLE_PREFIX} ${basePath}.${declined.join(` and ${basePath}.`)} ` +
|
|
67
|
+
`exists, but Stonyx loads ${basePath}.ts or ${basePath}.js only. ` +
|
|
68
|
+
`Rename or compile it — this is not a missing config.`);
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`${CONFIG_NOT_FOUND_PREFIX} ${basePath}.{ts,js}`);
|
|
71
|
+
}
|
|
72
|
+
if (matches.length > 1) {
|
|
73
|
+
console.warn(`Warning: both ${basePath}.ts and ${basePath}.js exist. Using .ts — delete the .js to silence this warning (it is likely a stale compiled artifact or postinstall stub).`);
|
|
74
|
+
}
|
|
75
|
+
const path = `${basePath}.${matches[0]}`;
|
|
76
|
+
try {
|
|
77
|
+
const mod = await import(pathToFileURL(path).href);
|
|
78
|
+
return mod.default;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error?.code;
|
|
82
|
+
if (code && FILE_TYPE_REFUSAL_CODES.has(code)) {
|
|
83
|
+
const sibling = matches.length > 1
|
|
84
|
+
? ` A ${basePath}.js also exists; .ts wins by design, so this is not resolved by leaving the .js in place.`
|
|
85
|
+
: '';
|
|
86
|
+
throw new Error(`${CONFIG_NOT_LOADABLE_PREFIX} ${path} exists, but this Node runtime refused to load it ` +
|
|
87
|
+
`(${code}): ${error.message}${sibling}`, { cause: error });
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
7
90
|
}
|
|
8
|
-
const mod = await import(pathToFileURL(path).href);
|
|
9
|
-
return mod.default;
|
|
10
91
|
}
|