@lunora/cli 1.0.0-alpha.12 → 1.0.0-alpha.121

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 (95) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/bin.mjs +2 -10
  4. package/dist/index.d.mts +357 -321
  5. package/dist/index.d.ts +357 -321
  6. package/dist/index.mjs +1 -19
  7. package/dist/packem_chunks/handler.mjs +1 -150
  8. package/dist/packem_chunks/handler10.mjs +1 -22
  9. package/dist/packem_chunks/handler11.mjs +1 -192
  10. package/dist/packem_chunks/handler12.mjs +2 -131
  11. package/dist/packem_chunks/handler13.mjs +2 -65
  12. package/dist/packem_chunks/handler14.mjs +1 -58
  13. package/dist/packem_chunks/handler15.mjs +1 -79
  14. package/dist/packem_chunks/handler16.mjs +1 -43
  15. package/dist/packem_chunks/handler17.mjs +1 -105
  16. package/dist/packem_chunks/handler18.mjs +3 -170
  17. package/dist/packem_chunks/handler19.mjs +1 -89
  18. package/dist/packem_chunks/handler2.mjs +2 -114
  19. package/dist/packem_chunks/handler20.mjs +1 -94
  20. package/dist/packem_chunks/handler21.mjs +2 -311
  21. package/dist/packem_chunks/handler22.mjs +99 -0
  22. package/dist/packem_chunks/handler23.mjs +11 -0
  23. package/dist/packem_chunks/handler3.mjs +2 -204
  24. package/dist/packem_chunks/handler4.mjs +1 -33
  25. package/dist/packem_chunks/handler5.mjs +1 -49
  26. package/dist/packem_chunks/handler6.mjs +1 -91
  27. package/dist/packem_chunks/handler7.mjs +1 -42
  28. package/dist/packem_chunks/handler8.mjs +1 -174
  29. package/dist/packem_chunks/handler9.mjs +8 -16
  30. package/dist/packem_chunks/planDevCommand.mjs +7 -500
  31. package/dist/packem_chunks/runCodegenCommand.mjs +4 -52
  32. package/dist/packem_chunks/runDeployCommand.mjs +1 -504
  33. package/dist/packem_chunks/runInitCommand.mjs +996 -1265
  34. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +9 -395
  35. package/dist/packem_chunks/runResetCommand.mjs +1 -41
  36. package/dist/packem_chunks/runRpcCommand.mjs +1 -68
  37. package/dist/packem_shared/COMMANDS-P8RHe9Zp.mjs +3 -0
  38. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-DL87PIEl.mjs +8 -0
  39. package/dist/packem_shared/admin-url-DfVehV3e.mjs +1 -0
  40. package/dist/packem_shared/api-spec-BENwiyUa.mjs +1 -0
  41. package/dist/packem_shared/buildRegistryIndex-DwySASBu.mjs +1 -0
  42. package/dist/packem_shared/codegen-error-DJN6pTH5.mjs +3 -0
  43. package/dist/packem_shared/command-Bjv4VmYA.mjs +1 -0
  44. package/dist/packem_shared/commands-BhX2Bo9B.mjs +19 -0
  45. package/dist/packem_shared/createLogger--C3x1FNE.mjs +2 -0
  46. package/dist/packem_shared/createRecordingSpawner-ByIqBepG.mjs +1 -0
  47. package/dist/packem_shared/detect-package-manager-B2eIuAiP.mjs +1 -0
  48. package/dist/packem_shared/diffSnapshots-9bDd9nOK.mjs +5 -0
  49. package/dist/packem_shared/docker-CKvYE9YF.mjs +1 -0
  50. package/dist/packem_shared/insertSchemaExtension-DZReBZ4_.mjs +8 -0
  51. package/dist/packem_shared/open-url-CTU_g9gr.mjs +1 -0
  52. package/dist/packem_shared/output-format-D7cKB-O5.mjs +2 -0
  53. package/dist/packem_shared/parseManifest-x3WsxKHz.mjs +1 -0
  54. package/dist/packem_shared/prompt-cancelled-C8Sj7OqI.mjs +1 -0
  55. package/dist/packem_shared/resolve-target-vxaXHmvg.mjs +1 -0
  56. package/dist/packem_shared/runAddCommand--TBYJQYi.mjs +1 -0
  57. package/dist/packem_shared/schema-drift-gate-CpqG-xP4.mjs +1 -0
  58. package/dist/packem_shared/schemaIrToSnapshot-Clwd2A8e.mjs +1 -0
  59. package/dist/packem_shared/storage-DOvAKGi4.mjs +1 -0
  60. package/dist/packem_shared/tui-prompts-CCt3jm1k.mjs +4 -0
  61. package/dist/packem_shared/vectorize-metadata-BvZbbqUD.mjs +1 -0
  62. package/dist/packem_shared/wrangler-name-HIl0Z6yx.mjs +1 -0
  63. package/dist/packem_shared/wrangler-secrets-BMRJwKQU.mjs +1 -0
  64. package/package.json +35 -16
  65. package/skills/README.md +28 -14
  66. package/skills/lunora/SKILL.md +48 -9
  67. package/skills/lunora-functions/SKILL.md +38 -3
  68. package/skills/lunora-migration-helper/SKILL.md +36 -17
  69. package/skills/lunora-performance-audit/SKILL.md +36 -6
  70. package/skills/lunora-quickstart/SKILL.md +86 -24
  71. package/skills/lunora-realtime/SKILL.md +43 -30
  72. package/dist/packem_shared/COMMANDS-D3h9Iwvl.mjs +0 -944
  73. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +0 -244
  74. package/dist/packem_shared/admin-url-4UzT-CI4.mjs +0 -19
  75. package/dist/packem_shared/api-spec-CtA6ilu4.mjs +0 -13
  76. package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +0 -38
  77. package/dist/packem_shared/command-BC30oSBW.mjs +0 -14
  78. package/dist/packem_shared/commands-DVr4lduS.mjs +0 -812
  79. package/dist/packem_shared/createLogger-B40gPzQo.mjs +0 -78
  80. package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +0 -43
  81. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
  82. package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +0 -161
  83. package/dist/packem_shared/docker-hMQ97KSQ.mjs +0 -21
  84. package/dist/packem_shared/insertSchemaExtension-DAqbfr9Z.mjs +0 -64
  85. package/dist/packem_shared/open-url-Dfq6fAyT.mjs +0 -41
  86. package/dist/packem_shared/output-format-wUvAN6AL.mjs +0 -17
  87. package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
  88. package/dist/packem_shared/parseManifest--vZf2FY1.mjs +0 -94
  89. package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +0 -16
  90. package/dist/packem_shared/runAddCommand-C-NOz8NJ.mjs +0 -4
  91. package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +0 -79
  92. package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +0 -43
  93. package/dist/packem_shared/storage-DL9OE4oE.mjs +0 -84
  94. package/dist/packem_shared/tui-prompts-M6OWsuyw.mjs +0 -663
  95. package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +0 -12
@@ -1,114 +1,2 @@
1
- import { mkdtempSync, rmSync, existsSync, readdirSync, statSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
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';
6
-
7
- const walk = (root) => {
8
- const entries = [];
9
- const recurse = (directory) => {
10
- for (const name of readdirSync(directory)) {
11
- const full = join(directory, name);
12
- const info = statSync(full);
13
- if (info.isDirectory()) {
14
- recurse(full);
15
- } else if (info.isFile()) {
16
- entries.push({ path: relative(root, full), sizeBytes: info.size });
17
- }
18
- }
19
- };
20
- recurse(root);
21
- return entries;
22
- };
23
- const formatBytes = (bytes) => {
24
- if (bytes < 1024) {
25
- return `${String(bytes)} B`;
26
- }
27
- if (bytes < 1024 * 1024) {
28
- return `${(bytes / 1024).toFixed(1)} KiB`;
29
- }
30
- return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;
31
- };
32
- const buildReport = (outdir) => {
33
- const all = walk(outdir);
34
- const sorted = all.toSorted((a, b) => b.sizeBytes - a.sizeBytes);
35
- const totalBytes = all.reduce((sum, entry) => sum + entry.sizeBytes, 0);
36
- const generatedFiles = all.filter((entry) => entry.path.includes("_generated"));
37
- return {
38
- generatedFiles,
39
- outdir,
40
- topModules: sorted.slice(0, 10),
41
- totalBytes,
42
- totalFiles: all.length
43
- };
44
- };
45
- const renderText = (report, logger) => {
46
- logger.info(`outdir: ${report.outdir}`);
47
- logger.info(`total: ${String(report.totalFiles)} files, ${formatBytes(report.totalBytes)}`);
48
- if (report.topModules.length > 0) {
49
- logger.info("");
50
- logger.info("top modules by size:");
51
- for (const entry of report.topModules) {
52
- logger.info(` ${formatBytes(entry.sizeBytes).padStart(10, " ")} ${entry.path}`);
53
- }
54
- }
55
- if (report.generatedFiles.length > 0) {
56
- logger.info("");
57
- logger.info(`_generated/ files: ${String(report.generatedFiles.length)}`);
58
- for (const entry of report.generatedFiles) {
59
- logger.info(` ${formatBytes(entry.sizeBytes).padStart(10, " ")} ${entry.path}`);
60
- }
61
- }
62
- };
63
- const runAnalyzeCommand = async (options) => {
64
- const cwd = options.cwd ?? process.cwd();
65
- const { logger } = options;
66
- let outdir;
67
- let descriptor;
68
- let temporary = false;
69
- if (options.inspectOnly) {
70
- outdir = options.inspectOnly;
71
- } else {
72
- outdir = mkdtempSync(join(tmpdir(), "lunora-analyze-"));
73
- temporary = true;
74
- descriptor = {
75
- args: ["exec", "wrangler", "deploy", "--dry-run", "--outdir", outdir],
76
- command: "pnpm",
77
- cwd
78
- };
79
- logger.info(`analyze: building via ${descriptor.command} ${descriptor.args.join(" ")}`);
80
- const spawner = options.spawner ?? defaultSpawner;
81
- const spawned = await spawner(descriptor);
82
- if (spawned.code !== 0) {
83
- logger.error(`analyze: wrangler dry-run failed (exit ${String(spawned.code)})`);
84
- rmSync(outdir, { force: true, recursive: true });
85
- return { code: spawned.code, descriptor, report: void 0 };
86
- }
87
- }
88
- try {
89
- if (!existsSync(outdir)) {
90
- logger.error(`analyze: outdir not found at ${outdir}`);
91
- return { code: 1, descriptor, report: void 0 };
92
- }
93
- const report = buildReport(outdir);
94
- if (options.json) {
95
- process.stdout.write(`${JSON.stringify(report, void 0, 2)}
96
- `);
97
- } else {
98
- renderText(report, logger);
99
- }
100
- return { code: 0, descriptor, report };
101
- } finally {
102
- if (temporary && existsSync(outdir)) {
103
- try {
104
- rmSync(outdir, { force: true, recursive: true });
105
- } catch {
106
- }
107
- }
108
- }
109
- };
110
- const execute = defineHandler(
111
- ({ cwd, logger, options }) => runAnalyzeCommand({ cwd, json: options.json === true, logger })
112
- );
113
-
114
- export { execute, runAnalyzeCommand };
1
+ import{mkdtempSync as p,rmSync as c,existsSync as l,readdirSync as u,statSync as y}from"node:fs";import{tmpdir as g}from"node:os";import{join as f,relative as m}from"node:path";import{i as $}from"../packem_shared/command-Bjv4VmYA.mjs";import{M as S,P as z}from"../packem_shared/detect-package-manager-B2eIuAiP.mjs";import{defaultSpawner as B}from"../packem_shared/createRecordingSpawner-ByIqBepG.mjs";const w=t=>{const r=[],e=i=>{for(const o of u(i)){const n=f(i,o),s=y(n);s.isDirectory()?e(n):s.isFile()&&r.push({path:m(t,n),sizeBytes:s.size})}};return e(t),r},d=t=>t<1024?`${String(t)} B`:t<1024*1024?`${(t/1024).toFixed(1)} KiB`:`${(t/(1024*1024)).toFixed(2)} MiB`,h=t=>{const r=w(t),e=r.toSorted((o,n)=>n.sizeBytes-o.sizeBytes),i=r.reduce((o,n)=>o+n.sizeBytes,0);return{generatedFiles:r.filter(o=>o.path.includes("_generated")),outdir:t,topModules:e.slice(0,10),totalBytes:i,totalFiles:r.length}},F=(t,r)=>{if(r.info(`outdir: ${t.outdir}`),r.info(`total: ${String(t.totalFiles)} files, ${d(t.totalBytes)}`),t.topModules.length>0){r.info(""),r.info("top modules by size:");for(const e of t.topModules)r.info(` ${d(e.sizeBytes).padStart(10," ")} ${e.path}`)}if(t.generatedFiles.length>0){r.info(""),r.info(`_generated/ files: ${String(t.generatedFiles.length)}`);for(const e of t.generatedFiles)r.info(` ${d(e.sizeBytes).padStart(10," ")} ${e.path}`)}},v=async t=>{const r=t.cwd??process.cwd(),{logger:e}=t;let i,o,n=!1;if(t.inspectOnly)i=t.inspectOnly;else{i=p(f(g(),"lunora-analyze-")),n=!0;const s=S(z(r),"wrangler",["deploy","--dry-run","--outdir",i]);o={args:s.args,command:s.command,cwd:r},e.info(`analyze: building via ${o.command} ${o.args.join(" ")}`);const a=await(t.spawner??B)(o);if(a.code!==0)return e.error(`analyze: wrangler dry-run failed (exit ${String(a.code)})`),c(i,{force:!0,recursive:!0}),{code:a.code,descriptor:o,report:void 0}}try{if(!l(i))return e.error(`analyze: outdir not found at ${i}`),{code:1,descriptor:o,report:void 0};const s=h(i);return t.json?process.stdout.write(`${JSON.stringify(s,void 0,2)}
2
+ `):F(s,e),{code:0,descriptor:o,report:s}}finally{if(n&&l(i))try{c(i,{force:!0,recursive:!0})}catch{}}},k=$(({cwd:t,logger:r,options:e})=>v({cwd:t,json:e.json===!0,logger:r}));export{k as execute,v as runAnalyzeCommand};
@@ -1,94 +1 @@
1
- import { readFileSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { parse } from 'jsonc-parser';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
5
- import { o as openUrl } from '../packem_shared/open-url-Dfq6fAyT.mjs';
6
-
7
- const DEFAULT_DEV_PORT = 8787;
8
- const STUDIO_PATH = "/_lunora/studio";
9
- const findWranglerFile = (projectRoot) => {
10
- for (const candidate of ["wrangler.jsonc", "wrangler.json"]) {
11
- const fullPath = join(projectRoot, candidate);
12
- if (existsSync(fullPath)) {
13
- return fullPath;
14
- }
15
- }
16
- return void 0;
17
- };
18
- const readWrangler = (projectRoot) => {
19
- const file = findWranglerFile(projectRoot);
20
- if (!file) {
21
- return void 0;
22
- }
23
- try {
24
- const parsed = parse(readFileSync(file, "utf8"));
25
- return parsed !== null && typeof parsed === "object" ? parsed : void 0;
26
- } catch {
27
- return void 0;
28
- }
29
- };
30
- const resolveDevPort = (wrangler) => {
31
- if (!wrangler) {
32
- return DEFAULT_DEV_PORT;
33
- }
34
- const { dev } = wrangler;
35
- if (dev !== null && typeof dev === "object") {
36
- const { port } = dev;
37
- if (typeof port === "number" && Number.isFinite(port)) {
38
- return port;
39
- }
40
- }
41
- return DEFAULT_DEV_PORT;
42
- };
43
- const resolveRemoteUrl = (wrangler) => {
44
- if (!wrangler) {
45
- return void 0;
46
- }
47
- const { routes } = wrangler;
48
- if (Array.isArray(routes) && routes.length > 0) {
49
- const first = routes[0];
50
- if (typeof first === "string") {
51
- return `https://${first.split("/")[0] ?? first}${STUDIO_PATH}`;
52
- }
53
- if (first !== null && typeof first === "object") {
54
- const { pattern } = first;
55
- if (typeof pattern === "string" && pattern.length > 0) {
56
- return `https://${pattern.split("/")[0] ?? pattern}${STUDIO_PATH}`;
57
- }
58
- }
59
- }
60
- const { name } = wrangler;
61
- if (typeof name === "string" && name.length > 0) {
62
- return `https://${name}.workers.dev${STUDIO_PATH}`;
63
- }
64
- return void 0;
65
- };
66
- const runViewCommand = async (options) => {
67
- const cwd = options.cwd ?? process.cwd();
68
- const wrangler = readWrangler(cwd);
69
- const { logger } = options;
70
- let url;
71
- if (options.remote) {
72
- url = resolveRemoteUrl(wrangler);
73
- if (!url) {
74
- logger.error("view --remote: could not determine the remote URL from wrangler config (set `routes` or `name`).");
75
- return { code: 1, url: void 0 };
76
- }
77
- } else {
78
- url = `http://localhost:${String(resolveDevPort(wrangler))}${STUDIO_PATH}`;
79
- }
80
- logger.info(`opening ${url}`);
81
- try {
82
- await openUrl(url, { opener: options.opener });
83
- } catch (error) {
84
- const message = error instanceof Error ? error.message : String(error);
85
- logger.error(`view: failed to open URL: ${message}`);
86
- return { code: 1, url };
87
- }
88
- return { code: 0, url };
89
- };
90
- const execute = defineHandler(
91
- ({ cwd, logger, options }) => runViewCommand({ cwd, logger, remote: options.remote === true })
92
- );
93
-
94
- export { execute, runViewCommand };
1
+ import{readFileSync as u,existsSync as l}from"node:fs";import{join as p}from"node:path";import{parse as a}from"jsonc-parser";import{i as m}from"../packem_shared/command-Bjv4VmYA.mjs";import{f as g}from"../packem_shared/open-url-CTU_g9gr.mjs";const c=8787,i="/_lunora/studio",d=e=>{for(const t of["wrangler.jsonc","wrangler.json"]){const r=p(e,t);if(l(r))return r}},y=e=>{const t=d(e);if(t)try{const r=a(u(t,"utf8"));return r!==null&&typeof r=="object"?r:void 0}catch{return}},w=e=>{if(!e)return c;const{dev:t}=e;if(t!==null&&typeof t=="object"){const{port:r}=t;if(typeof r=="number"&&Number.isFinite(r))return r}return c},h=e=>{if(!e)return;const{routes:t}=e;if(Array.isArray(t)&&t.length>0){const n=t[0];if(typeof n=="string")return`https://${n.split("/")[0]??n}${i}`;if(n!==null&&typeof n=="object"){const{pattern:o}=n;if(typeof o=="string"&&o.length>0)return`https://${o.split("/")[0]??o}${i}`}}const{name:r}=e;if(typeof r=="string"&&r.length>0)return`https://${r}.workers.dev${i}`},$=async e=>{const t=e.cwd??process.cwd(),r=y(t),{logger:n}=e;let o;if(e.remote){if(o=h(r),!o)return n.error("view --remote: could not determine the remote URL from wrangler config (set `routes` or `name`)."),{code:1,url:void 0}}else o=`http://localhost:${String(w(r))}${i}`;n.info(`opening ${o}`);try{await g(o,{opener:e.opener})}catch(s){const f=s instanceof Error?s.message:String(s);return n.error(`view: failed to open URL: ${f}`),{code:1,url:o}}return{code:0,url:o}},R=m(({cwd:e,logger:t,options:r})=>$({cwd:e,logger:t,remote:r.remote===!0}));export{R as execute,$ as runViewCommand};
@@ -1,311 +1,2 @@
1
- import { writeFileSync, existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { DEV_VARS_FILE, DEV_VARS_KEY_PATTERN, DEV_VARS_EXAMPLE_FILE, parseDevVariableEntries, isPlaceholderValue } from '@lunora/config';
4
- import { d as defineHandler } from '../packem_shared/command-BC30oSBW.mjs';
5
- import { defaultSpawner } from '../packem_shared/createRecordingSpawner-DxI3mebw.mjs';
6
- import { execFile } from 'node:child_process';
7
-
8
- const execCode = (error) => {
9
- if (!error) {
10
- return 0;
11
- }
12
- return typeof error.code === "number" ? error.code : 1;
13
- };
14
- const defaultRunner = (command, args, cwd) => new Promise((resolve) => {
15
- execFile(command, [...args], { cwd }, (error, stdout, stderr) => {
16
- resolve({ code: execCode(error), stderr, stdout });
17
- });
18
- });
19
- const parseSecretNames = (stdout) => {
20
- let parsed;
21
- try {
22
- parsed = JSON.parse(stdout);
23
- } catch {
24
- return void 0;
25
- }
26
- if (!Array.isArray(parsed)) {
27
- return void 0;
28
- }
29
- const names = parsed.map((entry) => entry !== null && typeof entry === "object" ? entry.name : void 0).filter((name) => typeof name === "string" && name.length > 0);
30
- return [...names].toSorted((a, b) => a.localeCompare(b));
31
- };
32
- const listRemoteSecrets = async (inputs) => {
33
- const args = ["exec", "wrangler", "secret", "list", "--format", "json"];
34
- if (inputs.env !== void 0) {
35
- args.push("--env", inputs.env);
36
- }
37
- if (inputs.temporary) {
38
- args.push("--temporary");
39
- }
40
- const runner = inputs.runner ?? defaultRunner;
41
- const result = await runner("pnpm", args, inputs.cwd);
42
- if (result.code !== 0) {
43
- return { error: result.stderr.trim() || `wrangler secret list exited ${String(result.code)}`, names: [], ok: false };
44
- }
45
- const names = parseSecretNames(result.stdout);
46
- if (names === void 0) {
47
- return { error: "could not parse `wrangler secret list --format json` output", names: [], ok: false };
48
- }
49
- return { names, ok: true };
50
- };
51
-
52
- const NEWLINE_PRESENT = /[\r\n]/u;
53
- const UNREPRESENTABLE_PRESENT = /["\\]/u;
54
- const parseDevVariables = (content) => {
55
- const map = /* @__PURE__ */ new Map();
56
- for (const entry of parseDevVariableEntries(content)) {
57
- map.set(entry.key, entry);
58
- }
59
- return map;
60
- };
61
- const serializeDevVariables = (map) => {
62
- const lines = [];
63
- for (const entry of map.values()) {
64
- lines.push(`${entry.key}="${entry.value}"`);
65
- }
66
- return `${lines.join("\n")}
67
- `;
68
- };
69
- const redact = (value) => {
70
- if (value.length <= 4) {
71
- return "****";
72
- }
73
- return `${value.slice(0, 4)}${"*".repeat(Math.min(8, value.length - 4))}`;
74
- };
75
- const loadDevVariables = (devVariablesPath) => {
76
- if (!existsSync(devVariablesPath)) {
77
- return /* @__PURE__ */ new Map();
78
- }
79
- return parseDevVariables(readFileSync(devVariablesPath, "utf8"));
80
- };
81
- const runEnvList = (context) => {
82
- const map = loadDevVariables(context.devVariablesPath);
83
- if (map.size === 0) {
84
- context.logger.info(`${DEV_VARS_FILE}: (empty)`);
85
- return { code: 0, descriptors: [] };
86
- }
87
- for (const entry of map.values()) {
88
- context.logger.info(`${entry.key}=${redact(entry.value)}`);
89
- }
90
- return { code: 0, descriptors: [] };
91
- };
92
- const runEnvGet = (context) => {
93
- const { devVariablesPath, logger, options } = context;
94
- if (!options.key) {
95
- logger.error("env get requires a key. Usage: lunora env get <KEY>");
96
- return { code: 1, descriptors: [] };
97
- }
98
- const entry = loadDevVariables(devVariablesPath).get(options.key);
99
- if (!entry) {
100
- logger.error(`env: ${options.key} is not set in ${DEV_VARS_FILE}`);
101
- return { code: 1, descriptors: [] };
102
- }
103
- process.stdout.write(`${entry.value}
104
- `);
105
- return { code: 0, descriptors: [] };
106
- };
107
- const runEnvSet = (context) => {
108
- const { devVariablesPath, logger, options } = context;
109
- if (!options.key) {
110
- logger.error("env set requires a key. Usage: lunora env set <KEY> <VALUE>");
111
- return { code: 1, descriptors: [] };
112
- }
113
- if (!DEV_VARS_KEY_PATTERN.test(options.key)) {
114
- logger.error(`env: invalid key "${options.key}" — must match [A-Za-z_][A-Za-z0-9_]*`);
115
- return { code: 1, descriptors: [] };
116
- }
117
- if (options.value === void 0) {
118
- logger.error("env set requires a value. Usage: lunora env set <KEY> <VALUE>");
119
- return { code: 1, descriptors: [] };
120
- }
121
- if (NEWLINE_PRESENT.test(options.value)) {
122
- logger.error(`env: value for "${options.key}" contains a newline, which .dev.vars cannot represent`);
123
- return { code: 1, descriptors: [] };
124
- }
125
- if (UNREPRESENTABLE_PRESENT.test(options.value)) {
126
- logger.error(`env: value for "${options.key}" contains a double-quote or backslash, which .dev.vars cannot round-trip`);
127
- return { code: 1, descriptors: [] };
128
- }
129
- const map = loadDevVariables(devVariablesPath);
130
- map.set(options.key, { key: options.key, value: options.value });
131
- writeFileSync(devVariablesPath, serializeDevVariables(map), "utf8");
132
- logger.success(`env: set ${options.key} (${redact(options.value)}) in ${DEV_VARS_FILE}`);
133
- return { code: 0, descriptors: [] };
134
- };
135
- const runEnvUnset = (context) => {
136
- const { devVariablesPath, logger, options } = context;
137
- if (!options.key) {
138
- logger.error("env unset requires a key. Usage: lunora env unset <KEY>");
139
- return { code: 1, descriptors: [] };
140
- }
141
- const map = loadDevVariables(devVariablesPath);
142
- if (!map.delete(options.key)) {
143
- logger.warn(`env: ${options.key} was not set in ${DEV_VARS_FILE}`);
144
- return { code: 0, descriptors: [] };
145
- }
146
- writeFileSync(devVariablesPath, serializeDevVariables(map), "utf8");
147
- logger.success(`env: unset ${options.key} in ${DEV_VARS_FILE}`);
148
- return { code: 0, descriptors: [] };
149
- };
150
- const runEnvPush = async (context) => {
151
- const { devVariablesPath, logger, options } = context;
152
- if (!options.yes) {
153
- logger.error("env push uploads secrets to Cloudflare. Re-run with --yes to confirm.");
154
- return { code: 1, descriptors: [] };
155
- }
156
- const map = loadDevVariables(devVariablesPath);
157
- if (map.size === 0) {
158
- logger.warn(`${DEV_VARS_FILE}: nothing to push (empty)`);
159
- return { code: 0, descriptors: [] };
160
- }
161
- const spawner = options.spawner ?? defaultSpawner;
162
- const descriptors = [];
163
- for (const entry of map.values()) {
164
- const args = ["exec", "wrangler", "secret", "put", entry.key];
165
- if (options.prod) {
166
- args.push("--env", "production");
167
- }
168
- if (options.temporary) {
169
- args.push("--temporary");
170
- }
171
- const descriptor = {
172
- args,
173
- command: "pnpm",
174
- cwd: options.cwd ?? process.cwd(),
175
- // `wrangler secret put <name>` reads the value from stdin. We
176
- // pipe it through the spawner's `input` channel so the secret
177
- // never lands on the command line, in env, or in shell history.
178
- input: entry.value
179
- };
180
- descriptors.push(descriptor);
181
- logger.info(`pushing ${entry.key} -> wrangler secret${options.prod ? " (production)" : ""}`);
182
- const result = await spawner(descriptor);
183
- if (result.code !== 0) {
184
- logger.error(`env push: failed at ${entry.key} (exit ${String(result.code)})`);
185
- return { code: result.code, descriptors };
186
- }
187
- }
188
- logger.success(`env: pushed ${String(map.size)} secret(s)`);
189
- return { code: 0, descriptors };
190
- };
191
- const fetchRemoteSecretNames = (context) => {
192
- const lister = context.options.secretLister ?? listRemoteSecrets;
193
- return lister({
194
- cwd: context.cwd,
195
- env: context.options.prod ? "production" : void 0,
196
- temporary: context.options.temporary
197
- });
198
- };
199
- const runEnvDiff = async (context) => {
200
- const { devVariablesPath, logger } = context;
201
- const remote = await fetchRemoteSecretNames(context);
202
- if (!remote.ok) {
203
- logger.error(`env diff: ${remote.error ?? "failed to list remote secrets"}`);
204
- return { code: 1, descriptors: [] };
205
- }
206
- const localKeys = new Set(loadDevVariables(devVariablesPath).keys());
207
- const remoteKeys = new Set(remote.names);
208
- const localOnly = [...localKeys].filter((key) => !remoteKeys.has(key)).toSorted((a, b) => a.localeCompare(b));
209
- const remoteOnly = remote.names.filter((name) => !localKeys.has(name));
210
- const both = [...localKeys].filter((key) => remoteKeys.has(key)).toSorted((a, b) => a.localeCompare(b));
211
- for (const key of localOnly) {
212
- logger.info(`local only (run \`lunora env push\`): ${key}`);
213
- }
214
- for (const key of remoteOnly) {
215
- logger.info(`remote only (in Cloudflare, not ${DEV_VARS_FILE}): ${key}`);
216
- }
217
- logger.info(`in both: ${String(both.length)} secret(s)`);
218
- if (localOnly.length === 0 && remoteOnly.length === 0) {
219
- logger.success("env diff: local and remote secret names match");
220
- }
221
- return { code: 0, descriptors: [] };
222
- };
223
- const runEnvDoctor = (context) => {
224
- const { cwd, devVariablesPath, logger } = context;
225
- const examplePath = join(cwd, DEV_VARS_EXAMPLE_FILE);
226
- if (!existsSync(examplePath)) {
227
- logger.info(`env doctor: no ${DEV_VARS_EXAMPLE_FILE} to check against — nothing to validate.`);
228
- return { code: 0, descriptors: [] };
229
- }
230
- const exampleKeys = parseDevVariableEntries(readFileSync(examplePath, "utf8")).map((entry) => entry.key);
231
- const current = loadDevVariables(devVariablesPath);
232
- if (!existsSync(devVariablesPath)) {
233
- logger.error(`env doctor: ${DEV_VARS_FILE} is missing. Run \`lunora dev\` to scaffold it, or \`lunora env set <KEY> <VALUE>\`.`);
234
- logger.info(`expected (from ${DEV_VARS_EXAMPLE_FILE}): ${exampleKeys.join(", ")}`);
235
- return { code: 1, descriptors: [] };
236
- }
237
- const missing = exampleKeys.filter((key) => !current.has(key));
238
- const placeholders = [...current.values()].filter((entry) => isPlaceholderValue(entry.value)).map((entry) => entry.key);
239
- const exampleKeySet = new Set(exampleKeys);
240
- const extra = [...current.keys()].filter((key) => !exampleKeySet.has(key));
241
- for (const key of missing) {
242
- logger.error(`missing: ${key} is in ${DEV_VARS_EXAMPLE_FILE} but not ${DEV_VARS_FILE}`);
243
- }
244
- for (const key of placeholders) {
245
- logger.error(`unset: ${key} still has a placeholder value`);
246
- }
247
- for (const key of extra) {
248
- logger.info(`extra: ${key} is set locally but not listed in ${DEV_VARS_EXAMPLE_FILE}`);
249
- }
250
- if (missing.length === 0 && placeholders.length === 0) {
251
- logger.success(`env doctor: ${DEV_VARS_FILE} looks good (${String(current.size)} var(s)).`);
252
- return { code: 0, descriptors: [] };
253
- }
254
- return { code: 1, descriptors: [] };
255
- };
256
- const runEnvCommand = async (options) => {
257
- const cwd = options.cwd ?? process.cwd();
258
- const context = {
259
- cwd,
260
- devVariablesPath: join(cwd, DEV_VARS_FILE),
261
- logger: options.logger,
262
- options
263
- };
264
- switch (options.subcommand) {
265
- case "diff": {
266
- return runEnvDiff(context);
267
- }
268
- case "doctor": {
269
- return runEnvDoctor(context);
270
- }
271
- case "get": {
272
- return runEnvGet(context);
273
- }
274
- case "list": {
275
- return runEnvList(context);
276
- }
277
- case "push": {
278
- return runEnvPush(context);
279
- }
280
- case "set": {
281
- return runEnvSet(context);
282
- }
283
- case "unset": {
284
- return runEnvUnset(context);
285
- }
286
- default: {
287
- options.logger.error(`env: unknown subcommand "${options.subcommand}"`);
288
- return { code: 1, descriptors: [] };
289
- }
290
- }
291
- };
292
- const isEnvSubcommand = (value) => value === "list" || value === "get" || value === "set" || value === "unset" || value === "push" || value === "diff" || value === "doctor";
293
- const execute = defineHandler(({ argument, cwd, logger, options }) => {
294
- const sub = argument[0];
295
- if (!isEnvSubcommand(sub)) {
296
- logger.error(`env: unknown subcommand "${sub ?? ""}" — expected list | get | set | unset | push | diff | doctor`);
297
- return { code: 1 };
298
- }
299
- return runEnvCommand({
300
- cwd,
301
- key: argument[1],
302
- logger,
303
- prod: options.prod === true,
304
- subcommand: sub,
305
- temporary: options.temporary === true,
306
- value: argument[2],
307
- yes: options.yes === true
308
- });
309
- });
310
-
311
- export { execute, runEnvCommand };
1
+ import{readLinkedProject as p}from"@lunora/config";import{i as f}from"../packem_shared/command-Bjv4VmYA.mjs";import{M as g,P as v}from"../packem_shared/detect-package-manager-B2eIuAiP.mjs";import{defaultSpawner as h}from"../packem_shared/createRecordingSpawner-ByIqBepG.mjs";import{createR2Sql as b}from"@lunora/bindings/r2sql";import{createPipelineLogReader as w}from"@lunora/runtime";const y=new Set(["debug","error","fatal","info","log","trace","warn"]),d=/^\d+$/,c=(r,t)=>{if(r===void 0)return;const o=r.trim();if(d.test(o))return Number(o);const e=Date.parse(o);if(Number.isNaN(e))throw new TypeError(`logs: invalid ${t} "${r}" — pass epoch-millis or an ISO 8601 date`);return e},S=r=>Buffer.from(JSON.stringify(r),"utf8").toString("base64url"),_=r=>{const t=r.trim();if(d.test(t))return{ts:Number(t)};let o;try{o=JSON.parse(Buffer.from(t,"base64url").toString("utf8"))}catch{throw new TypeError(`logs: invalid --cursor "${r}" — expected the token printed by the previous page (or a bare epoch-millis ts)`)}if(typeof o!="object"||o===null||typeof o.ts!="number"||!Number.isFinite(o.ts))throw new TypeError(`logs: invalid --cursor "${r}" — expected the token printed by the previous page (or a bare epoch-millis ts)`);const{ts:e}=o,s=o.seen,n=Array.isArray(s)?s.filter(i=>typeof i=="string"):void 0;return n!==void 0&&n.length>0?{seen:n,ts:e}:{ts:e}},u=(r,t)=>{if(r!==void 0){if(!y.has(r))throw new TypeError(`logs: invalid ${t} "${r}" — expected one of trace, debug, log, info, warn, error, fatal`);return r}},$=r=>{const t={},o=c(r.since,"--since");o!==void 0&&(t.sinceTs=o);const e=c(r.until,"--until");e!==void 0&&(t.untilTs=e);const s=u(r.level,"--level");s!==void 0&&(t.level=s);const n=u(r.minLevel,"--min-level");if(n!==void 0&&(t.minLevel=n),r.functionPrefix!==void 0&&(t.functionPathPrefix=r.functionPrefix),r.traceId!==void 0&&(t.traceId=r.traceId),r.shardKey!==void 0&&(t.shardKey=r.shardKey),r.userId!==void 0&&(t.userId=r.userId),r.limit!==void 0){const i=Number(r.limit);if(!Number.isFinite(i))throw new TypeError(`logs: invalid --limit "${r.limit}" — expected a number`);t.limit=i}return r.cursor!==void 0&&(t.cursor=_(r.cursor)),t},L=r=>{const t=new Date(r.ts).toISOString(),o=r.level.toUpperCase().padEnd(5);return`${t} ${o} ${r.functionPath} ${r.message}`},T=async r=>{const t=r.environment??process.env,o=t.R2_SQL_ACCOUNT_ID,e=t.R2_SQL_TOKEN,s=t.R2_SQL_BUCKET,n=[];if((o===void 0||o.length===0)&&n.push("R2_SQL_ACCOUNT_ID"),(e===void 0||e.length===0)&&n.push("R2_SQL_TOKEN"),(s===void 0||s.length===0)&&n.push("R2_SQL_BUCKET"),(r.table===void 0||r.table.length===0)&&n.push("--table"),n.length>0)return r.logger.error(`logs --durable: R2 SQL not configured (missing ${n.join(", ")}). The Pipeline must write to an R2 Data Catalog (Iceberg) table, and you must supply R2_SQL_ACCOUNT_ID / R2_SQL_TOKEN / R2_SQL_BUCKET plus --table — see the observability docs.`),{code:1,error:"not configured"};let i;try{i=$(r)}catch(a){return r.logger.error(a instanceof Error?a.message:String(a)),{code:1,error:"invalid option"}}const m=b({accountId:o,apiToken:e,bucket:s,fetch:r.fetch}),l=await w(m,{namespace:r.namespace,table:r.table}).query(i);for(const a of l.rows)process.stdout.write(`${r.ndjson===!0?JSON.stringify(a):L(a)}
2
+ `);return l.rows.length===0?r.logger.info("logs --durable: no matching log records"):l.nextCursor!==void 0&&r.logger.info(`logs --durable: more rows available — pass --cursor ${S(l.nextCursor)} for the next page`),{code:0,rows:l.rows}},I=new Set(["json","pretty"]),N=async r=>{const t=r.cwd??process.cwd();if(r.format!==void 0&&!I.has(r.format))return r.logger.error(`logs: unknown --format "${r.format}" — expected pretty | json`),{code:1,descriptor:void 0,error:"invalid format"};const o=["tail"];r.worker!==void 0&&o.push(r.worker);const e=r.env??p(t)?.env;e!==void 0&&o.push("--env",e),r.format!==void 0&&o.push("--format",r.format),r.status!==void 0&&o.push("--status",r.status),r.search!==void 0&&o.push("--search",r.search),r.temporary&&o.push("--temporary");const s=g(v(t),"wrangler",o),n={args:s.args,command:s.command,cwd:t};return r.logger.info(`tailing logs via ${n.command} ${n.args.join(" ")}`),{code:(await(r.spawner??h)(n)).code,descriptor:n}},P=f(({argument:r,cwd:t,logger:o,options:e})=>e.durable===!0?T({cursor:e.cursor,functionPrefix:e.functionPrefix,level:e.level,limit:e.limit,logger:o,minLevel:e.minLevel,namespace:e.namespace,ndjson:e.ndjson===!0,shardKey:e.shardKey,since:e.since,table:e.table,traceId:e.traceId,until:e.until,userId:e.userId}):N({cwd:t,env:e.env,format:e.format,logger:o,search:e.search,status:e.status,temporary:e.temporary===!0,worker:r[0]}));export{P as execute,N as runLogsCommand};
@@ -0,0 +1,99 @@
1
+ import{existsSync as T,readFileSync as O}from"node:fs";import{writeFile as x,mkdir as k}from"node:fs/promises";import{join as v}from"@visulima/path";import{i as B}from"../packem_shared/command-Bjv4VmYA.mjs";import{createRequire as q}from"node:module";import{LunoraError as w}from"@lunora/errors";import{parseSchema as U,applyAdditiveEdit as F}from"@lunora/config";const Y=/^\//,R=(e,n,t)=>{const a=q(v(t,"noop.cjs"));try{a.resolve(e)}catch{throw new w("INTERNAL",`\`lunora introspect\` needs the \`${n}\` driver to read this database, and it isn't installed.
2
+
3
+ Install it in your project:
4
+
5
+ pnpm add -D ${n}`)}return a(e)},j=e=>{const n=e.slice(0,Math.max(0,e.indexOf(":"))).toLowerCase();if(n==="postgres"||n==="postgresql")return"postgres";if(n==="mysql"||n==="mariadb")return"mysql";throw new w("INTERNAL",`Unrecognised database URL scheme \`${n}\`. Expected a \`postgres://\`, \`postgresql://\`, \`mysql://\`, or \`mariadb://\` connection string.`)},H=e=>{try{const n=new URL(e).pathname.replace(Y,"");return n===""?void 0:decodeURIComponent(n)}catch{return}},J=async(e,n,t,a=process.cwd())=>{if(n==="postgres"){const o=R("pg","pg",a).Client,c=new o({connectionString:e});return await c.connect(),{close:async()=>{await c.end()},execute:async(m,d)=>(await c.query(m,[...d])).rows,schema:t??"public"}}const s=R("mysql2/promise","mysql2",a).createConnection,r=await s(e),i=t??H(e);if(i===void 0)throw new w("INTERNAL","A MySQL connection string must name a database (`mysql://host/<database>`), or pass `--schema`.");return{close:async()=>{await r.end()},execute:async(o,c)=>{const[m]=await r.execute(o,[...c]);return Array.isArray(m)?m:[]},schema:i}},h=new Set(["_creationTime","_id"]),W={bigint:"v.bigint()",bigserial:"v.bigint()",bit:"v.string()",bool:"v.boolean()",boolean:"v.boolean()",box:"v.string()",bpchar:"v.string()",bytea:"v.bytes()",char:"v.string()",character:"v.string()","character varying":"v.string()",cidr:"v.string()",circle:"v.string()",citext:"v.string()",date:"v.date()","double precision":"v.number()",float4:"v.number()",float8:"v.number()",inet:"v.string()",int:"v.number()",int2:"v.number()",int4:"v.number()",int8:"v.bigint()",integer:"v.number()",interval:"v.string()",json:"v.any()",jsonb:"v.any()",line:"v.string()",lseg:"v.string()",macaddr:"v.string()",money:"v.number()",name:"v.string()",numeric:"v.number()",path:"v.string()",point:"v.string()",polygon:"v.string()",real:"v.number()",serial:"v.number()",smallint:"v.number()",smallserial:"v.number()",text:"v.string()",time:"v.string()","time with time zone":"v.string()","time without time zone":"v.string()",timestamp:"v.timestamp()","timestamp with time zone":"v.timestamp()","timestamp without time zone":"v.timestamp()",timestamptz:"v.timestamp()",tsquery:"v.string()",tsvector:"v.string()",uuid:"v.string()",varbit:"v.string()",varchar:"v.string()",xml:"v.string()"},P={bigint:"v.bigint()",binary:"v.bytes()",bit:"v.number()",blob:"v.bytes()",char:"v.string()",date:"v.date()",datetime:"v.timestamp()",decimal:"v.number()",double:"v.number()",enum:"v.string()",float:"v.number()",int:"v.number()",integer:"v.number()",json:"v.any()",longblob:"v.bytes()",longtext:"v.string()",mediumblob:"v.bytes()",mediumint:"v.number()",mediumtext:"v.string()",numeric:"v.number()",set:"v.string()",smallint:"v.number()",text:"v.string()",time:"v.string()",timestamp:"v.timestamp()",tinyblob:"v.bytes()",tinyint:"v.number()",tinytext:"v.string()",varbinary:"v.bytes()",varchar:"v.string()",year:"v.number()"},N=(e,n)=>{const t=(n==="postgres"?W:P)[e.dataType],a=e.references===void 0?t:`v.id(${JSON.stringify(e.references.table)})`;let s=a??"v.any()";for(let r=0;r<e.arrayDepth;r+=1)s=`v.array(${s})`;return e.nullable&&(s=`v.optional(${s})`),{expression:s,known:a!==void 0}},M=/^[A-Z_$][\w$]*$/i,K=/[^\dA-Z]+(.)?/gi,Q=/^\d+/,G=/[^\w-]/g,z=/[\u003C\u003E\u002F\u2028\u2029]/g,X={"/":String.raw`\u002F`,"\u2028":String.raw`\u2028`,"\u2029":String.raw`\u2029`,"<":String.raw`\u003C`,">":String.raw`\u003E`},b=e=>JSON.stringify(e).replaceAll(z,n=>X[n]??n),$=e=>M.test(e)?e:b(e),Z=e=>M.test(e)?`.${e}`:`[${b(e)}]`,I=e=>e.replaceAll("*/",String.raw`*\/`),V=e=>{const n=e.replaceAll(G,"_");return n===""?"table":n},ee=e=>{const n=e.replaceAll(K,(t,a)=>a?.toUpperCase()??"").replace(Q,"");return n===""?"table":`${n.charAt(0).toLowerCase()}${n.slice(1)}`},C=e=>e.columns.filter(n=>!h.has(n.name)),te=e=>{const n=new Set(e.primaryKey);for(const t of e.indexes)for(const a of t.columns)n.add(a);for(const t of e.columns)t.references!==void 0&&n.add(t.name);return[...n].filter(t=>!h.has(t))},D=(e,n,t,a)=>e.references===void 0||n.has(e.references.table)?e:(a.push(`${t}.${e.name}: references \`${e.references.table}\`, which isn't in the generated schema — emitted as a plain column instead of \`v.id(...)\`.`),{arrayDepth:e.arrayDepth,dataType:e.dataType,name:e.name,nullable:e.nullable}),ne=(e,n,t,a)=>{const s=[];for(const i of e.columns){if(h.has(i.name)){a.push(`${e.name}.${i.name}: skipped — \`${i.name}\` is a Lunora system column. Rename it in the source database or map it by hand.`);continue}const o=D(i,t,e.name,a),{expression:c,known:m}=N(o,n);!m&&o.references===void 0&&(a.push(`${e.name}.${i.name}: no mapping for SQL type \`${i.dataType}\` — emitted as \`v.any()\`.`),s.push(` // TODO: \`${I(i.dataType)}\` has no direct validator; narrow this.`)),s.push(` ${$(i.name)}: ${c},`)}const r=[' .global({ backend: "hyperdrive" })'];if(e.primaryKey.length>0&&e.primaryKey.every(i=>!h.has(i))){const i=e.primaryKey.map(o=>b(o)).join(", ");r.push(` .index(${b(`by_${e.primaryKey.join("_")}`)}, [${i}], { unique: true })`)}for(const i of e.indexes){const o=i.columns.filter(d=>!h.has(d));if(o.length===0)continue;const c=o.map(d=>b(d)).join(", "),m=i.unique?", { unique: true }":"";r.push(` .index(${b(i.name)}, [${c}]${m})`)}return` ${$(e.name)}: defineTable({
6
+ ${s.join(`
7
+ `)}
8
+ })
9
+ ${r.join(`
10
+ `)},`},ae=(e,n,t)=>{const a=new Set(e.tables.map(r=>r.name)),s=e.tables.map(r=>ne(r,e.dialect,a,t)).join(`
11
+ `);return`/**
12
+ * Generated by \`lunora introspect\` from an existing ${e.dialect==="postgres"?"Postgres":"MySQL"} database.
13
+ *
14
+ * This file is a STARTING POINT, not a build artifact — it is written once and is
15
+ * yours to edit. Review it before shipping: column types are mapped
16
+ * conservatively, and every table is \`.global({ backend: "hyperdrive" })\` because
17
+ * its rows live in the external database. Re-running \`lunora introspect\` will not
18
+ * overwrite it unless you pass \`--force\`.
19
+ */
20
+ import { defineSchema, defineTable, v } from "${n.serverImport}";
21
+
22
+ export default defineSchema({
23
+ ${s}
24
+ });
25
+ `},re=(e,n,t,a=new Set([e.name]),s=[])=>{const r=ee(e.name),i=te(e),o=new Map(C(e).map(l=>[l.name,l])),c=i.map(l=>{const g=o.get(l);if(g===void 0)return;const p=D({...g,nullable:!1},a,e.name,[]),{expression:E,known:A}=N(p,n);return!A&&p.references===void 0&&s.push(`${e.name}.${l}: filter falls back to \`v.any()\` (no mapping for SQL type \`${g.dataType}\`).`),` ${$(l)}: ${E},`}).filter(l=>l!==void 0),m=[b("_creationTime"),...i.map(l=>b(l))].join(", "),d=`ctx.db${Z(e.name)}`;return`/**
26
+ * Generated by \`lunora introspect\` for the \`${I(e.name)}\` table — a
27
+ * starting point you own and edit.
28
+ *
29
+ * Both procedures are RPC-only. Add \`.expose({ rest: true })\` once you've decided
30
+ * this data should be public, and gate them with your auth/RLS middleware first —
31
+ * \`introspect\` cannot know who is allowed to read this table.
32
+ */
33
+ import { defineListArgs, v } from "${t.serverImport}";
34
+
35
+ import type { Doc } from "./_generated/dataModel";
36
+ import { c } from "./_generated/server";
37
+
38
+ const ${r}List = defineListArgs<Doc<${b(e.name)}>>()({
39
+ // Only index-backed columns are published as filterable, so a caller cannot
40
+ // reach a column you did not choose to expose. Note that \`contains\` and the
41
+ // negative operators still scan — narrow this list, and review it, before
42
+ // adding \`.expose({ rest: true })\`.
43
+ filter: {
44
+ ${c.join(`
45
+ `)}
46
+ },
47
+ orderBy: [${m}],
48
+ });
49
+
50
+ export const list = c.query.input(${r}List.args).query(({ args, ctx }) => ${d}.findMany(${r}List.toQueryArgs(args)));
51
+
52
+ export const get = c.query.input({ id: v.id(${b(e.name)}) }).query(({ args, ctx }) => ${d}.get(args.id));
53
+ `},se=(e,n)=>{const t=[],a=new Set(e.tables.map(r=>r.name)),s=[{contents:ae(e,n,t),path:"schema.ts"}];if(n.procedures){const r=new Map([["schema","the generated schema module"]]);for(const i of e.tables){if(C(i).length===0){t.push(`${i.name}: no usable columns — procedure module skipped.`);continue}const o=V(i.name);o!==i.name&&t.push(`${i.name}: written as \`${o}.ts\` — the table name isn't usable as a filename.`);const c=r.get(o);if(c!==void 0){t.push(`${i.name}: procedure module skipped — its filename \`${o}.ts\` collides with table \`${c}\`.`);continue}r.set(o,i.name),s.push({contents:re(i,e.dialect,n,a,t),path:`${o}.ts`})}}return{files:s,warnings:t}},_=/^[A-Z_$][\w$]*$/i,ie=e=>e.startsWith("v.optional(")&&e.endsWith(")")?e.slice(11,-1):e,oe=(e,n,t)=>{const a=[],s=new Set(n?.columns.map(r=>r.name));for(const r of e.columns){if(h.has(r.name)||s.has(r.name))continue;if(!_.test(r.name)){t.warnings.push(`${e.name}.${r.name}: skipped — column names must be bare identifiers to merge.`);continue}const i=r.references!==void 0&&!t.present.has(r.references.table),{expression:o}=N(i?{...r,references:void 0}:r,t.dialect);a.push({column:r.name,kind:"addOptionalColumn",table:e.name,validator:ie(o)}),!r.nullable&&n!==void 0&&t.warnings.push(`${e.name}.${r.name}: added as \`v.optional(...)\` — a required column on an existing table needs a backfill migration.`)}return a},ce=(e,n,t)=>{const a=[],s=new Set(n?.indexes.map(r=>r.name));for(const r of e.indexes){const i=r.columns.filter(o=>!h.has(o));if(!(s.has(r.name)||i.length===0)){if(!_.test(r.name)||!i.every(o=>_.test(o))){t.push(`${e.name}: index \`${r.name}\` skipped — index and column names must be bare identifiers to merge.`);continue}a.push({fields:i,kind:"addIndex",name:r.name,table:e.name,...r.unique?{unique:!0}:{}})}}return a},me=(e,n,t)=>{const a=[],s=[],r=new Map(n.map(o=>[o.name,o])),i=new Set([...r.keys(),...e.tables.map(o=>o.name)]);for(const o of e.tables){if(!_.test(o.name)){s.push(`${o.name}: skipped — merging into an existing schema needs a table name that is a bare identifier.`);continue}const c=r.get(o.name);c===void 0&&a.push({global:{backend:"hyperdrive"},kind:"addTable",table:o.name}),a.push(...oe(o,c,{dialect:t,present:i,warnings:s}),...ce(o,c,s))}return{edits:a,warnings:s}},le=(e,n,t)=>{const a=U(e);if(!a.ok)return{applied:0,warnings:[`lunora/schema.ts could not be parsed (${a.reason}) — leave it alone and merge by hand, or pass --force.`]};const s=me(n,a.tables,t),r=[...s.warnings];let i=e,o=0;for(const c of s.edits){const m=F(i,c);m.ok?(i=m.text,o+=1):r.push(`${c.table}: skipped one edit (${m.reason}).`)}return{applied:o,warnings:r,...o===0?{}:{text:i}}},u=(e,n)=>{const t=e[n]??e[n.toLowerCase()]??e[n.toUpperCase()];return typeof t=="string"?t:typeof t=="number"||typeof t=="bigint"||typeof t=="boolean"?String(t):""},ue=`
54
+ SELECT c.table_name, c.column_name, c.is_nullable, c.data_type, c.udt_name
55
+ FROM information_schema.columns c
56
+ JOIN information_schema.tables t
57
+ ON t.table_schema = c.table_schema AND t.table_name = c.table_name
58
+ WHERE c.table_schema = $1 AND t.table_type = 'BASE TABLE'
59
+ ORDER BY c.table_name, c.ordinal_position`,de=`
60
+ SELECT tc.table_name, kcu.column_name
61
+ FROM information_schema.table_constraints tc
62
+ JOIN information_schema.key_column_usage kcu
63
+ ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
64
+ WHERE tc.table_schema = $1 AND tc.constraint_type = 'PRIMARY KEY'
65
+ ORDER BY tc.table_name, kcu.ordinal_position`,pe=`
66
+ SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table, ccu.column_name AS foreign_column
67
+ FROM information_schema.table_constraints tc
68
+ JOIN information_schema.key_column_usage kcu
69
+ ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
70
+ JOIN information_schema.constraint_column_usage ccu
71
+ ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
72
+ WHERE tc.table_schema = $1 AND tc.constraint_type = 'FOREIGN KEY'`,ge=`
73
+ SELECT t.relname AS table_name, i.relname AS index_name, ix.indisunique AS is_unique, a.attname AS column_name
74
+ FROM pg_class t
75
+ JOIN pg_index ix ON t.oid = ix.indrelid
76
+ JOIN pg_class i ON i.oid = ix.indexrelid
77
+ JOIN pg_namespace n ON n.oid = t.relnamespace
78
+ CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord)
79
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
80
+ WHERE n.nspname = $1 AND t.relkind = 'r' AND NOT ix.indisprimary
81
+ ORDER BY t.relname, i.relname, k.ord`,be=`
82
+ SELECT c.TABLE_NAME, c.COLUMN_NAME, c.IS_NULLABLE, c.DATA_TYPE
83
+ FROM information_schema.COLUMNS c
84
+ JOIN information_schema.TABLES t
85
+ ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
86
+ WHERE c.TABLE_SCHEMA = ? AND t.TABLE_TYPE = 'BASE TABLE'
87
+ ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`,fe=`
88
+ SELECT TABLE_NAME, COLUMN_NAME
89
+ FROM information_schema.KEY_COLUMN_USAGE
90
+ WHERE TABLE_SCHEMA = ? AND CONSTRAINT_NAME = 'PRIMARY'
91
+ ORDER BY TABLE_NAME, ORDINAL_POSITION`,he=`
92
+ SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME AS foreign_table, REFERENCED_COLUMN_NAME AS foreign_column
93
+ FROM information_schema.KEY_COLUMN_USAGE
94
+ WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,ve=`
95
+ SELECT TABLE_NAME, INDEX_NAME AS index_name, NON_UNIQUE, COLUMN_NAME
96
+ FROM information_schema.STATISTICS
97
+ WHERE TABLE_SCHEMA = ? AND INDEX_NAME <> 'PRIMARY'
98
+ ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`,y=e=>{const n=new Map;for(const t of e){const a=u(t,"table_name"),s=n.get(a);s===void 0?n.set(a,[t]):s.push(t)}return n},ye=(e,n)=>{const t=u(e,"data_type").toLowerCase();if(n!=="postgres")return{arrayDepth:0,dataType:t};const a=u(e,"udt_name").toLowerCase();return t==="array"&&a.startsWith("_")?{arrayDepth:1,dataType:a.slice(1)}:{arrayDepth:0,dataType:a===""?t:a}},_e=(e,n)=>{const t=new Map;for(const a of e){const s=u(a,"index_name"),r=n==="postgres"?u(a,"is_unique")==="true":u(a,"NON_UNIQUE")==="0",i=t.get(s);i===void 0?t.set(s,{columns:[u(a,"column_name")],unique:r}):i.columns.push(u(a,"column_name"))}return[...t.entries()].map(([a,s])=>({columns:s.columns,name:a,unique:s.unique}))},Ee=async(e,n,t)=>{const a=n==="postgres",[s,r,i,o]=await Promise.all([e(a?ue:be,[t]),e(a?de:fe,[t]),e(a?pe:he,[t]),e(a?ge:ve,[t])]),c=y(r),m=y(i),d=y(o),l=[];for(const[g,p]of y(s)){const E=new Map((m.get(g)??[]).map(f=>[u(f,"column_name"),{column:u(f,"foreign_column"),table:u(f,"foreign_table")}])),A=p.map(f=>{const S=u(f,"column_name"),L=E.get(S);return{...ye(f,n),name:S,nullable:u(f,"is_nullable").toUpperCase()==="YES",...L===void 0?{}:{references:L}}});l.push({columns:A,indexes:_e(d.get(g)??[],n),name:g,primaryKey:(c.get(g)??[]).map(f=>u(f,"column_name"))})}return l.sort((g,p)=>g.name.localeCompare(p.name)),{dialect:n,tables:l}},Ae=e=>{try{const n=JSON.parse(O(v(e,"package.json"),"utf8"));return n.dependencies?.lunorash===void 0&&n.devDependencies?.lunorash===void 0?"@lunora/server":"lunorash/server"}catch{return"@lunora/server"}},$e=async(e,n,t)=>{const a=v(n,e.path);return T(a)&&t.force!==!0?(t.logger.warn(`skipped lunora/${e.path} — it already exists (pass --force to overwrite)`),!1):t.dryRun===!0?(t.logger.info(`would write lunora/${e.path} (${String(e.contents.split(`
99
+ `).length)} lines)`),!1):(await k(n,{recursive:!0}),await x(a,e.contents,"utf8"),t.logger.info(`wrote lunora/${e.path}`),!0)},we=(e,n)=>{const t=n.tables;if(t===void 0||t.length===0)return e.tables;for(const a of t)e.tables.some(s=>s.name===a)||n.logger.warn(`--tables: no table named "${a}" exists in this schema — skipped.`);return e.tables.filter(a=>t.includes(a.name))},Ne=async(e,n,t,a)=>{const s=le(O(e,"utf8"),n,t);for(const r of s.warnings)a.logger.warn(r);return s.text===void 0?(a.logger.info("lunora/schema.ts is already up to date with the database."),!1):a.dryRun===!0?(a.logger.info(`would merge ${String(s.applied)} addition(s) into lunora/schema.ts`),!1):(await x(e,s.text,"utf8"),a.logger.info(`merged ${String(s.applied)} addition(s) into lunora/schema.ts`),!0)},Se=async e=>{const n=e.cwd??process.cwd();if(e.connection===void 0&&(e.url===void 0||e.url===""))return e.logger.error("`lunora introspect` needs a database URL: pass --url, or set DATABASE_URL."),{code:1,written:[]};const t=e.connection===void 0?j(e.url):e.dialect??"postgres",a=e.connection??await J(e.url,t,e.schema,n);let s;try{s=await Ee(a.execute,t,a.schema)}finally{e.connection===void 0&&await a.close()}const r=we(s,e);if(r.length===0)return e.logger.error("no tables found to introspect — check --schema and --tables."),{code:1,written:[]};const{files:i,warnings:o}=se({...s,tables:r},{procedures:e.procedures!==!1,serverImport:Ae(n)}),c=v(n,"lunora"),m=[],d=v(c,"schema.ts"),l=T(d)&&e.force!==!0,g=l?i.filter(p=>p.path!=="schema.ts"):i;l&&await Ne(d,{tables:r},t,e)&&m.push("schema.ts");for(const p of g)await $e(p,c,e)&&m.push(p.path);for(const p of o)e.logger.warn(p);return e.logger.info(`introspected ${String(r.length)} table(s) from ${t}. Review the generated files before running \`lunora dev\`.`),{code:0,written:m}},Ce=B(async({cwd:e,logger:n,options:t})=>({code:(await Se({cwd:e,dryRun:t.dryRun===!0,force:t.force===!0,logger:n,procedures:t.procedures!==!1,schema:t.schema,tables:t.tables===void 0?void 0:t.tables.split(",").map(a=>a.trim()),url:t.url??process.env.DATABASE_URL})).code}));export{Ce as execute,Ae as resolveServerImport,Se as runIntrospectCommand};