@c9up/ream 0.2.22 → 0.2.24
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/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +22 -1
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +22 -0
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js.map +1 -1
- package/dist/dev/fullReload.d.ts +23 -0
- package/dist/dev/fullReload.d.ts.map +1 -0
- package/dist/dev/fullReload.js +26 -0
- package/dist/dev/fullReload.js.map +1 -0
- package/dist/dev/hotGlob.d.ts +28 -0
- package/dist/dev/hotGlob.d.ts.map +1 -0
- package/dist/dev/hotGlob.js +67 -0
- package/dist/dev/hotGlob.js.map +1 -0
- package/dist/dev/hotGraph.d.ts +58 -0
- package/dist/dev/hotGraph.d.ts.map +1 -0
- package/dist/dev/hotGraph.js +91 -0
- package/dist/dev/hotGraph.js.map +1 -0
- package/dist/dev/hotWatcher.d.ts +34 -0
- package/dist/dev/hotWatcher.d.ts.map +1 -0
- package/dist/dev/hotWatcher.js +72 -0
- package/dist/dev/hotWatcher.js.map +1 -0
- package/dist/env/loadEnvFiles.d.ts +13 -0
- package/dist/env/loadEnvFiles.d.ts.map +1 -1
- package/dist/env/loadEnvFiles.js +65 -13
- package/dist/env/loadEnvFiles.js.map +1 -1
- package/dist/hot.d.ts +13 -16
- package/dist/hot.d.ts.map +1 -1
- package/dist/hot.js +110 -86
- package/dist/hot.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +4 -7
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/HttpKernel.ts +22 -2
- package/src/Ignitor.ts +24 -0
- package/src/dev/fullReload.ts +34 -0
- package/src/dev/hotGlob.ts +66 -0
- package/src/dev/hotGraph.ts +109 -0
- package/src/dev/hotWatcher.ts +89 -0
- package/src/env/loadEnvFiles.ts +69 -14
- package/src/hot.ts +116 -105
- package/src/index.ts +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The import graph a hot reload reasons over, and what it decides.
|
|
3
|
+
*
|
|
4
|
+
* Owned here rather than taken from `hot-hook`, which used to do this job. The
|
|
5
|
+
* package is 655 lines and four npm dependencies, and it had to be installed by
|
|
6
|
+
* every application because a dev-only loader has no business in a production
|
|
7
|
+
* install of the framework — so a project created before that convention simply
|
|
8
|
+
* lost hot reloading and was told nothing. The part that genuinely needs a
|
|
9
|
+
* library is the part Node now provides itself: `module.registerHooks` for the
|
|
10
|
+
* graph, `fs.watch` for the changes.
|
|
11
|
+
*
|
|
12
|
+
* NO node_modules dependency, deliberately: everything below is arithmetic on
|
|
13
|
+
* paths.
|
|
14
|
+
*
|
|
15
|
+
* The graph is built by the resolver as the program runs, never by reading
|
|
16
|
+
* files. A `resolve` hook is handed the importer and the imported, which is
|
|
17
|
+
* exactly an edge, and it sees dynamic imports as they happen — a static parse
|
|
18
|
+
* of the source could only guess at those.
|
|
19
|
+
*/
|
|
20
|
+
export class HotGraph {
|
|
21
|
+
#nodes = new Map();
|
|
22
|
+
#isBoundary;
|
|
23
|
+
#isRestart;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#isBoundary = options.isBoundary;
|
|
26
|
+
this.#isRestart = options.isRestart;
|
|
27
|
+
}
|
|
28
|
+
#node(file) {
|
|
29
|
+
let node = this.#nodes.get(file);
|
|
30
|
+
if (node === undefined) {
|
|
31
|
+
node = { importers: new Set(), version: 0 };
|
|
32
|
+
this.#nodes.set(file, node);
|
|
33
|
+
}
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
/** Record that `importer` imported `file`. Called from the resolve hook. */
|
|
37
|
+
link(file, importer) {
|
|
38
|
+
const node = this.#node(file);
|
|
39
|
+
if (importer !== undefined && importer !== file)
|
|
40
|
+
node.importers.add(importer);
|
|
41
|
+
}
|
|
42
|
+
/** The version to hang off the URL, or 0 while the module has never been invalidated. */
|
|
43
|
+
version(file) {
|
|
44
|
+
return this.#nodes.get(file)?.version ?? 0;
|
|
45
|
+
}
|
|
46
|
+
/** Whether the resolver has ever seen this file. */
|
|
47
|
+
knows(file) {
|
|
48
|
+
return this.#nodes.has(file);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* What to do about `file` having changed.
|
|
52
|
+
*
|
|
53
|
+
* Walks up from the changed file collecting what has to be re-imported, and
|
|
54
|
+
* stops at each boundary: a boundary is imported dynamically, so the next
|
|
55
|
+
* import of it re-resolves, and with it everything below that was collected
|
|
56
|
+
* on the way. A path that reaches a module nobody imports without crossing a
|
|
57
|
+
* boundary cannot be re-imported by anyone, and that is a full reload.
|
|
58
|
+
*
|
|
59
|
+
* Deciding before invalidating matters: a decision that turns out to be a
|
|
60
|
+
* full reload must leave no version bumped behind, or a later swap would
|
|
61
|
+
* hand the caller a URL for a module the process never re-imported.
|
|
62
|
+
*/
|
|
63
|
+
decide(file) {
|
|
64
|
+
if (this.#isRestart(file))
|
|
65
|
+
return { kind: 'reload', file, reason: 'restart-list' };
|
|
66
|
+
if (!this.#nodes.has(file))
|
|
67
|
+
return { kind: 'ignore' };
|
|
68
|
+
const collected = new Set();
|
|
69
|
+
const queue = [file];
|
|
70
|
+
while (queue.length > 0) {
|
|
71
|
+
const current = queue.pop();
|
|
72
|
+
if (current === undefined || collected.has(current))
|
|
73
|
+
continue;
|
|
74
|
+
collected.add(current);
|
|
75
|
+
// A boundary is where the walk stops: whoever imports it does so
|
|
76
|
+
// dynamically and will ask for it again.
|
|
77
|
+
if (this.#isBoundary(current))
|
|
78
|
+
continue;
|
|
79
|
+
const importers = this.#nodes.get(current)?.importers;
|
|
80
|
+
if (importers === undefined || importers.size === 0) {
|
|
81
|
+
return { kind: 'reload', file, reason: 'outside-boundaries' };
|
|
82
|
+
}
|
|
83
|
+
for (const importer of importers)
|
|
84
|
+
queue.push(importer);
|
|
85
|
+
}
|
|
86
|
+
for (const path of collected)
|
|
87
|
+
this.#node(path).version += 1;
|
|
88
|
+
return { kind: 'swap', invalidated: [...collected] };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=hotGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotGraph.js","sourceRoot":"","sources":["../../src/dev/hotGraph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watching the project for the changes a hot reload reacts to.
|
|
3
|
+
*
|
|
4
|
+
* `fs.watch` with `recursive: true`, which Node implements on Linux, macOS and
|
|
5
|
+
* Windows alike — the reason a watcher used to be a dependency here, and is not
|
|
6
|
+
* any more.
|
|
7
|
+
*
|
|
8
|
+
* Two behaviours are worth stating because they are not obvious from the API:
|
|
9
|
+
*
|
|
10
|
+
* A save is rarely one event. Editors write, rename and touch, and a single
|
|
11
|
+
* Ctrl-S can arrive as several notifications for the same file within a few
|
|
12
|
+
* milliseconds. Each one would walk the graph and bump a version, so they are
|
|
13
|
+
* coalesced.
|
|
14
|
+
*
|
|
15
|
+
* `ream dev` also watches from the outside, in Rust. That is not a duplicate:
|
|
16
|
+
* this watcher dies with the process it lives in, so a server that crashed on a
|
|
17
|
+
* syntax error has nothing left to notice the fix. The outer one covers exactly
|
|
18
|
+
* that window, and hands back over by restarting the process.
|
|
19
|
+
*/
|
|
20
|
+
export interface WatcherOptions {
|
|
21
|
+
/** Project root; every reported path is absolute, resolved against it. */
|
|
22
|
+
root: string;
|
|
23
|
+
/** Directories under the root to watch, when they exist. */
|
|
24
|
+
directories: readonly string[];
|
|
25
|
+
/** Individual files to watch — env files sit at the root, so no directory covers them. */
|
|
26
|
+
files: readonly string[];
|
|
27
|
+
/** Called once per settled change, with an absolute path. */
|
|
28
|
+
onChange: (file: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface Watcher {
|
|
31
|
+
close: () => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function watchProject(options: WatcherOptions): Watcher;
|
|
34
|
+
//# sourceMappingURL=hotWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotWatcher.d.ts","sourceRoot":"","sources":["../../src/dev/hotWatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,4DAA4D;IAC5D,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,0FAA0F;IAC1F,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CA+C7D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watching the project for the changes a hot reload reacts to.
|
|
3
|
+
*
|
|
4
|
+
* `fs.watch` with `recursive: true`, which Node implements on Linux, macOS and
|
|
5
|
+
* Windows alike — the reason a watcher used to be a dependency here, and is not
|
|
6
|
+
* any more.
|
|
7
|
+
*
|
|
8
|
+
* Two behaviours are worth stating because they are not obvious from the API:
|
|
9
|
+
*
|
|
10
|
+
* A save is rarely one event. Editors write, rename and touch, and a single
|
|
11
|
+
* Ctrl-S can arrive as several notifications for the same file within a few
|
|
12
|
+
* milliseconds. Each one would walk the graph and bump a version, so they are
|
|
13
|
+
* coalesced.
|
|
14
|
+
*
|
|
15
|
+
* `ream dev` also watches from the outside, in Rust. That is not a duplicate:
|
|
16
|
+
* this watcher dies with the process it lives in, so a server that crashed on a
|
|
17
|
+
* syntax error has nothing left to notice the fix. The outer one covers exactly
|
|
18
|
+
* that window, and hands back over by restarting the process.
|
|
19
|
+
*/
|
|
20
|
+
import { watch } from 'node:fs';
|
|
21
|
+
import { join, resolve } from 'node:path';
|
|
22
|
+
/** How long to wait for a save to finish arriving before acting on it. */
|
|
23
|
+
const SETTLE_MS = 40;
|
|
24
|
+
export function watchProject(options) {
|
|
25
|
+
const watchers = [];
|
|
26
|
+
const pending = new Map();
|
|
27
|
+
const report = (file) => {
|
|
28
|
+
const existing = pending.get(file);
|
|
29
|
+
if (existing !== undefined)
|
|
30
|
+
clearTimeout(existing);
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
pending.delete(file);
|
|
33
|
+
options.onChange(file);
|
|
34
|
+
}, SETTLE_MS);
|
|
35
|
+
// The dev server must still be able to exit while a save is settling.
|
|
36
|
+
timer.unref?.();
|
|
37
|
+
pending.set(file, timer);
|
|
38
|
+
};
|
|
39
|
+
for (const directory of options.directories) {
|
|
40
|
+
const absolute = resolve(options.root, directory);
|
|
41
|
+
try {
|
|
42
|
+
watchers.push(watch(absolute, { recursive: true }, (_event, name) => {
|
|
43
|
+
if (typeof name !== 'string' || name === '')
|
|
44
|
+
return;
|
|
45
|
+
report(join(absolute, name));
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// A directory this project does not have is not an error: the list is
|
|
50
|
+
// the framework's conventional layout, not a requirement.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const file of options.files) {
|
|
54
|
+
const absolute = resolve(options.root, file);
|
|
55
|
+
try {
|
|
56
|
+
watchers.push(watch(absolute, () => report(absolute)));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Same: a `.env` that does not exist is simply not watched.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
close: () => {
|
|
64
|
+
for (const timer of pending.values())
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
pending.clear();
|
|
67
|
+
for (const watcher of watchers)
|
|
68
|
+
watcher.close();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=hotWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotWatcher.js","sourceRoot":"","sources":["../../src/dev/hotWatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAkB,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,0EAA0E;AAC1E,MAAM,SAAS,GAAG,EAAE,CAAA;AAiBpB,MAAM,UAAU,YAAY,CAAC,OAAuB;IAClD,MAAM,QAAQ,GAAgB,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAA;IAEjD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAQ,EAAE;QACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;QAClD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC,EAAE,SAAS,CAAC,CAAA;QACb,sEAAsE;QACtE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QACjD,IAAI,CAAC;YACH,QAAQ,CAAC,IAAI,CACX,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;gBACpD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE;oBAAE,OAAM;gBACnD,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;YAC9B,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC5C,IAAI,CAAC;YACH,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YACzD,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,OAAO,CAAC,KAAK,EAAE,CAAA;QACjD,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.env*` files that will be read, most-specific first.
|
|
3
|
+
*
|
|
4
|
+
* Exported because `@c9up/ream/hot` has to WATCH exactly this list: nothing
|
|
5
|
+
* imports an env file, so hot-hook's dependency tree never sees one and an
|
|
6
|
+
* edit to `.env` changed nothing at all. A second copy of the list in the
|
|
7
|
+
* loader entry would drift from this one the first time the naming changes.
|
|
8
|
+
*
|
|
9
|
+
* @param options.skipEnvLocal Skip `.env.local` — see {@link loadEnvFiles}.
|
|
10
|
+
*/
|
|
11
|
+
export declare function envFileNames(options?: {
|
|
12
|
+
skipEnvLocal?: boolean;
|
|
13
|
+
}): string[];
|
|
1
14
|
/**
|
|
2
15
|
* Load `.env` files into `process.env` — the shared primitive behind both the
|
|
3
16
|
* Ignitor (HTTP/console boot) and `Env.create()` (config/test flow), mirroring
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFiles.d.ts","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"loadEnvFiles.d.ts","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,EAAE,CAW/E;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CAsCzF"}
|
package/dist/env/loadEnvFiles.js
CHANGED
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
3
|
import { parseEnv } from 'node:util';
|
|
4
4
|
import { interpolate } from './interpolate.js';
|
|
5
5
|
import { normalizeNodeEnv } from './nodeEnv.js';
|
|
6
|
+
/**
|
|
7
|
+
* The `.env*` files that will be read, most-specific first.
|
|
8
|
+
*
|
|
9
|
+
* Exported because `@c9up/ream/hot` has to WATCH exactly this list: nothing
|
|
10
|
+
* imports an env file, so hot-hook's dependency tree never sees one and an
|
|
11
|
+
* edit to `.env` changed nothing at all. A second copy of the list in the
|
|
12
|
+
* loader entry would drift from this one the first time the naming changes.
|
|
13
|
+
*
|
|
14
|
+
* @param options.skipEnvLocal Skip `.env.local` — see {@link loadEnvFiles}.
|
|
15
|
+
*/
|
|
16
|
+
export function envFileNames(options = {}) {
|
|
17
|
+
// Normalised, so `NODE_ENV=prod` loads `.env.production` — the file the
|
|
18
|
+
// deployment actually wrote — instead of looking for `.env.prod`.
|
|
19
|
+
const raw = normalizeNodeEnv(process.env.NODE_ENV);
|
|
20
|
+
const nodeEnv = raw === 'unknown' ? undefined : raw;
|
|
21
|
+
return [
|
|
22
|
+
nodeEnv ? `.env.${nodeEnv}.local` : null,
|
|
23
|
+
options.skipEnvLocal ? null : '.env.local',
|
|
24
|
+
nodeEnv ? `.env.${nodeEnv}` : null,
|
|
25
|
+
'.env',
|
|
26
|
+
].filter((name) => name !== null);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Where the `.env*` files are read from.
|
|
30
|
+
*
|
|
31
|
+
* Normally beside the application, which is what `appRoot` names. A BUILT
|
|
32
|
+
* application moves: `start/env.js` sits in `dist/`, so `new URL('../',
|
|
33
|
+
* import.meta.url)` resolves there and the files at the project root become
|
|
34
|
+
* invisible — `ream start` died on a `.env` it was standing next to.
|
|
35
|
+
*
|
|
36
|
+
* `ENV_PATH` is AdonisJS's own variable for this, named and shaped the same
|
|
37
|
+
* way: a DIRECTORY to read the files from, as in
|
|
38
|
+
* `ENV_PATH=/etc/secrets node server.js`. An application moving over keeps
|
|
39
|
+
* whatever it already sets.
|
|
40
|
+
*
|
|
41
|
+
* It is also why the ordering rule stays here rather than in the CLI: node's
|
|
42
|
+
* `--env-file` applies files last-wins while this loader applies them
|
|
43
|
+
* most-specific-first, and a second copy of that would be one to keep in step
|
|
44
|
+
* forever.
|
|
45
|
+
*/
|
|
46
|
+
function envDirectory(appRoot) {
|
|
47
|
+
const override = process.env.ENV_PATH;
|
|
48
|
+
if (override === undefined || override.trim() === '')
|
|
49
|
+
return appRoot;
|
|
50
|
+
// A directory URL, so `new URL('.env', directory)` lands inside it rather
|
|
51
|
+
// than beside it.
|
|
52
|
+
return pathToFileURL(override.endsWith('/') ? override : `${override}/`);
|
|
53
|
+
}
|
|
6
54
|
/**
|
|
7
55
|
* Load `.env` files into `process.env` — the shared primitive behind both the
|
|
8
56
|
* Ignitor (HTTP/console boot) and `Env.create()` (config/test flow), mirroring
|
|
@@ -17,24 +65,17 @@ import { normalizeNodeEnv } from './nodeEnv.js';
|
|
|
17
65
|
* developer's local overrides don't leak into tests).
|
|
18
66
|
*/
|
|
19
67
|
export function loadEnvFiles(appRoot, options = {}) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const nodeEnv = raw === 'unknown' ? undefined : raw;
|
|
24
|
-
const files = [
|
|
25
|
-
nodeEnv ? `.env.${nodeEnv}.local` : null,
|
|
26
|
-
options.skipEnvLocal ? null : '.env.local',
|
|
27
|
-
nodeEnv ? `.env.${nodeEnv}` : null,
|
|
28
|
-
'.env',
|
|
29
|
-
].filter((name) => name !== null);
|
|
30
|
-
for (const name of files) {
|
|
68
|
+
const directory = envDirectory(appRoot);
|
|
69
|
+
let found = false;
|
|
70
|
+
for (const name of envFileNames(options)) {
|
|
31
71
|
let contents;
|
|
32
72
|
try {
|
|
33
|
-
contents = readFileSync(fileURLToPath(new URL(name,
|
|
73
|
+
contents = readFileSync(fileURLToPath(new URL(name, directory)), 'utf8');
|
|
34
74
|
}
|
|
35
75
|
catch {
|
|
36
76
|
continue; // file absent — nothing to load
|
|
37
77
|
}
|
|
78
|
+
found = true;
|
|
38
79
|
const parsed = parseEnv(contents);
|
|
39
80
|
for (const [key, value] of Object.entries(parsed)) {
|
|
40
81
|
if (typeof value !== 'string' || process.env[key] !== undefined)
|
|
@@ -50,5 +91,16 @@ export function loadEnvFiles(appRoot, options = {}) {
|
|
|
50
91
|
});
|
|
51
92
|
}
|
|
52
93
|
}
|
|
94
|
+
// Upstream raises when `ENV_PATH` names a place with no env file in it, and
|
|
95
|
+
// for the reason silence would be wrong: the variable is somebody saying
|
|
96
|
+
// where the file IS. A typo in the path would otherwise start a server on
|
|
97
|
+
// whatever defaults happened to be around, which is how a deployment reads
|
|
98
|
+
// its staging config in production and nobody finds out until later.
|
|
99
|
+
//
|
|
100
|
+
// Without `ENV_PATH` a missing file stays silent: an application may have
|
|
101
|
+
// none at all, and every value may come from the environment.
|
|
102
|
+
if (!found && process.env.ENV_PATH !== undefined && process.env.ENV_PATH.trim() !== '') {
|
|
103
|
+
throw new Error(`ENV_PATH points at ${process.env.ENV_PATH}, which holds none of ${envFileNames(options).join(', ')}`);
|
|
104
|
+
}
|
|
53
105
|
}
|
|
54
106
|
//# sourceMappingURL=loadEnvFiles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFiles.js","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"loadEnvFiles.js","sourceRoot":"","sources":["../../src/env/loadEnvFiles.ts"],"names":[],"mappings":"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"}
|
package/dist/hot.d.ts
CHANGED
|
@@ -5,25 +5,22 @@
|
|
|
5
5
|
*
|
|
6
6
|
* node --import @swc-node/register/esm-register --import @c9up/ream/hot bin/server.ts
|
|
7
7
|
*
|
|
8
|
-
* It
|
|
9
|
-
*
|
|
10
|
-
* boundary changes it invalidates just that module instead of the process. The
|
|
11
|
-
* `hotHook` key in the application's `package.json` configures it, exactly as
|
|
12
|
-
* upstream reads it.
|
|
8
|
+
* It replaces a module that changed instead of the process that was serving it,
|
|
9
|
+
* so the container, the pools and the connections survive a save.
|
|
13
10
|
*
|
|
14
|
-
* NAMED DEVIATION —
|
|
11
|
+
* NAMED DEVIATION — this is ream's own, where AdonisJS uses `hot-hook`.
|
|
15
12
|
*
|
|
16
|
-
* Upstream
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
13
|
+
* Upstream ships that package in its starter kit, which means every application
|
|
14
|
+
* installs it, and one created before the convention existed silently loses hot
|
|
15
|
+
* reloading. The parts that once justified a library are now in Node:
|
|
16
|
+
* `module.registerHooks` gives the import graph, `fs.watch` gives the changes.
|
|
17
|
+
* What is left is the decision of what to invalidate, which is this file and
|
|
18
|
+
* its three neighbours — and no entry in anyone's `package.json`.
|
|
22
19
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* The graph comes from the resolver rather than from reading files: a resolve
|
|
21
|
+
* hook is handed the importer and the imported, which is exactly an edge, and
|
|
22
|
+
* it sees a dynamic import at the moment it happens rather than guessing at it
|
|
23
|
+
* from the source.
|
|
27
24
|
*/
|
|
28
25
|
export {};
|
|
29
26
|
//# sourceMappingURL=hot.d.ts.map
|
package/dist/hot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
|
package/dist/hot.js
CHANGED
|
@@ -5,108 +5,132 @@
|
|
|
5
5
|
*
|
|
6
6
|
* node --import @swc-node/register/esm-register --import @c9up/ream/hot bin/server.ts
|
|
7
7
|
*
|
|
8
|
-
* It
|
|
9
|
-
*
|
|
10
|
-
* boundary changes it invalidates just that module instead of the process. The
|
|
11
|
-
* `hotHook` key in the application's `package.json` configures it, exactly as
|
|
12
|
-
* upstream reads it.
|
|
8
|
+
* It replaces a module that changed instead of the process that was serving it,
|
|
9
|
+
* so the container, the pools and the connections survive a save.
|
|
13
10
|
*
|
|
14
|
-
* NAMED DEVIATION —
|
|
11
|
+
* NAMED DEVIATION — this is ream's own, where AdonisJS uses `hot-hook`.
|
|
15
12
|
*
|
|
16
|
-
* Upstream
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
13
|
+
* Upstream ships that package in its starter kit, which means every application
|
|
14
|
+
* installs it, and one created before the convention existed silently loses hot
|
|
15
|
+
* reloading. The parts that once justified a library are now in Node:
|
|
16
|
+
* `module.registerHooks` gives the import graph, `fs.watch` gives the changes.
|
|
17
|
+
* What is left is the decision of what to invalidate, which is this file and
|
|
18
|
+
* its three neighbours — and no entry in anyone's `package.json`.
|
|
22
19
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* The graph comes from the resolver rather than from reading files: a resolve
|
|
21
|
+
* hook is handed the importer and the imported, which is exactly an edge, and
|
|
22
|
+
* it sees a dynamic import at the moment it happens rather than guessing at it
|
|
23
|
+
* from the source.
|
|
27
24
|
*/
|
|
25
|
+
import { existsSync, writeSync } from 'node:fs';
|
|
26
|
+
import { readFile } from 'node:fs/promises';
|
|
27
|
+
import { registerHooks } from 'node:module';
|
|
28
|
+
import { dirname, relative, resolve as resolvePath } from 'node:path';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
import { fullReloadNotice } from './dev/fullReload.js';
|
|
28
31
|
import { hotReloadHappened } from './dev/hmr.js';
|
|
29
32
|
import { readHotHookConfig } from './dev/hotConfig.js';
|
|
33
|
+
import { globMatcher } from './dev/hotGlob.js';
|
|
34
|
+
import { HotGraph } from './dev/hotGraph.js';
|
|
35
|
+
import { watchProject } from './dev/hotWatcher.js';
|
|
36
|
+
import { envFileNames } from './env/loadEnvFiles.js';
|
|
30
37
|
/** Exit code meaning "restart me" — see `EXIT_RESTART` in the CLI's dev module. */
|
|
31
38
|
const FULL_RELOAD_EXIT_CODE = 75;
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
39
|
+
/** The query parameter that makes the loader fetch a module again. */
|
|
40
|
+
const VERSION_PARAM = 'ream_hot';
|
|
41
|
+
/** Directories a project keeps its own code in — the CLI watches the same list. */
|
|
42
|
+
const WATCH_DIRS = ['app', 'bin', 'config', 'start', 'database', 'providers', 'commands'];
|
|
43
|
+
const packageJsonPath = resolvePath(process.cwd(), 'package.json');
|
|
44
|
+
const root = dirname(packageJsonPath);
|
|
35
45
|
let config = {};
|
|
36
46
|
try {
|
|
37
47
|
config = readHotHookConfig(JSON.parse(await readFile(packageJsonPath, 'utf8')));
|
|
38
48
|
}
|
|
39
49
|
catch {
|
|
40
|
-
// No package.json, or unreadable:
|
|
41
|
-
//
|
|
50
|
+
// No package.json, or unreadable: a project with no boundaries declared gets
|
|
51
|
+
// a working server that restarts on change, which is the old behaviour.
|
|
42
52
|
}
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
const { boundaries = [], restart = [] } = config;
|
|
54
|
+
// Env files are not modules: nothing imports one, so the graph never sees them
|
|
55
|
+
// and an edit to `.env` did nothing at all — the values are read once, at boot.
|
|
56
|
+
// They always restart.
|
|
57
|
+
const restartFiles = new Set(envFileNames()
|
|
58
|
+
.map((name) => resolvePath(root, name))
|
|
59
|
+
.filter((file) => existsSync(file)));
|
|
60
|
+
const relativeToRoot = (file) => relative(root, file).replace(/\\/g, '/');
|
|
61
|
+
const matchesBoundary = globMatcher(boundaries);
|
|
62
|
+
const matchesRestartGlob = globMatcher(restart);
|
|
63
|
+
const graph = new HotGraph({
|
|
64
|
+
isBoundary: (file) => matchesBoundary(relativeToRoot(file)),
|
|
65
|
+
isRestart: (file) => restartFiles.has(file) || matchesRestartGlob(relativeToRoot(file)),
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Track every edge the resolver reports, and hand back a versioned URL for a
|
|
69
|
+
* module that has been invalidated.
|
|
70
|
+
*
|
|
71
|
+
* The version travels in the URL because that is the only handle V8 offers: a
|
|
72
|
+
* module is keyed by its URL, so a different one is a different module. The
|
|
73
|
+
* parameter is stripped off the importer before delegating, or a relative
|
|
74
|
+
* specifier would be resolved against a URL that does not name a directory.
|
|
75
|
+
*/
|
|
76
|
+
registerHooks({
|
|
77
|
+
resolve(specifier, context, nextResolve) {
|
|
78
|
+
let parentURL = context.parentURL;
|
|
79
|
+
let parentPath;
|
|
80
|
+
if (typeof parentURL === 'string' && parentURL.startsWith('file:')) {
|
|
81
|
+
const url = new URL(parentURL);
|
|
82
|
+
if (url.searchParams.has(VERSION_PARAM)) {
|
|
83
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
84
|
+
parentURL = url.href;
|
|
85
|
+
}
|
|
86
|
+
parentPath = fileURLToPath(url);
|
|
87
|
+
}
|
|
88
|
+
const result = nextResolve(specifier, { ...context, parentURL });
|
|
89
|
+
if (!result.url.startsWith('file:'))
|
|
90
|
+
return result;
|
|
91
|
+
const resolved = new URL(result.url);
|
|
92
|
+
resolved.searchParams.delete(VERSION_PARAM);
|
|
93
|
+
const file = fileURLToPath(resolved);
|
|
94
|
+
graph.link(file, parentPath);
|
|
95
|
+
const version = graph.version(file);
|
|
96
|
+
if (version === 0)
|
|
97
|
+
return result;
|
|
98
|
+
resolved.searchParams.set(VERSION_PARAM, String(version));
|
|
99
|
+
return { ...result, url: resolved.href };
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
/** Print a line that must survive the exit on the very next statement. */
|
|
103
|
+
function sayNow(line) {
|
|
104
|
+
// Synchronously: a piped stdout — what `ream dev` hands this process when it
|
|
105
|
+
// runs an asset watcher alongside — is asynchronous, and the line would be
|
|
106
|
+
// dropped on the way out.
|
|
107
|
+
try {
|
|
108
|
+
writeSync(1, `${line}\n`);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// The parent can close the pipe first on Ctrl-C. Losing the line is fine.
|
|
64
112
|
}
|
|
65
|
-
hot = candidate;
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
// Loud, and it names the fix. Silence here would start a perfectly healthy
|
|
69
|
-
// server that never picks up an edit, which reads as "HMR is broken" rather
|
|
70
|
-
// than "HMR is not installed".
|
|
71
|
-
//
|
|
72
|
-
// It does NOT throw: a dev server that refuses to start is worse than one
|
|
73
|
-
// without hot reloading, and the CLI only loads this file when it has
|
|
74
|
-
// already seen hot-hook in the project — so reaching here means it went
|
|
75
|
-
// missing under a running session.
|
|
76
|
-
console.error(`[ream] hot module replacement is unavailable: ${error instanceof Error ? error.message : String(error)}\n` +
|
|
77
|
-
'[ream] Install it with: pnpm add -D hot-hook, then restart `ream dev`.\n' +
|
|
78
|
-
'[ream] Until then the server runs, but an edit will not be picked up.');
|
|
79
113
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
process.
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
watchProject({
|
|
115
|
+
root,
|
|
116
|
+
directories: WATCH_DIRS,
|
|
117
|
+
files: [...restartFiles].map((file) => relative(root, file)),
|
|
118
|
+
onChange: (file) => {
|
|
119
|
+
const decision = graph.decide(file);
|
|
120
|
+
if (decision.kind === 'ignore')
|
|
121
|
+
return;
|
|
122
|
+
if (decision.kind === 'reload') {
|
|
123
|
+
sayNow(fullReloadNotice(decision.file, decision.reason, relativeToRoot));
|
|
124
|
+
// Exiting on a known code is how the Rust parent learns it must restart —
|
|
125
|
+
// see `EXIT_RESTART` in the CLI's dev module.
|
|
126
|
+
process.exit(FULL_RELOAD_EXIT_CODE);
|
|
127
|
+
}
|
|
128
|
+
// The router caches a promoted controller per route, and the kernel drops
|
|
129
|
+
// that cache when this counter moves. Without it the swap happens and the
|
|
130
|
+
// process keeps serving the class it promoted: a stable PID and stale
|
|
131
|
+
// output, which is exactly how it was first reported.
|
|
95
132
|
hotReloadHappened();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// gets its message: this observes, it does not intercept.
|
|
99
|
-
if (typeof previousSend === 'function') {
|
|
100
|
-
return Reflect.apply(previousSend, process, [message, ...rest]) === true;
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
};
|
|
104
|
-
await hot?.init({
|
|
105
|
-
...config,
|
|
106
|
-
rootDirectory: dirname(packageJsonPath),
|
|
107
|
-
root: config.root ? resolve(dirname(packageJsonPath), config.root) : undefined,
|
|
108
|
-
// Exiting on a known code is how the Rust parent learns it must restart —
|
|
109
|
-
// see `EXIT_RESTART` in the CLI's dev module.
|
|
110
|
-
onFullReloadAsked: () => process.exit(FULL_RELOAD_EXIT_CODE),
|
|
133
|
+
sayNow(`[ream] hot swap — ${relativeToRoot(file)}`);
|
|
134
|
+
},
|
|
111
135
|
});
|
|
112
136
|
//# sourceMappingURL=hot.js.map
|