@lunora/cli 1.0.0-alpha.73 → 1.0.0-alpha.75

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/index.d.mts CHANGED
@@ -651,6 +651,13 @@ interface RegistryEnvVariable {
651
651
  /** A default/example value for non-secret vars. */
652
652
  value?: string;
653
653
  }
654
+ /** A re-export the item needs injected into the worker entry point (class-B/C only). */
655
+ interface EntrypointReexport {
656
+ /** Optional JS comment placed above the re-export line. */
657
+ comment?: string;
658
+ /** Module specifier (e.g. `"_generated/workflows"` → `export * from "./lunora/_generated/workflows"`). */
659
+ module: string;
660
+ }
654
661
  /** The `registry.json` manifest shape. */
655
662
  interface RegistryManifest {
656
663
  /** wrangler.jsonc additions (best-effort structural edits). */
@@ -662,6 +669,8 @@ interface RegistryManifest {
662
669
  devDependencies?: Readonly<Record<string, string>>;
663
670
  /** Post-install guidance printed after the item is added (per-item next steps). */
664
671
  docs?: string;
672
+ /** Worker-entry re-exports the item needs (class-B/C only). */
673
+ entrypointReexports?: ReadonlyArray<EntrypointReexport>;
665
674
  /** Environment variables the item needs; scaffolded into `.dev.vars`. */
666
675
  envVars?: ReadonlyArray<RegistryEnvVariable>;
667
676
  files: ReadonlyArray<RegistryFile>;
package/dist/index.d.ts CHANGED
@@ -651,6 +651,13 @@ interface RegistryEnvVariable {
651
651
  /** A default/example value for non-secret vars. */
652
652
  value?: string;
653
653
  }
654
+ /** A re-export the item needs injected into the worker entry point (class-B/C only). */
655
+ interface EntrypointReexport {
656
+ /** Optional JS comment placed above the re-export line. */
657
+ comment?: string;
658
+ /** Module specifier (e.g. `"_generated/workflows"` → `export * from "./lunora/_generated/workflows"`). */
659
+ module: string;
660
+ }
654
661
  /** The `registry.json` manifest shape. */
655
662
  interface RegistryManifest {
656
663
  /** wrangler.jsonc additions (best-effort structural edits). */
@@ -662,6 +669,8 @@ interface RegistryManifest {
662
669
  devDependencies?: Readonly<Record<string, string>>;
663
670
  /** Post-install guidance printed after the item is added (per-item next steps). */
664
671
  docs?: string;
672
+ /** Worker-entry re-exports the item needs (class-B/C only). */
673
+ entrypointReexports?: ReadonlyArray<EntrypointReexport>;
665
674
  /** Environment variables the item needs; scaffolded into `.dev.vars`. */
666
675
  envVars?: ReadonlyArray<RegistryEnvVariable>;
667
676
  files: ReadonlyArray<RegistryFile>;
package/dist/index.mjs CHANGED
@@ -13,7 +13,7 @@ export { createLogger, pail } from './packem_shared/createLogger-CIWSHrTL.mjs';
13
13
  export { diffSnapshots, renderAddColumn, renderCreateIndex, renderCreateTable, renderDropIndex, renderDropTable, renderMigrationFile, validatorKindToSqlType } from './packem_shared/diffSnapshots-BeDvvNiF.mjs';
14
14
  export { default as schemaIrToSnapshot } from './packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
15
15
  export { createRecordingSpawner, defaultSpawner } from './packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
16
- export { default as parseManifest } from './packem_shared/parseManifest-BBrXCg9V.mjs';
16
+ export { default as parseManifest } from './packem_shared/parseManifest-Dbp-Q2q3.mjs';
17
17
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
18
- export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-CqM5FwGI.mjs';
19
- export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-vnRRkhNE.mjs';
18
+ export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BS5ig822.mjs';
19
+ export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-ClEvcz3V.mjs';
@@ -4,8 +4,8 @@ import { join, basename } from '@visulima/path';
4
4
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
5
  import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
6
6
  import { t as tuiText, a as tuiSelect } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
7
- import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-bGfgwOgF.mjs';
8
- import { r as runAddCommand } from '../packem_shared/commands-vnRRkhNE.mjs';
7
+ import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-DEXpJkXh.mjs';
8
+ import { r as runAddCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
9
9
 
10
10
  const providerToItem = (provider) => {
11
11
  const value = provider.trim().toLowerCase();
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
- import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-vnRRkhNE.mjs';
2
+ import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
3
3
 
4
4
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
5
5
  const subcommand = argument[0];
@@ -11,11 +11,11 @@ import { d as detectPackageManager, a as detectInstalledManagers, i as installAr
11
11
  import MagicString from 'magic-string';
12
12
  import { Project, SyntaxKind } from 'ts-morph';
13
13
  import { P as PromptCancelledError } from '../packem_shared/prompt-cancelled-APzX1Im-.mjs';
14
- import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-vnRRkhNE.mjs';
14
+ import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
15
15
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
16
16
  import { d as tuiMascot, e as tuiStep, f as tuiMoonrise, t as tuiText, g as tuiHeadline, h as tuiInfo, a as tuiSelect, b as tuiConfirm, w as withTuiSpinner, i as tuiNextSteps, j as tuiTasks, k as tuiMultiSelect, l as withTuiBadgeProgress } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
17
17
  import { logStep } from '../packem_shared/createLogger-CIWSHrTL.mjs';
18
- import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-bGfgwOgF.mjs';
18
+ import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-DEXpJkXh.mjs';
19
19
  import dns from 'node:dns/promises';
20
20
 
21
21
  const GITHUB_CONTENT = `name: Deploy
@@ -1470,7 +1470,49 @@ const ADAPTERS = {
1470
1470
  };
1471
1471
  const isOverlayFramework = (value) => Object.hasOwn(ADAPTERS, value);
1472
1472
 
1473
- const LUNORA_SCHEMA = `import { defineSchema, defineTable, v } from "lunorash/server";
1473
+ const RATELIMIT_SCHEMA = `import type { Middleware } from "lunorash/server";
1474
+ import { defineSchemaExtension, defineTable, definePlugin, v } from "lunorash/server";
1475
+ import { createDbStore, RateLimiter } from "lunorash/ratelimit";
1476
+ import type { RateLimitConfigMap } from "lunorash/ratelimit";
1477
+
1478
+ export const limits = {
1479
+ default: { kind: "token bucket", period: 60_000, rate: 10 },
1480
+ } as const satisfies RateLimitConfigMap;
1481
+
1482
+ export type LimitName = keyof typeof limits;
1483
+
1484
+ export const makeRateLimiter = (ctx: { db: unknown }): RateLimiter<LimitName> =>
1485
+ new RateLimiter<LimitName>({
1486
+ config: limits,
1487
+ store: createDbStore({ db: ctx.db as never, table: "ratelimit_buckets" }),
1488
+ });
1489
+
1490
+ const middleware: Middleware<{ api?: Record<string, unknown>; db: unknown }, { api: Record<string, unknown>; db: unknown }> = ({ ctx, next }) =>
1491
+ next({
1492
+ ctx: {
1493
+ ...ctx,
1494
+ api: { ...ctx.api, ratelimit: makeRateLimiter(ctx) },
1495
+ },
1496
+ });
1497
+
1498
+ export const ratelimit = definePlugin("ratelimit", {
1499
+ extension: defineSchemaExtension("ratelimit", {
1500
+ tables: {
1501
+ buckets: defineTable({
1502
+ key: v.string(),
1503
+ value: v.number(),
1504
+ ts: v.number(),
1505
+ prev: v.optional(v.number()),
1506
+ })
1507
+ .index("by_key", ["key"])
1508
+ .externallyManaged(),
1509
+ },
1510
+ }),
1511
+ middleware,
1512
+ });
1513
+ `;
1514
+ const LUNORA_SCHEMA = `import { ratelimit } from "./ratelimit/schema.js";
1515
+ import { defineSchema, defineTable, v } from "lunorash/server";
1474
1516
 
1475
1517
  export default defineSchema({
1476
1518
  messages: defineTable({
@@ -1479,22 +1521,17 @@ export default defineSchema({
1479
1521
  })
1480
1522
  .shardBy("channelId")
1481
1523
  .index("by_channel", ["channelId"]),
1482
- });
1524
+ }).extend(ratelimit.extension);
1483
1525
  `;
1484
- const LUNORA_MESSAGES = `import { RateLimiter, rateLimit } from "@lunora/ratelimit";
1526
+ const LUNORA_MESSAGES = `import { RateLimiter, rateLimit, createDbStore } from "lunorash/ratelimit";
1485
1527
 
1486
1528
  import { mutation, query, v } from "#lunora/_generated/server.js";
1487
1529
 
1488
- /**
1489
- * One in-memory limiter so the public \`send\` mutation isn't an open flood target
1490
- * out of the box. The default store is in-memory (per-isolate, resets on
1491
- * eviction) — fine for a starter; run \`lunora add ratelimit\` for the durable,
1492
- * \`ctx.db\`-backed store when you ship to production.
1493
- */
1494
- const limiter = new RateLimiter({
1530
+ const limiter = (ctx: { db: unknown }) => new RateLimiter({
1495
1531
  config: {
1496
1532
  send: { kind: "token bucket", period: 60_000, rate: 30 },
1497
1533
  },
1534
+ store: createDbStore({ db: ctx.db as never, table: "ratelimit_buckets" }),
1498
1535
  });
1499
1536
 
1500
1537
  export const list = query.input({ channelId: v.string().meta({ schema: { maxLength: 256 } }), limit: v.optional(v.number()) }).query(async ({ args, ctx }) => {
@@ -1620,6 +1657,7 @@ const patchBaseViteConfig = (target, logger) => {
1620
1657
  const applyLunoraOverlay = async (options) => {
1621
1658
  const { adapter, distTag, logger, name, target } = options;
1622
1659
  const written = [];
1660
+ writeFile(target, join$1("lunora", "ratelimit", "schema.ts"), RATELIMIT_SCHEMA, written);
1623
1661
  writeFile(target, join$1("lunora", "schema.ts"), LUNORA_SCHEMA, written);
1624
1662
  writeFile(target, join$1("lunora", "messages.ts"), LUNORA_MESSAGES, written);
1625
1663
  writeFile(target, join$1("src", "server.ts"), SERVER_ENTRY, written);
@@ -1819,6 +1857,7 @@ const stampLunoraDeps = (packageJsonText, distTag, versions) => {
1819
1857
  const PNPM_BUILT_DEPENDENCIES = [
1820
1858
  "@parcel/watcher",
1821
1859
  "esbuild",
1860
+ "lmdb",
1822
1861
  "msgpackr-extract",
1823
1862
  "rs-module-lexer",
1824
1863
  "sharp",
@@ -1833,8 +1872,8 @@ const pnpmWorkspaceYaml = () => [
1833
1872
  "# without the interactive `pnpm approve-builds` step; deny the optional native",
1834
1873
  "# builds a scaffold doesn't need (so no C/C++ toolchain is required).",
1835
1874
  "allowBuilds:",
1836
- ...PNPM_BUILT_DEPENDENCIES.map((name) => ` ${name}: true`),
1837
- ...PNPM_DENIED_BUILD_DEPENDENCIES.map((name) => ` ${name}: false`),
1875
+ ...PNPM_BUILT_DEPENDENCIES.map((name) => ` '${name}': true`),
1876
+ ...PNPM_DENIED_BUILD_DEPENDENCIES.map((name) => ` '${name}': false`),
1838
1877
  ""
1839
1878
  ].join("\n");
1840
1879
  const collectFiles = (directory) => {
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join } from '@visulima/path';
3
- import parseManifest from './parseManifest-BBrXCg9V.mjs';
3
+ import parseManifest from './parseManifest-Dbp-Q2q3.mjs';
4
4
 
5
5
  const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/gu;
6
6
  const stripControlChars = (value) => value === void 0 ? void 0 : value.replaceAll(CONTROL_CHARS, "");
@@ -1,16 +1,16 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
- import { dirname, join } from '@visulima/path';
2
+ import { dirname, join, relative } from '@visulima/path';
3
3
  import { DEV_VARS_FILE, parseDevVariableEntries } from '@lunora/config';
4
4
  import { modify, applyEdits, parse } from 'jsonc-parser';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { LunoraError } from '@lunora/errors';
7
7
  import { b as tuiConfirm } from './tui-prompts-BjEN8XgP.mjs';
8
- import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-CqM5FwGI.mjs';
8
+ import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BS5ig822.mjs';
9
9
  import { insertSchemaExtension } from './insertSchemaExtension-DAqbfr9Z.mjs';
10
10
  import { createHash } from 'node:crypto';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { downloadTemplate } from 'giget';
13
- import parseManifest from './parseManifest-BBrXCg9V.mjs';
13
+ import parseManifest from './parseManifest-Dbp-Q2q3.mjs';
14
14
 
15
15
  const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
16
16
  const STABLE_BRANCH = "main";
@@ -148,8 +148,8 @@ const resolveDepRange = (range) => {
148
148
  }
149
149
  return rest;
150
150
  };
151
- const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
152
- const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|runtime|server|values)(\/[^'"]*)?\1/gu;
151
+ const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/ratelimit", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
152
+ const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|ratelimit|runtime|server|values)(\/[^'"]*)?\1/gu;
153
153
  const projectUsesUmbrella = (projectRoot) => {
154
154
  const packageJsonPath = join(projectRoot, "package.json");
155
155
  if (!existsSync(packageJsonPath)) {
@@ -522,6 +522,93 @@ const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock,
522
522
  }
523
523
  return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
524
524
  };
525
+ const WORKER_ENTRY_FALLBACKS = ["src/server.ts", "src/server/index.ts", "src/index.ts", "src/worker.ts"];
526
+ const WRANGLER_MAIN_RE = /"main"\s*:\s*"([^"]+)"/u;
527
+ const readWranglerMain = (projectRoot) => {
528
+ for (const file of ["wrangler.jsonc", "wrangler.json"]) {
529
+ const path = join(projectRoot, file);
530
+ if (!existsSync(path)) {
531
+ continue;
532
+ }
533
+ const match = WRANGLER_MAIN_RE.exec(readFileSync(path, "utf8"));
534
+ if (match?.[1]) {
535
+ return match[1];
536
+ }
537
+ }
538
+ return void 0;
539
+ };
540
+ const findWorkerEntry = (projectRoot) => {
541
+ const main = readWranglerMain(projectRoot);
542
+ const candidates = main === void 0 ? WORKER_ENTRY_FALLBACKS : [main, ...WORKER_ENTRY_FALLBACKS];
543
+ for (const candidate of candidates) {
544
+ const absolute = join(projectRoot, candidate);
545
+ if (!existsSync(absolute)) {
546
+ continue;
547
+ }
548
+ const content = readFileSync(absolute, "utf8");
549
+ if (!content.includes("createShardDO(")) {
550
+ break;
551
+ }
552
+ return { entryPath: absolute, main: candidate, source: content };
553
+ }
554
+ return void 0;
555
+ };
556
+ const computeRelativeSpecifier = (entryPath, projectRoot, moduleName) => {
557
+ const importPath = relative(dirname(entryPath), join(projectRoot, "lunora", moduleName)).replaceAll("\\", "/");
558
+ return importPath.startsWith(".") ? importPath : `./${importPath}`;
559
+ };
560
+ const logClassAFallback = (entrypointReexports, logger) => {
561
+ for (const reexport of entrypointReexports) {
562
+ const specifier = `./lunora/${reexport.module}.js`;
563
+ const instruction = `Add \`export * from "${specifier}"\` to your worker entry`;
564
+ const suffix = reexport.comment ? ` (${reexport.comment})` : "";
565
+ logger.warn(`${instruction}${suffix}`);
566
+ }
567
+ return 0;
568
+ };
569
+ const buildReexportLines = (entrypointReexports, entryPath, projectRoot, source) => {
570
+ const lines = [];
571
+ for (const reexport of entrypointReexports) {
572
+ const specifier = computeRelativeSpecifier(entryPath, projectRoot, reexport.module);
573
+ const escapedSpecifier = specifier.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
574
+ const existingRe = new RegExp(String.raw`export\s+\*\s+from\s+["']${escapedSpecifier}\.js["']`, "u");
575
+ if (existingRe.test(source)) {
576
+ continue;
577
+ }
578
+ if (reexport.comment) {
579
+ lines.push(`
580
+ // ${reexport.comment}`);
581
+ }
582
+ lines.push(`export * from "${specifier}.js";`);
583
+ }
584
+ return lines;
585
+ };
586
+ const applyEntrypointReexports = (entrypointReexports, projectRoot, logger, diff) => {
587
+ if (entrypointReexports.length === 0) {
588
+ return 0;
589
+ }
590
+ const entry = findWorkerEntry(projectRoot);
591
+ if (entry === void 0) {
592
+ return logClassAFallback(entrypointReexports, logger);
593
+ }
594
+ const linesToAppend = buildReexportLines(entrypointReexports, entry.entryPath, projectRoot, entry.source);
595
+ if (linesToAppend.length === 0) {
596
+ return 0;
597
+ }
598
+ if (diff) {
599
+ for (const line of linesToAppend) {
600
+ if (line !== "") {
601
+ logger.info(`~ entrypoint: ${line}`);
602
+ }
603
+ }
604
+ return linesToAppend.length;
605
+ }
606
+ const separator = entry.source.endsWith("\n") ? "" : "\n";
607
+ writeFileSync(entry.entryPath, `${entry.source}${separator}${linesToAppend.join("\n")}
608
+ `, "utf8");
609
+ logger.success(`wrote ${String(linesToAppend.length)} entrypoint re-export(s) to ${relative(projectRoot, entry.entryPath)}`);
610
+ return linesToAppend.length;
611
+ };
525
612
  const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
526
613
  const written = [];
527
614
  const skipped = [];
@@ -534,6 +621,9 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
534
621
  const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
535
622
  (outcome.kind === "written" ? written : skipped).push(outcome.path);
536
623
  }
624
+ if (manifest.entrypointReexports !== void 0) {
625
+ applyEntrypointReexports(manifest.entrypointReexports, cwd, logger, reconcileOptions.diff === true);
626
+ }
537
627
  if (reconcileOptions.diff) {
538
628
  continue;
539
629
  }
@@ -707,6 +797,11 @@ const printPlan = (logger, manifest) => {
707
797
  const valueSuffix = variable.secret ? " (secret)" : ` = ${JSON.stringify(variable.value ?? "")}`;
708
798
  logger.info(` env ${variable.name}${valueSuffix}`);
709
799
  }
800
+ for (const reexport of manifest.entrypointReexports ?? []) {
801
+ const specifier = `./lunora/${reexport.module}`;
802
+ const suffix = reexport.comment ? ` // ${reexport.comment}` : "";
803
+ logger.info(` entry ${specifier}${suffix}`);
804
+ }
710
805
  };
711
806
  const printJsonPlan = (items) => {
712
807
  const planSnapshot = items.map(({ manifest }) => {
@@ -718,6 +813,9 @@ const printJsonPlan = (items) => {
718
813
  }),
719
814
  deps: Object.keys(manifest.deps ?? {}),
720
815
  devDependencies: Object.keys(manifest.devDependencies ?? {}),
816
+ entrypointReexports: (manifest.entrypointReexports ?? []).map((reexport) => {
817
+ return { module: reexport.module, ...reexport.comment ? { comment: reexport.comment } : {} };
818
+ }),
721
819
  envVars: (manifest.envVars ?? []).map((variable) => {
722
820
  return { name: variable.name, ...variable.secret ? { secret: true } : { value: variable.value ?? "" } };
723
821
  }),
@@ -3,6 +3,7 @@ import { LunoraError } from '@lunora/errors';
3
3
  const NEWLINE_PRESENT = /[\r\n]/u;
4
4
  const VALID_ENV_NAME = /^[A-Za-z_]\w*$/u;
5
5
  const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
6
+ const VALID_MODULE_PATH = /^[\w.-]+(?:\/[\w.-]+)*$/u;
6
7
  const parseManifest = (raw, itemName) => {
7
8
  if (typeof raw !== "object" || raw === null) {
8
9
  throw new LunoraError("INTERNAL", `registry.json for "${itemName}" is not an object`);
@@ -60,6 +61,24 @@ const parseManifest = (raw, itemName) => {
60
61
  const bindingRecord = value;
61
62
  return Array.isArray(bindingRecord.path) && bindingRecord.path.every((segment) => typeof segment === "string");
62
63
  }) : void 0;
64
+ const entrypointReexports = Array.isArray(record.entrypointReexports) ? record.entrypointReexports.filter(
65
+ (value) => typeof value === "object" && value !== null && typeof value.module === "string"
66
+ ).map((entry) => {
67
+ if (entry.module.includes("..") || entry.module.startsWith("/") || !VALID_MODULE_PATH.test(entry.module)) {
68
+ throw new LunoraError(
69
+ "INTERNAL",
70
+ `registry.json "${itemName}": entrypointReexports[].module "${entry.module}" must be a safe relative module path without path traversal or unsafe characters`
71
+ );
72
+ }
73
+ const reexport = { module: entry.module };
74
+ if (typeof entry.comment === "string") {
75
+ if (NEWLINE_PRESENT.test(entry.comment)) {
76
+ throw new LunoraError("INTERNAL", `registry.json "${itemName}": entrypointReexports[].comment must not contain a newline`);
77
+ }
78
+ reexport.comment = entry.comment;
79
+ }
80
+ return reexport;
81
+ }) : void 0;
63
82
  const envVariables = Array.isArray(record.envVars) ? record.envVars.filter(
64
83
  (value) => typeof value === "object" && value !== null && typeof value.name === "string"
65
84
  ).map((entry) => {
@@ -90,6 +109,7 @@ const parseManifest = (raw, itemName) => {
90
109
  description: typeof record.description === "string" ? record.description : void 0,
91
110
  devDependencies,
92
111
  docs: typeof record.docs === "string" ? record.docs : void 0,
112
+ entrypointReexports,
93
113
  envVars: envVariables,
94
114
  files,
95
115
  name,
@@ -0,0 +1,4 @@
1
+ import 'node:fs';
2
+ import '@visulima/path';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, g as runListCommand, b as runRegistryViewCommand } from './commands-ClEvcz3V.mjs';
4
+ import './buildRegistryIndex-BS5ig822.mjs';
@@ -1,4 +1,4 @@
1
- import { s as setBindingField } from './commands-vnRRkhNE.mjs';
1
+ import { s as setBindingField } from './commands-ClEvcz3V.mjs';
2
2
 
3
3
  const INVALID_SLUG_CHARS = /[^a-z0-9]+/u;
4
4
  const toKebabSlug = (input, min, max) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.73",
3
+ "version": "1.0.0-alpha.75",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -1,4 +0,0 @@
1
- import 'node:fs';
2
- import '@visulima/path';
3
- export { r as runAddCommand, a as runBuildIndexCommand, g as runListCommand, b as runRegistryViewCommand } from './commands-vnRRkhNE.mjs';
4
- import './buildRegistryIndex-CqM5FwGI.mjs';