@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.
- package/README.md +2 -0
- package/dist/Codemods.d.ts +13 -0
- package/dist/Codemods.d.ts.map +1 -1
- package/dist/Codemods.js +158 -23
- 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/console/Kernel.d.ts.map +1 -1
- package/dist/console/Kernel.js +20 -1
- package/dist/console/Kernel.js.map +1 -1
- package/dist/console/cliui.d.ts +9 -245
- package/dist/console/cliui.d.ts.map +1 -1
- package/dist/console/cliui.js +9 -583
- package/dist/console/cliui.js.map +1 -1
- package/dist/console/make/commands.d.ts +19 -0
- package/dist/console/make/commands.d.ts.map +1 -0
- package/dist/console/make/commands.js +252 -0
- package/dist/console/make/commands.js.map +1 -0
- package/dist/console/make/eject.d.ts +28 -0
- package/dist/console/make/eject.d.ts.map +1 -0
- package/dist/console/make/eject.js +161 -0
- package/dist/console/make/eject.js.map +1 -0
- package/dist/console/make/flush.d.ts +51 -0
- package/dist/console/make/flush.d.ts.map +1 -0
- package/dist/console/make/flush.js +93 -0
- package/dist/console/make/flush.js.map +1 -0
- package/dist/console/make/generators.d.ts +36 -0
- package/dist/console/make/generators.d.ts.map +1 -0
- package/dist/console/make/generators.js +148 -0
- package/dist/console/make/generators.js.map +1 -0
- package/dist/console/make/naming.d.ts +32 -0
- package/dist/console/make/naming.d.ts.map +1 -0
- package/dist/console/make/naming.js +82 -0
- package/dist/console/make/naming.js.map +1 -0
- package/dist/console/make/resolve.d.ts +46 -0
- package/dist/console/make/resolve.d.ts.map +1 -0
- package/dist/console/make/resolve.js +113 -0
- package/dist/console/make/resolve.js.map +1 -0
- package/dist/console/ui.d.ts +9 -15
- package/dist/console/ui.d.ts.map +1 -1
- package/dist/console/ui.js +11 -24
- package/dist/console/ui.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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/stubs/index.d.ts +11 -0
- package/dist/stubs/index.d.ts.map +1 -0
- package/dist/stubs/index.js +12 -0
- package/dist/stubs/index.js.map +1 -0
- package/dist/stubs/template.d.ts +63 -0
- package/dist/stubs/template.d.ts.map +1 -0
- package/dist/stubs/template.js +209 -0
- package/dist/stubs/template.js.map +1 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +7 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Codemods.ts +206 -29
- package/src/Ignitor.ts +44 -14
- package/src/console/Kernel.ts +21 -1
- package/src/console/cliui.ts +44 -754
- package/src/console/make/commands.ts +270 -0
- package/src/console/make/eject.ts +163 -0
- package/src/console/make/flush.ts +125 -0
- package/src/console/make/generators.ts +176 -0
- package/src/console/make/naming.ts +85 -0
- package/src/console/make/resolve.ts +134 -0
- package/src/console/ui.ts +12 -24
- package/src/dev/hmr.ts +102 -9
- package/src/dev/hotGlob.ts +28 -3
- package/src/hot.ts +59 -3
- package/src/index.ts +17 -3
- package/src/stubs/index.ts +14 -0
- package/src/stubs/template.ts +262 -0
- package/stubs/make/command.stub +29 -0
- package/stubs/make/controller.stub +30 -0
- package/stubs/make/entity.stub +11 -0
- package/stubs/make/event.stub +13 -0
- package/stubs/make/listener.stub +13 -0
- package/stubs/make/middleware.stub +16 -0
- package/stubs/make/provider.stub +26 -0
- package/stubs/make/service.stub +27 -0
- 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 '&';
|
|
44
|
+
if (char === '"')
|
|
45
|
+
return '"';
|
|
46
|
+
if (char === '<')
|
|
47
|
+
return '<';
|
|
48
|
+
return '>';
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA0BH,2EAA2E;AAC3E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,GAAG,oBAAoB,CAAA;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACvE,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;QACtC,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,OAAO,CAAA;QAChC,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,QAAQ,CAAA;QACjC,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,MAAM,CAAA;QAC/B,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,+EAA+E;AAC/E,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACnE,CAAC;AAED,MAAM,KAAK,GAAG,2BAA2B,CAAA;AACzC,MAAM,KAAK,GAAG,8BAA8B,CAAA;AAC5C,MAAM,IAAI,GAAG,gFAAgF,CAAA;AAE7F;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,UAA2B,EAAE;IACnE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAA;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,CAAC,SAAS,GAAG,CAAC,CAAA;IAEnB,2DAA2D;IAC3D,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,KAAK,CAAA;QAChD,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,GAAG,MAAM,CAAA;QACf,CAAC;QACD,OAAO,GAAG,EAAE,CAAA;IACd,CAAC,CAAA;IAED,IAAI,KAAK,GAA2B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrD,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,IAAI,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5D,IAAI,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QAEpC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QAE7B,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAClB,+CAA+C;QACjD,CAAC;aAAM,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YACzB,KAAK,EAAE,CAAA;YACP,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,IAAI,eAAe,CAAC,uBAAuB,KAAK,IAAI,CAAC,CAAA;YAC7D,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACrC,IAAI,IAAI,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC1D,CAAC;aAAM,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACrC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,CAAA;YAC1B,KAAK,EAAE,CAAA;YACP,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,eAAe,CAAC,WAAW,OAAO,sBAAsB,CAAC,CAAA;YACrE,CAAC;YACD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACvB,MAAM,IAAI,eAAe,CAAC,sBAAsB,MAAM,cAAc,OAAO,YAAY,CAAC,CAAA;YAC1F,CAAC;YACD,IAAI,IAAI,GAAG,CAAA;QACb,CAAC;aAAM,CAAC;YACN,wDAAwD;YACxD,OAAO,IAAI,UAAU,KAAK,IAAI,CAAA;QAChC,CAAC;QAED,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CAAC,iBAAiB,KAAK,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;IACnE,CAAC;IACD,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACxB,OAAO,IAAI,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IACjD,CAAC;IACD,KAAK,EAAE,CAAA;IAEP,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7E,OAAO,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAA;AACxC,CAAC;AAED,SAAS,SAAS,CAChB,MAAc,EACd,IAAY,EACZ,KAAe,EACf,QAAqB,EACrB,MAAiC;IAEjC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,uEAAuE;QACvE,uEAAuE;QACvE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,EAAE,CAAC;YACzD,IAAI,GAAG,KAAK,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,CAAC;QACD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC5B,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,eAAe,CAAC,wCAAwC,IAAI,IAAI,CAAC,CAAA;QAC7E,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,KAAK,GAAG,IAAI;aACf,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;aACb,IAAI,EAAE;aACN,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QACpB,OAAO,OAAO,IAAI,IAAI,KAAK,GAAG,CAAA;IAChC,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAC/B,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,OAAO,mBAAmB,IAAI,qBAAqB,CAAA;QACrD,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,CAAC,IAAI,GAAG,MAAM,EAAE,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI;aACtC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;aACb,IAAI,EAAE;aACN,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;aACtB,KAAK,CAAC,GAAG,CAAC,CAAA;QACb,OAAO,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI,IAAI,KAAK,eAAe,KAAK,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAA;IACxG,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAClB,OAAO,MAAM,IAAI,IAAI,CAAA;IACvB,CAAC;IACD,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,YAAY,IAAI,IAAI,CAAA;IAClD,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,QAAQ,CAAA;IAEtC,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;QACrB,8DAA8D;QAC9D,MAAM,IAAI,GAAa,EAAE,CAAA;QACzB,IAAI,KAAK,GAA2B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnD,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YACpC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,OAAO,UAAU,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IAC5E,CAAC;IAED,MAAM,IAAI,eAAe,CACvB,YAAY,MAAM,oDAChB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAC3E,GAAG,CACJ,CAAA;AACH,CAAC;AAED,iFAAiF;AACjF,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAClF,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,OAAO,CACrB,KAAa,EACb,UAA2B,EAAE;IAE7B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACvC,IAAI,MAA0F,CAAA;IAC9F,IAAI,CAAC;QACH,wEAAwE;QACxE,MAAM,GAAG,IAAI,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAkB,CAAA;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,eAAe,CACvB,+BAA+B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACxF,CAAA;IACH,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAA;IACtC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAA;IACnC,OAAO,CAAC,KAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;AAC7D,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,MAAM,CACpB,KAAa,EACb,QAAmB,EAAE,EACrB,UAA2B,EAAE;IAE7B,OAAO,OAAO,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;AACzE,CAAC"}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/ream",
|
|
3
|
-
"version": "0.2.
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|