@c9up/ream 0.2.25 → 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 +12 -0
- package/dist/Codemods.d.ts.map +1 -1
- package/dist/Codemods.js +114 -23
- package/dist/Codemods.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/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 +153 -29
- 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/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
package/src/Codemods.ts
CHANGED
|
@@ -1,15 +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>
|
|
6
9
|
addMetaFile(pattern: string, reloadServer?: boolean): Promise<void>
|
|
7
10
|
addEnvVars(vars: Record<string, string>): Promise<void>
|
|
8
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>
|
|
9
18
|
registerCommand(importPath: string): Promise<void>
|
|
10
19
|
registerMiddleware(importPath: string, options?: { tier?: 'server' | 'router' }): Promise<void>
|
|
11
20
|
}
|
|
12
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
|
+
|
|
13
75
|
type MiddlewareTier = 'server' | 'router'
|
|
14
76
|
|
|
15
77
|
function isMiddlewareTier(value: unknown): value is MiddlewareTier {
|
|
@@ -70,6 +132,55 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
|
|
|
70
132
|
const force = options?.force ?? false
|
|
71
133
|
const root = path.resolve(options?.cwd ?? process.cwd())
|
|
72
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
|
+
|
|
73
184
|
return {
|
|
74
185
|
/**
|
|
75
186
|
* Insert a provider import into `reamrc.ts` by string manipulation.
|
|
@@ -192,41 +303,54 @@ export function createCodemods(options?: { force?: boolean; cwd?: string }): Cod
|
|
|
192
303
|
},
|
|
193
304
|
|
|
194
305
|
async writeFile(filePath: string, content: string, opts?: { force?: boolean }): Promise<void> {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
const resolved = path.resolve(root, filePath)
|
|
199
|
-
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
|
|
200
|
-
throw new Error(`[configure] Refusing to write outside project root: ${filePath}`)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const shouldForce = opts?.force ?? force
|
|
204
|
-
if (fs.existsSync(resolved) && !shouldForce) return
|
|
205
|
-
|
|
206
|
-
const dir = path.dirname(resolved)
|
|
207
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
306
|
+
return writeGuarded(filePath, content, opts?.force ?? force)
|
|
307
|
+
},
|
|
208
308
|
|
|
209
|
-
|
|
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
|
+
}
|
|
210
330
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
}
|
|
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})`)
|
|
218
336
|
}
|
|
219
337
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
+
)
|
|
227
346
|
}
|
|
228
347
|
|
|
229
|
-
|
|
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 }
|
|
230
354
|
},
|
|
231
355
|
|
|
232
356
|
/**
|
package/src/console/Kernel.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { buildFlag } from './decorators.js'
|
|
|
18
18
|
import { ExceptionHandler } from './ExceptionHandler.js'
|
|
19
19
|
import HelpCommand from './HelpCommand.js'
|
|
20
20
|
import ListCommand from './ListCommand.js'
|
|
21
|
+
import { MAKE_COMMANDS } from './make/commands.js'
|
|
21
22
|
import { assignParsedValues, parseArgv, validateParsed } from './parser.js'
|
|
22
23
|
import { Prompt } from './prompts.js'
|
|
23
24
|
import { commandExec } from './tracing.js'
|
|
@@ -229,6 +230,14 @@ export class Kernel {
|
|
|
229
230
|
this.register(HelpCommand)
|
|
230
231
|
this.#defaults.add(HelpCommand.commandName)
|
|
231
232
|
|
|
233
|
+
// The generators. Registered as defaults, so an application that defines
|
|
234
|
+
// its own `make:controller` replaces this one rather than colliding with
|
|
235
|
+
// it — the same standing an app has over `list` and `help`.
|
|
236
|
+
for (const command of MAKE_COMMANDS) {
|
|
237
|
+
this.register(command)
|
|
238
|
+
this.#defaults.add(command.commandName)
|
|
239
|
+
}
|
|
240
|
+
|
|
232
241
|
// Console's `--help`, a global flag whose listener runs the help command.
|
|
233
242
|
this.defineFlag('help', {
|
|
234
243
|
type: 'boolean',
|
|
@@ -460,7 +469,18 @@ export class Kernel {
|
|
|
460
469
|
for (const metadata of await loader.getMetaData()) {
|
|
461
470
|
// Metadata only — the class is imported by `find()`, when someone asks
|
|
462
471
|
// for the command. `loading` / `loaded` belong there too.
|
|
463
|
-
|
|
472
|
+
//
|
|
473
|
+
// A name the kernel registered ITSELF gives way: `list`, `help` and
|
|
474
|
+
// the generators are defaults, and an application that ships its own
|
|
475
|
+
// `make:controller` must get it. Without this the application declared
|
|
476
|
+
// a command that silently never ran — the same rule `register()`
|
|
477
|
+
// applies, applied on the path an application actually uses.
|
|
478
|
+
const isDefault = this.#defaults.has(metadata.commandName)
|
|
479
|
+
if (!this.#commands.has(metadata.commandName) || isDefault) {
|
|
480
|
+
if (isDefault) {
|
|
481
|
+
this.#commands.delete(metadata.commandName)
|
|
482
|
+
this.#defaults.delete(metadata.commandName)
|
|
483
|
+
}
|
|
464
484
|
this.#pending.set(metadata.commandName, { metadata, loader })
|
|
465
485
|
}
|
|
466
486
|
// The metadata is what a manifest-style loader publishes; its aliases
|