@c9up/ream 0.2.24 → 0.2.25
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/Codemods.d.ts +1 -0
- package/dist/Codemods.d.ts.map +1 -1
- package/dist/Codemods.js +44 -0
- package/dist/Codemods.js.map +1 -1
- package/dist/Ignitor.d.ts +28 -0
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +15 -15
- package/dist/Ignitor.js.map +1 -1
- package/dist/dev/hmr.d.ts +52 -4
- package/dist/dev/hmr.d.ts.map +1 -1
- package/dist/dev/hmr.js +75 -9
- package/dist/dev/hmr.js.map +1 -1
- package/dist/dev/hotGlob.d.ts +10 -3
- package/dist/dev/hotGlob.d.ts.map +1 -1
- package/dist/dev/hotGlob.js +28 -3
- package/dist/dev/hotGlob.js.map +1 -1
- package/dist/hot.js +56 -3
- package/dist/hot.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Codemods.ts +53 -0
- package/src/Ignitor.ts +44 -14
- package/src/dev/hmr.ts +102 -9
- package/src/dev/hotGlob.ts +28 -3
- package/src/hot.ts +59 -3
- package/src/index.ts +1 -1
package/dist/dev/hotGlob.js
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
* what they need is to be right, not fast.
|
|
8
8
|
*
|
|
9
9
|
* The dialect is the one those files already use:
|
|
10
|
-
* *
|
|
11
|
-
* **
|
|
12
|
-
* ?
|
|
10
|
+
* * any run of characters inside one path segment
|
|
11
|
+
* ** any number of segments, including none
|
|
12
|
+
* ? exactly one character inside a segment
|
|
13
|
+
* {a,b} either alternative
|
|
14
|
+
*
|
|
15
|
+
* The brace form is the one a `metaFiles` entry uses —
|
|
16
|
+
* `resources/lang/**\/*.{json,yaml,yml}` — and it has to be understood on this
|
|
17
|
+
* side too: the CLI matches the same pattern when it copies those files into
|
|
18
|
+
* the build, and a dialect that differed between the two would let an entry
|
|
19
|
+
* copy at build time and never fire in development, or the reverse.
|
|
13
20
|
*
|
|
14
21
|
* A leading `./` is optional, and matching is done on POSIX-shaped paths
|
|
15
22
|
* relative to the project root, so a Windows checkout and a Linux one read the
|
|
@@ -42,6 +49,24 @@ export function globToRegExp(pattern) {
|
|
|
42
49
|
out += '[^/]';
|
|
43
50
|
continue;
|
|
44
51
|
}
|
|
52
|
+
if (char === '{') {
|
|
53
|
+
const close = cleaned.indexOf('}', i);
|
|
54
|
+
if (close !== -1) {
|
|
55
|
+
// Alternatives are literal text, never nested globs: that is the whole
|
|
56
|
+
// of the form these patterns use, and a recursive reading would accept
|
|
57
|
+
// shapes the CLI's matcher does not.
|
|
58
|
+
const options = cleaned
|
|
59
|
+
.slice(i + 1, close)
|
|
60
|
+
.split(',')
|
|
61
|
+
.map((option) => option.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
62
|
+
out += `(?:${options.join('|')})`;
|
|
63
|
+
i = close;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Unclosed: read literally rather than throwing. A pattern is a string in
|
|
67
|
+
// a config file, and a malformed one should match nothing, not take the
|
|
68
|
+
// dev server down at boot.
|
|
69
|
+
}
|
|
45
70
|
// Everything else is literal, including the characters a regular
|
|
46
71
|
// expression would otherwise read as syntax.
|
|
47
72
|
out += (char ?? '').replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
package/dist/dev/hotGlob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hotGlob.js","sourceRoot":"","sources":["../../src/dev/hotGlob.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hotGlob.js","sourceRoot":"","sources":["../../src/dev/hotGlob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,yDAAyD;AACzD,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IAC5C,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACvB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC3B,oEAAoE;gBACpE,2CAA2C;gBAC3C,CAAC,IAAI,CAAC,CAAA;gBACN,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAC3B,CAAC,IAAI,CAAC,CAAA;oBACN,GAAG,IAAI,aAAa,CAAA;gBACtB,CAAC;qBAAM,CAAC;oBACN,GAAG,IAAI,IAAI,CAAA;gBACb,CAAC;gBACD,SAAQ;YACV,CAAC;YACD,GAAG,IAAI,OAAO,CAAA;YACd,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,GAAG,IAAI,MAAM,CAAA;YACb,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YACrC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjB,uEAAuE;gBACvE,uEAAuE;gBACvE,qCAAqC;gBACrC,MAAM,OAAO,GAAG,OAAO;qBACpB,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;qBACnB,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAA;gBACjE,GAAG,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;gBACjC,CAAC,GAAG,KAAK,CAAA;gBACT,SAAQ;YACV,CAAC;YACD,0EAA0E;YAC1E,wEAAwE;YACxE,2BAA2B;QAC7B,CAAC;QACD,iEAAiE;QACjE,6CAA6C;QAC7C,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAA;IAC1D,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,QAA2B;IACrD,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAC9C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAA;IAChD,OAAO,CAAC,IAAY,EAAE,EAAE;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QAChE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;IACtE,CAAC,CAAA;AACH,CAAC"}
|
package/dist/hot.js
CHANGED
|
@@ -26,7 +26,7 @@ import { existsSync, writeSync } from 'node:fs';
|
|
|
26
26
|
import { readFile } from 'node:fs/promises';
|
|
27
27
|
import { registerHooks } from 'node:module';
|
|
28
28
|
import { dirname, relative, resolve as resolvePath } from 'node:path';
|
|
29
|
-
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
30
|
import { fullReloadNotice } from './dev/fullReload.js';
|
|
31
31
|
import { hotReloadHappened } from './dev/hmr.js';
|
|
32
32
|
import { readHotHookConfig } from './dev/hotConfig.js';
|
|
@@ -57,9 +57,57 @@ const { boundaries = [], restart = [] } = config;
|
|
|
57
57
|
const restartFiles = new Set(envFileNames()
|
|
58
58
|
.map((name) => resolvePath(root, name))
|
|
59
59
|
.filter((file) => existsSync(file)));
|
|
60
|
+
/**
|
|
61
|
+
* `metaFiles` entries that asked for a restart.
|
|
62
|
+
*
|
|
63
|
+
* Non-module files the application owns — translations, view templates — live
|
|
64
|
+
* in `reamrc.ts` because the BUILD has to copy them. A change to one is
|
|
65
|
+
* invisible to the import graph, so the only way it can do anything in
|
|
66
|
+
* development is by being named here.
|
|
67
|
+
*
|
|
68
|
+
* `reloadServer: false` is the interesting default, and the one upstream picks
|
|
69
|
+
* for translations: the entry exists so the file ships, and an edit does NOT
|
|
70
|
+
* restart. NAMED DEVIATION — upstream still logs such a change, because its
|
|
71
|
+
* watcher covers the whole project anyway. This one watches a fixed list of
|
|
72
|
+
* directories on purpose (`resources/` belongs to the asset watcher, and a
|
|
73
|
+
* stylesheet edit must not restart the server), so a `false` entry is simply
|
|
74
|
+
* not watched. The observable behaviour is the same: nothing happens.
|
|
75
|
+
*/
|
|
76
|
+
async function readReloadPatterns() {
|
|
77
|
+
try {
|
|
78
|
+
const rc = (await import(pathToFileURL(resolvePath(root, 'reamrc.ts')).href));
|
|
79
|
+
return (rc.default?.metaFiles ?? [])
|
|
80
|
+
.filter((entry) => entry.reloadServer === true && typeof entry.pattern === 'string')
|
|
81
|
+
.map((entry) => String(entry.pattern));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// No rc file, or one that does not load. The server itself reports a broken
|
|
85
|
+
// rc far more clearly than the loader could, and a dev server that refuses
|
|
86
|
+
// to start because of a watch list helps nobody.
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const reloadPatterns = await readReloadPatterns();
|
|
91
|
+
/**
|
|
92
|
+
* The directory a pattern lives under — everything before its first wildcard.
|
|
93
|
+
*
|
|
94
|
+
* `resources/lang/**\/*.json` is watched as `resources/lang`. Watching the
|
|
95
|
+
* project root instead would pull in `node_modules` and the build output.
|
|
96
|
+
*/
|
|
97
|
+
function watchRootOf(pattern) {
|
|
98
|
+
const segments = pattern.replace(/^\.\//, '').split('/');
|
|
99
|
+
const fixed = [];
|
|
100
|
+
for (const segment of segments) {
|
|
101
|
+
if (/[*?{[]/.test(segment))
|
|
102
|
+
break;
|
|
103
|
+
fixed.push(segment);
|
|
104
|
+
}
|
|
105
|
+
// A pattern that is a bare glob has no directory to narrow to.
|
|
106
|
+
return fixed.slice(0, -1).join('/') || fixed.join('/');
|
|
107
|
+
}
|
|
60
108
|
const relativeToRoot = (file) => relative(root, file).replace(/\\/g, '/');
|
|
61
109
|
const matchesBoundary = globMatcher(boundaries);
|
|
62
|
-
const matchesRestartGlob = globMatcher(restart);
|
|
110
|
+
const matchesRestartGlob = globMatcher([...restart, ...reloadPatterns]);
|
|
63
111
|
const graph = new HotGraph({
|
|
64
112
|
isBoundary: (file) => matchesBoundary(relativeToRoot(file)),
|
|
65
113
|
isRestart: (file) => restartFiles.has(file) || matchesRestartGlob(relativeToRoot(file)),
|
|
@@ -113,7 +161,12 @@ function sayNow(line) {
|
|
|
113
161
|
}
|
|
114
162
|
watchProject({
|
|
115
163
|
root,
|
|
116
|
-
directories:
|
|
164
|
+
directories: [
|
|
165
|
+
...WATCH_DIRS,
|
|
166
|
+
// Whatever a `reloadServer: true` entry named, and nothing else: the
|
|
167
|
+
// directories above are the ones the graph can reason about.
|
|
168
|
+
...reloadPatterns.map(watchRootOf).filter((dir) => dir !== ''),
|
|
169
|
+
],
|
|
117
170
|
files: [...restartFiles].map((file) => relative(root, file)),
|
|
118
171
|
onChange: (file) => {
|
|
119
172
|
const decision = graph.decide(file);
|
package/dist/hot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hot.js","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"hot.js","sourceRoot":"","sources":["../src/hot.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -121,5 +121,5 @@ export { BaseEvent, Emitter, type EmitterResolver, type ListenerClass, } from '.
|
|
|
121
121
|
export { type BufferedEvent, type BufferedEventName, type EventFinder, EventsBuffer, } from './events/EventsBuffer.js';
|
|
122
122
|
export type { ErrorReporter, LockBackend, LockBackendFactory, LockRedisClient, LockRedisResolver, RunTaskOutcome, ScheduleEvent, ScheduleEventSink, ScheduleInvocation, ScheduleMetadata, ScheduleProviderOptions, SchedulerConfig, SchedulerOptions, ScheduleTaskCompletedEvent, ScheduleTaskFailedEvent, ScheduleTaskSkippedEvent, ScheduleTaskStartedEvent, TaskInfo, TaskStats, } from './scheduler/index.js';
|
|
123
123
|
export { defineSchedulerConfig, getScheduleMetadata, locks, MemoryLockBackend, RedisLockBackend, SCHEDULE_METADATA_KEY, Schedule, ScheduleProvider, Scheduler, StatsTracker, } from './scheduler/index.js';
|
|
124
|
-
export declare const VERSION = "0.2.
|
|
124
|
+
export declare const VERSION = "0.2.25";
|
|
125
125
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -92,5 +92,5 @@ export { defineConfig as defineEventsConfig } from './events/config.js';
|
|
|
92
92
|
export { BaseEvent, Emitter, } from './events/Emitter.js';
|
|
93
93
|
export { EventsBuffer, } from './events/EventsBuffer.js';
|
|
94
94
|
export { defineSchedulerConfig, getScheduleMetadata, locks, MemoryLockBackend, RedisLockBackend, SCHEDULE_METADATA_KEY, Schedule, ScheduleProvider, Scheduler, StatsTracker, } from './scheduler/index.js';
|
|
95
|
-
export const VERSION = '0.2.
|
|
95
|
+
export const VERSION = '0.2.25';
|
|
96
96
|
//# sourceMappingURL=index.js.map
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
package/src/Codemods.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
export interface Codemods {
|
|
5
5
|
addProvider(importPath: string): Promise<void>
|
|
6
|
+
addMetaFile(pattern: string, reloadServer?: boolean): Promise<void>
|
|
6
7
|
addEnvVars(vars: Record<string, string>): Promise<void>
|
|
7
8
|
writeFile(filePath: string, content: string, options?: { force?: boolean }): Promise<void>
|
|
8
9
|
registerCommand(importPath: string): Promise<void>
|
|
@@ -120,6 +121,58 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
|
|
|
120
121
|
fs.writeFileSync(rcPath, content)
|
|
121
122
|
},
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Record a non-module file the application owns.
|
|
126
|
+
*
|
|
127
|
+
* A package calls this from `configure()` so its files reach the build:
|
|
128
|
+
* `addMetaFile('resources/lang/**\/*.json', false)`. The second argument
|
|
129
|
+
* is DELIBERATELY explicit at every call site — whether an edit costs a
|
|
130
|
+
* development restart is the decision the entry exists to record.
|
|
131
|
+
*
|
|
132
|
+
* Same regex codemod as `addProvider`, and the same limits: a `metaFiles`
|
|
133
|
+
* assembled by a spread or extracted into a `const` above the config is not
|
|
134
|
+
* rewritten. The block is created when absent, which `addProvider` cannot
|
|
135
|
+
* do — `providers` is always there, `metaFiles` usually is not.
|
|
136
|
+
*/
|
|
137
|
+
async addMetaFile(pattern: string, reloadServer = false): Promise<void> {
|
|
138
|
+
const rcPath = path.join(root, 'reamrc.ts')
|
|
139
|
+
if (!fs.existsSync(rcPath)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[configure] reamrc.ts not found — cannot register meta file ${pattern}. Run 'ream new' first.`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
assertCanonicallyInside(root, rcPath, 'reamrc.ts')
|
|
145
|
+
|
|
146
|
+
let content = fs.readFileSync(rcPath, 'utf8')
|
|
147
|
+
// Dedup on the PATTERN alone: the same glob registered twice with
|
|
148
|
+
// different flags is a contradiction, and the first one wins rather than
|
|
149
|
+
// both being written.
|
|
150
|
+
if (content.includes(`'${pattern}'`) || content.includes(`"${pattern}"`)) return
|
|
151
|
+
|
|
152
|
+
const eol = detectLineEnding(content)
|
|
153
|
+
const entry = ` { pattern: '${pattern}', reloadServer: ${reloadServer} },`
|
|
154
|
+
|
|
155
|
+
const existing = /metaFiles\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/.exec(content)
|
|
156
|
+
if (existing !== null) {
|
|
157
|
+
const insertAt = existing.index + existing[0].length
|
|
158
|
+
content = `${content.slice(0, insertAt)}${eol}${entry}${content.slice(insertAt)}`
|
|
159
|
+
fs.writeFileSync(rcPath, content)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// No block yet — open one beside `providers`, which every rc file has.
|
|
164
|
+
const providers = /providers\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/.exec(content)
|
|
165
|
+
if (providers === null) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`[configure] Could not find 'providers: [' in reamrc.ts — meta file ${pattern} not added. Check your reamrc.ts format.`,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
const blockAt = providers.index
|
|
171
|
+
const block = `metaFiles: [${eol}${entry}${eol} ],${eol} `
|
|
172
|
+
content = `${content.slice(0, blockAt)}${block}${content.slice(blockAt)}`
|
|
173
|
+
fs.writeFileSync(rcPath, content)
|
|
174
|
+
},
|
|
175
|
+
|
|
123
176
|
async addEnvVars(vars: Record<string, string>): Promise<void> {
|
|
124
177
|
const envPath = path.join(root, '.env')
|
|
125
178
|
// A symlinked .env must not redirect the write outside the root.
|
package/src/Ignitor.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { Application } from './Application.js'
|
|
|
17
17
|
import type { Console } from './console/Console.js'
|
|
18
18
|
import type { CommandLoader, Kernel as ConsoleKernelInstance } from './console/Kernel.js'
|
|
19
19
|
import { type CommandClass, isCommandClass } from './console/types.js'
|
|
20
|
-
import {
|
|
20
|
+
import { hmrClientScript, hmrEndpoint } from './dev/hmr.js'
|
|
21
21
|
import type { DirectoriesNode } from './directories.js'
|
|
22
22
|
import type { ErrorEvent } from './ErrorBoundary.js'
|
|
23
23
|
import { ErrorBoundary } from './ErrorBoundary.js'
|
|
@@ -137,6 +137,35 @@ export interface ReamrcConfig {
|
|
|
137
137
|
* `defineConfig`, which is a strange way to learn that the option exists.
|
|
138
138
|
*/
|
|
139
139
|
assets?: AssetsConfig
|
|
140
|
+
/**
|
|
141
|
+
* Files that are not modules but belong to the application.
|
|
142
|
+
*
|
|
143
|
+
* Translations, view templates, a mail signature: the build has no reason to
|
|
144
|
+
* know about them, and without being told it ships a `dist/` that boots and
|
|
145
|
+
* then cannot find them. Every pattern here is copied into the build output,
|
|
146
|
+
* keeping its path relative to the project root.
|
|
147
|
+
*
|
|
148
|
+
* `reloadServer` decides what a change does in DEVELOPMENT, and the default
|
|
149
|
+
* is the interesting half. Upstream registers translations with `false` —
|
|
150
|
+
* the files are watched so the build copies them, and an edit does NOT
|
|
151
|
+
* restart the server. A package that genuinely needs a restart asks for one
|
|
152
|
+
* by passing `true`.
|
|
153
|
+
*/
|
|
154
|
+
metaFiles?: MetaFileConfig[]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* One `metaFiles` entry.
|
|
159
|
+
*
|
|
160
|
+
* `reloadServer` is required rather than defaulted: whether an edit costs a
|
|
161
|
+
* restart is the whole decision this entry exists to record, and a default
|
|
162
|
+
* would let it be made by accident.
|
|
163
|
+
*/
|
|
164
|
+
export interface MetaFileConfig {
|
|
165
|
+
/** Glob, relative to the project root — `resources/lang/**\/*.json`. */
|
|
166
|
+
pattern: string
|
|
167
|
+
/** Restart the development server when a matching file changes. */
|
|
168
|
+
reloadServer: boolean
|
|
140
169
|
}
|
|
141
170
|
|
|
142
171
|
/** One test suite, as declared in the rc file (AdonisJS `tests.suites[]`). */
|
|
@@ -709,7 +738,6 @@ export class Ignitor {
|
|
|
709
738
|
// anything. Registered in `ready()` it came up after the server was already
|
|
710
739
|
// serving, so a page loaded in that window polled a route that did not
|
|
711
740
|
// exist yet.
|
|
712
|
-
this.#registerDevReloadRoute()
|
|
713
741
|
|
|
714
742
|
// Providers start BEFORE the preload files, matching upstream's warm-up
|
|
715
743
|
// order (`providers.start()` → `starting` hooks → preloads). A preload is
|
|
@@ -797,7 +825,10 @@ export class Ignitor {
|
|
|
797
825
|
container: this.#app.container,
|
|
798
826
|
exceptionHandler:
|
|
799
827
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
800
|
-
serverMiddleware:
|
|
828
|
+
serverMiddleware: [
|
|
829
|
+
...this.#frameworkServerMiddleware(),
|
|
830
|
+
...this.#server.getServerMiddleware(),
|
|
831
|
+
],
|
|
801
832
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
802
833
|
devReloadScript: this.#devReloadScript(),
|
|
803
834
|
onError: (error, ctx) => {
|
|
@@ -1288,19 +1319,18 @@ export class Ignitor {
|
|
|
1288
1319
|
}
|
|
1289
1320
|
|
|
1290
1321
|
/**
|
|
1291
|
-
*
|
|
1322
|
+
* The server middleware the framework itself contributes, ahead of the
|
|
1323
|
+
* application's.
|
|
1292
1324
|
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1325
|
+
* Only the hot-reload token, and only in development. It answers first
|
|
1326
|
+
* because the poll arrives once a second per open tab and its content
|
|
1327
|
+
* depends on nothing — no user, no session, no body. Behind an application's
|
|
1328
|
+
* middleware it paid for the whole stack each time, which for anything that
|
|
1329
|
+
* resolves a user from a cookie meant a `SELECT` per second for a string
|
|
1330
|
+
* holding a process id and a counter.
|
|
1296
1331
|
*/
|
|
1297
|
-
#
|
|
1298
|
-
|
|
1299
|
-
this.#router.get(HMR_PATH, ({ response }) => {
|
|
1300
|
-
response.header('cache-control', 'no-store')
|
|
1301
|
-
response.header('content-type', 'text/plain; charset=utf-8')
|
|
1302
|
-
response.send(hmrToken())
|
|
1303
|
-
})
|
|
1332
|
+
#frameworkServerMiddleware(): MiddlewareFunction[] {
|
|
1333
|
+
return this.isDevMode() ? [hmrEndpoint()] : []
|
|
1304
1334
|
}
|
|
1305
1335
|
|
|
1306
1336
|
getPhase(): string {
|
package/src/dev/hmr.ts
CHANGED
|
@@ -45,6 +45,90 @@ function holder(): CounterHolder {
|
|
|
45
45
|
export function hotReloadHappened(): void {
|
|
46
46
|
const store = holder()
|
|
47
47
|
store[COUNTER] = (store[COUNTER] ?? 0) + 1
|
|
48
|
+
notifyClients()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The dev clients currently listening.
|
|
53
|
+
*
|
|
54
|
+
* A `Set` and not a count: each entry is pushed to on a swap, and dropped when
|
|
55
|
+
* the tab closes. `unknown`-typed on purpose — this module must not import the
|
|
56
|
+
* HTTP layer, which imports it back.
|
|
57
|
+
*/
|
|
58
|
+
interface DevClient {
|
|
59
|
+
send: (event: string, data: unknown) => Promise<boolean>
|
|
60
|
+
onClose: (callback: () => void) => void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const clients = new Set<DevClient>()
|
|
64
|
+
|
|
65
|
+
/** The event name the injected script listens for. */
|
|
66
|
+
const HMR_EVENT = 'ream:hmr'
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Answer the dev client BEFORE any application middleware runs, and PUSH.
|
|
70
|
+
*
|
|
71
|
+
* Two problems, one change.
|
|
72
|
+
*
|
|
73
|
+
* It used to be an ordinary route polled once a second, per open tab, and a
|
|
74
|
+
* route runs the whole middleware stack. An application that resolves a user
|
|
75
|
+
* from a session cookie therefore ran `SELECT * FROM users` once a second for
|
|
76
|
+
* a response whose entire content is a process id and a counter, and which
|
|
77
|
+
* depends on no user, no session and no body.
|
|
78
|
+
*
|
|
79
|
+
* It is a stream now, so the cost is paid ONCE per tab instead of once a
|
|
80
|
+
* second, and a change reaches the browser immediately rather than up to a
|
|
81
|
+
* second later. Upstream gets the same thing from Vite's websocket; this needs
|
|
82
|
+
* no bundler, because the server already speaks SSE.
|
|
83
|
+
*
|
|
84
|
+
* Registered FIRST rather than merely outside the router: middleware that
|
|
85
|
+
* authenticates lives at either tier, and an application is free to put it in
|
|
86
|
+
* `server.use([...])` — many do, because it must run before routing.
|
|
87
|
+
*
|
|
88
|
+
* Development only. In production nothing injects the script and nothing
|
|
89
|
+
* registers this.
|
|
90
|
+
*/
|
|
91
|
+
export function hmrEndpoint(): (
|
|
92
|
+
ctx: {
|
|
93
|
+
request: { url: () => string }
|
|
94
|
+
response: { sse: () => Promise<DevClient> }
|
|
95
|
+
},
|
|
96
|
+
next: () => Promise<void>,
|
|
97
|
+
) => Promise<void> {
|
|
98
|
+
return async (ctx, next) => {
|
|
99
|
+
// `url()` carries the query string; a client may append a cache-buster.
|
|
100
|
+
const path = ctx.request.url().split('?')[0]
|
|
101
|
+
if (path !== HMR_PATH) {
|
|
102
|
+
await next()
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let stream: DevClient
|
|
107
|
+
try {
|
|
108
|
+
stream = await ctx.response.sse()
|
|
109
|
+
} catch {
|
|
110
|
+
// A host without the streaming NAPI — a mock server in a test, say.
|
|
111
|
+
// Losing the reload is fine; failing the request is not.
|
|
112
|
+
await next()
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
clients.add(stream)
|
|
117
|
+
stream.onClose(() => clients.delete(stream))
|
|
118
|
+
// Immediately, so a tab that connects after a restart compares against the
|
|
119
|
+
// CURRENT token rather than waiting for the next swap to learn anything.
|
|
120
|
+
await stream.send(HMR_EVENT, hmrToken())
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Tell every open tab the token moved. */
|
|
125
|
+
function notifyClients(): void {
|
|
126
|
+
const token = hmrToken()
|
|
127
|
+
for (const client of clients) {
|
|
128
|
+
// Detached: a swap must not wait on a socket, and a dead one is dropped by
|
|
129
|
+
// its own close handler.
|
|
130
|
+
void client.send(HMR_EVENT, token).catch(() => clients.delete(client))
|
|
131
|
+
}
|
|
48
132
|
}
|
|
49
133
|
|
|
50
134
|
/** How many hot swaps this process has seen. */
|
|
@@ -63,10 +147,15 @@ export function hmrToken(): string {
|
|
|
63
147
|
/**
|
|
64
148
|
* The script injected into HTML pages in dev.
|
|
65
149
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
150
|
+
* An `EventSource`, not a poll. The server pushes the token, so a change
|
|
151
|
+
* reaches the page immediately and an idle tab costs one open connection
|
|
152
|
+
* rather than a request per second through the whole middleware stack.
|
|
153
|
+
*
|
|
154
|
+
* It reloads when the token CHANGES, never when the connection fails: a
|
|
155
|
+
* restarting server is unreachable for a moment, and reloading then would only
|
|
156
|
+
* show the browser's error page. `EventSource` reconnects on its own, and the
|
|
157
|
+
* server greets the new connection with its token — which after a restart is a
|
|
158
|
+
* different boot id, so the reload happens then, once the server can serve it.
|
|
70
159
|
*/
|
|
71
160
|
export function hmrClientScript(nonce?: string): string {
|
|
72
161
|
// A nonce-based CSP — which the scaffold ships, via `@c9up/blackhole` —
|
|
@@ -75,9 +164,13 @@ export function hmrClientScript(nonce?: string): string {
|
|
|
75
164
|
// are escaped because a nonce is generated, not user input, but a broken
|
|
76
165
|
// attribute would silently disable the tag rather than fail loudly.
|
|
77
166
|
const attribute = nonce === undefined ? '' : ` nonce="${nonce.replace(/"/g, '"')}"`
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
return `<script${attribute}>(()=>{let t=null;const
|
|
167
|
+
// `addEventListener(HMR_EVENT)`, NOT `onmessage`: the stream names its
|
|
168
|
+
// events, and `onmessage` fires only for unnamed ones — a client wired that
|
|
169
|
+
// way receives nothing at all, silently, which is a mistake this codebase
|
|
170
|
+
// has already made once with relay.
|
|
171
|
+
return `<script${attribute}>(()=>{let t=null;const s=new EventSource(${JSON.stringify(
|
|
172
|
+
HMR_PATH,
|
|
173
|
+
)});s.addEventListener(${JSON.stringify(
|
|
174
|
+
HMR_EVENT,
|
|
175
|
+
)},e=>{let n;try{n=JSON.parse(e.data)}catch(_){n=e.data}if(t===null){t=n}else if(n!==t){s.close();location.reload()}})})()</script>`
|
|
83
176
|
}
|
package/src/dev/hotGlob.ts
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
* what they need is to be right, not fast.
|
|
8
8
|
*
|
|
9
9
|
* The dialect is the one those files already use:
|
|
10
|
-
* *
|
|
11
|
-
* **
|
|
12
|
-
* ?
|
|
10
|
+
* * any run of characters inside one path segment
|
|
11
|
+
* ** any number of segments, including none
|
|
12
|
+
* ? exactly one character inside a segment
|
|
13
|
+
* {a,b} either alternative
|
|
14
|
+
*
|
|
15
|
+
* The brace form is the one a `metaFiles` entry uses —
|
|
16
|
+
* `resources/lang/**\/*.{json,yaml,yml}` — and it has to be understood on this
|
|
17
|
+
* side too: the CLI matches the same pattern when it copies those files into
|
|
18
|
+
* the build, and a dialect that differed between the two would let an entry
|
|
19
|
+
* copy at build time and never fire in development, or the reverse.
|
|
13
20
|
*
|
|
14
21
|
* A leading `./` is optional, and matching is done on POSIX-shaped paths
|
|
15
22
|
* relative to the project root, so a Windows checkout and a Linux one read the
|
|
@@ -42,6 +49,24 @@ export function globToRegExp(pattern: string): RegExp {
|
|
|
42
49
|
out += '[^/]'
|
|
43
50
|
continue
|
|
44
51
|
}
|
|
52
|
+
if (char === '{') {
|
|
53
|
+
const close = cleaned.indexOf('}', i)
|
|
54
|
+
if (close !== -1) {
|
|
55
|
+
// Alternatives are literal text, never nested globs: that is the whole
|
|
56
|
+
// of the form these patterns use, and a recursive reading would accept
|
|
57
|
+
// shapes the CLI's matcher does not.
|
|
58
|
+
const options = cleaned
|
|
59
|
+
.slice(i + 1, close)
|
|
60
|
+
.split(',')
|
|
61
|
+
.map((option) => option.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
62
|
+
out += `(?:${options.join('|')})`
|
|
63
|
+
i = close
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
// Unclosed: read literally rather than throwing. A pattern is a string in
|
|
67
|
+
// a config file, and a malformed one should match nothing, not take the
|
|
68
|
+
// dev server down at boot.
|
|
69
|
+
}
|
|
45
70
|
// Everything else is literal, including the characters a regular
|
|
46
71
|
// expression would otherwise read as syntax.
|
|
47
72
|
out += (char ?? '').replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
package/src/hot.ts
CHANGED
|
@@ -27,7 +27,7 @@ import { existsSync, writeSync } from 'node:fs'
|
|
|
27
27
|
import { readFile } from 'node:fs/promises'
|
|
28
28
|
import { registerHooks } from 'node:module'
|
|
29
29
|
import { dirname, relative, resolve as resolvePath } from 'node:path'
|
|
30
|
-
import { fileURLToPath } from 'node:url'
|
|
30
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
31
31
|
import { fullReloadNotice } from './dev/fullReload.js'
|
|
32
32
|
import { hotReloadHappened } from './dev/hmr.js'
|
|
33
33
|
import { readHotHookConfig } from './dev/hotConfig.js'
|
|
@@ -66,9 +66,60 @@ const restartFiles = new Set(
|
|
|
66
66
|
.filter((file) => existsSync(file)),
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* `metaFiles` entries that asked for a restart.
|
|
71
|
+
*
|
|
72
|
+
* Non-module files the application owns — translations, view templates — live
|
|
73
|
+
* in `reamrc.ts` because the BUILD has to copy them. A change to one is
|
|
74
|
+
* invisible to the import graph, so the only way it can do anything in
|
|
75
|
+
* development is by being named here.
|
|
76
|
+
*
|
|
77
|
+
* `reloadServer: false` is the interesting default, and the one upstream picks
|
|
78
|
+
* for translations: the entry exists so the file ships, and an edit does NOT
|
|
79
|
+
* restart. NAMED DEVIATION — upstream still logs such a change, because its
|
|
80
|
+
* watcher covers the whole project anyway. This one watches a fixed list of
|
|
81
|
+
* directories on purpose (`resources/` belongs to the asset watcher, and a
|
|
82
|
+
* stylesheet edit must not restart the server), so a `false` entry is simply
|
|
83
|
+
* not watched. The observable behaviour is the same: nothing happens.
|
|
84
|
+
*/
|
|
85
|
+
async function readReloadPatterns(): Promise<string[]> {
|
|
86
|
+
try {
|
|
87
|
+
const rc = (await import(pathToFileURL(resolvePath(root, 'reamrc.ts')).href)) as {
|
|
88
|
+
default?: { metaFiles?: Array<{ pattern?: unknown; reloadServer?: unknown }> }
|
|
89
|
+
}
|
|
90
|
+
return (rc.default?.metaFiles ?? [])
|
|
91
|
+
.filter((entry) => entry.reloadServer === true && typeof entry.pattern === 'string')
|
|
92
|
+
.map((entry) => String(entry.pattern))
|
|
93
|
+
} catch {
|
|
94
|
+
// No rc file, or one that does not load. The server itself reports a broken
|
|
95
|
+
// rc far more clearly than the loader could, and a dev server that refuses
|
|
96
|
+
// to start because of a watch list helps nobody.
|
|
97
|
+
return []
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const reloadPatterns = await readReloadPatterns()
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The directory a pattern lives under — everything before its first wildcard.
|
|
105
|
+
*
|
|
106
|
+
* `resources/lang/**\/*.json` is watched as `resources/lang`. Watching the
|
|
107
|
+
* project root instead would pull in `node_modules` and the build output.
|
|
108
|
+
*/
|
|
109
|
+
function watchRootOf(pattern: string): string {
|
|
110
|
+
const segments = pattern.replace(/^\.\//, '').split('/')
|
|
111
|
+
const fixed: string[] = []
|
|
112
|
+
for (const segment of segments) {
|
|
113
|
+
if (/[*?{[]/.test(segment)) break
|
|
114
|
+
fixed.push(segment)
|
|
115
|
+
}
|
|
116
|
+
// A pattern that is a bare glob has no directory to narrow to.
|
|
117
|
+
return fixed.slice(0, -1).join('/') || fixed.join('/')
|
|
118
|
+
}
|
|
119
|
+
|
|
69
120
|
const relativeToRoot = (file: string): string => relative(root, file).replace(/\\/g, '/')
|
|
70
121
|
const matchesBoundary = globMatcher(boundaries)
|
|
71
|
-
const matchesRestartGlob = globMatcher(restart)
|
|
122
|
+
const matchesRestartGlob = globMatcher([...restart, ...reloadPatterns])
|
|
72
123
|
|
|
73
124
|
const graph = new HotGraph({
|
|
74
125
|
isBoundary: (file) => matchesBoundary(relativeToRoot(file)),
|
|
@@ -126,7 +177,12 @@ function sayNow(line: string): void {
|
|
|
126
177
|
|
|
127
178
|
watchProject({
|
|
128
179
|
root,
|
|
129
|
-
directories:
|
|
180
|
+
directories: [
|
|
181
|
+
...WATCH_DIRS,
|
|
182
|
+
// Whatever a `reloadServer: true` entry named, and nothing else: the
|
|
183
|
+
// directories above are the ones the graph can reason about.
|
|
184
|
+
...reloadPatterns.map(watchRootOf).filter((dir) => dir !== ''),
|
|
185
|
+
],
|
|
130
186
|
files: [...restartFiles].map((file) => relative(root, file)),
|
|
131
187
|
onChange: (file) => {
|
|
132
188
|
const decision = graph.decide(file)
|
package/src/index.ts
CHANGED