@lunora/cli 1.0.0-alpha.57 → 1.0.0-alpha.59
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 +1 -1
- package/dist/index.d.mts +36 -10
- package/dist/index.d.ts +36 -10
- package/dist/index.mjs +7 -7
- package/dist/packem_chunks/handler.mjs +4 -4
- package/dist/packem_chunks/handler10.mjs +2 -2
- package/dist/packem_chunks/handler11.mjs +2 -2
- package/dist/packem_chunks/handler12.mjs +1 -1
- package/dist/packem_chunks/handler13.mjs +1 -1
- package/dist/packem_chunks/handler14.mjs +1 -1
- package/dist/packem_chunks/handler15.mjs +2 -2
- package/dist/packem_chunks/handler16.mjs +2 -2
- package/dist/packem_chunks/handler17.mjs +2 -2
- package/dist/packem_chunks/handler18.mjs +1 -1
- package/dist/packem_chunks/handler19.mjs +2 -2
- package/dist/packem_chunks/handler2.mjs +2 -2
- package/dist/packem_chunks/handler20.mjs +4 -4
- package/dist/packem_chunks/handler21.mjs +2 -2
- package/dist/packem_chunks/handler3.mjs +6 -3
- package/dist/packem_chunks/handler4.mjs +2 -2
- package/dist/packem_chunks/handler5.mjs +2 -2
- package/dist/packem_chunks/handler6.mjs +2 -2
- package/dist/packem_chunks/handler7.mjs +2 -2
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +2 -2
- package/dist/packem_chunks/planDevCommand.mjs +498 -29
- package/dist/packem_chunks/runCodegenCommand.mjs +3 -3
- package/dist/packem_chunks/runDeployCommand.mjs +4 -4
- package/dist/packem_chunks/runInitCommand.mjs +5 -5
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +5 -4
- package/dist/packem_chunks/runResetCommand.mjs +1 -1
- package/dist/packem_chunks/runRpcCommand.mjs +3 -2
- package/dist/packem_shared/{COMMANDS-B0ftFD_3.mjs → COMMANDS-0BD3K1pb.mjs} +43 -3
- package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs} +3 -2
- package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
- package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-CqM5FwGI.mjs} +1 -1
- package/dist/packem_shared/{command-D3lB_4Az.mjs → command-lYnl4QyF.mjs} +1 -1
- package/dist/packem_shared/{commands-BK6Pt3Ni.mjs → commands-vnRRkhNE.mjs} +12 -9
- package/dist/packem_shared/{createLogger-B40gPzQo.mjs → createLogger-CIWSHrTL.mjs} +33 -6
- package/dist/packem_shared/{createRecordingSpawner-DxI3mebw.mjs → createRecordingSpawner-Cw5Iu73G.mjs} +12 -2
- package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
- package/dist/packem_shared/{output-format-wUvAN6AL.mjs → output-format-B4642rjE.mjs} +1 -1
- package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-BBrXCg9V.mjs} +16 -9
- package/dist/packem_shared/runAddCommand-BCcJzs_k.mjs +4 -0
- package/dist/packem_shared/{storage-C8SAm5zu.mjs → storage-bGfgwOgF.mjs} +1 -1
- package/package.json +7 -6
- package/skills/lunora-quickstart/SKILL.md +25 -5
- package/dist/packem_shared/runAddCommand-DWjH0Heb.mjs +0 -4
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { discoverSchema, discoverMigrations } from '@lunora/codegen';
|
|
4
|
+
import { LunoraError } from '@lunora/errors';
|
|
4
5
|
import { join } from '@visulima/path';
|
|
5
6
|
import { Project } from 'ts-morph';
|
|
6
7
|
import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
|
|
7
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
8
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
8
9
|
import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-BeDvvNiF.mjs';
|
|
9
10
|
import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
10
11
|
import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
|
|
11
|
-
import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-
|
|
12
|
+
import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs';
|
|
12
13
|
|
|
13
14
|
const SNAPSHOT_FILENAME = ".snapshot.json";
|
|
14
15
|
const NON_ALPHANUMERIC = /[^\da-z]+/gu;
|
|
@@ -41,12 +42,12 @@ const loadSnapshot = (path) => {
|
|
|
41
42
|
const raw = readFileSync(path, "utf8");
|
|
42
43
|
const parsed = JSON.parse(raw);
|
|
43
44
|
if (parsed.version !== 1) {
|
|
44
|
-
throw new
|
|
45
|
+
throw new LunoraError("INTERNAL", `unsupported snapshot version: ${parsed.version}`);
|
|
45
46
|
}
|
|
46
47
|
return parsed;
|
|
47
48
|
} catch (error) {
|
|
48
49
|
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
-
throw new
|
|
50
|
+
throw new LunoraError("INTERNAL", `failed to read ${path}: ${message}`, { cause: error });
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
const runMigrateGenerateCommand = (options) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, rmSync } from 'node:fs';
|
|
2
2
|
import { join } from '@visulima/path';
|
|
3
|
-
import { d as defineHandler } from '../packem_shared/command-
|
|
3
|
+
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
|
|
4
4
|
import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
|
|
5
5
|
|
|
6
6
|
const runResetCommand = async (options) => {
|
|
@@ -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,9 +1,11 @@
|
|
|
1
1
|
import { readFileSync, 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-
|
|
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';
|
|
7
9
|
import { tmpdir } from 'node:os';
|
|
8
10
|
import { join } from 'node:path';
|
|
9
11
|
|
|
@@ -213,9 +215,19 @@ const deploymentsCommand = {
|
|
|
213
215
|
};
|
|
214
216
|
|
|
215
217
|
const devCommand = {
|
|
218
|
+
argument: {
|
|
219
|
+
description: "Optional subcommand: stop (shut the running dev server down) | status (report it) | logs (print its captured output)",
|
|
220
|
+
name: "args",
|
|
221
|
+
type: String
|
|
222
|
+
},
|
|
216
223
|
description: "Run the dev stack: wrangler worker + studio + codegen watch",
|
|
217
224
|
examples: [
|
|
218
225
|
["lunora dev", "Run the worker + studio + codegen watch"],
|
|
226
|
+
["lunora dev --background", "Run detached: blocks until ready, prints URL + PID, then returns"],
|
|
227
|
+
["lunora dev stop", "Stop the background/tracked dev server (idempotent)"],
|
|
228
|
+
["lunora dev status", "Report the running dev server (URL, PID, uptime)"],
|
|
229
|
+
["lunora dev logs", "Print the captured dev-server log (background runs)"],
|
|
230
|
+
["lunora dev --json", "Machine-readable JSON log lines (also LUNORA_LOG_JSON=1)"],
|
|
219
231
|
["lunora dev --no-studio", "Skip the embedded studio server"],
|
|
220
232
|
["lunora dev --worker-port 8080", "Use a custom wrangler dev port"],
|
|
221
233
|
["lunora dev --remote", "Proxy D1/KV/R2 to the deployed worker (also LUNORA_REMOTE=1)"]
|
|
@@ -225,10 +237,19 @@ const devCommand = {
|
|
|
225
237
|
return { default: m.execute };
|
|
226
238
|
}),
|
|
227
239
|
name: "dev",
|
|
240
|
+
// KEEP IN SYNC with `daemonArguments` in `./lifecycle.ts`: a new flag that
|
|
241
|
+
// must reach a `--background` daemon has to be forwarded there explicitly.
|
|
228
242
|
options: [
|
|
229
243
|
{ description: `Which API spec(s) codegen emits: ${API_SPEC_HELP} (default openapi)`, name: "api-spec", type: String },
|
|
230
244
|
{ description: "Studio server port (default 6173)", name: "port", type: Number },
|
|
231
245
|
{ description: "wrangler dev port (default 8787)", name: "worker-port", type: Number },
|
|
246
|
+
{
|
|
247
|
+
description: "Run the dev server as a managed background process (auto-enabled when an AI agent is detected; LUNORA_AGENT_MODE=0 disables)",
|
|
248
|
+
name: "background",
|
|
249
|
+
type: Boolean
|
|
250
|
+
},
|
|
251
|
+
{ description: "Emit machine-readable JSON log lines (also LUNORA_LOG_JSON=1; auto-enabled for AI agents)", name: "json", type: Boolean },
|
|
252
|
+
{ description: "How many trailing lines `lunora dev logs` prints (default 100, 0 = all)", name: "lines", type: Number },
|
|
232
253
|
{ description: "Don't start the embedded studio server", name: "no-studio", type: Boolean },
|
|
233
254
|
{ description: "Don't watch + regenerate codegen", name: "no-codegen", type: Boolean },
|
|
234
255
|
{ description: "Proxy D1/KV/R2 bindings to the deployed worker (or set LUNORA_REMOTE=1)", name: "remote", type: Boolean }
|
|
@@ -711,6 +732,21 @@ const viewCommand = {
|
|
|
711
732
|
options: [{ description: "Open the deployed worker URL instead of localhost", name: "remote", type: Boolean }]
|
|
712
733
|
};
|
|
713
734
|
|
|
735
|
+
const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
|
|
736
|
+
const renderLunoraError = (error, options = {}) => {
|
|
737
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
738
|
+
const hint = resolveHint(isLunoraError(error) ? { code: error.code, hint: error.hint, message } : message);
|
|
739
|
+
const rendered = new VisulimaError({
|
|
740
|
+
// `renderError` iterates `hint` as lines; the shared flattener returns one
|
|
741
|
+
// string, so split it back to lines for the terminal renderer.
|
|
742
|
+
hint: hint === void 0 ? void 0 : flattenHint(hint).split("\n"),
|
|
743
|
+
message: options.reason === void 0 ? message : `${options.reason}: ${message}`,
|
|
744
|
+
name: error instanceof Error && error.name.length > 0 ? error.name : "Error"
|
|
745
|
+
});
|
|
746
|
+
rendered.stack = "";
|
|
747
|
+
return renderError(rendered, NO_STACK);
|
|
748
|
+
};
|
|
749
|
+
|
|
714
750
|
const editDistance = (a, b) => {
|
|
715
751
|
const distances = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
716
752
|
for (let row = 1; row <= a.length; row += 1) {
|
|
@@ -925,7 +961,11 @@ const reportRunError = (error) => {
|
|
|
925
961
|
const message = error instanceof Error ? error.message : String(error);
|
|
926
962
|
const unknown = UNKNOWN_COMMAND.exec(message);
|
|
927
963
|
if (!unknown?.groups) {
|
|
928
|
-
|
|
964
|
+
if (isLunoraError(error) || findSolutionByMessage(message) !== void 0) {
|
|
965
|
+
logger.error(renderLunoraError(error));
|
|
966
|
+
} else {
|
|
967
|
+
logger.error(message);
|
|
968
|
+
}
|
|
929
969
|
return;
|
|
930
970
|
}
|
|
931
971
|
const name = unknown.groups.name ?? "";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createWriteStream, createReadStream } from 'node:fs';
|
|
2
2
|
import { unlink, stat } from 'node:fs/promises';
|
|
3
|
+
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
import { r as resolveAdminBaseUrl } from './admin-url-4UzT-CI4.mjs';
|
|
4
5
|
|
|
5
6
|
const EXPORT_ENDPOINT_PATH = "/_lunora/admin/export";
|
|
@@ -183,7 +184,7 @@ const runImportCommand = async (options) => {
|
|
|
183
184
|
});
|
|
184
185
|
if (!response.ok) {
|
|
185
186
|
const text = await response.text().catch(() => "<no body>");
|
|
186
|
-
throw new
|
|
187
|
+
throw new LunoraError("INTERNAL", `import batch failed (HTTP ${String(response.status)}): ${text}`);
|
|
187
188
|
}
|
|
188
189
|
const json = await response.json();
|
|
189
190
|
if (json.inserted) {
|
|
@@ -213,7 +214,7 @@ const runImportCommand = async (options) => {
|
|
|
213
214
|
parsedDocument = JSON.parse(trimmed);
|
|
214
215
|
} catch (error) {
|
|
215
216
|
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
-
throw new
|
|
217
|
+
throw new LunoraError("INTERNAL", `invalid JSON on line ${String(lineNumber)}: ${message}`, { cause: error });
|
|
217
218
|
}
|
|
218
219
|
batch.push(JSON.stringify({ doc: parsedDocument, table: options.table }));
|
|
219
220
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const API_SPEC_VALUES = ["both", "none", "openapi", "openrpc"];
|
|
2
4
|
const API_SPEC_HELP = API_SPEC_VALUES.join(" | ");
|
|
3
5
|
const parseApiSpec = (value) => {
|
|
@@ -7,7 +9,7 @@ const parseApiSpec = (value) => {
|
|
|
7
9
|
if (API_SPEC_VALUES.includes(value)) {
|
|
8
10
|
return value;
|
|
9
11
|
}
|
|
10
|
-
throw new
|
|
12
|
+
throw new LunoraError("INTERNAL", `invalid --api-spec "${value}" — expected one of: ${API_SPEC_HELP}`);
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export { API_SPEC_HELP as A, parseApiSpec as p };
|
package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-CqM5FwGI.mjs}
RENAMED
|
@@ -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
|
|
3
|
+
import parseManifest from './parseManifest-BBrXCg9V.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,4 +1,4 @@
|
|
|
1
|
-
import { createLogger } from './createLogger-
|
|
1
|
+
import { createLogger } from './createLogger-CIWSHrTL.mjs';
|
|
2
2
|
import { P as PromptCancelledError, a as PROMPT_CANCEL_EXIT_CODE } from './prompt-cancelled-APzX1Im-.mjs';
|
|
3
3
|
|
|
4
4
|
const defineHandler = (body) => async (toolbox) => {
|
|
@@ -3,13 +3,14 @@ import { dirname, join } 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
|
+
import { LunoraError } from '@lunora/errors';
|
|
6
7
|
import { b as tuiConfirm } from './tui-prompts-BjEN8XgP.mjs';
|
|
7
|
-
import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-
|
|
8
|
+
import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-CqM5FwGI.mjs';
|
|
8
9
|
import { insertSchemaExtension } from './insertSchemaExtension-DAqbfr9Z.mjs';
|
|
9
10
|
import { createHash } from 'node:crypto';
|
|
10
11
|
import { tmpdir } from 'node:os';
|
|
11
12
|
import { downloadTemplate } from 'giget';
|
|
12
|
-
import parseManifest from './parseManifest
|
|
13
|
+
import parseManifest from './parseManifest-BBrXCg9V.mjs';
|
|
13
14
|
|
|
14
15
|
const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
|
|
15
16
|
const STABLE_BRANCH = "main";
|
|
@@ -54,7 +55,7 @@ const resolveVersionRef = (version) => {
|
|
|
54
55
|
const resolveSourceRef = (ref) => {
|
|
55
56
|
if (ref !== void 0 && ref.length > 0) {
|
|
56
57
|
if (!isSafeRef(ref)) {
|
|
57
|
-
throw new
|
|
58
|
+
throw new LunoraError("INTERNAL", `invalid --ref "${ref}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);
|
|
58
59
|
}
|
|
59
60
|
return ref;
|
|
60
61
|
}
|
|
@@ -457,11 +458,12 @@ export const schema = defineSchema({});
|
|
|
457
458
|
return { kind: "skipped", path: schemaPath };
|
|
458
459
|
}
|
|
459
460
|
if (result.reason === "invalid-identifier") {
|
|
460
|
-
throw new
|
|
461
|
+
throw new LunoraError(
|
|
462
|
+
"INTERNAL",
|
|
461
463
|
`schema-extension item "${itemKey}" is not a valid JS identifier — it is spliced into lunora/schema.ts as \`import { ${itemKey} }\` / \`.extend(${itemKey}.extension)\`. Rename the item to a valid identifier (no leading digit, no "-").`
|
|
462
464
|
);
|
|
463
465
|
}
|
|
464
|
-
throw new
|
|
466
|
+
throw new LunoraError("INTERNAL", `schema-extension merge failed for "${itemKey}": ${result.reason}`);
|
|
465
467
|
};
|
|
466
468
|
const previewWholeFile = (file, current, incoming, exists, logger) => {
|
|
467
469
|
const lines = renderDiff(current, incoming);
|
|
@@ -549,7 +551,8 @@ const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/registry";
|
|
|
549
551
|
const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
|
|
550
552
|
const assertSafeItemName = (name) => {
|
|
551
553
|
if (!VALID_ITEM_NAME.test(name)) {
|
|
552
|
-
throw new
|
|
554
|
+
throw new LunoraError(
|
|
555
|
+
"INTERNAL",
|
|
553
556
|
`invalid registry item name "${name}" — names must match ${VALID_ITEM_NAME.source} (letters, digits, "-", "_"; no path separators or "..")`
|
|
554
557
|
);
|
|
555
558
|
}
|
|
@@ -601,7 +604,7 @@ const resolveItemDirectory = async (name, options) => {
|
|
|
601
604
|
if (options.from !== void 0) {
|
|
602
605
|
const directory = join(options.from, name);
|
|
603
606
|
if (!existsSync(directory)) {
|
|
604
|
-
throw new
|
|
607
|
+
throw new LunoraError("INTERNAL", `registry item not found in local source: ${directory}`);
|
|
605
608
|
}
|
|
606
609
|
return { cleanup: () => {
|
|
607
610
|
}, directory };
|
|
@@ -612,7 +615,7 @@ const resolveItemDirectory = async (name, options) => {
|
|
|
612
615
|
const resolveRegistryRoot = async (options) => {
|
|
613
616
|
if (options.from !== void 0) {
|
|
614
617
|
if (!existsSync(options.from)) {
|
|
615
|
-
throw new
|
|
618
|
+
throw new LunoraError("INTERNAL", `registry root not found: ${options.from}`);
|
|
616
619
|
}
|
|
617
620
|
return { cleanup: () => {
|
|
618
621
|
}, root: options.from };
|
|
@@ -635,7 +638,7 @@ const resolvePlan = async (names, options) => {
|
|
|
635
638
|
return;
|
|
636
639
|
}
|
|
637
640
|
if (inProgress.has(name)) {
|
|
638
|
-
throw new
|
|
641
|
+
throw new LunoraError("INTERNAL", `cyclic registry dependency detected at "${name}"`);
|
|
639
642
|
}
|
|
640
643
|
inProgress.add(name);
|
|
641
644
|
const { cleanup, directory } = await resolveItemDirectory(name, options);
|
|
@@ -2,19 +2,46 @@ import { STEP_BADGE_NAMES, LunoraReporter } from '@lunora/config';
|
|
|
2
2
|
import { JsonReporter } from '@visulima/pail/reporter/json';
|
|
3
3
|
import { createPail } from '@visulima/pail/server';
|
|
4
4
|
|
|
5
|
+
let sharedPail;
|
|
6
|
+
let jsonForced = false;
|
|
7
|
+
let configuredReporters;
|
|
5
8
|
const wantJson = () => {
|
|
9
|
+
if (jsonForced) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
6
12
|
const flag = process.env.LUNORA_LOG_JSON;
|
|
7
13
|
return flag === "1" || flag === "true";
|
|
8
14
|
};
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
const constructReporter = (Reporter) => new Reporter();
|
|
16
|
+
const buildReporters = () => {
|
|
17
|
+
if (jsonForced) {
|
|
18
|
+
return [constructReporter(JsonReporter)];
|
|
19
|
+
}
|
|
20
|
+
if (configuredReporters !== void 0 && configuredReporters.length > 0) {
|
|
21
|
+
return configuredReporters;
|
|
22
|
+
}
|
|
23
|
+
return [constructReporter(wantJson() ? JsonReporter : LunoraReporter)];
|
|
24
|
+
};
|
|
25
|
+
const forceJsonLogging = () => {
|
|
26
|
+
if (jsonForced) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
jsonForced = true;
|
|
30
|
+
sharedPail = void 0;
|
|
31
|
+
};
|
|
32
|
+
const logHandlers = {
|
|
33
|
+
compose: (...reporters) => reporters,
|
|
34
|
+
console: () => constructReporter(LunoraReporter),
|
|
35
|
+
json: () => constructReporter(JsonReporter)
|
|
36
|
+
};
|
|
37
|
+
const configureLogHandlers = (reporters) => {
|
|
38
|
+
configuredReporters = Array.isArray(reporters) ? reporters : [reporters];
|
|
39
|
+
sharedPail = void 0;
|
|
12
40
|
};
|
|
13
41
|
const STEP_LOG_TYPES = Object.fromEntries(STEP_BADGE_NAMES.map((name) => [name, { label: name, logLevel: "informational" }]));
|
|
14
|
-
let sharedPail;
|
|
15
42
|
const getPail = () => {
|
|
16
43
|
sharedPail ??= createPail({
|
|
17
|
-
reporters:
|
|
44
|
+
reporters: buildReporters(),
|
|
18
45
|
scope: ["lunora"],
|
|
19
46
|
stderr: process.stderr,
|
|
20
47
|
stdout: process.stdout,
|
|
@@ -75,4 +102,4 @@ const logStep = (type, message) => {
|
|
|
75
102
|
getPail()[type](message);
|
|
76
103
|
};
|
|
77
104
|
|
|
78
|
-
export { createLogger, createStderrLogger, getPail, logStep, pail };
|
|
105
|
+
export { configureLogHandlers, createLogger, createStderrLogger, forceJsonLogging, getPail, logHandlers, logStep, pail };
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
|
|
3
|
+
const NEEDS_CMD_QUOTING = /\s/;
|
|
4
|
+
const spawnShellCompat = (command, args, platform = process.platform) => {
|
|
5
|
+
if (platform !== "win32" || command === process.execPath) {
|
|
6
|
+
return { args: [...args], command, shell: false };
|
|
7
|
+
}
|
|
8
|
+
const quote = (value) => NEEDS_CMD_QUOTING.test(value) ? `"${value}"` : value;
|
|
9
|
+
return { args: args.map((argument) => quote(argument)), command: quote(command), shell: true };
|
|
10
|
+
};
|
|
3
11
|
const defaultSpawner = (descriptor) => new Promise((resolve, reject) => {
|
|
4
12
|
const hasInput = typeof descriptor.input === "string";
|
|
5
13
|
const wantCapture = descriptor.captureStdout === true;
|
|
@@ -9,9 +17,11 @@ const defaultSpawner = (descriptor) => new Promise((resolve, reject) => {
|
|
|
9
17
|
} else if (descriptor.stdoutToStderr) {
|
|
10
18
|
stdout = 2;
|
|
11
19
|
}
|
|
12
|
-
const
|
|
20
|
+
const exec = spawnShellCompat(descriptor.command, descriptor.args);
|
|
21
|
+
const child = spawn(exec.command, exec.args, {
|
|
13
22
|
cwd: descriptor.cwd ?? process.cwd(),
|
|
14
23
|
env: descriptor.env ? { ...process.env, ...descriptor.env } : process.env,
|
|
24
|
+
shell: exec.shell,
|
|
15
25
|
stdio: [hasInput ? "pipe" : "inherit", stdout, "inherit"]
|
|
16
26
|
});
|
|
17
27
|
let captured = "";
|
|
@@ -40,4 +50,4 @@ const createRecordingSpawner = (exitCode = 0) => {
|
|
|
40
50
|
return { calls, spawner };
|
|
41
51
|
};
|
|
42
52
|
|
|
43
|
-
export { createRecordingSpawner, defaultSpawner };
|
|
53
|
+
export { createRecordingSpawner, defaultSpawner, spawnShellCompat };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { platform } from 'node:os';
|
|
3
|
+
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
|
|
4
5
|
const platformCommand = () => {
|
|
5
6
|
const os = platform();
|
|
@@ -29,10 +30,10 @@ const openUrl = async (url, options = {}) => {
|
|
|
29
30
|
try {
|
|
30
31
|
parsed = new URL(url);
|
|
31
32
|
} catch {
|
|
32
|
-
throw new
|
|
33
|
+
throw new LunoraError("INTERNAL", `Invalid URL: ${url}`);
|
|
33
34
|
}
|
|
34
35
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
35
|
-
throw new
|
|
36
|
+
throw new LunoraError("INTERNAL", `Refusing to open non-http(s) URL: ${url}`);
|
|
36
37
|
}
|
|
37
38
|
const opener = options.opener ?? platformOpener;
|
|
38
39
|
await opener(url);
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const NEWLINE_PRESENT = /[\r\n]/u;
|
|
2
4
|
const VALID_ENV_NAME = /^[A-Za-z_]\w*$/u;
|
|
3
5
|
const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
|
|
4
6
|
const parseManifest = (raw, itemName) => {
|
|
5
7
|
if (typeof raw !== "object" || raw === null) {
|
|
6
|
-
throw new
|
|
8
|
+
throw new LunoraError("INTERNAL", `registry.json for "${itemName}" is not an object`);
|
|
7
9
|
}
|
|
8
10
|
const record = raw;
|
|
9
11
|
const { name } = record;
|
|
10
12
|
if (typeof name !== "string" || name.length === 0) {
|
|
11
|
-
throw new
|
|
13
|
+
throw new LunoraError("INTERNAL", `registry.json for "${itemName}" is missing a string "name"`);
|
|
12
14
|
}
|
|
13
15
|
if (!VALID_ITEM_NAME.test(name)) {
|
|
14
|
-
throw new
|
|
16
|
+
throw new LunoraError(
|
|
17
|
+
"INTERNAL",
|
|
15
18
|
`registry.json for "${itemName}": name "${name}" must match ${VALID_ITEM_NAME.source} (letters, digits, "-", "_"; no path separators, "..", or code)`
|
|
16
19
|
);
|
|
17
20
|
}
|
|
@@ -21,7 +24,7 @@ const parseManifest = (raw, itemName) => {
|
|
|
21
24
|
}
|
|
22
25
|
const files = filesRaw.map((entry, index) => {
|
|
23
26
|
if (typeof entry !== "object" || entry === null) {
|
|
24
|
-
throw new
|
|
27
|
+
throw new LunoraError("INTERNAL", `registry.json "${itemName}": files[${String(index)}] is not an object`);
|
|
25
28
|
}
|
|
26
29
|
const fileRecord = entry;
|
|
27
30
|
const { from } = fileRecord;
|
|
@@ -31,14 +34,17 @@ const parseManifest = (raw, itemName) => {
|
|
|
31
34
|
throw new TypeError(`registry.json "${itemName}": files[${String(index)}] needs string "from" and "to"`);
|
|
32
35
|
}
|
|
33
36
|
if (merge !== "create-or-skip" && merge !== "schema-extension") {
|
|
34
|
-
throw new
|
|
37
|
+
throw new LunoraError("INTERNAL", `registry.json "${itemName}": files[${String(index)}].merge must be "create-or-skip" or "schema-extension"`);
|
|
35
38
|
}
|
|
36
39
|
for (const [field, value] of [
|
|
37
40
|
["from", from],
|
|
38
41
|
["to", to]
|
|
39
42
|
]) {
|
|
40
43
|
if (value.includes("..") || value.startsWith("/")) {
|
|
41
|
-
throw new
|
|
44
|
+
throw new LunoraError(
|
|
45
|
+
"INTERNAL",
|
|
46
|
+
`registry.json "${itemName}": files[${String(index)}].${field} "${value}" must be a relative path without ".."`
|
|
47
|
+
);
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
return { from, merge, to };
|
|
@@ -59,15 +65,16 @@ const parseManifest = (raw, itemName) => {
|
|
|
59
65
|
).map((entry) => {
|
|
60
66
|
const hasValue = typeof entry.value === "string";
|
|
61
67
|
if (!VALID_ENV_NAME.test(entry.name)) {
|
|
62
|
-
throw new
|
|
68
|
+
throw new LunoraError(
|
|
69
|
+
"INTERNAL",
|
|
63
70
|
`registry.json "${itemName}": envVars["${entry.name}"].name must match ${VALID_ENV_NAME.source} (letters, digits, underscore; no "=" or newline)`
|
|
64
71
|
);
|
|
65
72
|
}
|
|
66
73
|
if (hasValue && NEWLINE_PRESENT.test(entry.value)) {
|
|
67
|
-
throw new
|
|
74
|
+
throw new LunoraError("INTERNAL", `registry.json "${itemName}": envVars["${entry.name}"].value must not contain a newline`);
|
|
68
75
|
}
|
|
69
76
|
if (typeof entry.description === "string" && NEWLINE_PRESENT.test(entry.description)) {
|
|
70
|
-
throw new
|
|
77
|
+
throw new LunoraError("INTERNAL", `registry.json "${itemName}": envVars["${entry.name}"].description must not contain a newline`);
|
|
71
78
|
}
|
|
72
79
|
return {
|
|
73
80
|
...typeof entry.description === "string" ? { description: entry.description } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.59",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -52,11 +52,12 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@bomb.sh/tab": "0.0.17",
|
|
55
|
-
"@lunora/codegen": "1.0.0-alpha.
|
|
56
|
-
"@lunora/config": "1.0.0-alpha.
|
|
57
|
-
"@lunora/container": "1.0.0-alpha.
|
|
58
|
-
"@lunora/d1": "1.0.0-alpha.
|
|
59
|
-
"@lunora/
|
|
55
|
+
"@lunora/codegen": "1.0.0-alpha.29",
|
|
56
|
+
"@lunora/config": "1.0.0-alpha.47",
|
|
57
|
+
"@lunora/container": "1.0.0-alpha.6",
|
|
58
|
+
"@lunora/d1": "1.0.0-alpha.22",
|
|
59
|
+
"@lunora/errors": "1.0.0-alpha.1",
|
|
60
|
+
"@lunora/seed": "1.0.0-alpha.14",
|
|
60
61
|
"@visulima/cerebro": "3.0.0-alpha.32",
|
|
61
62
|
"@visulima/error": "6.0.0-alpha.34",
|
|
62
63
|
"@visulima/fs": "5.0.0-alpha.32",
|
|
@@ -31,9 +31,12 @@ Set up a working Lunora project as fast as possible.
|
|
|
31
31
|
Lunora into the current project.
|
|
32
32
|
4. Run `lunora codegen` to generate `lunora/_generated/` and typecheck the
|
|
33
33
|
schema + functions. This is the agent's feedback loop.
|
|
34
|
-
5. Start the dev loop
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
5. Start the dev loop. As an agent, run `lunora dev --background` — it starts
|
|
35
|
+
the server as a managed detached process, blocks until it accepts requests,
|
|
36
|
+
prints the URL + PID, and returns (under a detected AI agent, plain
|
|
37
|
+
`lunora dev` does this automatically, with JSON logs). Never leave a bare
|
|
38
|
+
`lunora dev` running in your own shell — it is long-running and does not
|
|
39
|
+
exit.
|
|
37
40
|
6. Verify a query/mutation round-trip works end to end.
|
|
38
41
|
|
|
39
42
|
## Path 1: New Project (Recommended)
|
|
@@ -87,7 +90,23 @@ not exit, so:
|
|
|
87
90
|
|
|
88
91
|
- **Local development (user at the keyboard):** ask the user to run `lunora dev`
|
|
89
92
|
in a terminal.
|
|
90
|
-
- **
|
|
93
|
+
- **Agents:** run `lunora dev --background`. It detaches the server, waits until
|
|
94
|
+
it answers HTTP, prints `Dev server running at <url> (pid <n>)`, and exits —
|
|
95
|
+
no orphaned shell, no PID bookkeeping. When Lunora detects an AI agent
|
|
96
|
+
(Claude Code, Cursor, Codex, …), plain `lunora dev` flips into this mode
|
|
97
|
+
automatically with JSON logs; `LUNORA_AGENT_MODE=0` opts out.
|
|
98
|
+
|
|
99
|
+
Manage the running server afterwards:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
lunora dev status --json # machine-readable: url, pid, uptime, logFile
|
|
103
|
+
lunora dev logs --lines 50 # tail the captured output (.lunora/dev.log)
|
|
104
|
+
lunora dev stop # idempotent — succeeds even if nothing runs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A second `lunora dev` never double-starts: it reports the existing instance
|
|
108
|
+
(`.lunora/dev.json` is the lockfile). Probe readiness or liveness at
|
|
109
|
+
`GET /_lunora/status` (`{"ok":true}`).
|
|
91
110
|
|
|
92
111
|
Vite serves on `http://localhost:5173` by default; the Worker is served on the
|
|
93
112
|
same origin via `@cloudflare/vite-plugin`.
|
|
@@ -236,5 +255,6 @@ ids, `.dev.vars` secrets, and container exports.
|
|
|
236
255
|
- [ ] New project: scaffolded with `lunora init --template <t>`.
|
|
237
256
|
- [ ] Existing app: ran `lunora init --here` and wired `LunoraProvider`.
|
|
238
257
|
- [ ] Ran `lunora codegen`: `lunora/_generated/` exists and typecheck is clean.
|
|
239
|
-
- [ ]
|
|
258
|
+
- [ ] Dev server is running — user terminal, or `lunora dev --background`
|
|
259
|
+
(check with `lunora dev status`).
|
|
240
260
|
- [ ] Verified a query/mutation round-trip re-renders the client live.
|