@lunora/cli 1.0.0-alpha.3 → 1.0.0-alpha.30

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 (52) 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 +73 -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 +71 -76
  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 +46 -49
  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 +1980 -77
  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/{command-BDXcJCCJ.mjs → command-D3lB_4Az.mjs} +6 -1
  36. package/dist/packem_shared/{commands-DIQ3nf0C.mjs → commands-B-gR09Z_.mjs} +124 -22
  37. package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-B40gPzQo.mjs} +9 -4
  38. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +61 -0
  39. package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
  40. package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
  41. package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-wUvAN6AL.mjs} +1 -1
  42. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
  43. package/dist/packem_shared/runAddCommand-bnY6-HKb.mjs +4 -0
  44. package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
  45. package/dist/packem_shared/storage-BIsph-Vk.mjs +84 -0
  46. package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
  47. package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +47 -0
  48. package/package.json +12 -11
  49. package/skills/lunora-setup-storage/SKILL.md +7 -3
  50. package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
  51. package/dist/packem_shared/runAddCommand-3I3JFZUG.mjs +0 -4
  52. /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",
@@ -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,13 +1,14 @@
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
+ import { fileURLToPath } from 'node:url';
6
+ import { b as tuiConfirm } from './tui-prompts-BjEN8XgP.mjs';
5
7
  import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
6
- import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
8
+ import { insertSchemaExtension } from './insertSchemaExtension-DAqbfr9Z.mjs';
7
9
  import { createHash } from 'node:crypto';
8
10
  import { tmpdir } from 'node:os';
9
11
  import { downloadTemplate } from 'giget';
10
- import { fileURLToPath } from 'node:url';
11
12
  import parseManifest from './parseManifest--vZf2FY1.mjs';
12
13
 
13
14
  const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
@@ -59,8 +60,71 @@ const resolveSourceRef = (ref) => {
59
60
  }
60
61
  return resolveVersionRef(resolveCliVersion());
61
62
  };
63
+ const STABLE_DIST_TAG = "latest";
64
+ const resolveDistTag = () => {
65
+ const ref = resolveVersionRef(resolveCliVersion());
66
+ return ref === STABLE_BRANCH ? STABLE_DIST_TAG : ref;
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
+ };
62
101
 
63
- const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
102
+ const resolveDepRange = (range) => {
103
+ if (!range.startsWith("workspace:")) {
104
+ return range;
105
+ }
106
+ const rest = range.slice("workspace:".length);
107
+ if (rest === "" || rest === "*" || rest === "^" || rest === "~") {
108
+ return resolveDistTag();
109
+ }
110
+ return rest;
111
+ };
112
+ const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
113
+ const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|runtime|server|values)(\/[^'"]*)?\1/gu;
114
+ const projectUsesUmbrella = (projectRoot) => {
115
+ const packageJsonPath = join(projectRoot, "package.json");
116
+ if (!existsSync(packageJsonPath)) {
117
+ return false;
118
+ }
119
+ try {
120
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
121
+ return parsed.dependencies?.lunorash !== void 0 || parsed.devDependencies?.lunorash !== void 0;
122
+ } catch {
123
+ return false;
124
+ }
125
+ };
126
+ const rewriteUmbrellaImports = (source) => source.replaceAll(UMBRELLA_IMPORT_RE, (_match, quote, base, subpath) => `${quote}lunorash/${base}${subpath ?? ""}${quote}`);
127
+ const applyDeps = (deps, projectRoot, logger, section = "dependencies", useUmbrella = false) => {
64
128
  const entries = Object.entries(deps);
65
129
  if (entries.length === 0) {
66
130
  return [];
@@ -74,11 +138,15 @@ const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
74
138
  const parsed = JSON.parse(text);
75
139
  const added = [];
76
140
  for (const [name, range] of entries) {
141
+ if (useUmbrella && UMBRELLA_REEXPORTED_DEPS.has(name)) {
142
+ logger.info(`dep provided by the lunorash umbrella, skipping: ${name}`);
143
+ continue;
144
+ }
77
145
  if (parsed.dependencies?.[name] !== void 0 || parsed.devDependencies?.[name] !== void 0) {
78
146
  logger.info(`dep already present: ${name}`);
79
147
  continue;
80
148
  }
81
- const edits = modify(text, [section, name], range, {
149
+ const edits = modify(text, [section, name], resolveDepRange(range), {
82
150
  formattingOptions: { insertSpaces: true, tabSize: 4 }
83
151
  });
84
152
  text = applyEdits(text, edits);
@@ -199,14 +267,14 @@ const applyBindings = (bindings, projectRoot, logger) => {
199
267
  }
200
268
  return applied;
201
269
  };
202
- const applyItemResources = (manifest, cwd, logger) => {
270
+ const applyItemResources = (manifest, cwd, logger, useUmbrella = false) => {
203
271
  const deps = [];
204
272
  const bindings = [];
205
273
  if (manifest.deps) {
206
- deps.push(...applyDeps(manifest.deps, cwd, logger));
274
+ deps.push(...applyDeps(manifest.deps, cwd, logger, "dependencies", useUmbrella));
207
275
  }
208
276
  if (manifest.devDependencies) {
209
- deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies"));
277
+ deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies", useUmbrella));
210
278
  }
211
279
  if (manifest.bindings) {
212
280
  bindings.push(...applyBindings(manifest.bindings, cwd, logger));
@@ -238,8 +306,8 @@ const confirmDepMutation = async (items, options) => {
238
306
  options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
239
307
  return false;
240
308
  }
241
- const confirmer = options.confirm ?? promptYesNo;
242
- 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?`);
243
311
  if (!confirmed) {
244
312
  options.logger.info("add: aborted");
245
313
  }
@@ -318,7 +386,12 @@ const renderDiff = (oldText, newText) => {
318
386
  return out;
319
387
  };
320
388
 
321
- const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff) => {
389
+ const CODE_FILE_RE = /\.[cm]?[jt]sx?$/u;
390
+ const readItemFile = (itemDirectory, file, useUmbrella) => {
391
+ const source = readFileSync(join(itemDirectory, file.from), "utf8");
392
+ return useUmbrella && CODE_FILE_RE.test(file.to) ? rewriteUmbrellaImports(source) : source;
393
+ };
394
+ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff, useUmbrella) => {
322
395
  const schemaPath = join(projectRoot, "lunora", "schema.ts");
323
396
  if (diff) {
324
397
  logger.info(`~ would merge .extend(${itemKey}.extension) into lunora/schema.ts (and create ${file.to} if absent)`);
@@ -327,9 +400,13 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
327
400
  const destinationPath = join(projectRoot, file.to);
328
401
  if (!existsSync(destinationPath)) {
329
402
  mkdirSync(dirname(destinationPath), { recursive: true });
330
- writeFileSync(destinationPath, readFileSync(join(itemDirectory, file.from), "utf8"), "utf8");
403
+ writeFileSync(destinationPath, readItemFile(itemDirectory, file, useUmbrella), "utf8");
331
404
  }
332
- const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : 'import { defineSchema } from "@lunora/server";\n\nexport const schema = defineSchema({});\n';
405
+ const baseModule = useUmbrella ? "lunorash/server" : "@lunora/server";
406
+ const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : `import { defineSchema } from "${baseModule}";
407
+
408
+ export const schema = defineSchema({});
409
+ `;
333
410
  const result = insertSchemaExtension(existingSchema, itemKey);
334
411
  if (result.ok) {
335
412
  mkdirSync(dirname(schemaPath), { recursive: true });
@@ -359,9 +436,9 @@ const previewWholeFile = (file, current, incoming, exists, logger) => {
359
436
  logger.info(` ${line}`);
360
437
  }
361
438
  };
362
- const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions) => {
439
+ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella) => {
363
440
  const destinationPath = join(projectRoot, file.to);
364
- const incoming = readFileSync(join(itemDirectory, file.from), "utf8");
441
+ const incoming = readItemFile(itemDirectory, file, useUmbrella);
365
442
  const exists = existsSync(destinationPath);
366
443
  const current = exists ? readFileSync(destinationPath, "utf8") : "";
367
444
  const write = (message) => {
@@ -399,11 +476,11 @@ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, l
399
476
  logger.warn(`conflict: ${file.to} has local edits and an upstream update — wrote ${file.to}.new (use --overwrite to take theirs)`);
400
477
  return { kind: "skipped", path: destinationPath };
401
478
  };
402
- const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}) => {
479
+ const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}, useUmbrella = false) => {
403
480
  if (file.merge === "schema-extension") {
404
- return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true);
481
+ return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true, useUmbrella);
405
482
  }
406
- return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions);
483
+ return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
407
484
  };
408
485
  const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
409
486
  const written = [];
@@ -411,15 +488,16 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
411
488
  const depsAdded = [];
412
489
  const bindingsApplied = [];
413
490
  const lock = readLock(cwd);
491
+ const useUmbrella = projectUsesUmbrella(cwd);
414
492
  for (const { directory, manifest } of items) {
415
493
  for (const file of manifest.files) {
416
- const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions);
494
+ const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
417
495
  (outcome.kind === "written" ? written : skipped).push(outcome.path);
418
496
  }
419
497
  if (reconcileOptions.diff) {
420
498
  continue;
421
499
  }
422
- const applied = applyItemResources(manifest, cwd, logger);
500
+ const applied = applyItemResources(manifest, cwd, logger, useUmbrella);
423
501
  depsAdded.push(...applied.deps);
424
502
  bindingsApplied.push(...applied.bindings);
425
503
  }
@@ -538,6 +616,26 @@ const resolvePlan = async (names, options) => {
538
616
  const emptyResult = () => {
539
617
  return { bindings: [], code: 0, deps: [], skipped: [], written: [] };
540
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
+ };
541
639
 
542
640
  const printPlan = (logger, manifest) => {
543
641
  const label = manifest.title ?? manifest.description;
@@ -643,8 +741,12 @@ const runAddCommand = async (options) => {
643
741
  }
644
742
  let cleanups = [];
645
743
  try {
646
- const { cleanups: planCleanups, items } = await resolvePlan(options.names, options);
744
+ const { cleanups: planCleanups, items: resolvedItems } = await resolvePlan(options.names, options);
647
745
  cleanups = planCleanups;
746
+ const { transformManifest } = options;
747
+ const items = transformManifest ? resolvedItems.map((item) => {
748
+ return { ...item, manifest: transformManifest(item.manifest) };
749
+ }) : resolvedItems;
648
750
  for (const { manifest } of items) {
649
751
  printPlan(options.logger, manifest);
650
752
  }
@@ -740,4 +842,4 @@ const runBuildIndexCommand = async (options) => {
740
842
  return empty;
741
843
  };
742
844
 
743
- export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveSourceRef as c, runListCommand as d, 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) => {