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 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
- // Missing test override is non-fatal; re-throw import errors that aren't "not found"
64
- if (!(err instanceof Error) || !err.message.startsWith('Config not found:'))
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 path = `${basePath}.js`;
5
- if (!existsSync(path)) {
6
- throw new Error(`Config not found: ${path}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonyx",
3
- "version": "0.2.3-alpha.46",
3
+ "version": "0.2.3-alpha.47",
4
4
  "description": "Base stonyx framework module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",