@lunora/cli 1.0.0-alpha.4 → 1.0.0-alpha.40

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 (53) hide show
  1. package/__assets__/package-og.svg +1 -1
  2. package/dist/bin.mjs +1 -1
  3. package/dist/index.d.mts +243 -114
  4. package/dist/index.d.ts +243 -114
  5. package/dist/index.mjs +7 -7
  6. package/dist/packem_chunks/handler.mjs +89 -7
  7. package/dist/packem_chunks/handler10.mjs +8 -14
  8. package/dist/packem_chunks/handler11.mjs +19 -189
  9. package/dist/packem_chunks/handler12.mjs +176 -115
  10. package/dist/packem_chunks/handler13.mjs +118 -52
  11. package/dist/packem_chunks/handler14.mjs +50 -43
  12. package/dist/packem_chunks/handler15.mjs +46 -67
  13. package/dist/packem_chunks/handler16.mjs +74 -37
  14. package/dist/packem_chunks/handler17.mjs +38 -100
  15. package/dist/packem_chunks/handler18.mjs +87 -154
  16. package/dist/packem_chunks/handler19.mjs +148 -67
  17. package/dist/packem_chunks/handler2.mjs +2 -2
  18. package/dist/packem_chunks/handler20.mjs +75 -75
  19. package/dist/packem_chunks/handler21.mjs +71 -288
  20. package/dist/packem_chunks/handler3.mjs +1 -1
  21. package/dist/packem_chunks/handler4.mjs +1 -1
  22. package/dist/packem_chunks/handler5.mjs +2 -2
  23. package/dist/packem_chunks/handler6.mjs +2 -2
  24. package/dist/packem_chunks/handler7.mjs +1 -1
  25. package/dist/packem_chunks/handler8.mjs +1 -1
  26. package/dist/packem_chunks/handler9.mjs +311 -12
  27. package/dist/packem_chunks/planDevCommand.mjs +48 -50
  28. package/dist/packem_chunks/runCodegenCommand.mjs +2 -2
  29. package/dist/packem_chunks/runDeployCommand.mjs +105 -15
  30. package/dist/packem_chunks/runInitCommand.mjs +1946 -79
  31. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +5 -5
  32. package/dist/packem_chunks/runResetCommand.mjs +4 -4
  33. package/dist/packem_chunks/runRpcCommand.mjs +1 -1
  34. package/dist/packem_shared/{COMMANDS-CHw4zOZ9.mjs → COMMANDS-B0ftFD_3.mjs} +47 -21
  35. package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +31 -0
  36. package/dist/packem_shared/{command-BDXcJCCJ.mjs → command-D3lB_4Az.mjs} +6 -1
  37. package/dist/packem_shared/{commands-SUPdjsu5.mjs → commands-B-gR09Z_.mjs} +64 -6
  38. package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-B40gPzQo.mjs} +9 -4
  39. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +61 -0
  40. package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
  41. package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
  42. package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-wUvAN6AL.mjs} +1 -1
  43. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
  44. package/dist/packem_shared/runAddCommand-bnY6-HKb.mjs +4 -0
  45. package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
  46. package/dist/packem_shared/storage-BIsph-Vk.mjs +84 -0
  47. package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
  48. package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +47 -0
  49. package/package.json +12 -10
  50. package/skills/lunora-setup-storage/SKILL.md +7 -3
  51. package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
  52. package/dist/packem_shared/runAddCommand-Txwh1Xw1.mjs +0 -4
  53. /package/dist/packem_shared/{defaultSpawner-DxI3mebw.mjs → createRecordingSpawner-DxI3mebw.mjs} +0 -0
@@ -1,13 +1,13 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync, mkdtempSync, rmSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
- import { discoverMigrations, discoverSchema } from '@lunora/codegen';
3
+ import { discoverSchema, discoverMigrations } from '@lunora/codegen';
4
4
  import { join } from '@visulima/path';
5
5
  import { Project } from 'ts-morph';
6
6
  import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
7
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
8
- import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-RR2ZE8Ya.mjs';
7
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
8
+ import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-BeDvvNiF.mjs';
9
9
  import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
10
- import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs';
10
+ import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
11
11
  import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
12
12
 
13
13
  const SNAPSHOT_FILENAME = ".snapshot.json";
@@ -1,7 +1,7 @@
1
1
  import { existsSync, rmSync } from 'node:fs';
2
- import { promptYesNo } from '@lunora/config';
3
2
  import { join } from '@visulima/path';
4
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
3
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
4
+ import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
5
5
 
6
6
  const runResetCommand = async (options) => {
7
7
  const cwd = options.cwd ?? process.cwd();
@@ -15,8 +15,8 @@ const runResetCommand = async (options) => {
15
15
  options.logger.error("reset: stdin is not a TTY — re-run with --yes to confirm deleting .wrangler/state");
16
16
  return { code: 1, removed: [] };
17
17
  }
18
- const confirmer = options.confirm ?? promptYesNo;
19
- const confirmed = await confirmer("This will delete .wrangler/state. Continue? [y/N] ");
18
+ const confirmer = options.confirm ?? tuiConfirm;
19
+ const confirmed = await confirmer("This will delete .wrangler/state. Continue?");
20
20
  if (!confirmed) {
21
21
  options.logger.info("reset: aborted");
22
22
  return { code: 1, removed: [] };
@@ -1,4 +1,4 @@
1
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
1
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
2
2
  import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
3
3
 
4
4
  const parseArgsJson = (raw) => {
@@ -3,7 +3,7 @@ import { createCerebro } from '@visulima/cerebro';
3
3
  import completionCommand from '@visulima/cerebro/command/completion';
4
4
  import versionCommand from '@visulima/cerebro/command/version';
5
5
  import { A as API_SPEC_HELP } from './api-spec-CtA6ilu4.mjs';
6
- import { createLogger } from './createLogger-CHPNjFw2.mjs';
6
+ import { createLogger } from './createLogger-B40gPzQo.mjs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
 
@@ -14,7 +14,8 @@ const addCommand = {
14
14
  ["lunora add auth", "Add authentication (asks which provider)"],
15
15
  ["lunora add auth --provider clerk", "Add Clerk auth without prompting"],
16
16
  ["lunora add email", "Add transactional email (Cloudflare Email Workers + dev mail catcher)"],
17
- ["lunora add storage", "Add the R2 storage registry item"],
17
+ ["lunora add storage", "Add the R2 storage registry item (asks for the bucket name)"],
18
+ ["lunora add storage --bucket my-app-uploads", "Add storage with a bucket name, no prompt"],
18
19
  ["lunora add crons", "Add the scheduled-jobs registry item"],
19
20
  ["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
20
21
  ],
@@ -25,7 +26,10 @@ const addCommand = {
25
26
  name: "add",
26
27
  options: [
27
28
  { description: "auth: provider to use without prompting (auth | clerk | auth0)", name: "provider", type: String },
28
- { description: "Skip the provider prompt and use the default (email & password)", name: "yes", type: Boolean },
29
+ { description: "auth: D1 database name to use without prompting (lowercase alphanumeric + hyphens)", name: "db", type: String },
30
+ { description: "storage: R2 bucket name to use without prompting (lowercase alphanumeric + hyphens)", name: "bucket", type: String },
31
+ { description: "email: verified destination address to use without prompting", name: "mail-to", type: String },
32
+ { description: "Skip prompts (auth provider, DB name, bucket name, mail destination) and use the defaults", name: "yes", type: Boolean },
29
33
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
30
34
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
31
35
  {
@@ -33,7 +37,8 @@ const addCommand = {
33
37
  name: "ref",
34
38
  type: String
35
39
  },
36
- { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean }
40
+ { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
41
+ { description: "Output format: pretty (default) or json", name: "format", type: String }
37
42
  ]
38
43
  };
39
44
 
@@ -256,21 +261,24 @@ const doctorCommand = {
256
261
  };
257
262
 
258
263
  const envCommand = {
259
- argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | push | diff | doctor", name: "subcommand", type: String },
260
- description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | push | diff | doctor)",
264
+ argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | generate [KEY] | push | diff | doctor", name: "subcommand", type: String },
265
+ description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | generate | push | diff | doctor)",
261
266
  examples: [
262
267
  ["lunora env list", "List .dev.vars keys"],
263
268
  ["lunora env set API_KEY secret", "Set a local variable"],
269
+ ["lunora env generate", "Generate strong values for the project's secrets (print KEY=value)"],
270
+ ["lunora env generate AUTH_SECRET --set", "Generate one secret and write it to .dev.vars"],
264
271
  ["lunora env push --yes", "Upload secrets to Cloudflare"],
265
272
  ["lunora env diff", "Compare local .dev.vars keys against Cloudflare"]
266
273
  ],
267
274
  group: "Data",
268
- loader: () => import('../packem_chunks/handler21.mjs').then((m) => {
275
+ loader: () => import('../packem_chunks/handler9.mjs').then((m) => {
269
276
  return { default: m.execute };
270
277
  }),
271
278
  name: "env",
272
279
  options: [
273
280
  { description: "Target production for `push` (passes --env production to wrangler)", name: "prod", type: Boolean },
281
+ { description: "For `generate` — write the generated secrets into .dev.vars instead of printing them", name: "set", type: Boolean },
274
282
  {
275
283
  description: "Push secrets to a temporary-account deployment when unauthenticated (wrangler secret put --temporary). Errors if you're already authenticated.",
276
284
  name: "temporary",
@@ -288,7 +296,7 @@ const exportCommand = {
288
296
  ["lunora export --tables messages,users", "Export only specific tables"]
289
297
  ],
290
298
  group: "Data",
291
- loader: () => import('../packem_chunks/handler9.mjs').then((m) => {
299
+ loader: () => import('../packem_chunks/handler10.mjs').then((m) => {
292
300
  return { default: m.execute };
293
301
  }),
294
302
  name: "export",
@@ -310,7 +318,7 @@ const importCommand = {
310
318
  description: "Bulk-insert rows from an NDJSON file via the worker's admin endpoint",
311
319
  examples: [["lunora import backup.ndjson", "Bulk-insert rows from an NDJSON file"]],
312
320
  group: "Data",
313
- loader: () => import('../packem_chunks/handler10.mjs').then((m) => {
321
+ loader: () => import('../packem_chunks/handler11.mjs').then((m) => {
314
322
  return { default: m.execute };
315
323
  }),
316
324
  name: "import",
@@ -334,7 +342,7 @@ const infoCommand = {
334
342
  ["lunora info --json", "Emit a JSON snapshot"]
335
343
  ],
336
344
  group: "Project",
337
- loader: () => import('../packem_chunks/handler11.mjs').then((m) => {
345
+ loader: () => import('../packem_chunks/handler12.mjs').then((m) => {
338
346
  return { default: m.execute };
339
347
  }),
340
348
  name: "info",
@@ -361,11 +369,19 @@ const initCommand = {
361
369
  options: [
362
370
  {
363
371
  alias: "t",
364
- defaultValue: "vite",
365
- description: "Template to scaffold (vite | standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid)",
372
+ // No default: when omitted, an interactive run shows the framework
373
+ // picker (default React overlay) and a non-interactive run errors.
374
+ // For React/Vue/Solid/Svelte SPAs use `--vite <framework>` (overlay);
375
+ // `-t` selects a bespoke template.
376
+ description: "Bespoke template (standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid). For an SPA use --vite react|vue|solid|svelte.",
366
377
  name: "template",
367
378
  type: String
368
379
  },
380
+ {
381
+ description: "Scaffold via the create-vite overlay for a framework (react | vue | solid | svelte | vanilla) — official create-vite base + Lunora layer",
382
+ name: "vite",
383
+ type: String
384
+ },
369
385
  {
370
386
  description: "Local templates root to copy from (offline-friendly; expects <type>/ subdirs)",
371
387
  name: "from",
@@ -407,6 +423,16 @@ const initCommand = {
407
423
  description: "Also scaffold a CI deploy pipeline: github (.github/workflows/deploy.yml) or gitlab (.gitlab-ci.yml)",
408
424
  name: "ci",
409
425
  type: String
426
+ },
427
+ {
428
+ description: "Add features non-interactively after scaffolding (comma-separated): auth | email | storage | ratelimit | crons | presence | backup",
429
+ name: "add",
430
+ type: String
431
+ },
432
+ {
433
+ description: "Walk through every step (prompts + output) without writing files, installing, or running git",
434
+ name: "dry-run",
435
+ type: Boolean
410
436
  }
411
437
  ]
412
438
  };
@@ -420,7 +446,7 @@ const insightsCommand = {
420
446
  ["lunora insights --prod --url https://app.example.com --token $LUNORA_ADMIN_TOKEN", "Report against production"]
421
447
  ],
422
448
  group: "Develop",
423
- loader: () => import('../packem_chunks/handler12.mjs').then((m) => {
449
+ loader: () => import('../packem_chunks/handler13.mjs').then((m) => {
424
450
  return { default: m.execute };
425
451
  }),
426
452
  name: "insights",
@@ -442,7 +468,7 @@ const linkCommand = {
442
468
  ["lunora link --remove", "Remove the link"]
443
469
  ],
444
470
  group: "Deploy",
445
- loader: () => import('../packem_chunks/handler13.mjs').then((m) => {
471
+ loader: () => import('../packem_chunks/handler14.mjs').then((m) => {
446
472
  return { default: m.execute };
447
473
  }),
448
474
  name: "link",
@@ -458,7 +484,7 @@ const logsCommand = {
458
484
  argument: { description: "Worker name (defaults to the name in wrangler config)", name: "worker", type: String },
459
485
  description: "Stream live logs from a deployed Lunora Worker",
460
486
  group: "Deploy",
461
- loader: () => import('../packem_chunks/handler14.mjs').then((m) => {
487
+ loader: () => import('../packem_chunks/handler15.mjs').then((m) => {
462
488
  return { default: m.execute };
463
489
  }),
464
490
  name: "logs",
@@ -518,7 +544,7 @@ const prepareCommand = {
518
544
  description: "Run codegen + binding reconcile + wrangler validation (no Vite) — for CI",
519
545
  examples: [["lunora prepare", "Codegen + binding reconcile + validate (CI, before deploy)"]],
520
546
  group: "Deploy",
521
- loader: () => import('../packem_chunks/handler15.mjs').then((m) => {
547
+ loader: () => import('../packem_chunks/handler16.mjs').then((m) => {
522
548
  return { default: m.execute };
523
549
  }),
524
550
  name: "prepare",
@@ -542,7 +568,7 @@ const registryCommand = {
542
568
  ["lunora registry build --check", "Verify the committed catalog is current"]
543
569
  ],
544
570
  group: "Project",
545
- loader: () => import('../packem_chunks/handler16.mjs').then((m) => {
571
+ loader: () => import('../packem_chunks/handler17.mjs').then((m) => {
546
572
  return { default: m.execute };
547
573
  }),
548
574
  name: "registry",
@@ -592,7 +618,7 @@ const rulesCommand = {
592
618
  ["lunora rules check --strict", "Exit non-zero when rules are missing (CI gate)"]
593
619
  ],
594
620
  group: "Project",
595
- loader: () => import('../packem_chunks/handler17.mjs').then((m) => {
621
+ loader: () => import('../packem_chunks/handler18.mjs').then((m) => {
596
622
  return { default: m.execute };
597
623
  }),
598
624
  name: "rules",
@@ -630,7 +656,7 @@ const seedCommand = {
630
656
  ["lunora seed --seed 7 --dry-run", "Print the NDJSON for seed 7 without inserting"]
631
657
  ],
632
658
  group: "Data",
633
- loader: () => import('../packem_chunks/handler18.mjs').then((m) => {
659
+ loader: () => import('../packem_chunks/handler19.mjs').then((m) => {
634
660
  return { default: m.execute };
635
661
  }),
636
662
  name: "seed",
@@ -659,7 +685,7 @@ const verifyCommand = {
659
685
  ["lunora verify --no-typecheck", "Skip the TypeScript type-check"]
660
686
  ],
661
687
  group: "Deploy",
662
- loader: () => import('../packem_chunks/handler19.mjs').then((m) => {
688
+ loader: () => import('../packem_chunks/handler20.mjs').then((m) => {
663
689
  return { default: m.execute };
664
690
  }),
665
691
  name: "verify",
@@ -678,7 +704,7 @@ const viewCommand = {
678
704
  ["lunora view --remote", "Open the deployed studio"]
679
705
  ],
680
706
  group: "Project",
681
- loader: () => import('../packem_chunks/handler20.mjs').then((m) => {
707
+ loader: () => import('../packem_chunks/handler21.mjs').then((m) => {
682
708
  return { default: m.execute };
683
709
  }),
684
710
  name: "view",
@@ -0,0 +1,31 @@
1
+ import { findLunoraSolution } from '@lunora/codegen';
2
+ import { VisulimaError, renderError } from '@visulima/error';
3
+
4
+ const flattenSolutionBody = (solution) => solution.body.split("\n").filter((line) => !line.startsWith("```")).join("\n").replaceAll(/\*\*(.+?)\*\*/gu, "$1").replaceAll(/`([^`]+)`/gu, "$1");
5
+ const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
6
+ const renderCodegenFailure = (error, reason) => {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ const solution = findLunoraSolution(message);
9
+ const rendered = new VisulimaError({
10
+ hint: solution ? [solution.header, "", flattenSolutionBody(solution)] : void 0,
11
+ message: reason === void 0 ? `codegen failed: ${message}` : `codegen failed (${reason}): ${message}`,
12
+ name: "CodegenError"
13
+ });
14
+ rendered.stack = "";
15
+ return renderError(rendered, NO_STACK);
16
+ };
17
+ const renderCodegenHint = (message) => {
18
+ const solution = findLunoraSolution(message);
19
+ if (solution === void 0) {
20
+ return void 0;
21
+ }
22
+ const rendered = new VisulimaError({
23
+ hint: [flattenSolutionBody(solution)],
24
+ message: solution.header,
25
+ name: "Hint"
26
+ });
27
+ rendered.stack = "";
28
+ return renderError(rendered, NO_STACK);
29
+ };
30
+
31
+ export { renderCodegenHint as a, renderCodegenFailure as r };
@@ -1,4 +1,5 @@
1
- import { createLogger } from './createLogger-CHPNjFw2.mjs';
1
+ import { createLogger } from './createLogger-B40gPzQo.mjs';
2
+ import { P as PromptCancelledError, a as PROMPT_CANCEL_EXIT_CODE } from './prompt-cancelled-APzX1Im-.mjs';
2
3
 
3
4
  const defineHandler = (body) => async (toolbox) => {
4
5
  const logger = createLogger();
@@ -6,6 +7,10 @@ const defineHandler = (body) => async (toolbox) => {
6
7
  const { code } = await body({ argument: toolbox.argument, cwd: toolbox.process.cwd, logger, options: toolbox.options });
7
8
  toolbox.process.exit(code);
8
9
  } catch (error) {
10
+ if (error instanceof PromptCancelledError) {
11
+ toolbox.process.exit(PROMPT_CANCEL_EXIT_CODE);
12
+ return;
13
+ }
9
14
  logger.error(error instanceof Error ? error.message : String(error));
10
15
  toolbox.process.exit(1);
11
16
  }
@@ -1,10 +1,11 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
2
  import { dirname, join } from '@visulima/path';
3
- import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
3
+ import { DEV_VARS_FILE, parseDevVariableEntries } from '@lunora/config';
4
4
  import { modify, applyEdits, parse } from 'jsonc-parser';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { b as tuiConfirm } from './tui-prompts-BjEN8XgP.mjs';
6
7
  import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
7
- import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
8
+ import { insertSchemaExtension } from './insertSchemaExtension-DAqbfr9Z.mjs';
8
9
  import { createHash } from 'node:crypto';
9
10
  import { tmpdir } from 'node:os';
10
11
  import { downloadTemplate } from 'giget';
@@ -64,6 +65,39 @@ const resolveDistTag = () => {
64
65
  const ref = resolveVersionRef(resolveCliVersion());
65
66
  return ref === STABLE_BRANCH ? STABLE_DIST_TAG : ref;
66
67
  };
68
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
69
+ const registryBase = () => {
70
+ const configured = process.env["npm_config_registry"];
71
+ const base = configured !== void 0 && configured.length > 0 ? configured : DEFAULT_REGISTRY;
72
+ return base.endsWith("/") ? base.slice(0, -1) : base;
73
+ };
74
+ const resolveTagVersion = async (packageName, tag) => {
75
+ try {
76
+ const response = await fetch(`${registryBase()}/${packageName.replace("/", "%2F")}`, {
77
+ headers: { accept: "application/vnd.npm.install-v1+json" },
78
+ signal: AbortSignal.timeout(1e4)
79
+ });
80
+ if (!response.ok) {
81
+ return void 0;
82
+ }
83
+ const packument = await response.json();
84
+ return packument["dist-tags"]?.[tag];
85
+ } catch {
86
+ return void 0;
87
+ }
88
+ };
89
+ const resolveTagVersions = async (names, tag) => {
90
+ const resolved = /* @__PURE__ */ new Map();
91
+ await Promise.all(
92
+ [...new Set(names)].map(async (name) => {
93
+ const version = await resolveTagVersion(name, tag);
94
+ if (version !== void 0) {
95
+ resolved.set(name, version);
96
+ }
97
+ })
98
+ );
99
+ return resolved;
100
+ };
67
101
 
68
102
  const resolveDepRange = (range) => {
69
103
  if (!range.startsWith("workspace:")) {
@@ -272,8 +306,8 @@ const confirmDepMutation = async (items, options) => {
272
306
  options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
273
307
  return false;
274
308
  }
275
- const confirmer = options.confirm ?? promptYesNo;
276
- const confirmed = await confirmer(`The requested items ${reasonText}. Continue? [y/N] `);
309
+ const confirmer = options.confirm ?? tuiConfirm;
310
+ const confirmed = await confirmer(`The requested items ${reasonText}. Continue?`);
277
311
  if (!confirmed) {
278
312
  options.logger.info("add: aborted");
279
313
  }
@@ -582,6 +616,26 @@ const resolvePlan = async (names, options) => {
582
616
  const emptyResult = () => {
583
617
  return { bindings: [], code: 0, deps: [], skipped: [], written: [] };
584
618
  };
619
+ const setBindingField = (manifest, section, match, field, fieldValue) => {
620
+ if (!manifest.bindings) {
621
+ return manifest;
622
+ }
623
+ return {
624
+ ...manifest,
625
+ bindings: manifest.bindings.map((binding) => {
626
+ if (binding.path[0] !== section || !Array.isArray(binding.value)) {
627
+ return binding;
628
+ }
629
+ const entries = binding.value;
630
+ return {
631
+ ...binding,
632
+ value: entries.map(
633
+ (entry) => typeof entry === "object" && entry !== null && entry[match.key] === match.value ? { ...entry, [field]: fieldValue } : entry
634
+ )
635
+ };
636
+ })
637
+ };
638
+ };
585
639
 
586
640
  const printPlan = (logger, manifest) => {
587
641
  const label = manifest.title ?? manifest.description;
@@ -687,8 +741,12 @@ const runAddCommand = async (options) => {
687
741
  }
688
742
  let cleanups = [];
689
743
  try {
690
- const { cleanups: planCleanups, items } = await resolvePlan(options.names, options);
744
+ const { cleanups: planCleanups, items: resolvedItems } = await resolvePlan(options.names, options);
691
745
  cleanups = planCleanups;
746
+ const { transformManifest } = options;
747
+ const items = transformManifest ? resolvedItems.map((item) => {
748
+ return { ...item, manifest: transformManifest(item.manifest) };
749
+ }) : resolvedItems;
692
750
  for (const { manifest } of items) {
693
751
  printPlan(options.logger, manifest);
694
752
  }
@@ -784,4 +842,4 @@ const runBuildIndexCommand = async (options) => {
784
842
  return empty;
785
843
  };
786
844
 
787
- export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveSourceRef as c, resolveDistTag as d, runListCommand as e, runAddCommand as r };
845
+ export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveTagVersions as c, resolveSourceRef as d, resolveDistTag as e, runListCommand as f, runAddCommand as r, setBindingField as s };
@@ -1,5 +1,5 @@
1
+ import { STEP_BADGE_NAMES, LunoraReporter } from '@lunora/config';
1
2
  import { JsonReporter } from '@visulima/pail/reporter/json';
2
- import { PrettyReporter } from '@visulima/pail/reporter/pretty';
3
3
  import { createPail } from '@visulima/pail/server';
4
4
 
5
5
  const wantJson = () => {
@@ -7,16 +7,18 @@ const wantJson = () => {
7
7
  return flag === "1" || flag === "true";
8
8
  };
9
9
  const buildReporter = () => {
10
- const Reporter = wantJson() ? JsonReporter : PrettyReporter;
10
+ const Reporter = wantJson() ? JsonReporter : LunoraReporter;
11
11
  return new Reporter();
12
12
  };
13
+ const STEP_LOG_TYPES = Object.fromEntries(STEP_BADGE_NAMES.map((name) => [name, { label: name, logLevel: "informational" }]));
13
14
  let sharedPail;
14
15
  const getPail = () => {
15
16
  sharedPail ??= createPail({
16
17
  reporters: [buildReporter()],
17
18
  scope: ["lunora"],
18
19
  stderr: process.stderr,
19
- stdout: process.stdout
20
+ stdout: process.stdout,
21
+ types: STEP_LOG_TYPES
20
22
  });
21
23
  return sharedPail;
22
24
  };
@@ -69,5 +71,8 @@ const pail = /* @__PURE__ */ new Proxy({}, {
69
71
  return typeof value === "function" ? value.bind(instance) : value;
70
72
  }
71
73
  });
74
+ const logStep = (type, message) => {
75
+ getPail()[type](message);
76
+ };
72
77
 
73
- export { createLogger, createStderrLogger, getPail, pail };
78
+ export { createLogger, createStderrLogger, getPail, logStep, pail };
@@ -0,0 +1,61 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join } from '@visulima/path';
4
+
5
+ const FALLBACK = "pnpm";
6
+ const KNOWN_MANAGERS = ["pnpm", "yarn", "npm", "bun"];
7
+ const INSTALL_PREFERENCE = ["pnpm", "bun", "yarn", "npm"];
8
+ const isManagerInstalled = (manager) => {
9
+ try {
10
+ return spawnSync(manager, ["--version"], { stdio: "ignore", timeout: 5e3 }).status === 0;
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+ const detectInstalledManagers = (probe = isManagerInstalled) => INSTALL_PREFERENCE.filter((manager) => probe(manager));
16
+ const installArgsFor = (manager) => {
17
+ return { args: ["install"], command: manager };
18
+ };
19
+ const parseDeclaredManager = (declared) => {
20
+ if (typeof declared !== "string") {
21
+ return void 0;
22
+ }
23
+ return KNOWN_MANAGERS.find((manager) => declared.startsWith(`${manager}@`));
24
+ };
25
+ const readDeclaredManager = (directory) => {
26
+ const candidate = join(directory, "package.json");
27
+ if (!existsSync(candidate)) {
28
+ return void 0;
29
+ }
30
+ try {
31
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
32
+ return parseDeclaredManager(parsed.packageManager);
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ };
37
+ const detectPackageManager = (startDirectory) => {
38
+ let directory = startDirectory;
39
+ while (directory && directory !== dirname(directory)) {
40
+ const declared = readDeclaredManager(directory);
41
+ if (declared !== void 0) {
42
+ return declared;
43
+ }
44
+ directory = dirname(directory);
45
+ }
46
+ return FALLBACK;
47
+ };
48
+ const execArgsFor = (manager, command, args) => {
49
+ if (manager === "yarn") {
50
+ return { args: [command, ...args], command: "yarn" };
51
+ }
52
+ if (manager === "bun") {
53
+ return { args: ["x", command, ...args], command: "bun" };
54
+ }
55
+ if (manager === "npm") {
56
+ return { args: ["--", command, ...args], command: "npx" };
57
+ }
58
+ return { args: ["exec", command, ...args], command: "pnpm" };
59
+ };
60
+
61
+ export { detectInstalledManagers as a, detectPackageManager as d, execArgsFor as e, installArgsFor as i };
@@ -1,4 +1,4 @@
1
- import { quoteIdentifier, columnRef, physicalIndexName, frameworkColumnDdl, sqlAffinityForKind } from '@lunora/d1/dialect';
1
+ import { quoteIdentifier, sqlAffinityForKind, frameworkColumnDdl, columnRef, physicalIndexName } from '@lunora/d1/dialect';
2
2
 
3
3
  const validatorKindToSqlType = (kind) => sqlAffinityForKind(kind);
4
4
  const renderColumnDefinition = (name, column) => {
@@ -1,4 +1,4 @@
1
- import { Project, SyntaxKind } from 'ts-morph';
1
+ import { Project, SyntaxKind, Node } from 'ts-morph';
2
2
 
3
3
  const VALID_JS_IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
4
4
  const startMarker = (key) => `// lunora:add:${key}:start`;
@@ -12,6 +12,19 @@ const findDefineSchemaCall = (callExpressions) => {
12
12
  }
13
13
  return void 0;
14
14
  };
15
+ const outermostChainExpression = (defineSchemaCall) => {
16
+ let node = defineSchemaCall;
17
+ for (let parent = node.getParent(); parent !== void 0; parent = node.getParent()) {
18
+ if (Node.isPropertyAccessExpression(parent) && parent.getExpression() === node) {
19
+ node = parent;
20
+ } else if (Node.isCallExpression(parent) && parent.getExpression() === node) {
21
+ node = parent;
22
+ } else {
23
+ break;
24
+ }
25
+ }
26
+ return node;
27
+ };
15
28
  const insertSchemaExtension = (source, key) => {
16
29
  if (!VALID_JS_IDENTIFIER.test(key)) {
17
30
  return { ok: false, reason: "invalid-identifier" };
@@ -33,15 +46,7 @@ const insertSchemaExtension = (source, key) => {
33
46
  if (tablesArgument?.getKind() !== SyntaxKind.ObjectLiteralExpression) {
34
47
  return { ok: false, reason: "non-object-argument" };
35
48
  }
36
- const variableDeclaration = defineSchemaCall.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
37
- if (!variableDeclaration) {
38
- return { ok: false, reason: "no-define-schema" };
39
- }
40
- const initializer = variableDeclaration.getInitializer();
41
- if (!initializer) {
42
- return { ok: false, reason: "no-define-schema" };
43
- }
44
- const insertAt = initializer.getEnd();
49
+ const insertAt = outermostChainExpression(defineSchemaCall).getEnd();
45
50
  const chainText = `
46
51
  ${startMarker(key)}
47
52
  .extend(${key}.extension)
@@ -1,4 +1,4 @@
1
- import { createStderrLogger } from './createLogger-CHPNjFw2.mjs';
1
+ import { createStderrLogger } from './createLogger-B40gPzQo.mjs';
2
2
 
3
3
  const OUTPUT_FORMATS = /* @__PURE__ */ new Set(["json", "pretty"]);
4
4
  const validateOutputFormat = (command, format) => {
@@ -0,0 +1,9 @@
1
+ class PromptCancelledError extends Error {
2
+ constructor() {
3
+ super("cancelled");
4
+ this.name = "PromptCancelledError";
5
+ }
6
+ }
7
+ const PROMPT_CANCEL_EXIT_CODE = 130;
8
+
9
+ export { PromptCancelledError as P, PROMPT_CANCEL_EXIT_CODE as a };
@@ -0,0 +1,4 @@
1
+ import 'node:fs';
2
+ import '@visulima/path';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, f as runListCommand, b as runRegistryViewCommand } from './commands-B-gR09Z_.mjs';
4
+ import './buildRegistryIndex-BcYe607_.mjs';
@@ -1,4 +1,4 @@
1
- import { validatorKindToSqlType } from './diffSnapshots-RR2ZE8Ya.mjs';
1
+ import { validatorKindToSqlType } from './diffSnapshots-BeDvvNiF.mjs';
2
2
 
3
3
  const validatorToColumn = (validator) => {
4
4
  if (validator.kind === "optional" && validator.inner) {