@lunora/cli 1.0.0-alpha.9 → 1.0.0-alpha.90

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 (59) 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 +358 -300
  5. package/dist/index.d.ts +358 -300
  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 +48 -67
  14. package/dist/packem_chunks/handler16.mjs +94 -37
  15. package/dist/packem_chunks/handler17.mjs +39 -100
  16. package/dist/packem_chunks/handler18.mjs +87 -152
  17. package/dist/packem_chunks/handler19.mjs +148 -67
  18. package/dist/packem_chunks/handler2.mjs +6 -4
  19. package/dist/packem_chunks/handler20.mjs +77 -75
  20. package/dist/packem_chunks/handler21.mjs +71 -288
  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 +1648 -188
  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-Dh0bSERi.mjs → COMMANDS-DZ0NnuqI.mjs} +103 -27
  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-BDXcJCCJ.mjs → command-lYnl4QyF.mjs} +6 -1
  41. package/dist/packem_shared/{commands-CkkATMMx.mjs → commands-D5Yxt9VY.mjs} +199 -20
  42. package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-CIWSHrTL.mjs} +40 -8
  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-7gyGR3h8.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-Bjo35hPa.mjs → storage-BXU4ax4O.mjs} +1 -1
  52. package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
  53. package/dist/packem_shared/wrangler-secrets-Coni-mER.mjs +49 -0
  54. package/package.json +17 -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-DepS9W9W.mjs +0 -4
  59. package/dist/packem_shared/tui-prompts-CA9lngSS.mjs +0 -269
package/dist/index.mjs CHANGED
@@ -1,19 +1,18 @@
1
- export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-Dh0bSERi.mjs';
1
+ export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-DZ0NnuqI.mjs';
2
2
  export { runCodegenCommand } from './packem_chunks/runCodegenCommand.mjs';
3
- export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
3
+ export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs';
4
4
  export { runDeployCommand } from './packem_chunks/runDeployCommand.mjs';
5
5
  export { planDevCommand, runDevCommand } from './packem_chunks/planDevCommand.mjs';
6
6
  export { runInitCommand } from './packem_chunks/runInitCommand.mjs';
7
7
  export { runMigrateGenerateCommand } from './packem_chunks/runMigrateGenerateCommand.mjs';
8
8
  export { runResetCommand } from './packem_chunks/runResetCommand.mjs';
9
9
  export { runRpcCommand } from './packem_chunks/runRpcCommand.mjs';
10
- export { default as parseArgs } from './packem_shared/parseArgs-YXFuKdEk.mjs';
11
- export { insertSchemaExtension } from './packem_shared/insertSchemaExtension-BuzF6-t2.mjs';
12
- export { createLogger, pail } from './packem_shared/createLogger-CHPNjFw2.mjs';
10
+ export { insertSchemaExtension } from './packem_shared/insertSchemaExtension-DAqbfr9Z.mjs';
11
+ export { createLogger, pail } from './packem_shared/createLogger-CIWSHrTL.mjs';
13
12
  export { diffSnapshots, renderAddColumn, renderCreateIndex, renderCreateTable, renderDropIndex, renderDropTable, renderMigrationFile, validatorKindToSqlType } from './packem_shared/diffSnapshots-BeDvvNiF.mjs';
14
13
  export { default as schemaIrToSnapshot } from './packem_shared/schemaIrToSnapshot-DdsljJT-.mjs';
15
- export { createRecordingSpawner, defaultSpawner } from './packem_shared/createRecordingSpawner-DxI3mebw.mjs';
16
- export { default as parseManifest } from './packem_shared/parseManifest--vZf2FY1.mjs';
14
+ export { createRecordingSpawner, defaultSpawner } from './packem_shared/createRecordingSpawner-WuSn20kb.mjs';
15
+ export { default as parseManifest } from './packem_shared/parseManifest-Dbp-Q2q3.mjs';
17
16
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
18
- export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BcYe607_.mjs';
19
- export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-CkkATMMx.mjs';
17
+ export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BS5ig822.mjs';
18
+ export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-D5Yxt9VY.mjs';
@@ -1,10 +1,11 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { findWranglerFile } from '@lunora/config';
3
3
  import { join, basename } from '@visulima/path';
4
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
5
- import { t as tuiText, a as tuiSelect } from '../packem_shared/tui-prompts-CA9lngSS.mjs';
6
- import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-Bjo35hPa.mjs';
7
- import { r as runAddCommand } from '../packem_shared/commands-CkkATMMx.mjs';
4
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
+ import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
6
+ import { t as tuiText, a as tuiSelect } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
7
+ import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-BXU4ax4O.mjs';
8
+ import { r as runAddCommand } from '../packem_shared/commands-D5Yxt9VY.mjs';
8
9
 
9
10
  const providerToItem = (provider) => {
10
11
  const value = provider.trim().toLowerCase();
@@ -130,6 +131,12 @@ const runAddFeature = async (options) => {
130
131
  return { code: result.code, items };
131
132
  };
132
133
  const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
134
+ const formatError = validateOutputFormat("add", options.format);
135
+ if (formatError !== void 0) {
136
+ logger.error(formatError);
137
+ return { code: 1 };
138
+ }
139
+ const effectiveLogger = loggerForFormat(options.format, logger);
133
140
  const result = await runAddFeature({
134
141
  allowUnsafeSource: options.allowUnsafeSource === true,
135
142
  bucket: options.bucket,
@@ -137,13 +144,16 @@ const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
137
144
  db: options.db,
138
145
  feature: argument[0],
139
146
  from: options.from,
140
- logger,
147
+ logger: effectiveLogger,
141
148
  mailTo: options.mailTo,
142
149
  provider: options.provider,
143
150
  ref: options.ref,
144
151
  source: options.source,
145
152
  yes: options.yes === true
146
153
  });
154
+ if (isJsonFormat(options.format)) {
155
+ printJson({ code: result.code, items: result.items });
156
+ }
147
157
  return { code: result.code };
148
158
  });
149
159
 
@@ -1,22 +1,16 @@
1
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
1
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
2
  import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
3
- import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
3
+ import { runExportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs';
4
4
 
5
- const execute = defineHandler(({ argument, cwd, logger, options }) => {
6
- const file = argument[0];
7
- if (!file) {
8
- logger.error("import requires a file. Usage: lunora import <path> [--table <name>]");
9
- return { code: 1 };
10
- }
11
- return runImportCommand({
12
- batchSize: options.batchSize,
13
- file,
5
+ const execute = defineHandler(
6
+ ({ argument, cwd, logger, options }) => runExportCommand({
14
7
  logger,
8
+ out: argument[0] ?? options.out,
15
9
  prod: options.prod === true,
16
- table: options.table,
10
+ tables: options.tables,
17
11
  token: options.token,
18
12
  url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url })
19
- });
20
- });
13
+ })
14
+ );
21
15
 
22
16
  export { execute };
@@ -1,192 +1,23 @@
1
- import { readFileSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { discoverSchema } from '@lunora/codegen';
4
- import { readLinkedProject } from '@lunora/config';
5
- import { parse } from 'jsonc-parser';
6
- import { Project } from 'ts-morph';
7
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
1
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
+ import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
3
+ import { runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs';
8
4
 
9
- const findWranglerFile = (projectRoot) => {
10
- for (const candidate of ["wrangler.jsonc", "wrangler.json"]) {
11
- const fullPath = join(projectRoot, candidate);
12
- if (existsSync(fullPath)) {
13
- return fullPath;
14
- }
15
- }
16
- return void 0;
17
- };
18
- const stringField = (record, key) => {
19
- if (record === null || typeof record !== "object") {
20
- return void 0;
21
- }
22
- const value = record[key];
23
- return typeof value === "string" && value.length > 0 ? value : void 0;
24
- };
25
- const arrayField = (record, key) => {
26
- if (record === null || typeof record !== "object") {
27
- return [];
28
- }
29
- const value = record[key];
30
- return Array.isArray(value) ? value : [];
31
- };
32
- const summariseWrangler = (raw) => {
33
- const durableObjectBindings = arrayField(raw.durable_objects ?? {}, "bindings");
34
- const d1 = arrayField(raw, "d1_databases");
35
- const vectorize = arrayField(raw, "vectorize");
36
- return {
37
- bindings: {
38
- d1: d1.map((entry) => stringField(entry, "binding") ?? "<unnamed>"),
39
- durableObjects: durableObjectBindings.map((entry) => stringField(entry, "name") ?? "<unnamed>"),
40
- vectorize: vectorize.map((entry) => stringField(entry, "binding") ?? "<unnamed>")
41
- },
42
- compatibilityDate: stringField(raw, "compatibility_date"),
43
- compatibilityFlags: arrayField(raw, "compatibility_flags").filter((entry) => typeof entry === "string"),
44
- main: stringField(raw, "main"),
45
- name: stringField(raw, "name")
46
- };
47
- };
48
- const summariseSchema = (schema) => {
49
- return {
50
- tables: schema.tables.map((table) => {
51
- let shard = "root";
52
- if (table.shardMode === "global") {
53
- shard = "global";
54
- } else if (typeof table.shardMode === "object") {
55
- shard = `shardBy(${table.shardMode.field})`;
56
- }
57
- return {
58
- indexes: table.indexes.length,
59
- name: table.name,
60
- shard
61
- };
62
- }),
63
- vectorIndexes: schema.vectorIndexes.length
64
- };
65
- };
66
- const collectLunoraPackages = (projectRoot) => {
67
- const pkgPath = join(projectRoot, "package.json");
68
- if (!existsSync(pkgPath)) {
69
- return [];
70
- }
71
- let pkg;
72
- try {
73
- pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
74
- } catch {
75
- return [];
76
- }
77
- if (pkg === null || typeof pkg !== "object") {
78
- return [];
79
- }
80
- const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
81
- const seen = /* @__PURE__ */ new Map();
82
- for (const section of sections) {
83
- const block = pkg[section];
84
- if (block === null || typeof block !== "object") {
85
- continue;
86
- }
87
- for (const [name, version] of Object.entries(block)) {
88
- if (!name.startsWith("@lunora/")) {
89
- continue;
90
- }
91
- if (typeof version === "string" && !seen.has(name)) {
92
- seen.set(name, version);
93
- }
94
- }
95
- }
96
- return [...seen.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([name, version]) => {
97
- return { name, version };
5
+ const execute = defineHandler(({ argument, cwd, logger, options }) => {
6
+ const file = argument[0];
7
+ if (!file) {
8
+ logger.error("import requires a file. Usage: lunora import <path> [--table <name>]");
9
+ return { code: 1 };
10
+ }
11
+ return runImportCommand({
12
+ batchSize: options.batchSize,
13
+ file,
14
+ logger,
15
+ prod: options.prod === true,
16
+ table: options.table,
17
+ token: options.token,
18
+ url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url }),
19
+ yes: options.yes === true
98
20
  });
99
- };
100
- const collectInfo = (projectRoot) => {
101
- const lunoraPackages = collectLunoraPackages(projectRoot);
102
- const wranglerPath = findWranglerFile(projectRoot);
103
- let wrangler;
104
- if (wranglerPath) {
105
- try {
106
- wrangler = summariseWrangler(parse(readFileSync(wranglerPath, "utf8")));
107
- } catch {
108
- wrangler = void 0;
109
- }
110
- }
111
- const schemaPath = join(projectRoot, "lunora", "schema.ts");
112
- let schema;
113
- let schemaError;
114
- if (existsSync(schemaPath)) {
115
- try {
116
- const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
117
- schema = summariseSchema(discoverSchema(project, schemaPath));
118
- } catch (error) {
119
- schemaError = error instanceof Error ? error.message : String(error);
120
- }
121
- }
122
- return {
123
- link: readLinkedProject(projectRoot),
124
- lunoraPackages,
125
- projectRoot,
126
- schema,
127
- schemaError,
128
- wrangler,
129
- wranglerPath
130
- };
131
- };
132
- const renderText = (snapshot, logger) => {
133
- logger.info(`project: ${snapshot.projectRoot}`);
134
- logger.info("");
135
- logger.info("@lunora/* packages:");
136
- if (snapshot.lunoraPackages.length === 0) {
137
- logger.info(" (none found in package.json)");
138
- } else {
139
- for (const pkg of snapshot.lunoraPackages) {
140
- logger.info(` ${pkg.name}@${pkg.version}`);
141
- }
142
- }
143
- logger.info("");
144
- if (snapshot.wrangler) {
145
- logger.info(`wrangler: ${snapshot.wranglerPath ?? ""}`);
146
- logger.info(` name: ${snapshot.wrangler.name ?? "<unset>"}`);
147
- logger.info(` main: ${snapshot.wrangler.main ?? "<unset>"}`);
148
- logger.info(` compatibility_date: ${snapshot.wrangler.compatibilityDate ?? "<unset>"}`);
149
- logger.info(` compatibility_flags: ${snapshot.wrangler.compatibilityFlags.join(", ") || "<none>"}`);
150
- logger.info(` durable objects: ${snapshot.wrangler.bindings.durableObjects.join(", ") || "<none>"}`);
151
- logger.info(` d1 databases: ${snapshot.wrangler.bindings.d1.join(", ") || "<none>"}`);
152
- logger.info(` vectorize indexes: ${snapshot.wrangler.bindings.vectorize.join(", ") || "<none>"}`);
153
- } else {
154
- logger.info("wrangler: (not found)");
155
- }
156
- logger.info("");
157
- if (snapshot.link) {
158
- logger.info(`link: ${snapshot.link.workerName ?? "(unnamed)"} -> ${snapshot.link.workerUrl ?? "<no url>"}`);
159
- if (snapshot.link.env !== void 0) {
160
- logger.info(` env: ${snapshot.link.env}`);
161
- }
162
- } else {
163
- logger.info("link: (not linked — run `lunora link --url <https://your-worker>`)");
164
- }
165
- logger.info("");
166
- if (snapshot.schemaError !== void 0) {
167
- logger.warn(`schema: parse error — ${snapshot.schemaError}`);
168
- } else if (snapshot.schema) {
169
- logger.info(`schema: ${String(snapshot.schema.tables.length)} table(s), ${String(snapshot.schema.vectorIndexes)} vector index(es)`);
170
- for (const table of snapshot.schema.tables) {
171
- logger.info(` ${table.name} [${table.shard}, ${String(table.indexes)} index(es)]`);
172
- }
173
- } else {
174
- logger.info("schema: (no lunora/schema.ts)");
175
- }
176
- };
177
- const runInfoCommand = (options) => {
178
- const cwd = options.cwd ?? process.cwd();
179
- const snapshot = collectInfo(cwd);
180
- if (options.json) {
181
- process.stdout.write(`${JSON.stringify(snapshot, void 0, 2)}
182
- `);
183
- } else {
184
- renderText(snapshot, options.logger);
185
- }
186
- return { code: 0, snapshot };
187
- };
188
- const execute = defineHandler(
189
- ({ cwd, logger, options }) => runInfoCommand({ cwd, json: options.json === true, logger })
190
- );
21
+ });
191
22
 
192
- export { collectInfo, execute, runInfoCommand };
23
+ export { execute };
@@ -1,131 +1,192 @@
1
- import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
2
- import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
3
- import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { discoverSchema } from '@lunora/codegen';
4
+ import { readLinkedProject } from '@lunora/config';
5
+ import { parse } from 'jsonc-parser';
6
+ import { Project } from 'ts-morph';
7
+ import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
4
8
 
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
+ const findWranglerFile = (projectRoot) => {
10
+ for (const candidate of ["wrangler.jsonc", "wrangler.json"]) {
11
+ const fullPath = join(projectRoot, candidate);
12
+ if (existsSync(fullPath)) {
13
+ return fullPath;
14
+ }
15
+ }
16
+ return void 0;
17
+ };
18
+ const stringField = (record, key) => {
19
+ if (record === null || typeof record !== "object") {
20
+ return void 0;
21
+ }
22
+ const value = record[key];
23
+ return typeof value === "string" && value.length > 0 ? value : void 0;
24
+ };
25
+ const arrayField = (record, key) => {
26
+ if (record === null || typeof record !== "object") {
27
+ return [];
28
+ }
29
+ const value = record[key];
30
+ return Array.isArray(value) ? value : [];
31
+ };
32
+ const summariseWrangler = (raw) => {
33
+ const durableObjectBindings = arrayField(raw.durable_objects ?? {}, "bindings");
34
+ const d1 = arrayField(raw, "d1_databases");
35
+ const vectorize = arrayField(raw, "vectorize");
9
36
  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
37
+ bindings: {
38
+ d1: d1.map((entry) => stringField(entry, "binding") ?? "<unnamed>"),
39
+ durableObjects: durableObjectBindings.map((entry) => stringField(entry, "name") ?? "<unnamed>"),
40
+ vectorize: vectorize.map((entry) => stringField(entry, "binding") ?? "<unnamed>")
41
+ },
42
+ compatibilityDate: stringField(raw, "compatibility_date"),
43
+ compatibilityFlags: arrayField(raw, "compatibility_flags").filter((entry) => typeof entry === "string"),
44
+ main: stringField(raw, "main"),
45
+ name: stringField(raw, "name")
18
46
  };
19
47
  };
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 };
25
- };
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");
48
+ const summariseSchema = (schema) => {
49
+ return {
50
+ tables: schema.tables.map((table) => {
51
+ let shard = "root";
52
+ if (table.shardMode === "global") {
53
+ shard = "global";
54
+ } else if (typeof table.shardMode === "object") {
55
+ shard = `shardBy(${table.shardMode.field})`;
56
+ }
57
+ return {
58
+ indexes: table.indexes.length,
59
+ name: table.name,
60
+ shard
61
+ };
62
+ }),
63
+ vectorIndexes: schema.vectorIndexes.length
64
+ };
58
65
  };
59
- const resolveLimit = (raw) => {
60
- if (raw === void 0 || !Number.isFinite(raw) || raw <= 0) {
61
- return DEFAULT_LIMIT;
66
+ const collectLunoraPackages = (projectRoot) => {
67
+ const pkgPath = join(projectRoot, "package.json");
68
+ if (!existsSync(pkgPath)) {
69
+ return [];
62
70
  }
63
- return Math.floor(raw);
64
- };
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 };
71
+ let pkg;
72
+ try {
73
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
74
+ } catch {
75
+ return [];
69
76
  }
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");
73
- return { code: 1 };
77
+ if (pkg === null || typeof pkg !== "object") {
78
+ return [];
74
79
  }
75
- const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
76
- if (baseUrl === void 0) {
77
- return { code: 1 };
80
+ const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
81
+ const seen = /* @__PURE__ */ new Map();
82
+ for (const section of sections) {
83
+ const block = pkg[section];
84
+ if (block === null || typeof block !== "object") {
85
+ continue;
86
+ }
87
+ for (const [name, version] of Object.entries(block)) {
88
+ if (!name.startsWith("@lunora/")) {
89
+ continue;
90
+ }
91
+ if (typeof version === "string" && !seen.has(name)) {
92
+ seen.set(name, version);
93
+ }
94
+ }
78
95
  }
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");
96
+ return [...seen.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([name, version]) => {
97
+ return { name, version };
98
+ });
99
+ };
100
+ const collectInfo = (projectRoot) => {
101
+ const lunoraPackages = collectLunoraPackages(projectRoot);
102
+ const wranglerPath = findWranglerFile(projectRoot);
103
+ let wrangler;
104
+ if (wranglerPath) {
105
+ try {
106
+ wrangler = summariseWrangler(parse(readFileSync(wranglerPath, "utf8")));
107
+ } catch {
108
+ wrangler = void 0;
109
+ }
83
110
  }
84
- const payload = { args: {}, functionPath: GET_FUNCTION_STATS_OP };
85
- if (options.shard !== void 0) {
86
- payload.shardKey = options.shard;
111
+ const schemaPath = join(projectRoot, "lunora", "schema.ts");
112
+ let schema;
113
+ let schemaError;
114
+ if (existsSync(schemaPath)) {
115
+ try {
116
+ const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
117
+ schema = summariseSchema(discoverSchema(project, schemaPath));
118
+ } catch (error) {
119
+ schemaError = error instanceof Error ? error.message : String(error);
120
+ }
87
121
  }
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 };
122
+ return {
123
+ link: readLinkedProject(projectRoot),
124
+ lunoraPackages,
125
+ projectRoot,
126
+ schema,
127
+ schemaError,
128
+ wrangler,
129
+ wranglerPath
130
+ };
131
+ };
132
+ const renderText = (snapshot, logger) => {
133
+ logger.info(`project: ${snapshot.projectRoot}`);
134
+ logger.info("");
135
+ logger.info("@lunora/* packages:");
136
+ if (snapshot.lunoraPackages.length === 0) {
137
+ logger.info(" (none found in package.json)");
138
+ } else {
139
+ for (const pkg of snapshot.lunoraPackages) {
140
+ logger.info(` ${pkg.name}@${pkg.version}`);
141
+ }
98
142
  }
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 };
143
+ logger.info("");
144
+ if (snapshot.wrangler) {
145
+ logger.info(`wrangler: ${snapshot.wranglerPath ?? ""}`);
146
+ logger.info(` name: ${snapshot.wrangler.name ?? "<unset>"}`);
147
+ logger.info(` main: ${snapshot.wrangler.main ?? "<unset>"}`);
148
+ logger.info(` compatibility_date: ${snapshot.wrangler.compatibilityDate ?? "<unset>"}`);
149
+ logger.info(` compatibility_flags: ${snapshot.wrangler.compatibilityFlags.join(", ") || "<none>"}`);
150
+ logger.info(` durable objects: ${snapshot.wrangler.bindings.durableObjects.join(", ") || "<none>"}`);
151
+ logger.info(` d1 databases: ${snapshot.wrangler.bindings.d1.join(", ") || "<none>"}`);
152
+ logger.info(` vectorize indexes: ${snapshot.wrangler.bindings.vectorize.join(", ") || "<none>"}`);
153
+ } else {
154
+ logger.info("wrangler: (not found)");
155
+ }
156
+ logger.info("");
157
+ if (snapshot.link) {
158
+ logger.info(`link: ${snapshot.link.workerName ?? "(unnamed)"} -> ${snapshot.link.workerUrl ?? "<no url>"}`);
159
+ if (snapshot.link.env !== void 0) {
160
+ logger.info(` env: ${snapshot.link.env}`);
161
+ }
162
+ } else {
163
+ logger.info("link: (not linked — run `lunora link --url <https://your-worker>`)");
105
164
  }
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 };
165
+ logger.info("");
166
+ if (snapshot.schemaError !== void 0) {
167
+ logger.warn(`schema: parse error — ${snapshot.schemaError}`);
168
+ } else if (snapshot.schema) {
169
+ logger.info(`schema: ${String(snapshot.schema.tables.length)} table(s), ${String(snapshot.schema.vectorIndexes)} vector index(es)`);
170
+ for (const table of snapshot.schema.tables) {
171
+ logger.info(` ${table.name} [${table.shard}, ${String(table.indexes)} index(es)]`);
172
+ }
173
+ } else {
174
+ logger.info("schema: (no lunora/schema.ts)");
111
175
  }
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 };
115
176
  };
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,
121
- logger,
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
- });
177
+ const runInfoCommand = (options) => {
178
+ const cwd = options.cwd ?? process.cwd();
179
+ const snapshot = collectInfo(cwd);
180
+ if (options.json) {
181
+ process.stdout.write(`${JSON.stringify(snapshot, void 0, 2)}
182
+ `);
183
+ } else {
184
+ renderText(snapshot, options.logger);
185
+ }
186
+ return { code: 0, snapshot };
187
+ };
188
+ const execute = defineHandler(
189
+ ({ cwd, logger, options }) => runInfoCommand({ cwd, json: options.json === true, logger })
190
+ );
130
191
 
131
- export { buildInsightsReport, execute, formatInsightsReport, runInsightsCommand };
192
+ export { collectInfo, execute, runInfoCommand };