@c9up/ream 0.2.24 → 0.2.26

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.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/dist/Codemods.d.ts +13 -0
  3. package/dist/Codemods.d.ts.map +1 -1
  4. package/dist/Codemods.js +158 -23
  5. package/dist/Codemods.js.map +1 -1
  6. package/dist/Ignitor.d.ts +28 -0
  7. package/dist/Ignitor.d.ts.map +1 -1
  8. package/dist/Ignitor.js +15 -15
  9. package/dist/Ignitor.js.map +1 -1
  10. package/dist/console/Kernel.d.ts.map +1 -1
  11. package/dist/console/Kernel.js +20 -1
  12. package/dist/console/Kernel.js.map +1 -1
  13. package/dist/console/cliui.d.ts +9 -245
  14. package/dist/console/cliui.d.ts.map +1 -1
  15. package/dist/console/cliui.js +9 -583
  16. package/dist/console/cliui.js.map +1 -1
  17. package/dist/console/make/commands.d.ts +19 -0
  18. package/dist/console/make/commands.d.ts.map +1 -0
  19. package/dist/console/make/commands.js +252 -0
  20. package/dist/console/make/commands.js.map +1 -0
  21. package/dist/console/make/eject.d.ts +28 -0
  22. package/dist/console/make/eject.d.ts.map +1 -0
  23. package/dist/console/make/eject.js +161 -0
  24. package/dist/console/make/eject.js.map +1 -0
  25. package/dist/console/make/flush.d.ts +51 -0
  26. package/dist/console/make/flush.d.ts.map +1 -0
  27. package/dist/console/make/flush.js +93 -0
  28. package/dist/console/make/flush.js.map +1 -0
  29. package/dist/console/make/generators.d.ts +36 -0
  30. package/dist/console/make/generators.d.ts.map +1 -0
  31. package/dist/console/make/generators.js +148 -0
  32. package/dist/console/make/generators.js.map +1 -0
  33. package/dist/console/make/naming.d.ts +32 -0
  34. package/dist/console/make/naming.d.ts.map +1 -0
  35. package/dist/console/make/naming.js +82 -0
  36. package/dist/console/make/naming.js.map +1 -0
  37. package/dist/console/make/resolve.d.ts +46 -0
  38. package/dist/console/make/resolve.d.ts.map +1 -0
  39. package/dist/console/make/resolve.js +113 -0
  40. package/dist/console/make/resolve.js.map +1 -0
  41. package/dist/console/ui.d.ts +9 -15
  42. package/dist/console/ui.d.ts.map +1 -1
  43. package/dist/console/ui.js +11 -24
  44. package/dist/console/ui.js.map +1 -1
  45. package/dist/dev/hmr.d.ts +52 -4
  46. package/dist/dev/hmr.d.ts.map +1 -1
  47. package/dist/dev/hmr.js +75 -9
  48. package/dist/dev/hmr.js.map +1 -1
  49. package/dist/dev/hotGlob.d.ts +10 -3
  50. package/dist/dev/hotGlob.d.ts.map +1 -1
  51. package/dist/dev/hotGlob.js +28 -3
  52. package/dist/dev/hotGlob.js.map +1 -1
  53. package/dist/hot.js +56 -3
  54. package/dist/hot.js.map +1 -1
  55. package/dist/index.d.ts +3 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -2
  58. package/dist/index.js.map +1 -1
  59. package/dist/stubs/index.d.ts +11 -0
  60. package/dist/stubs/index.d.ts.map +1 -0
  61. package/dist/stubs/index.js +12 -0
  62. package/dist/stubs/index.js.map +1 -0
  63. package/dist/stubs/template.d.ts +63 -0
  64. package/dist/stubs/template.d.ts.map +1 -0
  65. package/dist/stubs/template.js +209 -0
  66. package/dist/stubs/template.js.map +1 -0
  67. package/events.win32-x64-msvc.node +0 -0
  68. package/index.win32-x64-msvc.node +0 -0
  69. package/package.json +7 -1
  70. package/scheduler.win32-x64-msvc.node +0 -0
  71. package/src/Codemods.ts +206 -29
  72. package/src/Ignitor.ts +44 -14
  73. package/src/console/Kernel.ts +21 -1
  74. package/src/console/cliui.ts +44 -754
  75. package/src/console/make/commands.ts +270 -0
  76. package/src/console/make/eject.ts +163 -0
  77. package/src/console/make/flush.ts +125 -0
  78. package/src/console/make/generators.ts +176 -0
  79. package/src/console/make/naming.ts +85 -0
  80. package/src/console/make/resolve.ts +134 -0
  81. package/src/console/ui.ts +12 -24
  82. package/src/dev/hmr.ts +102 -9
  83. package/src/dev/hotGlob.ts +28 -3
  84. package/src/hot.ts +59 -3
  85. package/src/index.ts +17 -3
  86. package/src/stubs/index.ts +14 -0
  87. package/src/stubs/template.ts +262 -0
  88. package/stubs/make/command.stub +29 -0
  89. package/stubs/make/controller.stub +30 -0
  90. package/stubs/make/entity.stub +11 -0
  91. package/stubs/make/event.stub +13 -0
  92. package/stubs/make/listener.stub +13 -0
  93. package/stubs/make/middleware.stub +16 -0
  94. package/stubs/make/provider.stub +26 -0
  95. package/stubs/make/service.stub +27 -0
  96. package/stubs/make/validator.stub +10 -0
@@ -0,0 +1,209 @@
1
+ /**
2
+ * The stub template engine.
3
+ *
4
+ * A port of the one upstream uses (tempura), grammar for grammar: comments,
5
+ * declared props, inline variables, conditionals, loops, custom blocks, and
6
+ * the two interpolation forms. A template is compiled into a JavaScript
7
+ * function once and then called — the same design, for the same reason: a
8
+ * tree-walking interpreter re-reads the template on every render, and a stub
9
+ * is rendered in a loop by the generators.
10
+ *
11
+ * {{! a comment }}
12
+ * {{#expect name, entity }}
13
+ * {{#var plural = name + 's' }}
14
+ * {{#if entity.isModel }}…{{#elif entity.isView }}…{{#else}}…{{/if}}
15
+ * {{#each items as item, index }}…{{/each}}
16
+ * {{ name }} the value
17
+ * {{{ name }}} the value, unescaped
18
+ *
19
+ * NAMED DEVIATION — upstream escapes HTML in `{{ }}` and leaves `{{{ }}}`
20
+ * raw, because its templates are web pages. Ours are SOURCE FILES: escaping
21
+ * `"` into `"` there produces code that does not parse, and the mistake
22
+ * is silent. So `{{ }}` does not escape by default, `{{{ }}}` is accepted as
23
+ * the same thing (a stub written for upstream keeps working), and an `escape`
24
+ * option turns it back on for anyone templating markup.
25
+ *
26
+ * `new Function` compiles the result. That is how upstream does it too, and
27
+ * the trust boundary is the same: a stub ships inside a package whose
28
+ * `configure` hook is already executing.
29
+ */
30
+ /** Raised when a template cannot be read, naming what is wrong with it. */
31
+ export class StubSyntaxError extends Error {
32
+ code = 'E_REAM_STUB_SYNTAX';
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = 'StubSyntaxError';
36
+ }
37
+ }
38
+ /** HTML escaping, for a stub that does template markup. */
39
+ export function escapeHtml(value) {
40
+ const text = value === null || value === undefined ? '' : String(value);
41
+ return text.replace(/[&"<>]/g, (char) => {
42
+ if (char === '&')
43
+ return '&amp;';
44
+ if (char === '"')
45
+ return '&quot;';
46
+ if (char === '<')
47
+ return '&lt;';
48
+ return '&gt;';
49
+ });
50
+ }
51
+ /** The default: a source file wants its quotes and its angle brackets back. */
52
+ function asIs(value) {
53
+ return value === null || value === undefined ? '' : String(value);
54
+ }
55
+ const CURLY = /{{{?\s*([\s\S]*?)\s*}}}?/g;
56
+ const BLOCK = /^#\s*(\w[\w\d]*)\s*([\s\S]*)/;
57
+ const ARGS = /([a-zA-Z$_][^\s=]*)\s*=\s*((["`'])(?:(?=(\\?))\4.)*?\3|{[^}]*}|\[[^\]]*]|\S+)/g;
58
+ /**
59
+ * Compile a template into the body of a function.
60
+ *
61
+ * Kept separate from `compile` so a test can read what a template became —
62
+ * the generated source is the only place a subtle mistake shows itself.
63
+ */
64
+ export function generate(input, options = {}) {
65
+ const blocks = options.blocks ?? {};
66
+ const declared = new Set(options.props ?? []);
67
+ const stack = [];
68
+ let body = '';
69
+ let literal = '';
70
+ let last = 0;
71
+ CURLY.lastIndex = 0;
72
+ /** Flush the literal text collected since the last tag. */
73
+ const flush = () => {
74
+ if (literal.length > 0) {
75
+ body += `${body ? 'x+=' : '='}\`${literal}\`;`;
76
+ }
77
+ else if (body.length === 0) {
78
+ body = '="";';
79
+ }
80
+ literal = '';
81
+ };
82
+ let match = CURLY.exec(input);
83
+ while (match !== null) {
84
+ literal += escapeForTemplate(input.slice(last, match.index));
85
+ last = match.index + match[0].length;
86
+ const inner = (match[1] ?? '').trim();
87
+ const first = inner.charAt(0);
88
+ if (first === '!') {
89
+ // A comment. It leaves no trace in the output.
90
+ }
91
+ else if (first === '#') {
92
+ flush();
93
+ const parsed = BLOCK.exec(inner);
94
+ if (parsed === null) {
95
+ throw new StubSyntaxError(`Unreadable block: {{${inner}}}`);
96
+ }
97
+ const action = parsed[1] ?? '';
98
+ const rest = (parsed[2] ?? '').trim();
99
+ body += openBlock(action, rest, stack, declared, blocks);
100
+ }
101
+ else if (first === '/') {
102
+ const closing = inner.slice(1).trim();
103
+ const opened = stack.pop();
104
+ flush();
105
+ if (opened === undefined) {
106
+ throw new StubSyntaxError(`Closed "${closing}" with nothing open.`);
107
+ }
108
+ if (opened !== closing) {
109
+ throw new StubSyntaxError(`Expected to close "${opened}"; closed "${closing}" instead.`);
110
+ }
111
+ body += '}';
112
+ }
113
+ else {
114
+ // Both forms interpolate the same way — see the header.
115
+ literal += `\${$$e(${inner})}`;
116
+ }
117
+ match = CURLY.exec(input);
118
+ }
119
+ if (stack.length > 0) {
120
+ throw new StubSyntaxError(`Unterminated "${stack.pop()}" block.`);
121
+ }
122
+ if (last < input.length) {
123
+ literal += escapeForTemplate(input.slice(last));
124
+ }
125
+ flush();
126
+ const bindings = declared.size > 0 ? `{${[...declared].join()}}=$$s,x` : ' x';
127
+ return `var${bindings}${body}return x`;
128
+ }
129
+ function openBlock(action, rest, stack, declared, blocks) {
130
+ if (action === 'expect') {
131
+ // Names the template reads. Declaring them is what lets an unknown one
132
+ // fail as `x is not defined` rather than as `undefined` in the output.
133
+ for (const key of rest.split(/[\n\r\s\t]*,[\n\r\s\t]*/g)) {
134
+ if (key !== '')
135
+ declared.add(key);
136
+ }
137
+ return '';
138
+ }
139
+ if (action === 'var') {
140
+ const at = rest.indexOf('=');
141
+ if (at === -1) {
142
+ throw new StubSyntaxError(`{{#var}} needs an assignment: {{#var ${rest}}}`);
143
+ }
144
+ const name = rest.slice(0, at).trim();
145
+ const value = rest
146
+ .slice(at + 1)
147
+ .trim()
148
+ .replace(/;$/, '');
149
+ return `var ${name}=${value};`;
150
+ }
151
+ if (action === 'each') {
152
+ stack.push(action);
153
+ const at = rest.indexOf(' as ');
154
+ if (at === -1) {
155
+ return `for(var i=0,$$a=${rest};i<$$a.length;i++){`;
156
+ }
157
+ const list = rest.slice(0, at).trim();
158
+ const [item = 'item', index = 'i'] = rest
159
+ .slice(at + 4)
160
+ .trim()
161
+ .replace(/[()\s]/g, '')
162
+ .split(',');
163
+ return `for(var ${index}=0,${item},$$a=${list};${index}<$$a.length;${index}++){${item}=$$a[${index}];`;
164
+ }
165
+ if (action === 'if') {
166
+ stack.push(action);
167
+ return `if(${rest}){`;
168
+ }
169
+ if (action === 'elif')
170
+ return `}else if(${rest}){`;
171
+ if (action === 'else')
172
+ return '}else{';
173
+ if (action in blocks) {
174
+ // `{{#note tone="warn" }}` — the arguments become one object.
175
+ const args = [];
176
+ let found = ARGS.exec(rest);
177
+ while (found !== null) {
178
+ args.push(`${found[1]}:${found[2]}`);
179
+ found = ARGS.exec(rest);
180
+ }
181
+ ARGS.lastIndex = 0;
182
+ return `x+=$$b.${action}(${args.length > 0 ? `{${args.join()}}` : '{}'});`;
183
+ }
184
+ throw new StubSyntaxError(`Unknown "${action}" block. Known: expect, var, if, elif, else, each${Object.keys(blocks).length > 0 ? `, ${Object.keys(blocks).join(', ')}` : ''}.`);
185
+ }
186
+ /** Protect the literal text from the template literal it is about to live in. */
187
+ function escapeForTemplate(text) {
188
+ return text.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
189
+ }
190
+ /** Compile a template into a render function. */
191
+ export function compile(input, options = {}) {
192
+ const source = generate(input, options);
193
+ let render;
194
+ try {
195
+ // eslint-disable-next-line no-new-func -- the whole point of a compiler
196
+ render = new Function('$$e', '$$b', '$$s', source);
197
+ }
198
+ catch (error) {
199
+ throw new StubSyntaxError(`This stub does not compile: ${error instanceof Error ? error.message : String(error)}`);
200
+ }
201
+ const escaper = options.escape ?? asIs;
202
+ const blocks = options.blocks ?? {};
203
+ return (state) => render(escaper, blocks, state);
204
+ }
205
+ /** Compile and render in one go. */
206
+ export function render(input, state = {}, options = {}) {
207
+ return compile(input, { props: Object.keys(state), ...options })(state);
208
+ }
209
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/stubs/template.ts"],"names":[],"mappings":"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"}
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/ream",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "Ream — Rust-powered Node.js application framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -111,6 +111,10 @@
111
111
  "types": "./dist/helpers/fs.d.ts",
112
112
  "import": "./dist/helpers/fs.js"
113
113
  },
114
+ "./stubs": {
115
+ "types": "./dist/stubs/index.d.ts",
116
+ "import": "./dist/stubs/index.js"
117
+ },
114
118
  "./testing": {
115
119
  "types": "./dist/testing/TestClient.d.ts",
116
120
  "import": "./dist/testing/TestClient.js"
@@ -216,6 +220,7 @@
216
220
  "LICENSE",
217
221
  "README.md",
218
222
  "dist",
223
+ "stubs",
219
224
  "index.*.node",
220
225
  "scheduler.*.node",
221
226
  "events.*.node",
@@ -228,6 +233,7 @@
228
233
  "tsconfig.client.json"
229
234
  ],
230
235
  "dependencies": {
236
+ "@c9up/lumen": "^0.1.0",
231
237
  "file-type": "^22.0.2",
232
238
  "reflect-metadata": "^0.2.2"
233
239
  },
Binary file
package/src/Codemods.ts CHANGED
@@ -1,14 +1,77 @@
1
1
  import * as fs from 'node:fs'
2
2
  import * as path from 'node:path'
3
+ import { render as renderTemplate, type StubState } from './stubs/template.js'
4
+
5
+ export type { StubState }
3
6
 
4
7
  export interface Codemods {
5
8
  addProvider(importPath: string): Promise<void>
9
+ addMetaFile(pattern: string, reloadServer?: boolean): Promise<void>
6
10
  addEnvVars(vars: Record<string, string>): Promise<void>
7
11
  writeFile(filePath: string, content: string, options?: { force?: boolean }): Promise<void>
12
+ makeUsingStub(
13
+ stubsRoot: string,
14
+ stubPath: string,
15
+ state?: StubState,
16
+ options?: { force?: boolean },
17
+ ): Promise<GeneratedStub>
8
18
  registerCommand(importPath: string): Promise<void>
9
19
  registerMiddleware(importPath: string, options?: { tier?: 'server' | 'router' }): Promise<void>
10
20
  }
11
21
 
22
+ /** What a stub may interpolate. Scalars only — a stub is text, not a program. */
23
+
24
+ /** Where a stub landed, and what it wrote there. */
25
+ export interface GeneratedStub {
26
+ /** Relative to the project root. */
27
+ path: string
28
+ contents: string
29
+ }
30
+
31
+ /**
32
+ * A stub, split into the destination it declares and the body it renders.
33
+ *
34
+ * The format is the one `ream-cli`'s generators already read, deliberately:
35
+ * two stub dialects in one framework is a worse tax than the feature is worth.
36
+ *
37
+ * ---
38
+ * to: config/visa.ts
39
+ * ---
40
+ * export default defineConfig({ ... })
41
+ *
42
+ * Adonis declares the same thing with `{{{ exports({ to: … }) }}}`, which is
43
+ * JavaScript we cannot evaluate here — the front matter is that line, written
44
+ * so a text substitution can read it.
45
+ */
46
+ function splitFrontMatter(template: string): { to?: string; body: string } {
47
+ const trimmed = template.replace(/^[\uFEFF\r\n]+/, '')
48
+ if (!trimmed.startsWith('---')) return { body: template }
49
+
50
+ const after = trimmed.slice(3).replace(/^\r?\n/, '')
51
+ const end = after.indexOf('\n---')
52
+ if (end === -1) {
53
+ throw new Error('[configure] stub front matter opened with `---` but never closed')
54
+ }
55
+ const block = after.slice(0, end)
56
+ const body = after.slice(end + 4).replace(/^\r?\n/, '')
57
+
58
+ let to: string | undefined
59
+ for (const rawLine of block.split('\n')) {
60
+ const line = rawLine.trim()
61
+ if (line === '' || line.startsWith('#')) continue
62
+ const separator = line.indexOf(':')
63
+ if (separator === -1) {
64
+ throw new Error(`[configure] unreadable stub front-matter line: \`${line}\``)
65
+ }
66
+ const key = line.slice(0, separator).trim()
67
+ const value = line.slice(separator + 1).trim()
68
+ if (key === 'to') to = value
69
+ // Any other key is ignored rather than refused: a stub written for a
70
+ // later version of this loader must not break the current one.
71
+ }
72
+ return to === undefined ? { body } : { to, body }
73
+ }
74
+
12
75
  type MiddlewareTier = 'server' | 'router'
13
76
 
14
77
  function isMiddlewareTier(value: unknown): value is MiddlewareTier {
@@ -69,6 +132,55 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
69
132
  const force = options?.force ?? false
70
133
  const root = path.resolve(options?.cwd ?? process.cwd())
71
134
 
135
+ /**
136
+ * The one place a file reaches the disk.
137
+ *
138
+ * Shared by `writeFile` and `makeUsingStub` rather than copied, because what
139
+ * it does is not writing — it is refusing to write outside the project, to
140
+ * follow a symlink out of it, or over something that already exists.
141
+ */
142
+ async function writeGuarded(
143
+ filePath: string,
144
+ content: string,
145
+ shouldOverwrite: boolean,
146
+ ): Promise<void> {
147
+ if (path.isAbsolute(filePath)) {
148
+ throw new Error(`[configure] Absolute paths not allowed: ${filePath}`)
149
+ }
150
+ const resolved = path.resolve(root, filePath)
151
+ if (!resolved.startsWith(root + path.sep) && resolved !== root) {
152
+ throw new Error(`[configure] Refusing to write outside project root: ${filePath}`)
153
+ }
154
+
155
+ const shouldForce = shouldOverwrite
156
+ if (fs.existsSync(resolved) && !shouldForce) return
157
+
158
+ const dir = path.dirname(resolved)
159
+ fs.mkdirSync(dir, { recursive: true })
160
+
161
+ const canonRoot = fs.realpathSync(root)
162
+
163
+ if (fs.existsSync(dir)) {
164
+ const canonDir = fs.realpathSync(dir)
165
+ if (!canonDir.startsWith(canonRoot + path.sep) && canonDir !== canonRoot) {
166
+ throw new Error(
167
+ `[configure] Symlink escape detected: ${filePath} — directory resolves outside project root`,
168
+ )
169
+ }
170
+ }
171
+
172
+ if (fs.existsSync(resolved)) {
173
+ const canonFile = fs.realpathSync(resolved)
174
+ if (!canonFile.startsWith(canonRoot + path.sep)) {
175
+ throw new Error(
176
+ `[configure] Symlink escape detected: ${filePath} — file resolves outside project root`,
177
+ )
178
+ }
179
+ }
180
+
181
+ fs.writeFileSync(resolved, content)
182
+ }
183
+
72
184
  return {
73
185
  /**
74
186
  * Insert a provider import into `reamrc.ts` by string manipulation.
@@ -120,6 +232,58 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
120
232
  fs.writeFileSync(rcPath, content)
121
233
  },
122
234
 
235
+ /**
236
+ * Record a non-module file the application owns.
237
+ *
238
+ * A package calls this from `configure()` so its files reach the build:
239
+ * `addMetaFile('resources/lang/**\/*.json', false)`. The second argument
240
+ * is DELIBERATELY explicit at every call site — whether an edit costs a
241
+ * development restart is the decision the entry exists to record.
242
+ *
243
+ * Same regex codemod as `addProvider`, and the same limits: a `metaFiles`
244
+ * assembled by a spread or extracted into a `const` above the config is not
245
+ * rewritten. The block is created when absent, which `addProvider` cannot
246
+ * do — `providers` is always there, `metaFiles` usually is not.
247
+ */
248
+ async addMetaFile(pattern: string, reloadServer = false): Promise<void> {
249
+ const rcPath = path.join(root, 'reamrc.ts')
250
+ if (!fs.existsSync(rcPath)) {
251
+ throw new Error(
252
+ `[configure] reamrc.ts not found — cannot register meta file ${pattern}. Run 'ream new' first.`,
253
+ )
254
+ }
255
+ assertCanonicallyInside(root, rcPath, 'reamrc.ts')
256
+
257
+ let content = fs.readFileSync(rcPath, 'utf8')
258
+ // Dedup on the PATTERN alone: the same glob registered twice with
259
+ // different flags is a contradiction, and the first one wins rather than
260
+ // both being written.
261
+ if (content.includes(`'${pattern}'`) || content.includes(`"${pattern}"`)) return
262
+
263
+ const eol = detectLineEnding(content)
264
+ const entry = ` { pattern: '${pattern}', reloadServer: ${reloadServer} },`
265
+
266
+ const existing = /metaFiles\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/.exec(content)
267
+ if (existing !== null) {
268
+ const insertAt = existing.index + existing[0].length
269
+ content = `${content.slice(0, insertAt)}${eol}${entry}${content.slice(insertAt)}`
270
+ fs.writeFileSync(rcPath, content)
271
+ return
272
+ }
273
+
274
+ // No block yet — open one beside `providers`, which every rc file has.
275
+ const providers = /providers\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/.exec(content)
276
+ if (providers === null) {
277
+ throw new Error(
278
+ `[configure] Could not find 'providers: [' in reamrc.ts — meta file ${pattern} not added. Check your reamrc.ts format.`,
279
+ )
280
+ }
281
+ const blockAt = providers.index
282
+ const block = `metaFiles: [${eol}${entry}${eol} ],${eol} `
283
+ content = `${content.slice(0, blockAt)}${block}${content.slice(blockAt)}`
284
+ fs.writeFileSync(rcPath, content)
285
+ },
286
+
123
287
  async addEnvVars(vars: Record<string, string>): Promise<void> {
124
288
  const envPath = path.join(root, '.env')
125
289
  // A symlinked .env must not redirect the write outside the root.
@@ -139,41 +303,54 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
139
303
  },
140
304
 
141
305
  async writeFile(filePath: string, content: string, opts?: { force?: boolean }): Promise<void> {
142
- if (path.isAbsolute(filePath)) {
143
- throw new Error(`[configure] Absolute paths not allowed: ${filePath}`)
144
- }
145
- const resolved = path.resolve(root, filePath)
146
- if (!resolved.startsWith(root + path.sep) && resolved !== root) {
147
- throw new Error(`[configure] Refusing to write outside project root: ${filePath}`)
148
- }
149
-
150
- const shouldForce = opts?.force ?? force
151
- if (fs.existsSync(resolved) && !shouldForce) return
152
-
153
- const dir = path.dirname(resolved)
154
- fs.mkdirSync(dir, { recursive: true })
306
+ return writeGuarded(filePath, content, opts?.force ?? force)
307
+ },
155
308
 
156
- const canonRoot = fs.realpathSync(root)
309
+ /**
310
+ * Render a stub and write it where the stub says.
311
+ *
312
+ * The application's own copy wins: a stub published to `stubs/<stubPath>`
313
+ * is used instead of the package's, which is what lets someone change the
314
+ * config a package generates without forking it. Adonis calls the same
315
+ * thing "searches in publishTarget first"; `ream-cli` already does it for
316
+ * the `make:` generators, and this is the same rule on the configure side.
317
+ *
318
+ * A package therefore names its stub after itself — `config/visa.stub`,
319
+ * not `config.stub` — or two packages would publish to the same path.
320
+ */
321
+ async makeUsingStub(
322
+ stubsRoot: string,
323
+ stubPath: string,
324
+ state: StubState = {},
325
+ opts?: { force?: boolean },
326
+ ): Promise<GeneratedStub> {
327
+ if (stubPath.includes('..') || path.isAbsolute(stubPath)) {
328
+ throw new Error(`[configure] Unsafe stub path: ${stubPath}`)
329
+ }
157
330
 
158
- if (fs.existsSync(dir)) {
159
- const canonDir = fs.realpathSync(dir)
160
- if (!canonDir.startsWith(canonRoot + path.sep) && canonDir !== canonRoot) {
161
- throw new Error(
162
- `[configure] Symlink escape detected: ${filePath} — directory resolves outside project root`,
163
- )
164
- }
331
+ const published = path.resolve(root, 'stubs', stubPath)
332
+ const shipped = path.resolve(stubsRoot, stubPath)
333
+ const source = fs.existsSync(published) ? published : shipped
334
+ if (!fs.existsSync(source)) {
335
+ throw new Error(`[configure] Stub not found: ${stubPath} (looked in ${stubsRoot})`)
165
336
  }
166
337
 
167
- if (fs.existsSync(resolved)) {
168
- const canonFile = fs.realpathSync(resolved)
169
- if (!canonFile.startsWith(canonRoot + path.sep)) {
170
- throw new Error(
171
- `[configure] Symlink escape detected: ${filePath} — file resolves outside project root`,
172
- )
173
- }
338
+ const raw = fs.readFileSync(source, 'utf8')
339
+ const { to, body } = splitFrontMatter(raw)
340
+ if (to === undefined) {
341
+ // Without it there is nowhere to put the file, and guessing from the
342
+ // stub's own name would put a package's config wherever it was named.
343
+ throw new Error(
344
+ `[configure] Stub ${stubPath} declares no destination — add a \`to:\` front matter line.`,
345
+ )
174
346
  }
175
347
 
176
- fs.writeFileSync(resolved, content)
348
+ // The destination goes through the engine too: `to: config/{{ name }}.ts`
349
+ // is the reason a single stub can serve several outputs.
350
+ const destination = renderTemplate(to, state)
351
+ const contents = renderTemplate(body, state)
352
+ await writeGuarded(destination, contents, opts?.force ?? force)
353
+ return { path: destination, contents }
177
354
  },
178
355
 
179
356
  /**
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 { HMR_PATH, hmrClientScript, hmrToken } from './dev/hmr.js'
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: this.#server.getServerMiddleware(),
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
- * Serve the token the injected script polls.
1322
+ * The server middleware the framework itself contributes, ahead of the
1323
+ * application's.
1292
1324
  *
1293
- * Registered on the router in development, next to the application's own
1294
- * routes, so it goes through the same server as everything else. Plain text
1295
- * and uncached: it is read once a second and its whole content is the answer.
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
- #registerDevReloadRoute(): void {
1298
- if (!this.isDevMode()) return
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 {