@lunora/cli 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (48) hide show
  1. package/dist/bin.mjs +1 -1
  2. package/dist/index.d.mts +217 -113
  3. package/dist/index.d.ts +217 -113
  4. package/dist/index.mjs +7 -7
  5. package/dist/packem_chunks/handler.mjs +80 -6
  6. package/dist/packem_chunks/handler10.mjs +1 -1
  7. package/dist/packem_chunks/handler11.mjs +1 -1
  8. package/dist/packem_chunks/handler12.mjs +1 -1
  9. package/dist/packem_chunks/handler13.mjs +1 -1
  10. package/dist/packem_chunks/handler14.mjs +2 -2
  11. package/dist/packem_chunks/handler15.mjs +1 -1
  12. package/dist/packem_chunks/handler16.mjs +5 -3
  13. package/dist/packem_chunks/handler17.mjs +1 -1
  14. package/dist/packem_chunks/handler18.mjs +4 -6
  15. package/dist/packem_chunks/handler19.mjs +3 -3
  16. package/dist/packem_chunks/handler2.mjs +2 -2
  17. package/dist/packem_chunks/handler20.mjs +1 -1
  18. package/dist/packem_chunks/handler21.mjs +2 -2
  19. package/dist/packem_chunks/handler3.mjs +1 -1
  20. package/dist/packem_chunks/handler4.mjs +1 -1
  21. package/dist/packem_chunks/handler5.mjs +2 -2
  22. package/dist/packem_chunks/handler6.mjs +2 -2
  23. package/dist/packem_chunks/handler7.mjs +1 -1
  24. package/dist/packem_chunks/handler8.mjs +1 -1
  25. package/dist/packem_chunks/handler9.mjs +1 -1
  26. package/dist/packem_chunks/planDevCommand.mjs +6 -49
  27. package/dist/packem_chunks/runCodegenCommand.mjs +2 -2
  28. package/dist/packem_chunks/runDeployCommand.mjs +3 -3
  29. package/dist/packem_chunks/runInitCommand.mjs +1948 -107
  30. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +5 -5
  31. package/dist/packem_chunks/runResetCommand.mjs +4 -4
  32. package/dist/packem_chunks/runRpcCommand.mjs +1 -1
  33. package/dist/packem_shared/{COMMANDS-1V_KEx35.mjs → COMMANDS-D3h9Iwvl.mjs} +45 -6
  34. package/dist/packem_shared/{command-BDXcJCCJ.mjs → command-BC30oSBW.mjs} +1 -1
  35. package/dist/packem_shared/{runAddCommand-BZGkRnBs.mjs → commands-hl0mRqqg.mjs} +177 -25
  36. package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-B40gPzQo.mjs} +9 -4
  37. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +61 -0
  38. package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
  39. package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
  40. package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-wUvAN6AL.mjs} +1 -1
  41. package/dist/packem_shared/runAddCommand-vJdgiR5t.mjs +4 -0
  42. package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
  43. package/dist/packem_shared/storage-B7hHSTZP.mjs +84 -0
  44. package/dist/packem_shared/tui-prompts-M6OWsuyw.mjs +663 -0
  45. package/package.json +11 -10
  46. package/skills/lunora-setup-storage/SKILL.md +7 -3
  47. package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
  48. /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-BC30oSBW.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-BC30oSBW.mjs';
4
+ import { b as tuiConfirm } from '../packem_shared/tui-prompts-M6OWsuyw.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-BC30oSBW.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,8 +14,10 @@ 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"],
18
- ["lunora add crons", "Add the scheduled-jobs 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"],
19
+ ["lunora add crons", "Add the scheduled-jobs registry item"],
20
+ ["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
19
21
  ],
20
22
  group: "Project",
21
23
  loader: () => import('../packem_chunks/handler.mjs').then((m) => {
@@ -24,9 +26,17 @@ const addCommand = {
24
26
  name: "add",
25
27
  options: [
26
28
  { description: "auth: provider to use without prompting (auth | clerk | auth0)", name: "provider", type: String },
27
- { 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 },
28
33
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
29
34
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
35
+ {
36
+ description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
37
+ name: "ref",
38
+ type: String
39
+ },
30
40
  { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean }
31
41
  ]
32
42
  };
@@ -342,6 +352,7 @@ const initCommand = {
342
352
  ["lunora init my-app", "Scaffold with the default (vite) template"],
343
353
  ["lunora init my-app -t tanstack-start-react", "Scaffold a TanStack Start (React) app"],
344
354
  ["lunora init my-app -t tanstack-start-solid", "Scaffold a TanStack Start (Solid) app"],
355
+ ["lunora init my-app --ref alpha", "Scaffold from the alpha branch's templates"],
345
356
  ["lunora init --here", "Add Lunora to the current project"],
346
357
  ["lunora init my-app --ci github", "Scaffold + add a GitHub Actions deploy pipeline"],
347
358
  ["lunora init my-app --ci gitlab", "Scaffold + add a GitLab CI deploy pipeline"]
@@ -354,11 +365,19 @@ const initCommand = {
354
365
  options: [
355
366
  {
356
367
  alias: "t",
357
- defaultValue: "vite",
358
- description: "Template to scaffold (vite | standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid)",
368
+ // No default: when omitted, an interactive run shows the framework
369
+ // picker (default React overlay) and a non-interactive run errors.
370
+ // For React/Vue/Solid/Svelte SPAs use `--vite <framework>` (overlay);
371
+ // `-t` selects a bespoke template.
372
+ description: "Bespoke template (standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid). For an SPA use --vite react|vue|solid|svelte.",
359
373
  name: "template",
360
374
  type: String
361
375
  },
376
+ {
377
+ description: "Scaffold via the create-vite overlay for a framework (react | vue | solid | svelte | vanilla) — official create-vite base + Lunora layer",
378
+ name: "vite",
379
+ type: String
380
+ },
362
381
  {
363
382
  description: "Local templates root to copy from (offline-friendly; expects <type>/ subdirs)",
364
383
  name: "from",
@@ -369,6 +388,11 @@ const initCommand = {
369
388
  name: "source",
370
389
  type: String
371
390
  },
391
+ {
392
+ description: "Fetch templates from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
393
+ name: "ref",
394
+ type: String
395
+ },
372
396
  {
373
397
  description: "Permit --source values outside gh:/github:/https:// (e.g. local file://)",
374
398
  name: "allow-unsafe-source",
@@ -395,6 +419,16 @@ const initCommand = {
395
419
  description: "Also scaffold a CI deploy pipeline: github (.github/workflows/deploy.yml) or gitlab (.gitlab-ci.yml)",
396
420
  name: "ci",
397
421
  type: String
422
+ },
423
+ {
424
+ description: "Add features non-interactively after scaffolding (comma-separated): auth | email | storage | ratelimit | crons | presence | backup",
425
+ name: "add",
426
+ type: String
427
+ },
428
+ {
429
+ description: "Walk through every step (prompts + output) without writing files, installing, or running git",
430
+ name: "dry-run",
431
+ type: Boolean
398
432
  }
399
433
  ]
400
434
  };
@@ -541,6 +575,11 @@ const registryCommand = {
541
575
  { description: "add: skip the package.json mutation confirmation prompt", name: "yes", type: Boolean },
542
576
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
543
577
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
578
+ {
579
+ description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
580
+ name: "ref",
581
+ type: String
582
+ },
544
583
  { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
545
584
  { description: "Emit JSON output (add plan / list)", name: "json", type: Boolean },
546
585
  { description: "build: output path for the catalog (default <root>/index.json)", name: "out", type: String },
@@ -1,4 +1,4 @@
1
- import { createLogger } from './createLogger-CHPNjFw2.mjs';
1
+ import { createLogger } from './createLogger-B40gPzQo.mjs';
2
2
 
3
3
  const defineHandler = (body) => async (toolbox) => {
4
4
  const logger = createLogger();
@@ -1,15 +1,130 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
- import { join, dirname } from '@visulima/path';
3
- import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
2
+ import { dirname, join } from '@visulima/path';
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-M6OWsuyw.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
12
  import parseManifest from './parseManifest--vZf2FY1.mjs';
11
13
 
12
- const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
14
+ const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
15
+ const STABLE_BRANCH = "main";
16
+ const PRERELEASE_CHANNEL_BRANCHES = /* @__PURE__ */ new Set(["alpha", "beta", "next"]);
17
+ const SAFE_REF = /^[\w./@-]+$/;
18
+ const isSafeRef = (ref) => !ref.includes("..") && SAFE_REF.test(ref);
19
+ const resolveCliVersion = () => {
20
+ try {
21
+ let directory = dirname(fileURLToPath(import.meta.url));
22
+ for (let index = 0; index < 6; index += 1) {
23
+ const candidate = join(directory, "package.json");
24
+ if (existsSync(candidate)) {
25
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
26
+ if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
27
+ return parsed.version;
28
+ }
29
+ }
30
+ const parent = dirname(directory);
31
+ if (parent === directory) {
32
+ break;
33
+ }
34
+ directory = parent;
35
+ }
36
+ } catch {
37
+ }
38
+ return "0.0.0";
39
+ };
40
+ const resolveVersionRef = (version) => {
41
+ if (version === "0.0.0") {
42
+ return DEFAULT_SOURCE_REF_FALLBACK;
43
+ }
44
+ const core = version.split("+")[0] ?? version;
45
+ const dashIndex = core.indexOf("-");
46
+ if (dashIndex !== -1) {
47
+ const [channel] = core.slice(dashIndex + 1).split(".");
48
+ if (channel !== void 0 && PRERELEASE_CHANNEL_BRANCHES.has(channel)) {
49
+ return channel;
50
+ }
51
+ }
52
+ return STABLE_BRANCH;
53
+ };
54
+ const resolveSourceRef = (ref) => {
55
+ if (ref !== void 0 && ref.length > 0) {
56
+ if (!isSafeRef(ref)) {
57
+ throw new Error(`invalid --ref "${ref}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);
58
+ }
59
+ return ref;
60
+ }
61
+ return resolveVersionRef(resolveCliVersion());
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
+ };
101
+
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) => {
13
128
  const entries = Object.entries(deps);
14
129
  if (entries.length === 0) {
15
130
  return [];
@@ -23,11 +138,15 @@ const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
23
138
  const parsed = JSON.parse(text);
24
139
  const added = [];
25
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
+ }
26
145
  if (parsed.dependencies?.[name] !== void 0 || parsed.devDependencies?.[name] !== void 0) {
27
146
  logger.info(`dep already present: ${name}`);
28
147
  continue;
29
148
  }
30
- const edits = modify(text, [section, name], range, {
149
+ const edits = modify(text, [section, name], resolveDepRange(range), {
31
150
  formattingOptions: { insertSpaces: true, tabSize: 4 }
32
151
  });
33
152
  text = applyEdits(text, edits);
@@ -148,14 +267,14 @@ const applyBindings = (bindings, projectRoot, logger) => {
148
267
  }
149
268
  return applied;
150
269
  };
151
- const applyItemResources = (manifest, cwd, logger) => {
270
+ const applyItemResources = (manifest, cwd, logger, useUmbrella = false) => {
152
271
  const deps = [];
153
272
  const bindings = [];
154
273
  if (manifest.deps) {
155
- deps.push(...applyDeps(manifest.deps, cwd, logger));
274
+ deps.push(...applyDeps(manifest.deps, cwd, logger, "dependencies", useUmbrella));
156
275
  }
157
276
  if (manifest.devDependencies) {
158
- deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies"));
277
+ deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies", useUmbrella));
159
278
  }
160
279
  if (manifest.bindings) {
161
280
  bindings.push(...applyBindings(manifest.bindings, cwd, logger));
@@ -187,8 +306,8 @@ const confirmDepMutation = async (items, options) => {
187
306
  options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
188
307
  return false;
189
308
  }
190
- const confirmer = options.confirm ?? promptYesNo;
191
- 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?`);
192
311
  if (!confirmed) {
193
312
  options.logger.info("add: aborted");
194
313
  }
@@ -267,7 +386,12 @@ const renderDiff = (oldText, newText) => {
267
386
  return out;
268
387
  };
269
388
 
270
- 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) => {
271
395
  const schemaPath = join(projectRoot, "lunora", "schema.ts");
272
396
  if (diff) {
273
397
  logger.info(`~ would merge .extend(${itemKey}.extension) into lunora/schema.ts (and create ${file.to} if absent)`);
@@ -276,9 +400,13 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
276
400
  const destinationPath = join(projectRoot, file.to);
277
401
  if (!existsSync(destinationPath)) {
278
402
  mkdirSync(dirname(destinationPath), { recursive: true });
279
- writeFileSync(destinationPath, readFileSync(join(itemDirectory, file.from), "utf8"), "utf8");
403
+ writeFileSync(destinationPath, readItemFile(itemDirectory, file, useUmbrella), "utf8");
280
404
  }
281
- 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
+ `;
282
410
  const result = insertSchemaExtension(existingSchema, itemKey);
283
411
  if (result.ok) {
284
412
  mkdirSync(dirname(schemaPath), { recursive: true });
@@ -308,9 +436,9 @@ const previewWholeFile = (file, current, incoming, exists, logger) => {
308
436
  logger.info(` ${line}`);
309
437
  }
310
438
  };
311
- const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions) => {
439
+ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella) => {
312
440
  const destinationPath = join(projectRoot, file.to);
313
- const incoming = readFileSync(join(itemDirectory, file.from), "utf8");
441
+ const incoming = readItemFile(itemDirectory, file, useUmbrella);
314
442
  const exists = existsSync(destinationPath);
315
443
  const current = exists ? readFileSync(destinationPath, "utf8") : "";
316
444
  const write = (message) => {
@@ -348,11 +476,11 @@ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, l
348
476
  logger.warn(`conflict: ${file.to} has local edits and an upstream update — wrote ${file.to}.new (use --overwrite to take theirs)`);
349
477
  return { kind: "skipped", path: destinationPath };
350
478
  };
351
- const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}) => {
479
+ const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}, useUmbrella = false) => {
352
480
  if (file.merge === "schema-extension") {
353
- return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true);
481
+ return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true, useUmbrella);
354
482
  }
355
- return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions);
483
+ return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
356
484
  };
357
485
  const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
358
486
  const written = [];
@@ -360,15 +488,16 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
360
488
  const depsAdded = [];
361
489
  const bindingsApplied = [];
362
490
  const lock = readLock(cwd);
491
+ const useUmbrella = projectUsesUmbrella(cwd);
363
492
  for (const { directory, manifest } of items) {
364
493
  for (const file of manifest.files) {
365
- const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions);
494
+ const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
366
495
  (outcome.kind === "written" ? written : skipped).push(outcome.path);
367
496
  }
368
497
  if (reconcileOptions.diff) {
369
498
  continue;
370
499
  }
371
- const applied = applyItemResources(manifest, cwd, logger);
500
+ const applied = applyItemResources(manifest, cwd, logger, useUmbrella);
372
501
  depsAdded.push(...applied.deps);
373
502
  bindingsApplied.push(...applied.bindings);
374
503
  }
@@ -379,7 +508,6 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
379
508
  };
380
509
 
381
510
  const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/registry";
382
- const DEFAULT_SOURCE_REF = "alpha";
383
511
  const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
384
512
  const assertSafeItemName = (name) => {
385
513
  if (!VALID_ITEM_NAME.test(name)) {
@@ -431,7 +559,7 @@ const resolveItemDirectory = async (name, options) => {
431
559
  }, directory };
432
560
  }
433
561
  const base = options.source ?? DEFAULT_SOURCE_BASE;
434
- return fetchToStaging(`${base}/${name}#${DEFAULT_SOURCE_REF}`, "item", options.logger);
562
+ return fetchToStaging(`${base}/${name}#${resolveSourceRef(options.ref)}`, "item", options.logger);
435
563
  };
436
564
  const resolveRegistryRoot = async (options) => {
437
565
  if (options.from !== void 0) {
@@ -442,7 +570,7 @@ const resolveRegistryRoot = async (options) => {
442
570
  }, root: options.from };
443
571
  }
444
572
  const base = options.source ?? DEFAULT_SOURCE_BASE;
445
- const { cleanup, directory } = await fetchToStaging(`${base}#${DEFAULT_SOURCE_REF}`, "registry", options.logger);
573
+ const { cleanup, directory } = await fetchToStaging(`${base}#${resolveSourceRef(options.ref)}`, "registry", options.logger);
446
574
  return { cleanup, root: directory };
447
575
  };
448
576
  const readManifest = (itemDirectory, name) => {
@@ -488,6 +616,26 @@ const resolvePlan = async (names, options) => {
488
616
  const emptyResult = () => {
489
617
  return { bindings: [], code: 0, deps: [], skipped: [], written: [] };
490
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
+ };
491
639
 
492
640
  const printPlan = (logger, manifest) => {
493
641
  const label = manifest.title ?? manifest.description;
@@ -593,8 +741,12 @@ const runAddCommand = async (options) => {
593
741
  }
594
742
  let cleanups = [];
595
743
  try {
596
- const { cleanups: planCleanups, items } = await resolvePlan(options.names, options);
744
+ const { cleanups: planCleanups, items: resolvedItems } = await resolvePlan(options.names, options);
597
745
  cleanups = planCleanups;
746
+ const { transformManifest } = options;
747
+ const items = transformManifest ? resolvedItems.map((item) => {
748
+ return { ...item, manifest: transformManifest(item.manifest) };
749
+ }) : resolvedItems;
598
750
  for (const { manifest } of items) {
599
751
  printPlan(options.logger, manifest);
600
752
  }
@@ -690,4 +842,4 @@ const runBuildIndexCommand = async (options) => {
690
842
  return empty;
691
843
  };
692
844
 
693
- export { runAddCommand, runBuildIndexCommand, runListCommand, runRegistryViewCommand };
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,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-hl0mRqqg.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) {