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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './packem_shared/COMMANDS-1V_KEx35.mjs';
2
+ import { runCli } from './packem_shared/COMMANDS-CHw4zOZ9.mjs';
3
3
 
4
4
  try {
5
5
  const code = await runCli();
package/dist/index.d.mts CHANGED
@@ -572,14 +572,22 @@ interface InitCommandOptions {
572
572
  * and the auth-provider sub-select.
573
573
  */
574
574
  prompt?: Pick<OfferDeps, "multiSelect" | "select">;
575
+ /**
576
+ * Override the git ref (branch, tag, or commit) the default template source
577
+ * is fetched from. Takes precedence over the version-derived ref. Ignored
578
+ * when `source` or `from` is set.
579
+ */
580
+ ref?: string;
575
581
  /** Local registry root for the offer's `runAddCommand` (offline / tests). Mirrors `from` but for registry items. */
576
582
  registryFrom?: string;
577
583
  /** Override the remote registry source base for the offer (default `gh:anolilab/lunora/registry`). */
578
584
  registrySource?: string;
579
585
  /**
580
586
  * Override the remote source giget downloads from. Default:
581
- * `gh:anolilab/lunora/templates/&lt;templateType>#v&lt;cliVersion>`. Tests
582
- * typically use `from` instead to skip the network.
587
+ * `gh:anolilab/lunora/templates/&lt;templateType>#&lt;ref>`, where `&lt;ref>` is
588
+ * the `ref` option when set, else derived from the CLI version (pre-release
589
+ * channels → their branch, stable → `main`). Tests typically use `from`
590
+ * instead to skip the network.
583
591
  */
584
592
  source?: string;
585
593
  templateType?: Template;
@@ -707,6 +715,8 @@ interface AddCommandOptions {
707
715
  out?: string;
708
716
  /** Force-overwrite existing files (take the incoming copy) instead of skipping/conflicting. */
709
717
  overwrite?: boolean;
718
+ /** Override the git ref (branch, tag, or commit) items are fetched from (default: version-derived); appended to the `source` base when that is set. Ignored when `from` is set. */
719
+ ref?: string;
710
720
  /** Override the remote registry source base (default gh:anolilab/lunora/registry). */
711
721
  source?: string;
712
722
  /** Skip the package.json mutation confirmation prompt. */
package/dist/index.d.ts CHANGED
@@ -572,14 +572,22 @@ interface InitCommandOptions {
572
572
  * and the auth-provider sub-select.
573
573
  */
574
574
  prompt?: Pick<OfferDeps, "multiSelect" | "select">;
575
+ /**
576
+ * Override the git ref (branch, tag, or commit) the default template source
577
+ * is fetched from. Takes precedence over the version-derived ref. Ignored
578
+ * when `source` or `from` is set.
579
+ */
580
+ ref?: string;
575
581
  /** Local registry root for the offer's `runAddCommand` (offline / tests). Mirrors `from` but for registry items. */
576
582
  registryFrom?: string;
577
583
  /** Override the remote registry source base for the offer (default `gh:anolilab/lunora/registry`). */
578
584
  registrySource?: string;
579
585
  /**
580
586
  * Override the remote source giget downloads from. Default:
581
- * `gh:anolilab/lunora/templates/&lt;templateType>#v&lt;cliVersion>`. Tests
582
- * typically use `from` instead to skip the network.
587
+ * `gh:anolilab/lunora/templates/&lt;templateType>#&lt;ref>`, where `&lt;ref>` is
588
+ * the `ref` option when set, else derived from the CLI version (pre-release
589
+ * channels → their branch, stable → `main`). Tests typically use `from`
590
+ * instead to skip the network.
583
591
  */
584
592
  source?: string;
585
593
  templateType?: Template;
@@ -707,6 +715,8 @@ interface AddCommandOptions {
707
715
  out?: string;
708
716
  /** Force-overwrite existing files (take the incoming copy) instead of skipping/conflicting. */
709
717
  overwrite?: boolean;
718
+ /** Override the git ref (branch, tag, or commit) items are fetched from (default: version-derived); appended to the `source` base when that is set. Ignored when `from` is set. */
719
+ ref?: string;
710
720
  /** Override the remote registry source base (default gh:anolilab/lunora/registry). */
711
721
  source?: string;
712
722
  /** Skip the package.json mutation confirmation prompt. */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-1V_KEx35.mjs';
1
+ export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-CHw4zOZ9.mjs';
2
2
  export { runCodegenCommand } from './packem_chunks/runCodegenCommand.mjs';
3
3
  export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
4
4
  export { runDeployCommand } from './packem_chunks/runDeployCommand.mjs';
@@ -16,4 +16,4 @@ export { createRecordingSpawner, defaultSpawner } from './packem_shared/defaultS
16
16
  export { default as parseManifest } from './packem_shared/parseManifest--vZf2FY1.mjs';
17
17
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
18
18
  export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BcYe607_.mjs';
19
- export { runAddCommand, runBuildIndexCommand, runRegistryViewCommand } from './packem_shared/runAddCommand-BZGkRnBs.mjs';
19
+ export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-SUPdjsu5.mjs';
@@ -3,7 +3,7 @@ import { findWranglerFile, promptSelect } from '@lunora/config';
3
3
  import { join } from '@visulima/path';
4
4
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
5
5
  import { n as normalizeFeature, E as EMAIL_ITEM, D as DEFAULT_AUTH_ITEM, p as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS } from '../packem_shared/features-ocSSpZtS.mjs';
6
- import { runAddCommand } from '../packem_shared/runAddCommand-BZGkRnBs.mjs';
6
+ import { r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
7
7
 
8
8
  const providerToItem = (provider) => {
9
9
  const value = provider.trim().toLowerCase();
@@ -54,6 +54,7 @@ const runAddFeature = async (options) => {
54
54
  from: options.from,
55
55
  logger: options.logger,
56
56
  names: [...items],
57
+ ref: options.ref,
57
58
  source: options.source,
58
59
  yes: true
59
60
  });
@@ -67,6 +68,7 @@ const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
67
68
  from: options.from,
68
69
  logger,
69
70
  provider: options.provider,
71
+ ref: options.ref,
70
72
  source: options.source,
71
73
  yes: options.yes === true
72
74
  });
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
2
- import { runAddCommand, runRegistryViewCommand, runBuildIndexCommand } from '../packem_shared/runAddCommand-BZGkRnBs.mjs';
2
+ import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
3
3
 
4
4
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
5
5
  const subcommand = argument[0];
@@ -15,12 +15,13 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
15
15
  logger,
16
16
  names,
17
17
  overwrite: options.overwrite === true,
18
+ ref: options.ref,
18
19
  source: options.source,
19
20
  yes: options.yes === true
20
21
  });
21
22
  }
22
23
  if (subcommand === "list") {
23
- return runAddCommand({ cwd, from: options.from, json: options.json === true, list: true, logger, names: [], source: options.source });
24
+ return runAddCommand({ cwd, from: options.from, json: options.json === true, list: true, logger, names: [], ref: options.ref, source: options.source });
24
25
  }
25
26
  if (subcommand === "view") {
26
27
  return runRegistryViewCommand({
@@ -28,6 +29,7 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
28
29
  from: options.from,
29
30
  logger,
30
31
  names,
32
+ ref: options.ref,
31
33
  source: options.source
32
34
  });
33
35
  }
@@ -1,16 +1,16 @@
1
1
  import { existsSync, mkdirSync, writeFileSync, readdirSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
- import { fileURLToPath } from 'node:url';
4
3
  import { detectFramework as detectFramework$1, isInteractive, promptSelect, promptMultiSelect } from '@lunora/config';
5
4
  import { walkSync } from '@visulima/fs';
6
- import { resolve, join as join$1, relative, dirname as dirname$1 } from '@visulima/path';
5
+ import { resolve, join as join$1, relative, dirname as dirname$1, basename } from '@visulima/path';
7
6
  import { downloadTemplate } from 'giget';
7
+ import { modify, applyEdits } from 'jsonc-parser';
8
8
  import { join, dirname } from 'node:path';
9
9
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
10
10
  import MagicString from 'magic-string';
11
11
  import { Project, SyntaxKind } from 'ts-morph';
12
+ import { c as resolveSourceRef, d as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
12
13
  import { p as promptAuthProvider, E as EMAIL_ITEM } from '../packem_shared/features-ocSSpZtS.mjs';
13
- import { runAddCommand } from '../packem_shared/runAddCommand-BZGkRnBs.mjs';
14
14
 
15
15
  const GITHUB_CONTENT = `name: Deploy
16
16
 
@@ -20,6 +20,10 @@ on:
20
20
  pull_request:
21
21
  workflow_dispatch:
22
22
 
23
+ # Prerequisite: commit your pnpm-lock.yaml. \`pnpm install --frozen-lockfile\`
24
+ # (below) and the pnpm cache both require it — run \`pnpm install\` locally and
25
+ # commit the lockfile before pushing, or the first CI run fails.
26
+ #
23
27
  # Set these repository secrets (Settings → Secrets and variables → Actions):
24
28
  # CLOUDFLARE_API_TOKEN — a Workers-scoped API token
25
29
  # CLOUDFLARE_ACCOUNT_ID — your Cloudflare account id
@@ -66,6 +70,10 @@ jobs:
66
70
  const GITLAB_CONTENT = `stages:
67
71
  - deploy
68
72
 
73
+ # Prerequisite: commit your pnpm-lock.yaml. \`pnpm install --frozen-lockfile\`
74
+ # (below) requires it — run \`pnpm install\` locally and commit the lockfile
75
+ # before pushing, or the first pipeline fails.
76
+ #
69
77
  # Set these as masked CI/CD variables (Settings → CI/CD → Variables):
70
78
  # CLOUDFLARE_API_TOKEN — a Workers-scoped API token
71
79
  # CLOUDFLARE_ACCOUNT_ID — your Cloudflare account id
@@ -127,6 +135,9 @@ const scaffoldCiWorkflow = (projectRoot, provider, logger, options = {}) => {
127
135
  } else {
128
136
  logger.success(`--ci ${provider}: wrote ${spec.file}`);
129
137
  logger.info(`--ci ${provider}: set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as ${spec.secretsHint} to enable deploys.`);
138
+ logger.info(
139
+ `--ci ${provider}: run \`pnpm install\` and commit pnpm-lock.yaml before pushing — the pipeline runs \`pnpm install --frozen-lockfile\`.`
140
+ );
130
141
  }
131
142
  return result;
132
143
  } catch (error) {
@@ -324,32 +335,6 @@ export const send = mutation
324
335
  });
325
336
  `;
326
337
  const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/templates";
327
- const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
328
- const resolveCliVersion = () => {
329
- try {
330
- let directory = dirname$1(fileURLToPath(import.meta.url));
331
- for (let index = 0; index < 5; index += 1) {
332
- const candidate = join$1(directory, "package.json");
333
- if (existsSync(candidate)) {
334
- const parsed = JSON.parse(readFileSync(candidate, "utf8"));
335
- if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
336
- return parsed.version;
337
- }
338
- }
339
- const parent = dirname$1(directory);
340
- if (parent === directory) {
341
- break;
342
- }
343
- directory = parent;
344
- }
345
- } catch {
346
- }
347
- return "0.0.0";
348
- };
349
- const resolveDefaultSourceRef = () => {
350
- const version = resolveCliVersion();
351
- return version === "0.0.0" ? DEFAULT_SOURCE_REF_FALLBACK : `v${version}`;
352
- };
353
338
  const isTextFile = (filePath) => {
354
339
  const lastDot = filePath.lastIndexOf(".");
355
340
  if (lastDot === -1) {
@@ -358,6 +343,26 @@ const isTextFile = (filePath) => {
358
343
  return TEXT_EXTENSIONS.has(filePath.slice(lastDot));
359
344
  };
360
345
  const substitute = (content, name) => content.replaceAll("{{name}}", name);
346
+ const isLunoraDep = (name) => name === "lunorash" || name.startsWith("@lunora/");
347
+ const stampLunoraDeps = (packageJsonText, distTag) => {
348
+ let parsed;
349
+ try {
350
+ parsed = JSON.parse(packageJsonText);
351
+ } catch {
352
+ return packageJsonText;
353
+ }
354
+ let text = packageJsonText;
355
+ for (const section of ["dependencies", "devDependencies"]) {
356
+ for (const name of Object.keys(parsed[section] ?? {})) {
357
+ if (!isLunoraDep(name)) {
358
+ continue;
359
+ }
360
+ const edits = modify(text, [section, name], distTag, { formattingOptions: { insertSpaces: true, tabSize: 4 } });
361
+ text = applyEdits(text, edits);
362
+ }
363
+ }
364
+ return text;
365
+ };
361
366
  const collectFiles = (directory) => {
362
367
  const out = [];
363
368
  for (const entry of walkSync(directory, { includeDirs: false, includeFiles: true })) {
@@ -368,12 +373,16 @@ const collectFiles = (directory) => {
368
373
  const copyTemplate = (sourceDirectory, target, name) => {
369
374
  const files = collectFiles(sourceDirectory);
370
375
  const written = [];
376
+ const distTag = resolveDistTag();
371
377
  for (const source of files) {
372
378
  const relativePath = relative(sourceDirectory, source);
373
379
  const destination = join$1(target, relativePath);
374
380
  mkdirSync(dirname$1(destination), { recursive: true });
375
381
  const raw = readFileSync(source);
376
- const text = isTextFile(source) ? substitute(raw.toString("utf8"), name) : void 0;
382
+ let text = isTextFile(source) ? substitute(raw.toString("utf8"), name) : void 0;
383
+ if (text !== void 0 && basename(source) === "package.json") {
384
+ text = stampLunoraDeps(text, distTag);
385
+ }
377
386
  if (text === void 0) {
378
387
  writeFileSync(destination, raw);
379
388
  } else {
@@ -383,11 +392,11 @@ const copyTemplate = (sourceDirectory, target, name) => {
383
392
  }
384
393
  return written;
385
394
  };
386
- const resolveTemplateSource = (templateType, source) => {
395
+ const resolveTemplateSource = (templateType, source, ref) => {
387
396
  if (source !== void 0 && source.length > 0) {
388
397
  return source;
389
398
  }
390
- return `${DEFAULT_SOURCE_BASE}/${templateType}#${resolveDefaultSourceRef()}`;
399
+ return `${DEFAULT_SOURCE_BASE}/${templateType}#${resolveSourceRef(ref)}`;
391
400
  };
392
401
  const isSafeSource = (source) => {
393
402
  if (source.includes("..")) {
@@ -412,11 +421,12 @@ const scaffoldFromLocal = (fromRoot, templateType, target, name, logger) => {
412
421
  logScaffoldSuccess(logger, written, target, name);
413
422
  return { code: 0, files: written, target };
414
423
  };
415
- const scaffoldFromRemote = async (source, templateType, target, name, logger) => {
424
+ const scaffoldFromRemote = async (options) => {
425
+ const { logger, name, ref, source, target, templateType } = options;
416
426
  const stagingRoot = mkdtempSync(join$1(tmpdir(), "lunora-init-fetch-"));
417
427
  const stagingDirectory = join$1(stagingRoot, "template");
418
428
  try {
419
- const remote = resolveTemplateSource(templateType, source);
429
+ const remote = resolveTemplateSource(templateType, source, ref);
420
430
  logger.info(`fetching template from ${remote}`);
421
431
  const downloaded = await downloadTemplate(remote, {
422
432
  cwd: stagingRoot,
@@ -566,6 +576,7 @@ const maybeOfferExtras = async (options, projectDirectory) => {
566
576
  from: options.registryFrom,
567
577
  logger: options.logger,
568
578
  names: [...names],
579
+ ref: options.ref,
569
580
  source: options.registrySource,
570
581
  yes: true
571
582
  });
@@ -607,7 +618,7 @@ const scaffoldNewProject = async (options, cwd) => {
607
618
  );
608
619
  return { code: 1, files: [], target };
609
620
  }
610
- return scaffoldFromRemote(options.source, templateType, target, name, options.logger);
621
+ return scaffoldFromRemote({ logger: options.logger, name, ref: options.ref, source: options.source, target, templateType });
611
622
  };
612
623
  const runInitCommand = async (options) => {
613
624
  const cwd = options.cwd ?? process.cwd();
@@ -643,10 +654,11 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
643
654
  interactive: options.interactive === true ? true : void 0,
644
655
  logger,
645
656
  name: argument[0],
657
+ ref: options.ref,
646
658
  source: options.source,
647
659
  templateType: template,
648
660
  yes: options.yes === true
649
661
  });
650
662
  });
651
663
 
652
- export { execute, isTemplate, runInitCommand };
664
+ export { execute, isTemplate, resolveTemplateSource, runInitCommand };
@@ -15,7 +15,8 @@ const addCommand = {
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
17
  ["lunora add storage", "Add the R2 storage registry item"],
18
- ["lunora add crons", "Add the scheduled-jobs registry item"]
18
+ ["lunora add crons", "Add the scheduled-jobs registry item"],
19
+ ["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
19
20
  ],
20
21
  group: "Project",
21
22
  loader: () => import('../packem_chunks/handler.mjs').then((m) => {
@@ -27,6 +28,11 @@ const addCommand = {
27
28
  { description: "Skip the provider prompt and use the default (email & password)", name: "yes", type: Boolean },
28
29
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
29
30
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
31
+ {
32
+ description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
33
+ name: "ref",
34
+ type: String
35
+ },
30
36
  { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean }
31
37
  ]
32
38
  };
@@ -342,6 +348,7 @@ const initCommand = {
342
348
  ["lunora init my-app", "Scaffold with the default (vite) template"],
343
349
  ["lunora init my-app -t tanstack-start-react", "Scaffold a TanStack Start (React) app"],
344
350
  ["lunora init my-app -t tanstack-start-solid", "Scaffold a TanStack Start (Solid) app"],
351
+ ["lunora init my-app --ref alpha", "Scaffold from the alpha branch's templates"],
345
352
  ["lunora init --here", "Add Lunora to the current project"],
346
353
  ["lunora init my-app --ci github", "Scaffold + add a GitHub Actions deploy pipeline"],
347
354
  ["lunora init my-app --ci gitlab", "Scaffold + add a GitLab CI deploy pipeline"]
@@ -369,6 +376,11 @@ const initCommand = {
369
376
  name: "source",
370
377
  type: String
371
378
  },
379
+ {
380
+ description: "Fetch templates from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
381
+ name: "ref",
382
+ type: String
383
+ },
372
384
  {
373
385
  description: "Permit --source values outside gh:/github:/https:// (e.g. local file://)",
374
386
  name: "allow-unsafe-source",
@@ -541,6 +553,11 @@ const registryCommand = {
541
553
  { description: "add: skip the package.json mutation confirmation prompt", name: "yes", type: Boolean },
542
554
  { description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
543
555
  { description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
556
+ {
557
+ description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
558
+ name: "ref",
559
+ type: String
560
+ },
544
561
  { description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
545
562
  { description: "Emit JSON output (add plan / list)", name: "json", type: Boolean },
546
563
  { description: "build: output path for the catalog (default <root>/index.json)", name: "out", type: String },
@@ -1,7 +1,8 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
- import { join, dirname } from '@visulima/path';
2
+ import { dirname, join } from '@visulima/path';
3
3
  import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
4
4
  import { modify, applyEdits, parse } from 'jsonc-parser';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
6
7
  import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
7
8
  import { createHash } from 'node:crypto';
@@ -9,7 +10,87 @@ import { tmpdir } from 'node:os';
9
10
  import { downloadTemplate } from 'giget';
10
11
  import parseManifest from './parseManifest--vZf2FY1.mjs';
11
12
 
12
- const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
13
+ const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
14
+ const STABLE_BRANCH = "main";
15
+ const PRERELEASE_CHANNEL_BRANCHES = /* @__PURE__ */ new Set(["alpha", "beta", "next"]);
16
+ const SAFE_REF = /^[\w./@-]+$/;
17
+ const isSafeRef = (ref) => !ref.includes("..") && SAFE_REF.test(ref);
18
+ const resolveCliVersion = () => {
19
+ try {
20
+ let directory = dirname(fileURLToPath(import.meta.url));
21
+ for (let index = 0; index < 6; index += 1) {
22
+ const candidate = join(directory, "package.json");
23
+ if (existsSync(candidate)) {
24
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
25
+ if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
26
+ return parsed.version;
27
+ }
28
+ }
29
+ const parent = dirname(directory);
30
+ if (parent === directory) {
31
+ break;
32
+ }
33
+ directory = parent;
34
+ }
35
+ } catch {
36
+ }
37
+ return "0.0.0";
38
+ };
39
+ const resolveVersionRef = (version) => {
40
+ if (version === "0.0.0") {
41
+ return DEFAULT_SOURCE_REF_FALLBACK;
42
+ }
43
+ const core = version.split("+")[0] ?? version;
44
+ const dashIndex = core.indexOf("-");
45
+ if (dashIndex !== -1) {
46
+ const [channel] = core.slice(dashIndex + 1).split(".");
47
+ if (channel !== void 0 && PRERELEASE_CHANNEL_BRANCHES.has(channel)) {
48
+ return channel;
49
+ }
50
+ }
51
+ return STABLE_BRANCH;
52
+ };
53
+ const resolveSourceRef = (ref) => {
54
+ if (ref !== void 0 && ref.length > 0) {
55
+ if (!isSafeRef(ref)) {
56
+ throw new Error(`invalid --ref "${ref}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);
57
+ }
58
+ return ref;
59
+ }
60
+ return resolveVersionRef(resolveCliVersion());
61
+ };
62
+ const STABLE_DIST_TAG = "latest";
63
+ const resolveDistTag = () => {
64
+ const ref = resolveVersionRef(resolveCliVersion());
65
+ return ref === STABLE_BRANCH ? STABLE_DIST_TAG : ref;
66
+ };
67
+
68
+ const resolveDepRange = (range) => {
69
+ if (!range.startsWith("workspace:")) {
70
+ return range;
71
+ }
72
+ const rest = range.slice("workspace:".length);
73
+ if (rest === "" || rest === "*" || rest === "^" || rest === "~") {
74
+ return resolveDistTag();
75
+ }
76
+ return rest;
77
+ };
78
+ const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
79
+ const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|runtime|server|values)(\/[^'"]*)?\1/gu;
80
+ const projectUsesUmbrella = (projectRoot) => {
81
+ const packageJsonPath = join(projectRoot, "package.json");
82
+ if (!existsSync(packageJsonPath)) {
83
+ return false;
84
+ }
85
+ try {
86
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
87
+ return parsed.dependencies?.lunorash !== void 0 || parsed.devDependencies?.lunorash !== void 0;
88
+ } catch {
89
+ return false;
90
+ }
91
+ };
92
+ const rewriteUmbrellaImports = (source) => source.replaceAll(UMBRELLA_IMPORT_RE, (_match, quote, base, subpath) => `${quote}lunorash/${base}${subpath ?? ""}${quote}`);
93
+ const applyDeps = (deps, projectRoot, logger, section = "dependencies", useUmbrella = false) => {
13
94
  const entries = Object.entries(deps);
14
95
  if (entries.length === 0) {
15
96
  return [];
@@ -23,11 +104,15 @@ const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
23
104
  const parsed = JSON.parse(text);
24
105
  const added = [];
25
106
  for (const [name, range] of entries) {
107
+ if (useUmbrella && UMBRELLA_REEXPORTED_DEPS.has(name)) {
108
+ logger.info(`dep provided by the lunorash umbrella, skipping: ${name}`);
109
+ continue;
110
+ }
26
111
  if (parsed.dependencies?.[name] !== void 0 || parsed.devDependencies?.[name] !== void 0) {
27
112
  logger.info(`dep already present: ${name}`);
28
113
  continue;
29
114
  }
30
- const edits = modify(text, [section, name], range, {
115
+ const edits = modify(text, [section, name], resolveDepRange(range), {
31
116
  formattingOptions: { insertSpaces: true, tabSize: 4 }
32
117
  });
33
118
  text = applyEdits(text, edits);
@@ -148,14 +233,14 @@ const applyBindings = (bindings, projectRoot, logger) => {
148
233
  }
149
234
  return applied;
150
235
  };
151
- const applyItemResources = (manifest, cwd, logger) => {
236
+ const applyItemResources = (manifest, cwd, logger, useUmbrella = false) => {
152
237
  const deps = [];
153
238
  const bindings = [];
154
239
  if (manifest.deps) {
155
- deps.push(...applyDeps(manifest.deps, cwd, logger));
240
+ deps.push(...applyDeps(manifest.deps, cwd, logger, "dependencies", useUmbrella));
156
241
  }
157
242
  if (manifest.devDependencies) {
158
- deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies"));
243
+ deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies", useUmbrella));
159
244
  }
160
245
  if (manifest.bindings) {
161
246
  bindings.push(...applyBindings(manifest.bindings, cwd, logger));
@@ -267,7 +352,12 @@ const renderDiff = (oldText, newText) => {
267
352
  return out;
268
353
  };
269
354
 
270
- const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff) => {
355
+ const CODE_FILE_RE = /\.[cm]?[jt]sx?$/u;
356
+ const readItemFile = (itemDirectory, file, useUmbrella) => {
357
+ const source = readFileSync(join(itemDirectory, file.from), "utf8");
358
+ return useUmbrella && CODE_FILE_RE.test(file.to) ? rewriteUmbrellaImports(source) : source;
359
+ };
360
+ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff, useUmbrella) => {
271
361
  const schemaPath = join(projectRoot, "lunora", "schema.ts");
272
362
  if (diff) {
273
363
  logger.info(`~ would merge .extend(${itemKey}.extension) into lunora/schema.ts (and create ${file.to} if absent)`);
@@ -276,9 +366,13 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
276
366
  const destinationPath = join(projectRoot, file.to);
277
367
  if (!existsSync(destinationPath)) {
278
368
  mkdirSync(dirname(destinationPath), { recursive: true });
279
- writeFileSync(destinationPath, readFileSync(join(itemDirectory, file.from), "utf8"), "utf8");
369
+ writeFileSync(destinationPath, readItemFile(itemDirectory, file, useUmbrella), "utf8");
280
370
  }
281
- const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : 'import { defineSchema } from "@lunora/server";\n\nexport const schema = defineSchema({});\n';
371
+ const baseModule = useUmbrella ? "lunorash/server" : "@lunora/server";
372
+ const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : `import { defineSchema } from "${baseModule}";
373
+
374
+ export const schema = defineSchema({});
375
+ `;
282
376
  const result = insertSchemaExtension(existingSchema, itemKey);
283
377
  if (result.ok) {
284
378
  mkdirSync(dirname(schemaPath), { recursive: true });
@@ -308,9 +402,9 @@ const previewWholeFile = (file, current, incoming, exists, logger) => {
308
402
  logger.info(` ${line}`);
309
403
  }
310
404
  };
311
- const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions) => {
405
+ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella) => {
312
406
  const destinationPath = join(projectRoot, file.to);
313
- const incoming = readFileSync(join(itemDirectory, file.from), "utf8");
407
+ const incoming = readItemFile(itemDirectory, file, useUmbrella);
314
408
  const exists = existsSync(destinationPath);
315
409
  const current = exists ? readFileSync(destinationPath, "utf8") : "";
316
410
  const write = (message) => {
@@ -348,11 +442,11 @@ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, l
348
442
  logger.warn(`conflict: ${file.to} has local edits and an upstream update — wrote ${file.to}.new (use --overwrite to take theirs)`);
349
443
  return { kind: "skipped", path: destinationPath };
350
444
  };
351
- const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}) => {
445
+ const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}, useUmbrella = false) => {
352
446
  if (file.merge === "schema-extension") {
353
- return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true);
447
+ return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true, useUmbrella);
354
448
  }
355
- return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions);
449
+ return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
356
450
  };
357
451
  const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
358
452
  const written = [];
@@ -360,15 +454,16 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
360
454
  const depsAdded = [];
361
455
  const bindingsApplied = [];
362
456
  const lock = readLock(cwd);
457
+ const useUmbrella = projectUsesUmbrella(cwd);
363
458
  for (const { directory, manifest } of items) {
364
459
  for (const file of manifest.files) {
365
- const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions);
460
+ const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
366
461
  (outcome.kind === "written" ? written : skipped).push(outcome.path);
367
462
  }
368
463
  if (reconcileOptions.diff) {
369
464
  continue;
370
465
  }
371
- const applied = applyItemResources(manifest, cwd, logger);
466
+ const applied = applyItemResources(manifest, cwd, logger, useUmbrella);
372
467
  depsAdded.push(...applied.deps);
373
468
  bindingsApplied.push(...applied.bindings);
374
469
  }
@@ -379,7 +474,6 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
379
474
  };
380
475
 
381
476
  const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/registry";
382
- const DEFAULT_SOURCE_REF = "alpha";
383
477
  const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
384
478
  const assertSafeItemName = (name) => {
385
479
  if (!VALID_ITEM_NAME.test(name)) {
@@ -431,7 +525,7 @@ const resolveItemDirectory = async (name, options) => {
431
525
  }, directory };
432
526
  }
433
527
  const base = options.source ?? DEFAULT_SOURCE_BASE;
434
- return fetchToStaging(`${base}/${name}#${DEFAULT_SOURCE_REF}`, "item", options.logger);
528
+ return fetchToStaging(`${base}/${name}#${resolveSourceRef(options.ref)}`, "item", options.logger);
435
529
  };
436
530
  const resolveRegistryRoot = async (options) => {
437
531
  if (options.from !== void 0) {
@@ -442,7 +536,7 @@ const resolveRegistryRoot = async (options) => {
442
536
  }, root: options.from };
443
537
  }
444
538
  const base = options.source ?? DEFAULT_SOURCE_BASE;
445
- const { cleanup, directory } = await fetchToStaging(`${base}#${DEFAULT_SOURCE_REF}`, "registry", options.logger);
539
+ const { cleanup, directory } = await fetchToStaging(`${base}#${resolveSourceRef(options.ref)}`, "registry", options.logger);
446
540
  return { cleanup, root: directory };
447
541
  };
448
542
  const readManifest = (itemDirectory, name) => {
@@ -690,4 +784,4 @@ const runBuildIndexCommand = async (options) => {
690
784
  return empty;
691
785
  };
692
786
 
693
- export { runAddCommand, runBuildIndexCommand, runListCommand, runRegistryViewCommand };
787
+ export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveSourceRef as c, resolveDistTag as d, runListCommand as e, runAddCommand as r };
@@ -0,0 +1,4 @@
1
+ import 'node:fs';
2
+ import '@visulima/path';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, e as runListCommand, b as runRegistryViewCommand } from './commands-SUPdjsu5.mjs';
4
+ import './buildRegistryIndex-BcYe607_.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -53,12 +53,12 @@
53
53
  "dependencies": {
54
54
  "@bomb.sh/tab": "0.0.17",
55
55
  "@lunora/codegen": "1.0.0-alpha.2",
56
- "@lunora/config": "1.0.0-alpha.2",
56
+ "@lunora/config": "1.0.0-alpha.3",
57
57
  "@lunora/container": "1.0.0-alpha.1",
58
- "@lunora/d1": "1.0.0-alpha.2",
58
+ "@lunora/d1": "1.0.0-alpha.3",
59
59
  "@lunora/seed": "1.0.0-alpha.1",
60
60
  "@lunora/server": "1.0.0-alpha.1",
61
- "@lunora/studio": "1.0.0-alpha.1",
61
+ "@lunora/studio": "1.0.0-alpha.2",
62
62
  "@lunora/values": "1.0.0-alpha.1",
63
63
  "@visulima/cerebro": "3.0.0-alpha.32",
64
64
  "@visulima/fs": "5.0.0-alpha.32",