@lunora/cli 1.0.0-alpha.22 → 1.0.0-alpha.24

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 (41) hide show
  1. package/dist/bin.mjs +1 -1
  2. package/dist/index.d.mts +33 -0
  3. package/dist/index.d.ts +33 -0
  4. package/dist/index.mjs +2 -2
  5. package/dist/packem_chunks/handler.mjs +15 -5
  6. package/dist/packem_chunks/handler10.mjs +8 -14
  7. package/dist/packem_chunks/handler11.mjs +19 -189
  8. package/dist/packem_chunks/handler12.mjs +176 -115
  9. package/dist/packem_chunks/handler13.mjs +118 -52
  10. package/dist/packem_chunks/handler14.mjs +50 -43
  11. package/dist/packem_chunks/handler15.mjs +46 -67
  12. package/dist/packem_chunks/handler16.mjs +73 -37
  13. package/dist/packem_chunks/handler17.mjs +38 -100
  14. package/dist/packem_chunks/handler18.mjs +87 -152
  15. package/dist/packem_chunks/handler19.mjs +148 -67
  16. package/dist/packem_chunks/handler2.mjs +1 -1
  17. package/dist/packem_chunks/handler20.mjs +71 -76
  18. package/dist/packem_chunks/handler21.mjs +71 -288
  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 +1 -1
  22. package/dist/packem_chunks/handler6.mjs +1 -1
  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 +311 -12
  26. package/dist/packem_chunks/planDevCommand.mjs +2 -2
  27. package/dist/packem_chunks/runCodegenCommand.mjs +1 -1
  28. package/dist/packem_chunks/runDeployCommand.mjs +103 -13
  29. package/dist/packem_chunks/runInitCommand.mjs +44 -6
  30. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +1 -1
  31. package/dist/packem_chunks/runResetCommand.mjs +2 -2
  32. package/dist/packem_chunks/runRpcCommand.mjs +1 -1
  33. package/dist/packem_shared/{COMMANDS-D3h9Iwvl.mjs → COMMANDS-B0ftFD_3.mjs} +20 -16
  34. package/dist/packem_shared/{command-BC30oSBW.mjs → command-D3lB_4Az.mjs} +5 -0
  35. package/dist/packem_shared/{commands-hl0mRqqg.mjs → commands-B-gR09Z_.mjs} +1 -1
  36. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
  37. package/dist/packem_shared/{runAddCommand-vJdgiR5t.mjs → runAddCommand-bnY6-HKb.mjs} +1 -1
  38. package/dist/packem_shared/{storage-B7hHSTZP.mjs → storage-BIsph-Vk.mjs} +1 -1
  39. package/dist/packem_shared/{tui-prompts-M6OWsuyw.mjs → tui-prompts-BjEN8XgP.mjs} +2 -7
  40. package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +47 -0
  41. package/package.json +3 -3
@@ -1,89 +1,170 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { runCodegen } from '@lunora/codegen';
4
- import { p as parseApiSpec } from '../packem_shared/api-spec-CtA6ilu4.mjs';
5
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
6
- import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-wUvAN6AL.mjs';
7
- import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
8
- import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
9
- import { validateWranglerProject } from '@lunora/config';
2
+ import { mkdtemp, writeFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { discoverSchema, schemaFromIr } from '@lunora/codegen';
5
+ import { seedPlan } from '@lunora/seed';
6
+ import { join } from '@visulima/path';
7
+ import { Project } from 'ts-morph';
8
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
9
+ import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
10
+ import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
11
+ import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
12
+ import { runResetCommand } from './runResetCommand.mjs';
10
13
 
11
- const runTypecheckStep = async (cwd, spawner) => {
12
- if (!existsSync(join(cwd, "tsconfig.json"))) {
13
- return { warning: "no tsconfig.json found — skipping TypeScript type-check" };
14
+ const isLocalUrl = (url) => {
15
+ if (url === void 0) {
16
+ return true;
17
+ }
18
+ try {
19
+ const { hostname } = new URL(url);
20
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
21
+ } catch {
22
+ return false;
14
23
  }
15
- const result = await spawner({ args: ["exec", "tsc", "--noEmit", "-p", "tsconfig.json"], command: "pnpm", cwd });
16
- return result.code === 0 ? {} : { error: `type errors: tsc --noEmit exited ${String(result.code)}` };
17
24
  };
18
- const reportVerifyResult = (logger, errors, warnings, wranglerPath) => {
19
- if (errors.length === 0 && warnings.length === 0) {
20
- logger.success("verify: project is valid");
21
- return { code: 0, errors: [], warnings: [], wranglerPath };
22
- }
23
- if (warnings.length > 0) {
24
- logger.warn("verify: warnings:");
25
- for (const warning of warnings) {
26
- logger.warn(` - ${warning}`);
27
- }
25
+ const ndjsonReplacer = (_key, value) => {
26
+ if (typeof value === "bigint") {
27
+ return Number(value);
28
+ }
29
+ if (value instanceof ArrayBuffer) {
30
+ return [...new Uint8Array(value)];
31
+ }
32
+ return value;
33
+ };
34
+ const seedFailure = (code) => {
35
+ return { code, conflicts: 0, generated: 0, inserted: 0, ndjson: "" };
36
+ };
37
+ const guardSeedTargets = (options, schemaPath) => {
38
+ if (!existsSync(schemaPath)) {
39
+ options.logger.error(`schema not found: ${schemaPath} — run \`vis generate lunora-table --name=<name>\` to create one`);
40
+ return seedFailure(1);
41
+ }
42
+ if (options.reset === true && (options.prod === true || !isLocalUrl(options.url))) {
43
+ options.logger.error("--reset only clears local .wrangler/state and cannot be combined with --prod or a remote --url");
44
+ return seedFailure(1);
28
45
  }
29
- if (errors.length > 0) {
30
- logger.error("verify: errors:");
31
- for (const error of errors) {
32
- logger.error(` - ${error}`);
46
+ return void 0;
47
+ };
48
+ const insertSeedRows = async (ndjson, generated, cwd, options) => {
49
+ const scratchDirectory = await mkdtemp(join(tmpdir(), "lunora-seed-"));
50
+ const temporaryFile = join(scratchDirectory, "rows.ndjson");
51
+ await writeFile(temporaryFile, ndjson, "utf8");
52
+ try {
53
+ const result = await runImportCommand({
54
+ batchSize: options.batchSize,
55
+ fetchImpl: options.fetchImpl,
56
+ file: temporaryFile,
57
+ logger: options.logger,
58
+ prod: options.prod,
59
+ token: options.token,
60
+ url: options.url
61
+ });
62
+ const conflicts = result.body?.conflicts ?? 0;
63
+ if (conflicts > 0) {
64
+ options.logger.warn(
65
+ `${String(conflicts)} row(s) skipped — their _id already exists. Seeding is deterministic; re-run with --reset to wipe local state first, or a different --seed for fresh ids.`
66
+ );
33
67
  }
34
- return { code: 1, errors, warnings, wranglerPath };
68
+ return { code: result.code, conflicts, generated, inserted: result.inserted, ndjson };
69
+ } finally {
70
+ await rm(scratchDirectory, { force: true, recursive: true }).catch(() => {
71
+ });
72
+ }
73
+ };
74
+ const validateSeedTable = (options, ir) => {
75
+ if (options.table === void 0 || ir.tables.some((table) => table.name === options.table)) {
76
+ return void 0;
35
77
  }
36
- logger.success("verify: project is valid (with warnings)");
37
- return { code: 0, errors: [], warnings, wranglerPath };
78
+ const available = ir.tables.map((table) => table.name).join(", ");
79
+ options.logger.error(`unknown table "${options.table}" schema defines: ${available || "(no tables)"}`);
80
+ return seedFailure(1);
38
81
  };
39
- const runVerifyCommand = async (options) => {
82
+ const confirmRemoteSeedTarget = async (options, generated) => {
83
+ const targetsRemote = options.prod === true || !isLocalUrl(options.url);
84
+ if (!targetsRemote || options.yes === true) {
85
+ return void 0;
86
+ }
87
+ if (!process.stdin.isTTY && options.confirm === void 0) {
88
+ options.logger.error("seed: refusing to insert into a non-local target without confirmation — re-run with --yes");
89
+ return seedFailure(1);
90
+ }
91
+ const confirmer = options.confirm ?? tuiConfirm;
92
+ const confirmed = await confirmer(`This will insert ${String(generated)} generated row(s) into ${options.url ?? "the production worker"}. Continue?`);
93
+ if (!confirmed) {
94
+ options.logger.info("seed: aborted");
95
+ return seedFailure(1);
96
+ }
97
+ return void 0;
98
+ };
99
+ const runSeedCommand = async (options) => {
40
100
  const cwd = options.cwd ?? process.cwd();
41
- const logger = loggerForFormat(options.format, options.logger);
42
- const formatError = validateOutputFormat("verify", options.format);
43
- if (formatError !== void 0) {
44
- options.logger.error(formatError);
45
- return { code: 1, error: formatError, errors: [], warnings: [], wranglerPath: void 0 };
46
- }
47
- const validation = validateWranglerProject({ projectRoot: cwd });
48
- const errors = [...validation.report.errors];
49
- const warnings = [...validation.report.warnings];
50
- try {
51
- const codegen = runCodegen({ apiSpec: options.apiSpec, dryRun: true, projectRoot: cwd });
52
- const gate = runSchemaDriftGate({ allowDrift: options.allowSchemaDrift === true, codegen, logger, readOnly: true });
53
- if (gate.blocked) {
54
- errors.push(gate.reason);
101
+ const schemaPath = join(cwd, "lunora", "schema.ts");
102
+ const guard = guardSeedTargets(options, schemaPath);
103
+ if (guard !== void 0) {
104
+ return guard;
105
+ }
106
+ const project = new Project({ skipAddingFilesFromTsConfig: true });
107
+ const ir = discoverSchema(project, schemaPath);
108
+ const unknownTable = validateSeedTable(options, ir);
109
+ if (unknownTable !== void 0) {
110
+ return unknownTable;
111
+ }
112
+ const schema = schemaFromIr(ir);
113
+ const plan = seedPlan(schema, {
114
+ defaultCount: options.count ?? 10,
115
+ only: options.table === void 0 ? void 0 : [options.table],
116
+ seed: options.seed ?? 0
117
+ });
118
+ const lines = [];
119
+ for (const { rows, table } of plan) {
120
+ for (const row of rows) {
121
+ lines.push(JSON.stringify({ doc: row, table }, ndjsonReplacer));
55
122
  }
56
- } catch (error) {
57
- const message = error instanceof Error ? error.message : String(error);
58
- errors.push(`codegen failed: ${message}`);
59
- }
60
- if (options.typecheck !== false) {
61
- const typecheck = await runTypecheckStep(cwd, options.spawner ?? defaultSpawner);
62
- if (typecheck.error !== void 0) {
63
- errors.push(typecheck.error);
123
+ }
124
+ const ndjson = lines.length > 0 ? `${lines.join("\n")}
125
+ ` : "";
126
+ const generated = lines.length;
127
+ if (options.dryRun === true) {
128
+ if (ndjson.length > 0) {
129
+ process.stdout.write(ndjson);
64
130
  }
65
- if (typecheck.warning !== void 0) {
66
- warnings.push(typecheck.warning);
131
+ options.logger.info(`generated ${String(generated)} row(s) across ${String(plan.length)} table(s) dry run, nothing inserted`);
132
+ return { code: 0, conflicts: 0, generated, inserted: 0, ndjson };
133
+ }
134
+ if (options.reset === true) {
135
+ const reset = await runResetCommand({ cwd, logger: options.logger, yes: true });
136
+ if (reset.code !== 0) {
137
+ return { code: reset.code, conflicts: 0, generated, inserted: 0, ndjson };
67
138
  }
68
139
  }
69
- const result = reportVerifyResult(logger, errors, warnings, validation.wranglerPath);
70
- if (isJsonFormat(options.format)) {
71
- printJson(result);
140
+ if (generated === 0) {
141
+ options.logger.warn("no rows generated — nothing to insert");
142
+ return { code: 0, conflicts: 0, generated: 0, inserted: 0, ndjson };
143
+ }
144
+ const aborted = await confirmRemoteSeedTarget(options, generated);
145
+ if (aborted !== void 0) {
146
+ return aborted;
72
147
  }
73
- return result;
148
+ return insertSeedRows(ndjson, generated, cwd, options);
74
149
  };
75
150
  const execute = defineHandler(async ({ cwd, logger, options }) => {
76
- const result = await runVerifyCommand({
77
- allowSchemaDrift: options.allowSchemaDrift === true,
78
- apiSpec: parseApiSpec(options.apiSpec),
151
+ const result = await runSeedCommand({
152
+ batchSize: options.batchSize,
153
+ count: options.count,
79
154
  cwd,
80
- format: options.format,
155
+ dryRun: options.dryRun === true,
81
156
  logger,
82
- // `--no-typecheck` is declared as a `no-*` option but cerebro exposes it
83
- // under the negated `typecheck` key (false when passed, true when absent).
84
- typecheck: options.typecheck === false ? false : void 0
157
+ prod: options.prod === true,
158
+ reset: options.reset === true,
159
+ seed: options.seed,
160
+ table: options.table,
161
+ token: options.token,
162
+ // Resolve the link here (only under --prod) so seed's own remote/confirm
163
+ // logic and the downstream import both see the same effective target.
164
+ url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url }),
165
+ yes: options.yes === true
85
166
  });
86
167
  return { code: result.code };
87
168
  });
88
169
 
89
- export { execute, runVerifyCommand };
170
+ export { execute, runSeedCommand };
@@ -1,7 +1,7 @@
1
1
  import { mkdtempSync, rmSync, existsSync, readdirSync, statSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join, relative } from 'node:path';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
4
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
5
5
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
6
6
 
7
7
  const walk = (root) => {
@@ -1,94 +1,89 @@
1
- import { readFileSync, existsSync } from 'node:fs';
1
+ import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { parse } from 'jsonc-parser';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
5
- import { o as openUrl } from '../packem_shared/open-url-Dfq6fAyT.mjs';
3
+ import { runCodegen } from '@lunora/codegen';
4
+ import { p as parseApiSpec } from '../packem_shared/api-spec-CtA6ilu4.mjs';
5
+ import { d as defineHandler } from '../packem_shared/command-D3lB_4Az.mjs';
6
+ import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-wUvAN6AL.mjs';
7
+ import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
8
+ import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
9
+ import { validateWranglerProject } from '@lunora/config';
6
10
 
7
- const DEFAULT_DEV_PORT = 8787;
8
- const STUDIO_PATH = "/_lunora/studio";
9
- const findWranglerFile = (projectRoot) => {
10
- for (const candidate of ["wrangler.jsonc", "wrangler.json"]) {
11
- const fullPath = join(projectRoot, candidate);
12
- if (existsSync(fullPath)) {
13
- return fullPath;
14
- }
15
- }
16
- return void 0;
17
- };
18
- const readWrangler = (projectRoot) => {
19
- const file = findWranglerFile(projectRoot);
20
- if (!file) {
21
- return void 0;
22
- }
23
- try {
24
- const parsed = parse(readFileSync(file, "utf8"));
25
- return parsed !== null && typeof parsed === "object" ? parsed : void 0;
26
- } catch {
27
- return void 0;
11
+ const runTypecheckStep = async (cwd, spawner) => {
12
+ if (!existsSync(join(cwd, "tsconfig.json"))) {
13
+ return { warning: "no tsconfig.json found — skipping TypeScript type-check" };
28
14
  }
15
+ const result = await spawner({ args: ["exec", "tsc", "--noEmit", "-p", "tsconfig.json"], command: "pnpm", cwd });
16
+ return result.code === 0 ? {} : { error: `type errors: tsc --noEmit exited ${String(result.code)}` };
29
17
  };
30
- const resolveDevPort = (wrangler) => {
31
- if (!wrangler) {
32
- return DEFAULT_DEV_PORT;
18
+ const reportVerifyResult = (logger, errors, warnings, wranglerPath) => {
19
+ if (errors.length === 0 && warnings.length === 0) {
20
+ logger.success("verify: project is valid");
21
+ return { code: 0, errors: [], warnings: [], wranglerPath };
33
22
  }
34
- const { dev } = wrangler;
35
- if (dev !== null && typeof dev === "object") {
36
- const { port } = dev;
37
- if (typeof port === "number" && Number.isFinite(port)) {
38
- return port;
23
+ if (warnings.length > 0) {
24
+ logger.warn("verify: warnings:");
25
+ for (const warning of warnings) {
26
+ logger.warn(` - ${warning}`);
39
27
  }
40
28
  }
41
- return DEFAULT_DEV_PORT;
42
- };
43
- const resolveRemoteUrl = (wrangler) => {
44
- if (!wrangler) {
45
- return void 0;
46
- }
47
- const { routes } = wrangler;
48
- if (Array.isArray(routes) && routes.length > 0) {
49
- const first = routes[0];
50
- if (typeof first === "string") {
51
- return `https://${first.split("/")[0] ?? first}${STUDIO_PATH}`;
52
- }
53
- if (first !== null && typeof first === "object") {
54
- const { pattern } = first;
55
- if (typeof pattern === "string" && pattern.length > 0) {
56
- return `https://${pattern.split("/")[0] ?? pattern}${STUDIO_PATH}`;
57
- }
29
+ if (errors.length > 0) {
30
+ logger.error("verify: errors:");
31
+ for (const error of errors) {
32
+ logger.error(` - ${error}`);
58
33
  }
34
+ return { code: 1, errors, warnings, wranglerPath };
59
35
  }
60
- const { name } = wrangler;
61
- if (typeof name === "string" && name.length > 0) {
62
- return `https://${name}.workers.dev${STUDIO_PATH}`;
63
- }
64
- return void 0;
36
+ logger.success("verify: project is valid (with warnings)");
37
+ return { code: 0, errors: [], warnings, wranglerPath };
65
38
  };
66
- const runViewCommand = async (options) => {
39
+ const runVerifyCommand = async (options) => {
67
40
  const cwd = options.cwd ?? process.cwd();
68
- const wrangler = readWrangler(cwd);
69
- const { logger } = options;
70
- let url;
71
- if (options.remote) {
72
- url = resolveRemoteUrl(wrangler);
73
- if (!url) {
74
- logger.error("view --remote: could not determine the remote URL from wrangler config (set `routes` or `name`).");
75
- return { code: 1, url: void 0 };
76
- }
77
- } else {
78
- url = `http://localhost:${String(resolveDevPort(wrangler))}${STUDIO_PATH}`;
41
+ const logger = loggerForFormat(options.format, options.logger);
42
+ const formatError = validateOutputFormat("verify", options.format);
43
+ if (formatError !== void 0) {
44
+ options.logger.error(formatError);
45
+ return { code: 1, error: formatError, errors: [], warnings: [], wranglerPath: void 0 };
79
46
  }
80
- logger.info(`opening ${url}`);
47
+ const validation = validateWranglerProject({ projectRoot: cwd });
48
+ const errors = [...validation.report.errors];
49
+ const warnings = [...validation.report.warnings];
81
50
  try {
82
- await openUrl(url, { opener: options.opener });
51
+ const codegen = runCodegen({ apiSpec: options.apiSpec, dryRun: true, projectRoot: cwd });
52
+ const gate = runSchemaDriftGate({ allowDrift: options.allowSchemaDrift === true, codegen, logger, readOnly: true });
53
+ if (gate.blocked) {
54
+ errors.push(gate.reason);
55
+ }
83
56
  } catch (error) {
84
57
  const message = error instanceof Error ? error.message : String(error);
85
- logger.error(`view: failed to open URL: ${message}`);
86
- return { code: 1, url };
58
+ errors.push(`codegen failed: ${message}`);
59
+ }
60
+ if (options.typecheck !== false) {
61
+ const typecheck = await runTypecheckStep(cwd, options.spawner ?? defaultSpawner);
62
+ if (typecheck.error !== void 0) {
63
+ errors.push(typecheck.error);
64
+ }
65
+ if (typecheck.warning !== void 0) {
66
+ warnings.push(typecheck.warning);
67
+ }
68
+ }
69
+ const result = reportVerifyResult(logger, errors, warnings, validation.wranglerPath);
70
+ if (isJsonFormat(options.format)) {
71
+ printJson(result);
87
72
  }
88
- return { code: 0, url };
73
+ return result;
89
74
  };
90
- const execute = defineHandler(
91
- ({ cwd, logger, options }) => runViewCommand({ cwd, logger, remote: options.remote === true })
92
- );
75
+ const execute = defineHandler(async ({ cwd, logger, options }) => {
76
+ const result = await runVerifyCommand({
77
+ allowSchemaDrift: options.allowSchemaDrift === true,
78
+ apiSpec: parseApiSpec(options.apiSpec),
79
+ cwd,
80
+ format: options.format,
81
+ logger,
82
+ // `--no-typecheck` is declared as a `no-*` option but cerebro exposes it
83
+ // under the negated `typecheck` key (false when passed, true when absent).
84
+ typecheck: options.typecheck === false ? false : void 0
85
+ });
86
+ return { code: result.code };
87
+ });
93
88
 
94
- export { execute, runViewCommand };
89
+ export { execute, runVerifyCommand };