@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.
- package/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/bin.mjs +2 -10
- package/dist/index.d.mts +357 -321
- package/dist/index.d.ts +357 -321
- package/dist/index.mjs +1 -19
- package/dist/packem_chunks/handler.mjs +1 -150
- package/dist/packem_chunks/handler10.mjs +1 -22
- package/dist/packem_chunks/handler11.mjs +1 -192
- package/dist/packem_chunks/handler12.mjs +2 -131
- package/dist/packem_chunks/handler13.mjs +2 -65
- package/dist/packem_chunks/handler14.mjs +1 -58
- package/dist/packem_chunks/handler15.mjs +1 -79
- package/dist/packem_chunks/handler16.mjs +1 -43
- package/dist/packem_chunks/handler17.mjs +1 -105
- package/dist/packem_chunks/handler18.mjs +3 -170
- package/dist/packem_chunks/handler19.mjs +1 -89
- package/dist/packem_chunks/handler2.mjs +2 -114
- package/dist/packem_chunks/handler20.mjs +1 -94
- package/dist/packem_chunks/handler21.mjs +2 -311
- package/dist/packem_chunks/handler22.mjs +99 -0
- package/dist/packem_chunks/handler23.mjs +11 -0
- package/dist/packem_chunks/handler3.mjs +2 -204
- package/dist/packem_chunks/handler4.mjs +1 -33
- package/dist/packem_chunks/handler5.mjs +1 -49
- package/dist/packem_chunks/handler6.mjs +1 -91
- package/dist/packem_chunks/handler7.mjs +1 -42
- package/dist/packem_chunks/handler8.mjs +1 -174
- package/dist/packem_chunks/handler9.mjs +8 -16
- package/dist/packem_chunks/planDevCommand.mjs +7 -500
- package/dist/packem_chunks/runCodegenCommand.mjs +4 -52
- package/dist/packem_chunks/runDeployCommand.mjs +1 -504
- package/dist/packem_chunks/runInitCommand.mjs +996 -1265
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +9 -395
- package/dist/packem_chunks/runResetCommand.mjs +1 -41
- package/dist/packem_chunks/runRpcCommand.mjs +1 -68
- package/dist/packem_shared/COMMANDS-P8RHe9Zp.mjs +3 -0
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-DL87PIEl.mjs +8 -0
- package/dist/packem_shared/admin-url-DfVehV3e.mjs +1 -0
- package/dist/packem_shared/api-spec-BENwiyUa.mjs +1 -0
- package/dist/packem_shared/buildRegistryIndex-DwySASBu.mjs +1 -0
- package/dist/packem_shared/codegen-error-DJN6pTH5.mjs +3 -0
- package/dist/packem_shared/command-Bjv4VmYA.mjs +1 -0
- package/dist/packem_shared/commands-BhX2Bo9B.mjs +19 -0
- package/dist/packem_shared/createLogger--C3x1FNE.mjs +2 -0
- package/dist/packem_shared/createRecordingSpawner-ByIqBepG.mjs +1 -0
- package/dist/packem_shared/detect-package-manager-B2eIuAiP.mjs +1 -0
- package/dist/packem_shared/diffSnapshots-9bDd9nOK.mjs +5 -0
- package/dist/packem_shared/docker-CKvYE9YF.mjs +1 -0
- package/dist/packem_shared/insertSchemaExtension-DZReBZ4_.mjs +8 -0
- package/dist/packem_shared/open-url-CTU_g9gr.mjs +1 -0
- package/dist/packem_shared/output-format-D7cKB-O5.mjs +2 -0
- package/dist/packem_shared/parseManifest-x3WsxKHz.mjs +1 -0
- package/dist/packem_shared/prompt-cancelled-C8Sj7OqI.mjs +1 -0
- package/dist/packem_shared/resolve-target-vxaXHmvg.mjs +1 -0
- package/dist/packem_shared/runAddCommand--TBYJQYi.mjs +1 -0
- package/dist/packem_shared/schema-drift-gate-CpqG-xP4.mjs +1 -0
- package/dist/packem_shared/schemaIrToSnapshot-Clwd2A8e.mjs +1 -0
- package/dist/packem_shared/storage-DOvAKGi4.mjs +1 -0
- package/dist/packem_shared/tui-prompts-CCt3jm1k.mjs +4 -0
- package/dist/packem_shared/vectorize-metadata-BvZbbqUD.mjs +1 -0
- package/dist/packem_shared/wrangler-name-HIl0Z6yx.mjs +1 -0
- package/dist/packem_shared/wrangler-secrets-BMRJwKQU.mjs +1 -0
- package/package.json +35 -16
- package/skills/README.md +28 -14
- package/skills/lunora/SKILL.md +48 -9
- package/skills/lunora-functions/SKILL.md +38 -3
- package/skills/lunora-migration-helper/SKILL.md +36 -17
- package/skills/lunora-performance-audit/SKILL.md +36 -6
- package/skills/lunora-quickstart/SKILL.md +86 -24
- package/skills/lunora-realtime/SKILL.md +43 -30
- package/dist/packem_shared/COMMANDS-D3h9Iwvl.mjs +0 -944
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +0 -244
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +0 -19
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +0 -13
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +0 -38
- package/dist/packem_shared/command-BC30oSBW.mjs +0 -14
- package/dist/packem_shared/commands-DVr4lduS.mjs +0 -812
- package/dist/packem_shared/createLogger-B40gPzQo.mjs +0 -78
- package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +0 -43
- package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
- package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +0 -161
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +0 -21
- package/dist/packem_shared/insertSchemaExtension-DAqbfr9Z.mjs +0 -64
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +0 -41
- package/dist/packem_shared/output-format-wUvAN6AL.mjs +0 -17
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +0 -94
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +0 -16
- package/dist/packem_shared/runAddCommand-C-NOz8NJ.mjs +0 -4
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +0 -79
- package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +0 -43
- package/dist/packem_shared/storage-DL9OE4oE.mjs +0 -84
- package/dist/packem_shared/tui-prompts-M6OWsuyw.mjs +0 -663
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +0 -12
|
@@ -1,114 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
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 {
|
|
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 {
|
|
2
|
-
|
|
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};
|