stonyx 0.2.3-alpha.47 → 0.2.3-alpha.48
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/help.js +2 -2
- package/dist/util/import-config.d.ts +22 -0
- package/dist/util/import-config.js +93 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ stonyx serve
|
|
|
22
22
|
The `new` command walks you through module selection and generates a ready-to-run project. From there, the CLI handles bootstrapping:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
stonyx serve # Bootstrap + run app.js
|
|
25
|
+
stonyx serve # Bootstrap + run app.ts (or app.js)
|
|
26
26
|
stonyx test # Bootstrap + run tests
|
|
27
27
|
stonyx help # Show all available commands
|
|
28
28
|
```
|
package/dist/cli/help.js
CHANGED
|
@@ -23,8 +23,8 @@ export default async function help({ args, builtInCommands, loadModuleCommands }
|
|
|
23
23
|
}
|
|
24
24
|
console.log('\nAliases: s=serve, t=test, n=new, h=help\n');
|
|
25
25
|
console.log('Project conventions:\n');
|
|
26
|
-
console.log(' Entry point: app.js');
|
|
27
|
-
console.log(' Config: config/environment.js');
|
|
26
|
+
console.log(' Entry point: app.ts (or app.js)');
|
|
27
|
+
console.log(' Config: config/environment.ts (or .js)');
|
|
28
28
|
console.log(' DB schema: config/db-schema.js');
|
|
29
29
|
console.log(' Models: models/');
|
|
30
30
|
console.log(' Serializers: serializers/');
|
|
@@ -18,4 +18,26 @@ export declare const CONFIG_NOT_FOUND_PREFIX = "Config not found:";
|
|
|
18
18
|
* both produced the byte-identical `Config not found: .../config/environment.js`.
|
|
19
19
|
*/
|
|
20
20
|
export declare const CONFIG_NOT_LOADABLE_PREFIX = "Config present but not loadable:";
|
|
21
|
+
/**
|
|
22
|
+
* Did this runtime refuse OUR config file, or something our config imported?
|
|
23
|
+
*
|
|
24
|
+
* `FILE_TYPE_REFUSAL_CODES.has(code)` alone cannot tell. A config that loaded
|
|
25
|
+
* and ran perfectly well, but whose own nested `import()` hit a `.ts` under
|
|
26
|
+
* `node_modules`, throws the SAME code — and was reported as "this Node runtime
|
|
27
|
+
* refused to load your config", naming a file that is fine. That misdirection
|
|
28
|
+
* points the consumer at the one file that is not the problem, and the likeliest
|
|
29
|
+
* instance of it is the exact `.ts`-under-node_modules case this loader's own
|
|
30
|
+
* docs tell people to look for.
|
|
31
|
+
*
|
|
32
|
+
* Returns true when the refusal names our path, or when no path could be
|
|
33
|
+
* extracted at all (unknown message shape — keep re-framing rather than
|
|
34
|
+
* silently losing the loud error that invariant I2 exists for).
|
|
35
|
+
*
|
|
36
|
+
* Exported ONLY so its branches can be reached directly. Driving it through
|
|
37
|
+
* `importConfig` reaches the two path-comparison outcomes and nothing else:
|
|
38
|
+
* the "no path extractable" fallback and the `realPath` catch both survived
|
|
39
|
+
* mutation with the suite green. A predicate inside the guard that makes
|
|
40
|
+
* failures loud does not get to have branches no test can reach.
|
|
41
|
+
*/
|
|
42
|
+
export declare function refusalIsAboutTheConfig(message: string, configPath: string): boolean;
|
|
21
43
|
export declare function importConfig<T = unknown>(basePath: string): Promise<T>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { pathToFileURL } from 'url';
|
|
1
|
+
import { existsSync, realpathSync } from 'fs';
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
3
3
|
/**
|
|
4
4
|
* Extensions `importConfig` will load, in preference order.
|
|
5
5
|
*
|
|
@@ -18,10 +18,18 @@ import { pathToFileURL } from 'url';
|
|
|
18
18
|
*/
|
|
19
19
|
const LOADABLE_EXTENSIONS = ['ts', 'js'];
|
|
20
20
|
/**
|
|
21
|
-
* Extensions a consumer plausibly writes a config as, that this loader
|
|
22
|
-
*
|
|
21
|
+
* Extensions a consumer plausibly writes a config as, that this loader does not
|
|
22
|
+
* RESOLVE. Their only purpose is to be DETECTED: see `CONFIG_NOT_LOADABLE_PREFIX`.
|
|
23
|
+
*
|
|
24
|
+
* Named for what is actually true of them. `.mjs`, `.cjs` and `.json` are all
|
|
25
|
+
* perfectly readable by Node — `import()` handles every one of them today.
|
|
26
|
+
* Nothing here is unreadable; they are simply not in `LOADABLE_EXTENSIONS`, so
|
|
27
|
+
* this loader never forms a path with them, and a consumer who writes one gets
|
|
28
|
+
* "config present but not loadable" instead of a silent "not found". The old
|
|
29
|
+
* name, UNREADABLE_EXTENSIONS, asserted a property of Node that is false, and
|
|
30
|
+
* invited a future reader to "fix" it by adding them to the resolver.
|
|
23
31
|
*/
|
|
24
|
-
const
|
|
32
|
+
const UNRESOLVED_EXTENSIONS = ['mts', 'cts', 'tsx', 'jsx', 'mjs', 'cjs', 'json'];
|
|
25
33
|
/**
|
|
26
34
|
* "There is no config here." Callers are allowed to treat this as benign —
|
|
27
35
|
* `src/main.ts` does, for the optional `test/config/environment.*` override.
|
|
@@ -58,10 +66,69 @@ const FILE_TYPE_REFUSAL_CODES = new Set([
|
|
|
58
66
|
'ERR_UNKNOWN_FILE_EXTENSION',
|
|
59
67
|
'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
|
|
60
68
|
]);
|
|
69
|
+
/**
|
|
70
|
+
* The first absolute POSIX path named in a Node error message.
|
|
71
|
+
*
|
|
72
|
+
* Both refusal codes name the file they refused, but in different shapes —
|
|
73
|
+
* measured on node v24.13.0:
|
|
74
|
+
*
|
|
75
|
+
* ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING
|
|
76
|
+
* Stripping types is currently unsupported for files under node_modules,
|
|
77
|
+
* for "/private/tmp/x/node_modules/dep/thing.ts"
|
|
78
|
+
* ERR_UNKNOWN_FILE_EXTENSION
|
|
79
|
+
* Unknown file extension ".ts" for /private/tmp/x/environment.ts
|
|
80
|
+
*
|
|
81
|
+
* The second names a bare extension BEFORE it names the file, which is why
|
|
82
|
+
* this requires a leading `/` rather than matching the first quoted token.
|
|
83
|
+
* Neither error exposes the path as an own property: `Object.keys(error)` is
|
|
84
|
+
* `['code']` for both, so the message is the only source.
|
|
85
|
+
*
|
|
86
|
+
* Deliberately POSIX-only. A Windows path is simply not extracted, and
|
|
87
|
+
* `refusalIsAboutTheConfig` treats "no path extracted" as "cannot tell" and
|
|
88
|
+
* keeps the pre-existing behaviour, so this degrades rather than misfires.
|
|
89
|
+
*/
|
|
90
|
+
const REFUSED_PATH_PATTERN = /(?:^|[\s"'(])((?:file:\/\/)?\/[^\s"'()]+\.[A-Za-z0-9]+)/;
|
|
91
|
+
/** Resolves symlinks so `/tmp/...` and `/private/tmp/...` compare equal. */
|
|
92
|
+
function realPath(path) {
|
|
93
|
+
try {
|
|
94
|
+
return realpathSync(path);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Did this runtime refuse OUR config file, or something our config imported?
|
|
102
|
+
*
|
|
103
|
+
* `FILE_TYPE_REFUSAL_CODES.has(code)` alone cannot tell. A config that loaded
|
|
104
|
+
* and ran perfectly well, but whose own nested `import()` hit a `.ts` under
|
|
105
|
+
* `node_modules`, throws the SAME code — and was reported as "this Node runtime
|
|
106
|
+
* refused to load your config", naming a file that is fine. That misdirection
|
|
107
|
+
* points the consumer at the one file that is not the problem, and the likeliest
|
|
108
|
+
* instance of it is the exact `.ts`-under-node_modules case this loader's own
|
|
109
|
+
* docs tell people to look for.
|
|
110
|
+
*
|
|
111
|
+
* Returns true when the refusal names our path, or when no path could be
|
|
112
|
+
* extracted at all (unknown message shape — keep re-framing rather than
|
|
113
|
+
* silently losing the loud error that invariant I2 exists for).
|
|
114
|
+
*
|
|
115
|
+
* Exported ONLY so its branches can be reached directly. Driving it through
|
|
116
|
+
* `importConfig` reaches the two path-comparison outcomes and nothing else:
|
|
117
|
+
* the "no path extractable" fallback and the `realPath` catch both survived
|
|
118
|
+
* mutation with the suite green. A predicate inside the guard that makes
|
|
119
|
+
* failures loud does not get to have branches no test can reach.
|
|
120
|
+
*/
|
|
121
|
+
export function refusalIsAboutTheConfig(message, configPath) {
|
|
122
|
+
const refused = REFUSED_PATH_PATTERN.exec(message)?.[1];
|
|
123
|
+
if (!refused)
|
|
124
|
+
return true;
|
|
125
|
+
const refusedPath = refused.startsWith('file://') ? fileURLToPath(refused) : refused;
|
|
126
|
+
return refusedPath === configPath || realPath(refusedPath) === realPath(configPath);
|
|
127
|
+
}
|
|
61
128
|
export async function importConfig(basePath) {
|
|
62
129
|
const matches = LOADABLE_EXTENSIONS.filter(ext => existsSync(`${basePath}.${ext}`));
|
|
63
130
|
if (matches.length === 0) {
|
|
64
|
-
const declined =
|
|
131
|
+
const declined = UNRESOLVED_EXTENSIONS.filter(ext => existsSync(`${basePath}.${ext}`));
|
|
65
132
|
if (declined.length > 0) {
|
|
66
133
|
throw new Error(`${CONFIG_NOT_LOADABLE_PREFIX} ${basePath}.${declined.join(` and ${basePath}.`)} ` +
|
|
67
134
|
`exists, but Stonyx loads ${basePath}.ts or ${basePath}.js only. ` +
|
|
@@ -79,12 +146,29 @@ export async function importConfig(basePath) {
|
|
|
79
146
|
}
|
|
80
147
|
catch (error) {
|
|
81
148
|
const code = error?.code;
|
|
82
|
-
if (code && FILE_TYPE_REFUSAL_CODES.has(code)) {
|
|
149
|
+
if (code && FILE_TYPE_REFUSAL_CODES.has(code) && refusalIsAboutTheConfig(error.message, path)) {
|
|
150
|
+
// Both files exist AND the `.ts` was refused. This is the one state where
|
|
151
|
+
// the both-present warning printed a few lines above is actively wrong
|
|
152
|
+
// advice: it says "delete the .js", and here the `.js` is the only file
|
|
153
|
+
// this runtime can read. Say so explicitly rather than leaving the
|
|
154
|
+
// consumer to reconcile two messages from the same call.
|
|
155
|
+
//
|
|
156
|
+
// It is also a deliberate behaviour change. `stonyx@0.2.3-beta.95`
|
|
157
|
+
// resolved `${basePath}.js` unconditionally, so this exact state LOADED
|
|
158
|
+
// and booted; measured against that loader, same fixture, plain node:
|
|
159
|
+
// it returned the `.js` default export with no warning. Preferring `.ts`
|
|
160
|
+
// is the point of #105, and the population most likely to hold both
|
|
161
|
+
// files is the one that worked around #105 by renaming to `.js` while
|
|
162
|
+
// the postinstall kept writing the `.ts` stub. So this message has to
|
|
163
|
+
// carry the "this used to work" fact, not just the remedy.
|
|
83
164
|
const sibling = matches.length > 1
|
|
84
|
-
? `
|
|
165
|
+
? ` ${basePath}.js also exists and this runtime CAN read it, but .ts wins the preference order` +
|
|
166
|
+
` so the .js is never reached. Disregard the "delete the .js" warning above — in THIS state the` +
|
|
167
|
+
` fix is the opposite one: remove or compile ${basePath}.ts.` +
|
|
168
|
+
` (stonyx@0.2.3-beta.95 loaded the .js here; preferring .ts is a deliberate change — abofs/stonyx#105.)`
|
|
85
169
|
: '';
|
|
86
170
|
throw new Error(`${CONFIG_NOT_LOADABLE_PREFIX} ${path} exists, but this Node runtime refused to load it ` +
|
|
87
|
-
`(${code}): ${error.message}
|
|
171
|
+
`(${code}): ${error.message}.${sibling}`, { cause: error });
|
|
88
172
|
}
|
|
89
173
|
throw error;
|
|
90
174
|
}
|