@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.
Files changed (78) hide show
  1. package/README.md +2 -0
  2. package/dist/Codemods.d.ts +12 -0
  3. package/dist/Codemods.d.ts.map +1 -1
  4. package/dist/Codemods.js +114 -23
  5. package/dist/Codemods.js.map +1 -1
  6. package/dist/console/Kernel.d.ts.map +1 -1
  7. package/dist/console/Kernel.js +20 -1
  8. package/dist/console/Kernel.js.map +1 -1
  9. package/dist/console/cliui.d.ts +9 -245
  10. package/dist/console/cliui.d.ts.map +1 -1
  11. package/dist/console/cliui.js +9 -583
  12. package/dist/console/cliui.js.map +1 -1
  13. package/dist/console/make/commands.d.ts +19 -0
  14. package/dist/console/make/commands.d.ts.map +1 -0
  15. package/dist/console/make/commands.js +252 -0
  16. package/dist/console/make/commands.js.map +1 -0
  17. package/dist/console/make/eject.d.ts +28 -0
  18. package/dist/console/make/eject.d.ts.map +1 -0
  19. package/dist/console/make/eject.js +161 -0
  20. package/dist/console/make/eject.js.map +1 -0
  21. package/dist/console/make/flush.d.ts +51 -0
  22. package/dist/console/make/flush.d.ts.map +1 -0
  23. package/dist/console/make/flush.js +93 -0
  24. package/dist/console/make/flush.js.map +1 -0
  25. package/dist/console/make/generators.d.ts +36 -0
  26. package/dist/console/make/generators.d.ts.map +1 -0
  27. package/dist/console/make/generators.js +148 -0
  28. package/dist/console/make/generators.js.map +1 -0
  29. package/dist/console/make/naming.d.ts +32 -0
  30. package/dist/console/make/naming.d.ts.map +1 -0
  31. package/dist/console/make/naming.js +82 -0
  32. package/dist/console/make/naming.js.map +1 -0
  33. package/dist/console/make/resolve.d.ts +46 -0
  34. package/dist/console/make/resolve.d.ts.map +1 -0
  35. package/dist/console/make/resolve.js +113 -0
  36. package/dist/console/make/resolve.js.map +1 -0
  37. package/dist/console/ui.d.ts +9 -15
  38. package/dist/console/ui.d.ts.map +1 -1
  39. package/dist/console/ui.js +11 -24
  40. package/dist/console/ui.js.map +1 -1
  41. package/dist/index.d.ts +3 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +4 -2
  44. package/dist/index.js.map +1 -1
  45. package/dist/stubs/index.d.ts +11 -0
  46. package/dist/stubs/index.d.ts.map +1 -0
  47. package/dist/stubs/index.js +12 -0
  48. package/dist/stubs/index.js.map +1 -0
  49. package/dist/stubs/template.d.ts +63 -0
  50. package/dist/stubs/template.d.ts.map +1 -0
  51. package/dist/stubs/template.js +209 -0
  52. package/dist/stubs/template.js.map +1 -0
  53. package/events.win32-x64-msvc.node +0 -0
  54. package/index.win32-x64-msvc.node +0 -0
  55. package/package.json +7 -1
  56. package/scheduler.win32-x64-msvc.node +0 -0
  57. package/src/Codemods.ts +153 -29
  58. package/src/console/Kernel.ts +21 -1
  59. package/src/console/cliui.ts +44 -754
  60. package/src/console/make/commands.ts +270 -0
  61. package/src/console/make/eject.ts +163 -0
  62. package/src/console/make/flush.ts +125 -0
  63. package/src/console/make/generators.ts +176 -0
  64. package/src/console/make/naming.ts +85 -0
  65. package/src/console/make/resolve.ts +134 -0
  66. package/src/console/ui.ts +12 -24
  67. package/src/index.ts +17 -3
  68. package/src/stubs/index.ts +14 -0
  69. package/src/stubs/template.ts +262 -0
  70. package/stubs/make/command.stub +29 -0
  71. package/stubs/make/controller.stub +30 -0
  72. package/stubs/make/entity.stub +11 -0
  73. package/stubs/make/event.stub +13 -0
  74. package/stubs/make/listener.stub +13 -0
  75. package/stubs/make/middleware.stub +16 -0
  76. package/stubs/make/provider.stub +26 -0
  77. package/stubs/make/service.stub +27 -0
  78. 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
- if (path.isAbsolute(filePath)) {
196
- throw new Error(`[configure] Absolute paths not allowed: ${filePath}`)
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
- 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
+ }
210
330
 
211
- if (fs.existsSync(dir)) {
212
- const canonDir = fs.realpathSync(dir)
213
- if (!canonDir.startsWith(canonRoot + path.sep) && canonDir !== canonRoot) {
214
- throw new Error(
215
- `[configure] Symlink escape detected: ${filePath} — directory resolves outside project root`,
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
- if (fs.existsSync(resolved)) {
221
- const canonFile = fs.realpathSync(resolved)
222
- if (!canonFile.startsWith(canonRoot + path.sep)) {
223
- throw new Error(
224
- `[configure] Symlink escape detected: ${filePath} — file resolves outside project root`,
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
- 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 }
230
354
  },
231
355
 
232
356
  /**
@@ -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
- if (!this.#commands.has(metadata.commandName)) {
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