@lunora/cli 1.0.0-alpha.10 → 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.
Files changed (58) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/bin.mjs +1 -1
  4. package/dist/index.d.mts +345 -320
  5. package/dist/index.d.ts +345 -320
  6. package/dist/index.mjs +8 -9
  7. package/dist/packem_chunks/handler.mjs +15 -5
  8. package/dist/packem_chunks/handler10.mjs +8 -14
  9. package/dist/packem_chunks/handler11.mjs +20 -189
  10. package/dist/packem_chunks/handler12.mjs +176 -115
  11. package/dist/packem_chunks/handler13.mjs +118 -52
  12. package/dist/packem_chunks/handler14.mjs +50 -43
  13. package/dist/packem_chunks/handler15.mjs +27 -6
  14. package/dist/packem_chunks/handler16.mjs +3 -2
  15. package/dist/packem_chunks/handler17.mjs +1 -1
  16. package/dist/packem_chunks/handler18.mjs +3 -3
  17. package/dist/packem_chunks/handler19.mjs +44 -5
  18. package/dist/packem_chunks/handler2.mjs +6 -4
  19. package/dist/packem_chunks/handler20.mjs +2 -2
  20. package/dist/packem_chunks/handler21.mjs +158 -267
  21. package/dist/packem_chunks/handler3.mjs +8 -4
  22. package/dist/packem_chunks/handler4.mjs +2 -2
  23. package/dist/packem_chunks/handler5.mjs +7 -4
  24. package/dist/packem_chunks/handler6.mjs +10 -7
  25. package/dist/packem_chunks/handler7.mjs +2 -2
  26. package/dist/packem_chunks/handler8.mjs +1 -1
  27. package/dist/packem_chunks/handler9.mjs +330 -12
  28. package/dist/packem_chunks/planDevCommand.mjs +762 -81
  29. package/dist/packem_chunks/runCodegenCommand.mjs +5 -4
  30. package/dist/packem_chunks/runDeployCommand.mjs +137 -22
  31. package/dist/packem_chunks/runInitCommand.mjs +1193 -98
  32. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +94 -47
  33. package/dist/packem_chunks/runResetCommand.mjs +2 -2
  34. package/dist/packem_chunks/runRpcCommand.mjs +3 -2
  35. package/dist/packem_shared/{COMMANDS-D3h9Iwvl.mjs → COMMANDS-BOJIDoVY.mjs} +112 -25
  36. package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs} +7 -2
  37. package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
  38. package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-BS5ig822.mjs} +1 -1
  39. package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +31 -0
  40. package/dist/packem_shared/{command-BC30oSBW.mjs → command-lYnl4QyF.mjs} +6 -1
  41. package/dist/packem_shared/{commands-DPKWlqqX.mjs → commands-D5Yxt9VY.mjs} +199 -20
  42. package/dist/packem_shared/{createLogger-B40gPzQo.mjs → createLogger-CIWSHrTL.mjs} +33 -6
  43. package/dist/packem_shared/{createRecordingSpawner-DxI3mebw.mjs → createRecordingSpawner-WuSn20kb.mjs} +23 -2
  44. package/dist/packem_shared/detect-package-manager-v4hHpQd0.mjs +62 -0
  45. package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
  46. package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
  47. package/dist/packem_shared/{output-format-wUvAN6AL.mjs → output-format-B4642rjE.mjs} +1 -1
  48. package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-Dbp-Q2q3.mjs} +36 -9
  49. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
  50. package/dist/packem_shared/runAddCommand-D1hgfqFQ.mjs +4 -0
  51. package/dist/packem_shared/{storage-2RJBhUC4.mjs → storage-BXU4ax4O.mjs} +1 -1
  52. package/dist/packem_shared/{tui-prompts-DEiPCKV-.mjs → tui-prompts-BjEN8XgP.mjs} +9 -12
  53. package/dist/packem_shared/wrangler-secrets-Coni-mER.mjs +49 -0
  54. package/package.json +19 -14
  55. package/skills/lunora-quickstart/SKILL.md +26 -5
  56. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
  57. package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
  58. package/dist/packem_shared/runAddCommand-CTRA_JlL.mjs +0 -4
@@ -1,65 +1,131 @@
1
- import { existsSync, rmSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { writeLinkedProject, LINKED_PROJECT_FILE } from '@lunora/config';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
5
- import { r as readWranglerName } from '../packem_shared/wrangler-name-cy4yhm9j.mjs';
1
+ import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
2
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
3
+ import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
6
4
 
7
- const isValidWorkerUrl = (value) => {
8
- try {
9
- const { protocol } = new URL(value);
10
- return protocol === "http:" || protocol === "https:";
11
- } catch {
12
- return false;
13
- }
5
+ const GET_FUNCTION_STATS_OP = "__lunora_admin__:getFunctionStats";
6
+ const DEFAULT_LIMIT = 10;
7
+ const TRAILING_SLASH = /\/$/u;
8
+ const toInsightRow = (stat, rate) => {
9
+ return {
10
+ calls: stat.calls,
11
+ conflicts: stat.conflicts ?? 0,
12
+ errors: stat.errors,
13
+ lastErrorMessage: stat.lastErrorMessage,
14
+ maxDurationMs: stat.maxDurationMs,
15
+ meanDurationMs: stat.calls === 0 ? 0 : stat.totalDurationMs / stat.calls,
16
+ path: stat.path,
17
+ rate
18
+ };
19
+ };
20
+ const buildInsightsReport = (functions, limit) => {
21
+ const writeContention = functions.filter((stat) => (stat.conflicts ?? 0) > 0).map((stat) => toInsightRow(stat, stat.calls === 0 ? 0 : (stat.conflicts ?? 0) / stat.calls)).toSorted((a, b) => b.rate - a.rate || b.conflicts - a.conflicts).slice(0, limit);
22
+ const errorHotspots = functions.filter((stat) => stat.errors > 0).map((stat) => toInsightRow(stat, stat.calls === 0 ? 0 : stat.errors / stat.calls)).toSorted((a, b) => b.rate - a.rate || b.errors - a.errors).slice(0, limit);
23
+ const latencyOutliers = functions.map((stat) => toInsightRow(stat, 0)).toSorted((a, b) => b.maxDurationMs - a.maxDurationMs).slice(0, limit);
24
+ return { errorHotspots, latencyOutliers, totalFunctions: functions.length, writeContention };
14
25
  };
15
- const runLinkRemove = (cwd, logger) => {
16
- const path = join(cwd, LINKED_PROJECT_FILE);
17
- if (!existsSync(path)) {
18
- logger.warn(`link: no ${LINKED_PROJECT_FILE} to remove`);
19
- return { code: 0 };
26
+ const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
27
+ const formatMs = (ms) => ms < 1e3 ? `${Math.round(ms).toString()}ms` : `${(ms / 1e3).toFixed(2)}s`;
28
+ const formatSection = (heading, rows, emptyNote, renderRow) => [
29
+ heading,
30
+ ...rows.length === 0 ? [` ${emptyNote}`] : rows.map((row) => ` ${renderRow(row)}`)
31
+ ];
32
+ const formatInsightsReport = (report) => {
33
+ const errorTail = (row) => row.lastErrorMessage ? ` — ${row.lastErrorMessage}` : "";
34
+ return [
35
+ `Insights over ${report.totalFunctions.toString()} function${report.totalFunctions === 1 ? "" : "s"}`,
36
+ "",
37
+ ...formatSection(
38
+ "Write-conflict hot-spots (OCC contention — candidates for sharding):",
39
+ report.writeContention,
40
+ "none — no write conflicts observed",
41
+ (row) => `${row.path} ${row.conflicts.toString()}/${row.calls.toString()} calls (${percent(row.rate)})`
42
+ ),
43
+ "",
44
+ ...formatSection(
45
+ "Error hot-spots:",
46
+ report.errorHotspots,
47
+ "none — no errors observed",
48
+ (row) => `${row.path} ${row.errors.toString()}/${row.calls.toString()} calls (${percent(row.rate)})${errorTail(row)}`
49
+ ),
50
+ "",
51
+ ...formatSection(
52
+ "Latency outliers (slowest single call):",
53
+ report.latencyOutliers,
54
+ "none — no functions have run",
55
+ (row) => `${row.path} max ${formatMs(row.maxDurationMs)}, mean ${formatMs(row.meanDurationMs)} over ${row.calls.toString()} calls`
56
+ )
57
+ ].join("\n");
58
+ };
59
+ const resolveLimit = (raw) => {
60
+ if (raw === void 0 || !Number.isFinite(raw) || raw <= 0) {
61
+ return DEFAULT_LIMIT;
20
62
  }
21
- rmSync(path);
22
- logger.success(`link: removed ${LINKED_PROJECT_FILE}`);
23
- return { code: 0 };
63
+ return Math.floor(raw);
24
64
  };
25
- const runLinkCommand = (options) => {
26
- const cwd = options.cwd ?? process.cwd();
27
- const { logger } = options;
28
- if (options.remove) {
29
- return runLinkRemove(cwd, logger);
65
+ const runInsightsCommand = async (options) => {
66
+ if (options.prod && options.url === void 0) {
67
+ options.logger.error("--prod requires an explicit --url (refusing to report from the implicit localhost worker)");
68
+ return { code: 1 };
30
69
  }
31
- if (options.url === void 0 || options.url === "") {
32
- logger.error("link requires a deployed Worker URL. Usage: lunora link --url <https://your-worker>");
70
+ const token = options.token ?? process.env.LUNORA_ADMIN_TOKEN;
71
+ if (!token) {
72
+ options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
33
73
  return { code: 1 };
34
74
  }
35
- if (!isValidWorkerUrl(options.url)) {
36
- logger.error(`link: invalid --url "${options.url}" — expected an http(s) URL`);
75
+ const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
76
+ if (baseUrl === void 0) {
37
77
  return { code: 1 };
38
78
  }
39
- const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
40
- const link = {
41
- env: options.env,
42
- linkedAt: now(),
43
- workerName: options.name ?? readWranglerName(cwd),
44
- workerUrl: options.url
45
- };
46
- const path = writeLinkedProject(cwd, link);
47
- logger.success(`link: ${link.workerName ?? "(unnamed worker)"} -> ${options.url}`);
48
- logger.info(`link: wrote ${path}`);
49
- if (link.env !== void 0) {
50
- logger.info(`link: environment "${link.env}"`);
79
+ const requestUrl = `${baseUrl.replace(TRAILING_SLASH, "")}/_lunora/rpc`;
80
+ const fetchImpl = globalThis.fetch;
81
+ if (typeof fetchImpl !== "function") {
82
+ throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");
83
+ }
84
+ const payload = { args: {}, functionPath: GET_FUNCTION_STATS_OP };
85
+ if (options.shard !== void 0) {
86
+ payload.shardKey = options.shard;
87
+ }
88
+ options.logger.info(`POST ${requestUrl} -> insights`);
89
+ const response = await fetchImpl(requestUrl, {
90
+ body: JSON.stringify(payload),
91
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
92
+ method: "POST"
93
+ });
94
+ const text = await response.text();
95
+ if (!response.ok) {
96
+ options.logger.error(`insights failed: HTTP ${String(response.status)}: ${text}`);
97
+ return { code: 1 };
98
+ }
99
+ let parsed;
100
+ try {
101
+ parsed = JSON.parse(text);
102
+ } catch {
103
+ options.logger.error(`insights failed: worker returned non-JSON: ${text}`);
104
+ return { code: 1 };
105
+ }
106
+ const result = parsed.result ?? parsed;
107
+ const { functions } = result;
108
+ if (!Array.isArray(functions)) {
109
+ options.logger.error("insights failed: response carried no `functions` array");
110
+ return { code: 1 };
51
111
  }
52
- return { code: 0, link };
112
+ const report = buildInsightsReport(functions, resolveLimit(options.limit));
113
+ options.logger.info(options.json ? JSON.stringify(report, void 0, 2) : formatInsightsReport(report));
114
+ return { code: 0, report };
53
115
  };
54
- const execute = defineHandler(
55
- ({ cwd, logger, options }) => runLinkCommand({
56
- cwd,
57
- env: options.env,
116
+ const execute = defineHandler(({ cwd, logger, options }) => {
117
+ const limit = options.limit === void 0 ? void 0 : Number.parseInt(options.limit, 10);
118
+ return runInsightsCommand({
119
+ json: options.json,
120
+ limit,
58
121
  logger,
59
- name: options.name,
60
- remove: options.remove === true,
61
- url: options.url
62
- })
63
- );
122
+ prod: options.prod,
123
+ shard: options.shard,
124
+ token: options.token,
125
+ // Fall back to the `.lunora/project.json` link when `--prod` is set, so a
126
+ // linked checkout doesn't need --url repeated for prod insights.
127
+ url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url })
128
+ });
129
+ });
64
130
 
65
- export { execute, runLinkCommand };
131
+ export { buildInsightsReport, execute, formatInsightsReport, runInsightsCommand };
@@ -1,58 +1,65 @@
1
- import { readLinkedProject } from '@lunora/config';
2
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
3
- import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
1
+ import { existsSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { writeLinkedProject, LINKED_PROJECT_FILE } from '@lunora/config';
4
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
+ import { r as readWranglerName } from '../packem_shared/wrangler-name-cy4yhm9j.mjs';
4
6
 
5
- const LOG_FORMATS = /* @__PURE__ */ new Set(["json", "pretty"]);
6
- const runLogsCommand = async (options) => {
7
- const cwd = options.cwd ?? process.cwd();
8
- if (options.format !== void 0 && !LOG_FORMATS.has(options.format)) {
9
- options.logger.error(`logs: unknown --format "${options.format}" — expected pretty | json`);
10
- return { code: 1, descriptor: void 0, error: "invalid format" };
11
- }
12
- const args = ["exec", "wrangler", "tail"];
13
- if (options.worker !== void 0) {
14
- args.push(options.worker);
15
- }
16
- const env = options.env ?? readLinkedProject(cwd)?.env;
17
- if (env !== void 0) {
18
- args.push("--env", env);
7
+ const isValidWorkerUrl = (value) => {
8
+ try {
9
+ const { protocol } = new URL(value);
10
+ return protocol === "http:" || protocol === "https:";
11
+ } catch {
12
+ return false;
19
13
  }
20
- if (options.format !== void 0) {
21
- args.push("--format", options.format);
14
+ };
15
+ const runLinkRemove = (cwd, logger) => {
16
+ const path = join(cwd, LINKED_PROJECT_FILE);
17
+ if (!existsSync(path)) {
18
+ logger.warn(`link: no ${LINKED_PROJECT_FILE} to remove`);
19
+ return { code: 0 };
22
20
  }
23
- if (options.status !== void 0) {
24
- args.push("--status", options.status);
21
+ rmSync(path);
22
+ logger.success(`link: removed ${LINKED_PROJECT_FILE}`);
23
+ return { code: 0 };
24
+ };
25
+ const runLinkCommand = (options) => {
26
+ const cwd = options.cwd ?? process.cwd();
27
+ const { logger } = options;
28
+ if (options.remove) {
29
+ return runLinkRemove(cwd, logger);
25
30
  }
26
- if (options.search !== void 0) {
27
- args.push("--search", options.search);
31
+ if (options.url === void 0 || options.url === "") {
32
+ logger.error("link requires a deployed Worker URL. Usage: lunora link --url <https://your-worker>");
33
+ return { code: 1 };
28
34
  }
29
- if (options.temporary) {
30
- args.push("--temporary");
35
+ if (!isValidWorkerUrl(options.url)) {
36
+ logger.error(`link: invalid --url "${options.url}" — expected an http(s) URL`);
37
+ return { code: 1 };
31
38
  }
32
- const descriptor = {
33
- args,
34
- command: "pnpm",
35
- cwd
36
- };
37
- options.logger.info(`tailing logs via ${descriptor.command} ${descriptor.args.join(" ")}`);
38
- const spawner = options.spawner ?? defaultSpawner;
39
- const result = await spawner(descriptor);
40
- return {
41
- code: result.code,
42
- descriptor
39
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
40
+ const link = {
41
+ env: options.env,
42
+ linkedAt: now(),
43
+ workerName: options.name ?? readWranglerName(cwd),
44
+ workerUrl: options.url
43
45
  };
46
+ const path = writeLinkedProject(cwd, link);
47
+ logger.success(`link: ${link.workerName ?? "(unnamed worker)"} -> ${options.url}`);
48
+ logger.info(`link: wrote ${path}`);
49
+ if (link.env !== void 0) {
50
+ logger.info(`link: environment "${link.env}"`);
51
+ }
52
+ return { code: 0, link };
44
53
  };
45
54
  const execute = defineHandler(
46
- ({ argument, cwd, logger, options }) => runLogsCommand({
55
+ ({ cwd, logger, options }) => runLinkCommand({
47
56
  cwd,
48
57
  env: options.env,
49
- format: options.format,
50
58
  logger,
51
- search: options.search,
52
- status: options.status,
53
- temporary: options.temporary === true,
54
- worker: argument[0]
59
+ name: options.name,
60
+ remove: options.remove === true,
61
+ url: options.url
55
62
  })
56
63
  );
57
64
 
58
- export { execute, runLogsCommand };
65
+ export { execute, runLinkCommand };
@@ -1,10 +1,11 @@
1
1
  import { runCodegen } from '@lunora/codegen';
2
- import { validateWranglerProject, inferLunoraBindings, reconcileWranglerBindings } from '@lunora/config';
3
- import { p as parseApiSpec } from '../packem_shared/api-spec-CtA6ilu4.mjs';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
2
+ import { validateWranglerProject, inferLunoraBindings, reconcileWranglerBindings, reconcileWranglerCompatibilityDate, reconcileWranglerCrons } from '@lunora/config';
3
+ import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
4
+ import { r as renderCodegenFailure } from '../packem_shared/codegen-error-DJG-ghs_.mjs';
5
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
6
  import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
6
7
 
7
- const provisionBindings = async (cwd, logger) => {
8
+ const provisionBindings = async (cwd, logger, cronTriggers = []) => {
8
9
  try {
9
10
  const inferred = await inferLunoraBindings({ projectRoot: cwd });
10
11
  const reconciled = reconcileWranglerBindings(cwd, inferred);
@@ -18,6 +19,26 @@ const provisionBindings = async (cwd, logger) => {
18
19
  const message = error instanceof Error ? error.message : String(error);
19
20
  logger.warn(`binding inference skipped: ${message}`);
20
21
  }
22
+ try {
23
+ const reconciled = reconcileWranglerCompatibilityDate(cwd);
24
+ if (reconciled.changed) {
25
+ logger.success(
26
+ `bumped compatibility_date to ${reconciled.date ?? "unknown"} (Workers Cache enabled) → ${reconciled.wranglerPath ?? "wrangler.jsonc"}`
27
+ );
28
+ }
29
+ } catch (error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ logger.warn(`compatibility date sync skipped: ${message}`);
32
+ }
33
+ try {
34
+ const reconciled = reconcileWranglerCrons(cwd, cronTriggers);
35
+ if (reconciled.changed) {
36
+ logger.success(`synced ${String(cronTriggers.length)} cron trigger(s) → ${reconciled.wranglerPath ?? "wrangler.jsonc"}`);
37
+ }
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ logger.warn(`cron trigger sync skipped: ${message}`);
41
+ }
21
42
  };
22
43
  const runPrepareCommand = async (options) => {
23
44
  const cwd = options.cwd ?? process.cwd();
@@ -28,7 +49,7 @@ const runPrepareCommand = async (options) => {
28
49
  options.logger.success("codegen complete");
29
50
  } catch (error) {
30
51
  const message = error instanceof Error ? error.message : String(error);
31
- options.logger.error(`codegen failed: ${message}`);
52
+ options.logger.error(renderCodegenFailure(error));
32
53
  return {
33
54
  code: 1,
34
55
  error: `codegen failed: ${message}`,
@@ -49,7 +70,7 @@ const runPrepareCommand = async (options) => {
49
70
  validation: { problems: [], wranglerPath: void 0 }
50
71
  };
51
72
  }
52
- await provisionBindings(cwd, options.logger);
73
+ await provisionBindings(cwd, options.logger, codegen.cronTriggers);
53
74
  const validation = validateWranglerProject({ projectRoot: cwd });
54
75
  if (validation.problems.length > 0) {
55
76
  options.logger.error("wrangler.jsonc validation failed:");
@@ -1,5 +1,5 @@
1
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
2
- import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-DPKWlqqX.mjs';
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-D5Yxt9VY.mjs';
3
3
 
4
4
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
5
5
  const subcommand = argument[0];
@@ -26,6 +26,7 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
26
26
  if (subcommand === "view") {
27
27
  return runRegistryViewCommand({
28
28
  allowUnsafeSource: options.allowUnsafeSource === true,
29
+ cwd,
29
30
  from: options.from,
30
31
  logger,
31
32
  names,
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSy
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { AGENT_RULES_DIR, detectAgentRules, LUNORA_SKILL_NAMES } from '@lunora/config';
4
4
  import { join, relative, dirname } from '@visulima/path';
5
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
5
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
6
6
 
7
7
  const resolveBundledSkillsDirectory = (startDirectory = dirname(fileURLToPath(import.meta.url))) => {
8
8
  let directory = startDirectory;
@@ -5,10 +5,10 @@ import { discoverSchema, schemaFromIr } from '@lunora/codegen';
5
5
  import { seedPlan } from '@lunora/seed';
6
6
  import { join } from '@visulima/path';
7
7
  import { Project } from 'ts-morph';
8
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
8
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
9
9
  import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
10
- import { b as tuiConfirm } from '../packem_shared/tui-prompts-DEiPCKV-.mjs';
11
- import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
10
+ import { b as tuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
11
+ import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs';
12
12
  import { runResetCommand } from './runResetCommand.mjs';
13
13
 
14
14
  const isLocalUrl = (url) => {
@@ -1,20 +1,50 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
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';
4
+ import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
5
+ import { a as renderCodegenHint } from '../packem_shared/codegen-error-DJG-ghs_.mjs';
6
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
7
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-v4hHpQd0.mjs';
8
+ import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
7
9
  import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
8
- import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
10
+ import { defaultSpawner } from '../packem_shared/createRecordingSpawner-WuSn20kb.mjs';
9
11
  import { validateWranglerProject } from '@lunora/config';
10
12
 
11
13
  const runTypecheckStep = async (cwd, spawner) => {
12
14
  if (!existsSync(join(cwd, "tsconfig.json"))) {
13
15
  return { warning: "no tsconfig.json found — skipping TypeScript type-check" };
14
16
  }
15
- const result = await spawner({ args: ["exec", "tsc", "--noEmit", "-p", "tsconfig.json"], command: "pnpm", cwd });
17
+ const exec = execArgsFor(detectPackageManager(cwd), "tsc", ["--noEmit", "-p", "tsconfig.json"]);
18
+ const result = await spawner({ args: exec.args, command: exec.command, cwd });
16
19
  return result.code === 0 ? {} : { error: `type errors: tsc --noEmit exited ${String(result.code)}` };
17
20
  };
21
+ const HEALTH_PATH = "/_lunora/health";
22
+ const joinHealthUrl = (base) => (base.endsWith("/") ? base.slice(0, -1) : base) + HEALTH_PATH;
23
+ const runHealthProbeStep = async (healthUrl, healthFetch) => {
24
+ const url = joinHealthUrl(healthUrl);
25
+ let response;
26
+ try {
27
+ response = await healthFetch(url);
28
+ } catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ return { error: `health probe failed: could not reach ${url} (${message})` };
31
+ }
32
+ if (response.ok) {
33
+ return {};
34
+ }
35
+ return { error: `health probe failed: ${url} returned HTTP ${String(response.status)}` };
36
+ };
37
+ const probeHealthIfRequested = async (options, logger) => {
38
+ if (options.healthUrl === void 0 || options.healthUrl === "") {
39
+ return void 0;
40
+ }
41
+ const probe = await runHealthProbeStep(options.healthUrl, options.healthFetch ?? ((url) => fetch(url)));
42
+ if (probe.error === void 0) {
43
+ logger.success(`verify: health probe ok (${joinHealthUrl(options.healthUrl)})`);
44
+ return void 0;
45
+ }
46
+ return probe.error;
47
+ };
18
48
  const reportVerifyResult = (logger, errors, warnings, wranglerPath) => {
19
49
  if (errors.length === 0 && warnings.length === 0) {
20
50
  logger.success("verify: project is valid");
@@ -30,6 +60,10 @@ const reportVerifyResult = (logger, errors, warnings, wranglerPath) => {
30
60
  logger.error("verify: errors:");
31
61
  for (const error of errors) {
32
62
  logger.error(` - ${error}`);
63
+ const hint = renderCodegenHint(error);
64
+ if (hint !== void 0) {
65
+ logger.error(hint);
66
+ }
33
67
  }
34
68
  return { code: 1, errors, warnings, wranglerPath };
35
69
  }
@@ -66,6 +100,10 @@ const runVerifyCommand = async (options) => {
66
100
  warnings.push(typecheck.warning);
67
101
  }
68
102
  }
103
+ const healthError = await probeHealthIfRequested(options, logger);
104
+ if (healthError !== void 0) {
105
+ errors.push(healthError);
106
+ }
69
107
  const result = reportVerifyResult(logger, errors, warnings, validation.wranglerPath);
70
108
  if (isJsonFormat(options.format)) {
71
109
  printJson(result);
@@ -78,6 +116,7 @@ const execute = defineHandler(async ({ cwd, logger, options }) => {
78
116
  apiSpec: parseApiSpec(options.apiSpec),
79
117
  cwd,
80
118
  format: options.format,
119
+ healthUrl: options.healthUrl,
81
120
  logger,
82
121
  // `--no-typecheck` is declared as a `no-*` option but cerebro exposes it
83
122
  // under the negated `typecheck` key (false when passed, true when absent).
@@ -1,8 +1,9 @@
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';
5
- import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
4
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-v4hHpQd0.mjs';
6
+ import { defaultSpawner } from '../packem_shared/createRecordingSpawner-WuSn20kb.mjs';
6
7
 
7
8
  const walk = (root) => {
8
9
  const entries = [];
@@ -71,9 +72,10 @@ const runAnalyzeCommand = async (options) => {
71
72
  } else {
72
73
  outdir = mkdtempSync(join(tmpdir(), "lunora-analyze-"));
73
74
  temporary = true;
75
+ const exec = execArgsFor(detectPackageManager(cwd), "wrangler", ["deploy", "--dry-run", "--outdir", outdir]);
74
76
  descriptor = {
75
- args: ["exec", "wrangler", "deploy", "--dry-run", "--outdir", outdir],
76
- command: "pnpm",
77
+ args: exec.args,
78
+ command: exec.command,
77
79
  cwd
78
80
  };
79
81
  logger.info(`analyze: building via ${descriptor.command} ${descriptor.args.join(" ")}`);
@@ -1,8 +1,8 @@
1
1
  import { readFileSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
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';
4
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
+ import { o as openUrl } from '../packem_shared/open-url-4PBLY9X0.mjs';
6
6
 
7
7
  const DEFAULT_DEV_PORT = 8787;
8
8
  const STUDIO_PATH = "/_lunora/studio";