@lunora/cli 1.0.0-alpha.1 → 1.0.0-alpha.100
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/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/bin.mjs +1 -1
- package/dist/index.d.mts +473 -344
- package/dist/index.d.ts +473 -344
- package/dist/index.mjs +10 -11
- package/dist/packem_chunks/handler.mjs +91 -7
- package/dist/packem_chunks/handler10.mjs +8 -14
- package/dist/packem_chunks/handler11.mjs +20 -189
- package/dist/packem_chunks/handler12.mjs +176 -115
- package/dist/packem_chunks/handler13.mjs +118 -52
- package/dist/packem_chunks/handler14.mjs +50 -43
- package/dist/packem_chunks/handler15.mjs +27 -6
- package/dist/packem_chunks/handler16.mjs +6 -3
- package/dist/packem_chunks/handler17.mjs +1 -1
- package/dist/packem_chunks/handler18.mjs +5 -7
- package/dist/packem_chunks/handler19.mjs +44 -5
- package/dist/packem_chunks/handler2.mjs +6 -4
- package/dist/packem_chunks/handler20.mjs +2 -2
- package/dist/packem_chunks/handler21.mjs +158 -267
- package/dist/packem_chunks/handler3.mjs +8 -4
- package/dist/packem_chunks/handler4.mjs +2 -2
- package/dist/packem_chunks/handler5.mjs +7 -4
- package/dist/packem_chunks/handler6.mjs +10 -7
- package/dist/packem_chunks/handler7.mjs +2 -2
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +330 -12
- package/dist/packem_chunks/planDevCommand.mjs +759 -121
- package/dist/packem_chunks/runCodegenCommand.mjs +5 -4
- package/dist/packem_chunks/runDeployCommand.mjs +137 -22
- package/dist/packem_chunks/runInitCommand.mjs +2053 -112
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +98 -51
- package/dist/packem_chunks/runResetCommand.mjs +4 -4
- package/dist/packem_chunks/runRpcCommand.mjs +3 -2
- package/dist/packem_shared/{COMMANDS-1V_KEx35.mjs → COMMANDS-BOJIDoVY.mjs} +154 -28
- package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs} +7 -2
- package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
- package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-BS5ig822.mjs} +1 -1
- package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +31 -0
- package/dist/packem_shared/{command-BDXcJCCJ.mjs → command-lYnl4QyF.mjs} +6 -1
- package/dist/packem_shared/{runAddCommand-BZGkRnBs.mjs → commands-D5Yxt9VY.mjs} +331 -33
- package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-CIWSHrTL.mjs} +40 -8
- package/dist/packem_shared/{defaultSpawner-DxI3mebw.mjs → createRecordingSpawner-WuSn20kb.mjs} +23 -2
- package/dist/packem_shared/detect-package-manager-v4hHpQd0.mjs +62 -0
- package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
- package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
- package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
- package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-B4642rjE.mjs} +1 -1
- package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-Dbp-Q2q3.mjs} +36 -9
- package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
- package/dist/packem_shared/runAddCommand-D1hgfqFQ.mjs +4 -0
- package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
- package/dist/packem_shared/storage-BXU4ax4O.mjs +84 -0
- package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
- package/dist/packem_shared/wrangler-secrets-Coni-mER.mjs +49 -0
- package/package.json +23 -17
- package/skills/lunora-quickstart/SKILL.md +26 -5
- package/skills/lunora-setup-storage/SKILL.md +7 -3
- package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync,
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { discoverSchema, discoverMigrations } from '@lunora/codegen';
|
|
4
|
+
import { isInteractive, promptSelect, promptText } from '@lunora/config';
|
|
5
|
+
import { LunoraError } from '@lunora/errors';
|
|
4
6
|
import { join } from '@visulima/path';
|
|
5
7
|
import { Project } from 'ts-morph';
|
|
6
8
|
import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
|
|
7
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
8
|
-
import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-
|
|
9
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
10
|
+
import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-BeDvvNiF.mjs';
|
|
9
11
|
import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
10
|
-
import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-
|
|
11
|
-
import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-
|
|
12
|
+
import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
|
|
13
|
+
import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs';
|
|
12
14
|
|
|
13
15
|
const SNAPSHOT_FILENAME = ".snapshot.json";
|
|
14
16
|
const NON_ALPHANUMERIC = /[^\da-z]+/gu;
|
|
@@ -41,12 +43,12 @@ const loadSnapshot = (path) => {
|
|
|
41
43
|
const raw = readFileSync(path, "utf8");
|
|
42
44
|
const parsed = JSON.parse(raw);
|
|
43
45
|
if (parsed.version !== 1) {
|
|
44
|
-
throw new
|
|
46
|
+
throw new LunoraError("INTERNAL", `unsupported snapshot version: ${parsed.version}`);
|
|
45
47
|
}
|
|
46
48
|
return parsed;
|
|
47
49
|
} catch (error) {
|
|
48
50
|
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
-
throw new
|
|
51
|
+
throw new LunoraError("INTERNAL", `failed to read ${path}: ${message}`, { cause: error });
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
const runMigrateGenerateCommand = (options) => {
|
|
@@ -151,7 +153,47 @@ const camelCase = (slug) => slug.split("-").filter((part) => part.length > 0).ma
|
|
|
151
153
|
}
|
|
152
154
|
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
153
155
|
}).join("");
|
|
154
|
-
const
|
|
156
|
+
const discoverKnownTables = (cwd) => {
|
|
157
|
+
const schemaPath = join(cwd, "lunora", "schema.ts");
|
|
158
|
+
if (!existsSync(schemaPath)) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
163
|
+
return discoverSchema(project, schemaPath).tables.map((table) => table.name);
|
|
164
|
+
} catch {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const promptForTable = async (tables) => {
|
|
169
|
+
if (tables.length > 0) {
|
|
170
|
+
return promptSelect(
|
|
171
|
+
"Which table does this migration iterate?",
|
|
172
|
+
tables.map((name) => {
|
|
173
|
+
return { label: name, value: name };
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return promptText("Target table for the migration: ");
|
|
178
|
+
};
|
|
179
|
+
const resolveCreateTable = async (cwd, options) => {
|
|
180
|
+
if (options.table !== void 0) {
|
|
181
|
+
return options.table;
|
|
182
|
+
}
|
|
183
|
+
const prompt = options.promptTable ?? (isInteractive() ? promptForTable : void 0);
|
|
184
|
+
if (prompt === void 0) {
|
|
185
|
+
options.logger.error("migrate create requires a target table when not running interactively — re-run with --table <table>");
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
const answer = await prompt(discoverKnownTables(cwd));
|
|
189
|
+
const table = answer?.trim();
|
|
190
|
+
if (table === void 0 || table === "") {
|
|
191
|
+
options.logger.error("no table selected — re-run with --table <table>");
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
return table;
|
|
195
|
+
};
|
|
196
|
+
const runMigrateCreateCommand = async (options) => {
|
|
155
197
|
const cwd = options.cwd ?? process.cwd();
|
|
156
198
|
const slug = kebabCase(options.name);
|
|
157
199
|
if (slug === "") {
|
|
@@ -165,9 +207,12 @@ const runMigrateCreateCommand = (options) => {
|
|
|
165
207
|
);
|
|
166
208
|
return { code: 1, file: "" };
|
|
167
209
|
}
|
|
168
|
-
const table =
|
|
210
|
+
const table = await resolveCreateTable(cwd, options);
|
|
211
|
+
if (table === void 0) {
|
|
212
|
+
return { code: 1, file: "" };
|
|
213
|
+
}
|
|
169
214
|
if (!IDENTIFIER_PATTERN.test(table)) {
|
|
170
|
-
options.logger.error(`invalid
|
|
215
|
+
options.logger.error(`invalid table: "${table}" — must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
171
216
|
return { code: 1, file: "" };
|
|
172
217
|
}
|
|
173
218
|
const lunoraDirectory = join(cwd, "lunora");
|
|
@@ -195,9 +240,6 @@ ${content}`;
|
|
|
195
240
|
${block}
|
|
196
241
|
`, "utf8");
|
|
197
242
|
options.logger.success(`scaffolded migration "${slug}" in ${file}`);
|
|
198
|
-
if (options.table === void 0) {
|
|
199
|
-
options.logger.warn(`set the \`table\` field on "${slug}" — it defaults to "${table}"`);
|
|
200
|
-
}
|
|
201
243
|
return { code: 0, file };
|
|
202
244
|
};
|
|
203
245
|
const resolveMigrationTable = (cwd, id) => {
|
|
@@ -305,44 +347,49 @@ const runMigrateToHyperdriveCommand = async (options) => {
|
|
|
305
347
|
}
|
|
306
348
|
const temporaryDirectory = options.out === void 0 ? mkdtempSync(join(tmpdir(), "lunora-d1ps-")) : void 0;
|
|
307
349
|
const dumpPath = options.out ?? join(temporaryDirectory, "dump.ndjson");
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
350
|
+
try {
|
|
351
|
+
logger.info(`Exporting .global() data from the D1 source (${fromUrl ?? "http://localhost:8787"}) …`);
|
|
352
|
+
const exportResult = await runExportCommand({
|
|
353
|
+
fetchImpl: options.fetchImpl,
|
|
354
|
+
logger,
|
|
355
|
+
out: dumpPath,
|
|
356
|
+
prod: options.prod,
|
|
357
|
+
tables: options.tables,
|
|
358
|
+
token: options.fromToken,
|
|
359
|
+
url: fromUrl
|
|
360
|
+
});
|
|
361
|
+
if (exportResult.code !== 0) {
|
|
362
|
+
return { code: exportResult.code };
|
|
363
|
+
}
|
|
364
|
+
logger.info(`Exported ${String(exportResult.rows)} row(s) (${String(exportResult.bytes)} bytes).`);
|
|
365
|
+
logger.info(`Importing into the Hyperdrive target (${toUrl ?? "http://localhost:8787"}) …`);
|
|
366
|
+
const importResult = await runImportCommand({
|
|
367
|
+
batchSize: options.batchSize,
|
|
368
|
+
fetchImpl: options.fetchImpl,
|
|
369
|
+
file: dumpPath,
|
|
370
|
+
logger,
|
|
371
|
+
prod: options.prod,
|
|
372
|
+
token: options.toToken,
|
|
373
|
+
url: toUrl
|
|
374
|
+
});
|
|
375
|
+
if (importResult.code !== 0) {
|
|
376
|
+
return { code: importResult.code };
|
|
377
|
+
}
|
|
378
|
+
if (importResult.inserted === exportResult.rows) {
|
|
379
|
+
logger.info(
|
|
380
|
+
`✓ Migrated ${String(exportResult.rows)} row(s) — counts match. Verify your app reads from Hyperdrive, then decommission the D1 binding.`
|
|
381
|
+
);
|
|
382
|
+
} else {
|
|
383
|
+
logger.warn(
|
|
384
|
+
`Imported ${String(importResult.inserted)} of ${String(exportResult.rows)} exported row(s) — the remainder likely already existed in the target (see conflicts above). Re-run after resolving, or inspect the dump with --out.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return { code: 0 };
|
|
388
|
+
} finally {
|
|
389
|
+
if (temporaryDirectory !== void 0) {
|
|
390
|
+
rmSync(temporaryDirectory, { force: true, recursive: true });
|
|
391
|
+
}
|
|
344
392
|
}
|
|
345
|
-
return { code: 0 };
|
|
346
393
|
};
|
|
347
394
|
const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
348
395
|
const sub = argument[0];
|
|
@@ -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-
|
|
3
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
4
|
+
import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
|
|
5
5
|
|
|
6
6
|
const runResetCommand = async (options) => {
|
|
7
7
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -15,8 +15,8 @@ const runResetCommand = async (options) => {
|
|
|
15
15
|
options.logger.error("reset: stdin is not a TTY — re-run with --yes to confirm deleting .wrangler/state");
|
|
16
16
|
return { code: 1, removed: [] };
|
|
17
17
|
}
|
|
18
|
-
const confirmer = options.confirm ??
|
|
19
|
-
const confirmed = await confirmer("This will delete .wrangler/state. Continue?
|
|
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,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
2
3
|
import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
3
4
|
|
|
4
5
|
const parseArgsJson = (raw) => {
|
|
@@ -9,7 +10,7 @@ const parseArgsJson = (raw) => {
|
|
|
9
10
|
return JSON.parse(raw);
|
|
10
11
|
} catch (error) {
|
|
11
12
|
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
-
throw new
|
|
13
|
+
throw new LunoraError("INTERNAL", `failed to parse --args as JSON: ${message}`, { cause: error });
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
const TRAILING_SLASH = /\/$/u;
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, readFileSync, lstatSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolveHint, isLunoraError, flattenHint, findSolutionByMessage } from '@lunora/errors';
|
|
2
3
|
import { createCerebro } from '@visulima/cerebro';
|
|
3
4
|
import completionCommand from '@visulima/cerebro/command/completion';
|
|
4
5
|
import versionCommand from '@visulima/cerebro/command/version';
|
|
5
|
-
import { A as API_SPEC_HELP } from './api-spec-
|
|
6
|
-
import { createLogger } from './createLogger-
|
|
7
|
-
import {
|
|
6
|
+
import { A as API_SPEC_HELP } from './api-spec-Bx0iKbxA.mjs';
|
|
7
|
+
import { createLogger } from './createLogger-CIWSHrTL.mjs';
|
|
8
|
+
import { VisulimaError, renderError } from '@visulima/error';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
8
10
|
import { join } from 'node:path';
|
|
9
11
|
|
|
10
12
|
const addCommand = {
|
|
11
|
-
argument: {
|
|
12
|
-
|
|
13
|
+
argument: {
|
|
14
|
+
description: "Feature or registry item: ai | auth | email | storage | crons | presence | queue | workflow | flags | backup | …",
|
|
15
|
+
name: "feature",
|
|
16
|
+
type: String
|
|
17
|
+
},
|
|
18
|
+
description: "Add a feature or registry item (ai, auth, email, storage, crons, …) to the current Lunora project",
|
|
13
19
|
examples: [
|
|
14
20
|
["lunora add auth", "Add authentication (asks which provider)"],
|
|
15
21
|
["lunora add auth --provider clerk", "Add Clerk auth without prompting"],
|
|
16
22
|
["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
|
|
23
|
+
["lunora add storage", "Add the R2 storage registry item (asks for the bucket name)"],
|
|
24
|
+
["lunora add storage --bucket my-app-uploads", "Add storage with a bucket name, no prompt"],
|
|
25
|
+
["lunora add crons", "Add the scheduled-jobs registry item"],
|
|
26
|
+
["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
|
|
19
27
|
],
|
|
20
28
|
group: "Project",
|
|
21
29
|
loader: () => import('../packem_chunks/handler.mjs').then((m) => {
|
|
@@ -24,10 +32,19 @@ const addCommand = {
|
|
|
24
32
|
name: "add",
|
|
25
33
|
options: [
|
|
26
34
|
{ description: "auth: provider to use without prompting (auth | clerk | auth0)", name: "provider", type: String },
|
|
27
|
-
{ description: "
|
|
35
|
+
{ description: "auth: D1 database name to use without prompting (lowercase alphanumeric + hyphens)", name: "db", type: String },
|
|
36
|
+
{ description: "storage: R2 bucket name to use without prompting (lowercase alphanumeric + hyphens)", name: "bucket", type: String },
|
|
37
|
+
{ description: "email: verified destination address to use without prompting", name: "mail-to", type: String },
|
|
38
|
+
{ description: "Skip prompts (auth provider, DB name, bucket name, mail destination) and use the defaults", name: "yes", type: Boolean },
|
|
28
39
|
{ description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
|
|
29
40
|
{ description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
|
|
30
|
-
{
|
|
41
|
+
{
|
|
42
|
+
description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
43
|
+
name: "ref",
|
|
44
|
+
type: String
|
|
45
|
+
},
|
|
46
|
+
{ description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
|
|
47
|
+
{ description: "Output format: pretty (default) or json", name: "format", type: String }
|
|
31
48
|
]
|
|
32
49
|
};
|
|
33
50
|
|
|
@@ -202,9 +219,19 @@ const deploymentsCommand = {
|
|
|
202
219
|
};
|
|
203
220
|
|
|
204
221
|
const devCommand = {
|
|
222
|
+
argument: {
|
|
223
|
+
description: "Optional subcommand: stop (shut the running dev server down) | status (report it) | logs (print its captured output)",
|
|
224
|
+
name: "args",
|
|
225
|
+
type: String
|
|
226
|
+
},
|
|
205
227
|
description: "Run the dev stack: wrangler worker + studio + codegen watch",
|
|
206
228
|
examples: [
|
|
207
229
|
["lunora dev", "Run the worker + studio + codegen watch"],
|
|
230
|
+
["lunora dev --background", "Run detached: blocks until ready, prints URL + PID, then returns"],
|
|
231
|
+
["lunora dev stop", "Stop the background/tracked dev server (idempotent)"],
|
|
232
|
+
["lunora dev status", "Report the running dev server (URL, PID, uptime)"],
|
|
233
|
+
["lunora dev logs", "Print the captured dev-server log (background runs)"],
|
|
234
|
+
["lunora dev --json", "Machine-readable JSON log lines (also LUNORA_LOG_JSON=1)"],
|
|
208
235
|
["lunora dev --no-studio", "Skip the embedded studio server"],
|
|
209
236
|
["lunora dev --worker-port 8080", "Use a custom wrangler dev port"],
|
|
210
237
|
["lunora dev --remote", "Proxy D1/KV/R2 to the deployed worker (also LUNORA_REMOTE=1)"]
|
|
@@ -214,10 +241,19 @@ const devCommand = {
|
|
|
214
241
|
return { default: m.execute };
|
|
215
242
|
}),
|
|
216
243
|
name: "dev",
|
|
244
|
+
// KEEP IN SYNC with `daemonArguments` in `./lifecycle.ts`: a new flag that
|
|
245
|
+
// must reach a `--background` daemon has to be forwarded there explicitly.
|
|
217
246
|
options: [
|
|
218
247
|
{ description: `Which API spec(s) codegen emits: ${API_SPEC_HELP} (default openapi)`, name: "api-spec", type: String },
|
|
219
248
|
{ description: "Studio server port (default 6173)", name: "port", type: Number },
|
|
220
249
|
{ description: "wrangler dev port (default 8787)", name: "worker-port", type: Number },
|
|
250
|
+
{
|
|
251
|
+
description: "Run the dev server as a managed background process (auto-enabled when an AI agent is detected; LUNORA_AGENT_MODE=0 disables)",
|
|
252
|
+
name: "background",
|
|
253
|
+
type: Boolean
|
|
254
|
+
},
|
|
255
|
+
{ description: "Emit machine-readable JSON log lines (also LUNORA_LOG_JSON=1; auto-enabled for AI agents)", name: "json", type: Boolean },
|
|
256
|
+
{ description: "How many trailing lines `lunora dev logs` prints (default 100, 0 = all)", name: "lines", type: Number },
|
|
221
257
|
{ description: "Don't start the embedded studio server", name: "no-studio", type: Boolean },
|
|
222
258
|
{ description: "Don't watch + regenerate codegen", name: "no-codegen", type: Boolean },
|
|
223
259
|
{ description: "Proxy D1/KV/R2 bindings to the deployed worker (or set LUNORA_REMOTE=1)", name: "remote", type: Boolean }
|
|
@@ -250,21 +286,24 @@ const doctorCommand = {
|
|
|
250
286
|
};
|
|
251
287
|
|
|
252
288
|
const envCommand = {
|
|
253
|
-
argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | push | diff | doctor", name: "subcommand", type: String },
|
|
254
|
-
description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | push | diff | doctor)",
|
|
289
|
+
argument: { description: "list | get <KEY> | set <KEY> <VALUE> | unset <KEY> | generate [KEY] | push | diff | doctor", name: "subcommand", type: String },
|
|
290
|
+
description: "Manage .dev.vars and sync secrets via wrangler (list | get | set | unset | generate | push | diff | doctor)",
|
|
255
291
|
examples: [
|
|
256
292
|
["lunora env list", "List .dev.vars keys"],
|
|
257
293
|
["lunora env set API_KEY secret", "Set a local variable"],
|
|
294
|
+
["lunora env generate", "Generate strong values for the project's secrets (print KEY=value)"],
|
|
295
|
+
["lunora env generate AUTH_SECRET --set", "Generate one secret and write it to .dev.vars"],
|
|
258
296
|
["lunora env push --yes", "Upload secrets to Cloudflare"],
|
|
259
297
|
["lunora env diff", "Compare local .dev.vars keys against Cloudflare"]
|
|
260
298
|
],
|
|
261
299
|
group: "Data",
|
|
262
|
-
loader: () => import('../packem_chunks/
|
|
300
|
+
loader: () => import('../packem_chunks/handler9.mjs').then((m) => {
|
|
263
301
|
return { default: m.execute };
|
|
264
302
|
}),
|
|
265
303
|
name: "env",
|
|
266
304
|
options: [
|
|
267
305
|
{ description: "Target production for `push` (passes --env production to wrangler)", name: "prod", type: Boolean },
|
|
306
|
+
{ description: "For `generate` — write the generated secrets into .dev.vars instead of printing them", name: "set", type: Boolean },
|
|
268
307
|
{
|
|
269
308
|
description: "Push secrets to a temporary-account deployment when unauthenticated (wrangler secret put --temporary). Errors if you're already authenticated.",
|
|
270
309
|
name: "temporary",
|
|
@@ -282,7 +321,7 @@ const exportCommand = {
|
|
|
282
321
|
["lunora export --tables messages,users", "Export only specific tables"]
|
|
283
322
|
],
|
|
284
323
|
group: "Data",
|
|
285
|
-
loader: () => import('../packem_chunks/
|
|
324
|
+
loader: () => import('../packem_chunks/handler10.mjs').then((m) => {
|
|
286
325
|
return { default: m.execute };
|
|
287
326
|
}),
|
|
288
327
|
name: "export",
|
|
@@ -304,7 +343,7 @@ const importCommand = {
|
|
|
304
343
|
description: "Bulk-insert rows from an NDJSON file via the worker's admin endpoint",
|
|
305
344
|
examples: [["lunora import backup.ndjson", "Bulk-insert rows from an NDJSON file"]],
|
|
306
345
|
group: "Data",
|
|
307
|
-
loader: () => import('../packem_chunks/
|
|
346
|
+
loader: () => import('../packem_chunks/handler11.mjs').then((m) => {
|
|
308
347
|
return { default: m.execute };
|
|
309
348
|
}),
|
|
310
349
|
name: "import",
|
|
@@ -312,6 +351,7 @@ const importCommand = {
|
|
|
312
351
|
{ description: "Wrap each bare doc as `{table:<name>,doc:...}`", name: "table", type: String },
|
|
313
352
|
{ description: "Rows per HTTP request (default 500)", name: "batch-size", type: Number },
|
|
314
353
|
{ description: "Target production — requires an explicit --url", name: "prod", type: Boolean },
|
|
354
|
+
{ description: "Confirm bulk-writing production (required with --prod)", name: "yes", type: Boolean },
|
|
315
355
|
{ description: "Worker URL (default http://localhost:8787)", name: "url", type: String },
|
|
316
356
|
{
|
|
317
357
|
description: "Admin bearer token (prefer LUNORA_ADMIN_TOKEN; --token is visible to other local processes via the process table)",
|
|
@@ -328,7 +368,7 @@ const infoCommand = {
|
|
|
328
368
|
["lunora info --json", "Emit a JSON snapshot"]
|
|
329
369
|
],
|
|
330
370
|
group: "Project",
|
|
331
|
-
loader: () => import('../packem_chunks/
|
|
371
|
+
loader: () => import('../packem_chunks/handler12.mjs').then((m) => {
|
|
332
372
|
return { default: m.execute };
|
|
333
373
|
}),
|
|
334
374
|
name: "info",
|
|
@@ -340,8 +380,10 @@ const initCommand = {
|
|
|
340
380
|
description: "Scaffold a new Lunora project",
|
|
341
381
|
examples: [
|
|
342
382
|
["lunora init my-app", "Scaffold with the default (vite) template"],
|
|
383
|
+
["lunora init my-app -t next", "Scaffold a Next.js app"],
|
|
343
384
|
["lunora init my-app -t tanstack-start-react", "Scaffold a TanStack Start (React) app"],
|
|
344
385
|
["lunora init my-app -t tanstack-start-solid", "Scaffold a TanStack Start (Solid) app"],
|
|
386
|
+
["lunora init my-app --ref alpha", "Scaffold from the alpha branch's templates"],
|
|
345
387
|
["lunora init --here", "Add Lunora to the current project"],
|
|
346
388
|
["lunora init my-app --ci github", "Scaffold + add a GitHub Actions deploy pipeline"],
|
|
347
389
|
["lunora init my-app --ci gitlab", "Scaffold + add a GitLab CI deploy pipeline"]
|
|
@@ -354,11 +396,19 @@ const initCommand = {
|
|
|
354
396
|
options: [
|
|
355
397
|
{
|
|
356
398
|
alias: "t",
|
|
357
|
-
|
|
358
|
-
|
|
399
|
+
// No default: when omitted, an interactive run shows the framework
|
|
400
|
+
// picker (default React overlay) and a non-interactive run errors.
|
|
401
|
+
// For React/Vue/Solid/Svelte SPAs use `--vite <framework>` (overlay);
|
|
402
|
+
// `-t` selects a bespoke template.
|
|
403
|
+
description: "Bespoke template (standalone | astro | next | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid). For an SPA use --vite react|vue|solid|svelte.",
|
|
359
404
|
name: "template",
|
|
360
405
|
type: String
|
|
361
406
|
},
|
|
407
|
+
{
|
|
408
|
+
description: "Scaffold via the create-vite overlay for a framework (react | vue | solid | svelte | vanilla) — official create-vite base + Lunora layer",
|
|
409
|
+
name: "vite",
|
|
410
|
+
type: String
|
|
411
|
+
},
|
|
362
412
|
{
|
|
363
413
|
description: "Local templates root to copy from (offline-friendly; expects <type>/ subdirs)",
|
|
364
414
|
name: "from",
|
|
@@ -369,6 +419,11 @@ const initCommand = {
|
|
|
369
419
|
name: "source",
|
|
370
420
|
type: String
|
|
371
421
|
},
|
|
422
|
+
{
|
|
423
|
+
description: "Fetch templates from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
424
|
+
name: "ref",
|
|
425
|
+
type: String
|
|
426
|
+
},
|
|
372
427
|
{
|
|
373
428
|
description: "Permit --source values outside gh:/github:/https:// (e.g. local file://)",
|
|
374
429
|
name: "allow-unsafe-source",
|
|
@@ -395,6 +450,16 @@ const initCommand = {
|
|
|
395
450
|
description: "Also scaffold a CI deploy pipeline: github (.github/workflows/deploy.yml) or gitlab (.gitlab-ci.yml)",
|
|
396
451
|
name: "ci",
|
|
397
452
|
type: String
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
description: "Add features non-interactively after scaffolding (comma-separated): ai | auth | backup | browser | cloudflare-access | crons | email | flags | hyperdrive | payment | presence | queue | storage | workflow",
|
|
456
|
+
name: "add",
|
|
457
|
+
type: String
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
description: "Walk through every step (prompts + output) without writing files, installing, or running git",
|
|
461
|
+
name: "dry-run",
|
|
462
|
+
type: Boolean
|
|
398
463
|
}
|
|
399
464
|
]
|
|
400
465
|
};
|
|
@@ -408,7 +473,7 @@ const insightsCommand = {
|
|
|
408
473
|
["lunora insights --prod --url https://app.example.com --token $LUNORA_ADMIN_TOKEN", "Report against production"]
|
|
409
474
|
],
|
|
410
475
|
group: "Develop",
|
|
411
|
-
loader: () => import('../packem_chunks/
|
|
476
|
+
loader: () => import('../packem_chunks/handler13.mjs').then((m) => {
|
|
412
477
|
return { default: m.execute };
|
|
413
478
|
}),
|
|
414
479
|
name: "insights",
|
|
@@ -430,7 +495,7 @@ const linkCommand = {
|
|
|
430
495
|
["lunora link --remove", "Remove the link"]
|
|
431
496
|
],
|
|
432
497
|
group: "Deploy",
|
|
433
|
-
loader: () => import('../packem_chunks/
|
|
498
|
+
loader: () => import('../packem_chunks/handler14.mjs').then((m) => {
|
|
434
499
|
return { default: m.execute };
|
|
435
500
|
}),
|
|
436
501
|
name: "link",
|
|
@@ -444,9 +509,9 @@ const linkCommand = {
|
|
|
444
509
|
|
|
445
510
|
const logsCommand = {
|
|
446
511
|
argument: { description: "Worker name (defaults to the name in wrangler config)", name: "worker", type: String },
|
|
447
|
-
description: "Stream live logs from a deployed
|
|
512
|
+
description: "Stream live logs from a deployed Worker, or read the durable log archive with --durable",
|
|
448
513
|
group: "Deploy",
|
|
449
|
-
loader: () => import('../packem_chunks/
|
|
514
|
+
loader: () => import('../packem_chunks/handler21.mjs').then((m) => {
|
|
450
515
|
return { default: m.execute };
|
|
451
516
|
}),
|
|
452
517
|
name: "logs",
|
|
@@ -459,7 +524,22 @@ const logsCommand = {
|
|
|
459
524
|
description: "Tail a temporary-account deployment when unauthenticated (wrangler tail --temporary). Errors if you're already authenticated.",
|
|
460
525
|
name: "temporary",
|
|
461
526
|
type: Boolean
|
|
462
|
-
}
|
|
527
|
+
},
|
|
528
|
+
// --- durable-archive path (--durable) ---
|
|
529
|
+
{ description: "Read the durable log archive (pipelineLogSink → R2) via R2 SQL instead of tailing live", name: "durable", type: Boolean },
|
|
530
|
+
{ description: "durable: Iceberg table the Pipeline writes to (required with --durable)", name: "table", type: String },
|
|
531
|
+
{ description: "durable: Iceberg namespace (R2 Data Catalog database) the table lives in", name: "namespace", type: String },
|
|
532
|
+
{ description: "durable: lower time bound (epoch-millis or ISO 8601), inclusive", name: "since", type: String },
|
|
533
|
+
{ description: "durable: upper time bound (epoch-millis or ISO 8601), inclusive", name: "until", type: String },
|
|
534
|
+
{ description: "durable: exact severity filter (trace|debug|log|info|warn|error|fatal)", name: "level", type: String },
|
|
535
|
+
{ description: "durable: severity floor — this level and every more-severe one", name: "min-level", type: String },
|
|
536
|
+
{ description: "durable: keep function paths starting with this prefix (LIKE 'prefix%')", name: "function-prefix", type: String },
|
|
537
|
+
{ description: "durable: trace-id filter", name: "trace-id", type: String },
|
|
538
|
+
{ description: "durable: shard-key filter", name: "shard-key", type: String },
|
|
539
|
+
{ description: "durable: user-id filter", name: "user-id", type: String },
|
|
540
|
+
{ description: "durable: max rows (clamped to 1–10000; default 500)", name: "limit", type: String },
|
|
541
|
+
{ description: "durable: resume after a prior page — the ts from its nextCursor", name: "cursor", type: String },
|
|
542
|
+
{ description: "durable: emit one JSON object per line instead of a table", name: "ndjson", type: Boolean }
|
|
463
543
|
]
|
|
464
544
|
};
|
|
465
545
|
|
|
@@ -480,7 +560,7 @@ const migrateCommand = {
|
|
|
480
560
|
name: "migrate",
|
|
481
561
|
options: [
|
|
482
562
|
{ description: "Migration name slug (e.g. add_users_email)", name: "name", type: String },
|
|
483
|
-
{ description: "Target table for `create`", name: "table", type: String },
|
|
563
|
+
{ description: "Target table for `create` (prompted for interactively when omitted)", name: "table", type: String },
|
|
484
564
|
{ description: "Preview a data migration without rewriting rows", name: "dry-run", type: Boolean },
|
|
485
565
|
{ description: "Rows per batch for a data migration", name: "batch-size", type: Number },
|
|
486
566
|
{ description: "Cap batches processed this run (maps to the runner's maxBatches)", name: "steps", type: Number },
|
|
@@ -541,6 +621,11 @@ const registryCommand = {
|
|
|
541
621
|
{ description: "add: skip the package.json mutation confirmation prompt", name: "yes", type: Boolean },
|
|
542
622
|
{ description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
|
|
543
623
|
{ description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
|
|
624
|
+
{
|
|
625
|
+
description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
626
|
+
name: "ref",
|
|
627
|
+
type: String
|
|
628
|
+
},
|
|
544
629
|
{ description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
|
|
545
630
|
{ description: "Emit JSON output (add plan / list)", name: "json", type: Boolean },
|
|
546
631
|
{ description: "build: output path for the catalog (default <root>/index.json)", name: "out", type: String },
|
|
@@ -639,7 +724,8 @@ const verifyCommand = {
|
|
|
639
724
|
description: "Validate wrangler.jsonc + codegen dry-run + tsc --noEmit (no files written)",
|
|
640
725
|
examples: [
|
|
641
726
|
["lunora verify", "Validate wrangler + codegen + tsc"],
|
|
642
|
-
["lunora verify --no-typecheck", "Skip the TypeScript type-check"]
|
|
727
|
+
["lunora verify --no-typecheck", "Skip the TypeScript type-check"],
|
|
728
|
+
["lunora verify --health-url https://my-app.workers.dev", "Also probe the deployment's /_lunora/health"]
|
|
643
729
|
],
|
|
644
730
|
group: "Deploy",
|
|
645
731
|
loader: () => import('../packem_chunks/handler19.mjs').then((m) => {
|
|
@@ -650,6 +736,11 @@ const verifyCommand = {
|
|
|
650
736
|
{ description: "Treat breaking schema drift as a warning instead of a failure", name: "allow-schema-drift", type: Boolean },
|
|
651
737
|
{ description: `Which API spec(s) to emit: ${API_SPEC_HELP} (default openapi)`, name: "api-spec", type: String },
|
|
652
738
|
{ description: "Output format: pretty (default) or json", name: "format", type: String },
|
|
739
|
+
{
|
|
740
|
+
description: "Probe this deployment's /_lunora/health endpoint (off by default; keeps verify offline-safe)",
|
|
741
|
+
name: "health-url",
|
|
742
|
+
type: String
|
|
743
|
+
},
|
|
653
744
|
{ description: "Skip the TypeScript type-check step", name: "no-typecheck", type: Boolean }
|
|
654
745
|
]
|
|
655
746
|
};
|
|
@@ -668,6 +759,21 @@ const viewCommand = {
|
|
|
668
759
|
options: [{ description: "Open the deployed worker URL instead of localhost", name: "remote", type: Boolean }]
|
|
669
760
|
};
|
|
670
761
|
|
|
762
|
+
const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
|
|
763
|
+
const renderLunoraError = (error, options = {}) => {
|
|
764
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
765
|
+
const hint = resolveHint(isLunoraError(error) ? { code: error.code, hint: error.hint, message } : message);
|
|
766
|
+
const rendered = new VisulimaError({
|
|
767
|
+
// `renderError` iterates `hint` as lines; the shared flattener returns one
|
|
768
|
+
// string, so split it back to lines for the terminal renderer.
|
|
769
|
+
hint: hint === void 0 ? void 0 : flattenHint(hint).split("\n"),
|
|
770
|
+
message: options.reason === void 0 ? message : `${options.reason}: ${message}`,
|
|
771
|
+
name: error instanceof Error && error.name.length > 0 ? error.name : "Error"
|
|
772
|
+
});
|
|
773
|
+
rendered.stack = "";
|
|
774
|
+
return renderError(rendered, NO_STACK);
|
|
775
|
+
};
|
|
776
|
+
|
|
671
777
|
const editDistance = (a, b) => {
|
|
672
778
|
const distances = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
673
779
|
for (let row = 1; row <= a.length; row += 1) {
|
|
@@ -730,6 +836,15 @@ const isNewer = (current, latest) => compareVersions(latest, current) > 0;
|
|
|
730
836
|
const isCacheFresh = (checkedAt, nowMs, ttlMs) => nowMs - checkedAt < ttlMs;
|
|
731
837
|
const formatUpdateNotice = (current, latest) => `Update available for @lunora/cli: ${current} → ${latest} — run \`pnpm add -D @lunora/cli@latest\``;
|
|
732
838
|
const cacheFilePath = (cacheDirectory) => join(cacheDirectory, "lunora-cli-update.json");
|
|
839
|
+
const defaultCacheDirectory = (env) => {
|
|
840
|
+
const base = env.XDG_CACHE_HOME && env.XDG_CACHE_HOME.length > 0 ? env.XDG_CACHE_HOME : join(homedir(), ".cache");
|
|
841
|
+
const directory = join(base, "lunora");
|
|
842
|
+
try {
|
|
843
|
+
mkdirSync(directory, { mode: 448, recursive: true });
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
return directory;
|
|
847
|
+
};
|
|
733
848
|
const readCache = (cacheDirectory) => {
|
|
734
849
|
try {
|
|
735
850
|
const parsed = JSON.parse(readFileSync(cacheFilePath(cacheDirectory), "utf8"));
|
|
@@ -745,7 +860,14 @@ const readCache = (cacheDirectory) => {
|
|
|
745
860
|
};
|
|
746
861
|
const writeCache = (cacheDirectory, cache) => {
|
|
747
862
|
try {
|
|
748
|
-
|
|
863
|
+
const path = cacheFilePath(cacheDirectory);
|
|
864
|
+
try {
|
|
865
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
writeFileSync(path, `${JSON.stringify(cache)}
|
|
749
871
|
`, "utf8");
|
|
750
872
|
} catch {
|
|
751
873
|
}
|
|
@@ -770,7 +892,7 @@ const maybeNotifyUpdate = async (deps) => {
|
|
|
770
892
|
if (isNotifierDisabled(deps.current, env, isTty)) {
|
|
771
893
|
return;
|
|
772
894
|
}
|
|
773
|
-
const cacheDirectory = deps.cacheDir ??
|
|
895
|
+
const cacheDirectory = deps.cacheDir ?? defaultCacheDirectory(env);
|
|
774
896
|
const nowMs = (deps.now ?? Date.now)();
|
|
775
897
|
const ttlMs = deps.ttlMs ?? CACHE_TTL_MS;
|
|
776
898
|
const cache = readCache(cacheDirectory);
|
|
@@ -882,7 +1004,11 @@ const reportRunError = (error) => {
|
|
|
882
1004
|
const message = error instanceof Error ? error.message : String(error);
|
|
883
1005
|
const unknown = UNKNOWN_COMMAND.exec(message);
|
|
884
1006
|
if (!unknown?.groups) {
|
|
885
|
-
|
|
1007
|
+
if (isLunoraError(error) || findSolutionByMessage(message) !== void 0) {
|
|
1008
|
+
logger.error(renderLunoraError(error));
|
|
1009
|
+
} else {
|
|
1010
|
+
logger.error(message);
|
|
1011
|
+
}
|
|
886
1012
|
return;
|
|
887
1013
|
}
|
|
888
1014
|
const name = unknown.groups.name ?? "";
|