@lunora/cli 1.0.0-alpha.12 → 1.0.0-alpha.120
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 +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-CQNSsSgS.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 +23 -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,11 @@
|
|
|
1
|
+
import{VERSION as J}from"../packem_shared/COMMANDS-CQNSsSgS.mjs";import{i as W}from"../packem_shared/command-Bjv4VmYA.mjs";import{existsSync as u,readFileSync as y,realpathSync as D,statSync as T,writeFileSync as z,chmodSync as I,renameSync as H,unlinkSync as K,mkdirSync as q}from"node:fs";import{homedir as w}from"node:os";import{findWranglerFile as G,readLiveDevServerState as b}from"@lunora/config";import{join as s,dirname as V,relative as Z}from"@visulima/path";import{P as B,M as Q}from"../packem_shared/detect-package-manager-B2eIuAiP.mjs";import{stringify as X}from"smol-toml";import{modify as x,applyEdits as E,parse as Y}from"jsonc-parser";import{resolveAdminToken as oo}from"@lunora/config/studio-host";import{connectLocalStdio as to}from"@lunora/mcp";const m=o=>({args:[...o.args],command:o.command,...o.env===void 0?{}:{env:o.env}}),$=o=>o.transport==="http"?{type:"http",url:o.url}:m(o),eo=o=>o.transport==="http"?{httpUrl:o.url}:m(o),ro=o=>o.transport==="http"?{serverUrl:o.url}:m(o),no=o=>({args:["-y","mcp-remote",o],command:"npx"}),ao=o=>o.transport==="http"?no(o.url):m(o),io=o=>o.transport==="http"?{url:o.url}:m(o),lo=o=>o.transport==="http"?{enabled:!0,type:"remote",url:o.url}:{command:[o.command,...o.args],enabled:!0,type:"local",...o.env===void 0?{}:{environment:o.env}},so=o=>o.transport==="http"?{disabled:!1,type:"streamableHttp",url:o.url}:{disabled:!1,...m(o)},co=({home:o,platform:t})=>{if(t==="darwin")return{global:s(o,"Library","Application Support","Claude","claude_desktop_config.json")};if(t==="win32"){const e=process.env.APPDATA;return e===void 0||e===""?{}:{global:s(e,"Claude","claude_desktop_config.json")}}return t==="linux"?{global:s(o,".config","Claude","claude_desktop_config.json")}:{}},po=(o,t)=>X({mcp_servers:{[o]:t.transport==="http"?{url:t.url}:{args:[...t.args],command:t.command,...t.env===void 0?{}:{env:t.env}}}}),h=[{buildEntry:$,paths:({projectRoot:o})=>({project:s(o,".mcp.json")}),format:"json",id:"claude-code",key:"mcpServers",label:"Claude Code"},{buildEntry:$,paths:({home:o,projectRoot:t})=>({global:s(o,".cursor","mcp.json"),project:s(t,".cursor","mcp.json")}),format:"json",id:"cursor",key:"mcpServers",label:"Cursor"},{buildEntry:$,paths:({projectRoot:o})=>({project:s(o,".vscode","mcp.json")}),format:"json",id:"vscode",key:"servers",label:"VS Code (GitHub Copilot)"},{buildEntry:eo,paths:({home:o,projectRoot:t})=>({global:s(o,".gemini","settings.json"),project:s(t,".gemini","settings.json")}),format:"json",id:"gemini",key:"mcpServers",label:"Gemini CLI"},{buildEntry:ao,paths:co,format:"json",id:"claude-desktop",key:"mcpServers",label:"Claude Desktop"},{buildEntry:ro,paths:({home:o})=>({global:s(o,".codeium","windsurf","mcp_config.json")}),format:"json",id:"windsurf",key:"mcpServers",label:"Windsurf"},{buildEntry:lo,paths:({home:o,projectRoot:t})=>({global:s(o,".config","opencode","opencode.json"),project:s(t,"opencode.json")}),format:"json",id:"opencode",key:"mcp",label:"OpenCode"},{buildEntry:so,paths:({home:o})=>({global:s(o,".cline","mcp.json")}),format:"json",id:"cline",key:"mcpServers",label:"Cline"},{buildEntry:io,paths:({home:o})=>({global:s(o,".config","zed","settings.json")}),format:"json",id:"zed",key:"context_servers",label:"Zed"},{paths:({home:o,projectRoot:t})=>({global:s(o,".codex","config.toml"),project:s(t,".codex","config.toml")}),format:"manual",id:"codex",label:"Codex CLI",renderSnippet:po}],C=h.map(o=>o.id),uo=o=>h.find(t=>t.id===o.toLowerCase()),v=(o,t)=>{let e=o,r;try{e=D(o),r=T(e).mode}catch{}const n=`${e}.lunora-${Math.random().toString(36).slice(2,10)}.tmp`;try{z(n,t,"utf8"),r!==void 0&&I(n,r),H(n,e)}catch(a){try{K(n)}catch{}throw a}},R={formattingOptions:{insertSpaces:!0,tabSize:4}},k=o=>{const t=[],e=Y(o,t,{allowTrailingComma:!0});return{errors:t,value:e}},g=o=>typeof o=="object"&&o!==null&&!Array.isArray(o),j=(o,t,e)=>{if(!g(o))return;const r=o[t];return g(r)?r[e]:void 0},N=o=>{if(!u(o.path))return"absent";let t;try{t=y(o.path,"utf8")}catch{return"invalid"}if(t.trim().length===0)return"absent";const{errors:e,value:r}=k(t);return e.length>0||!g(r)?"invalid":j(r,o.key,o.name)===void 0?"absent":"present"},mo=o=>N(o)==="present",fo=o=>{const{entry:t,force:e=!1,key:r,name:n,path:a}=o;if(!u(a))return q(V(a),{recursive:!0}),v(a,`${JSON.stringify({[r]:{[n]:t}},void 0,4)}
|
|
2
|
+
`),{action:"created",path:a};const i=y(a,"utf8");if(i.trim().length===0)return v(a,`${JSON.stringify({[r]:{[n]:t}},void 0,4)}
|
|
3
|
+
`),{action:"created",path:a};const{errors:l,value:c}=k(i);if(l.length>0)return{action:"invalid",error:`parse error at offset ${String(l[0]?.offset??0)}`,path:a};if(!g(c))return{action:"invalid",error:"the file's root is not a JSON object",path:a};const f=c[r];if(f!==void 0&&!g(f))return{action:"invalid",error:`"${r}" is not an object`,path:a};if(j(c,r,n)!==void 0&&!e)return{action:"skipped",path:a};try{const d=x(i,[r,n],t,R);d.length>0&&v(a,E(i,d))}catch(d){return{action:"invalid",error:d instanceof Error?d.message:String(d),path:a}}return{action:"updated",path:a}},go=o=>{const{key:t,name:e,path:r}=o;if(!u(r))return{action:"absent",path:r};let n;try{n=y(r,"utf8")}catch(l){return{action:"invalid",error:l instanceof Error?l.message:String(l),path:r}}const{errors:a,value:i}=k(n);if(a.length>0)return{action:"invalid",error:`parse error at offset ${String(a[0]?.offset??0)}`,path:r};if(j(i,t,e)===void 0)return{action:"absent",path:r};try{const l=x(n,[t,e],void 0,R);l.length>0&&v(r,E(n,l))}catch(l){return{action:"invalid",error:l instanceof Error?l.message:String(l),path:r}}return{action:"removed",path:r}},ho=["global","project"],p=(o,t)=>{const e=Z(t,o);return e.length>0&&!e.startsWith("..")?e:o},vo=(o,t)=>{const e=o.paths(t);return ho.flatMap(r=>{const n=e[r];return n===void 0?[]:[{client:o,path:n,scope:r}]})},yo=(o,t,e,r)=>{const n=o.paths(t),a=e==="global"?"project":"global",i=n[e]??(r?void 0:n[a]);if(i!==void 0)return{client:o,path:i,scope:n[e]===void 0?a:e}},$o=o=>h.filter(t=>Object.values(t.paths(o)).some(e=>u(e))),U=(o,t,e,r)=>{if(o.length===0)return t();const n=[];for(const a of o){const i=uo(a);if(i===void 0){e.error(`mcp ${r}: unknown client "${a}". Known clients: ${C.join(", ")}.`);return}n.push(i)}return n},bo=o=>o.client.format==="json",wo="https://lunora.sh/mcp",_="lunora-docs",M="lunora",ko=o=>u(s(o,"lunora"))&&G(o)!==void 0,jo=o=>{const{args:t,command:e}=Q(o,"lunora",["mcp","serve"]);return{args:t,command:e,transport:"stdio"}},So=o=>({transport:"http",url:o}),Oo=o=>({home:o.home,platform:o.platform,projectRoot:o.cwd}),A=(o,t,e)=>yo(o,Oo(e),e.scope??t.preferredScope,e.scope!==void 0||!t.allowScopeFallback),xo=(o,t,e)=>{const r=A(o,t,e);if(r===void 0){e.logger.warn(`${o.label}: has no ${e.scope??t.preferredScope}-scoped config — skipped "${t.name}".`);return}const{path:n}=r,a=o.buildEntry(t.spec);if(e.print===!0){const c=p(n,e.cwd);return e.force!==!0&&mo({key:o.key,name:t.name,path:n})?(e.logger.info(`${o.label}: "${t.name}" already configured in ${c} — a real install would skip it (re-run with --force to replace).`),{action:"printed",path:n}):(e.logger.info(`${o.label} → ${c}
|
|
4
|
+
${JSON.stringify({[o.key]:{[t.name]:a}},void 0,4)}`),{action:"printed",path:n})}const i=fo({entry:a,force:e.force===!0,key:o.key,name:t.name,path:n}),l=p(n,e.cwd);return i.action==="invalid"?e.logger.error(`${o.label}: ${l} is not valid JSON (${i.error??"unknown error"}) — left untouched.`):i.action==="skipped"?e.logger.info(`${o.label}: "${t.name}" already configured in ${l} — re-run with --force to replace it.`):e.logger.success(`${o.label}: ${i.action==="created"?"created":"updated"} ${l} with "${t.name}".`),{action:i.action,path:n}},Eo=(o,t,e)=>{const r=A(o,t,e)?.path;if(r===void 0){e.logger.warn(`${o.label}: no known config location on ${e.platform} — skipped.`);return}return e.logger.info(`${o.label}: add this to ${p(r,e.cwd)}
|
|
5
|
+
|
|
6
|
+
${o.renderSnippet(t.name,t.spec)}`),{action:"printed",path:r}},Co=o=>{const t=[];return o.localOnly!==!0&&t.push({allowScopeFallback:!0,name:_,preferredScope:"global",spec:So(o.docsUrl??wo)}),o.docsOnly!==!0&&ko(o.cwd)&&t.push({allowScopeFallback:!1,name:M,preferredScope:"project",spec:jo(B(o.cwd))}),t},Ro=o=>{const t=o.home??w(),e=o.platform??process.platform;o.logger.info("Supported MCP clients:");for(const r of h){const n=r.paths({home:t,platform:e,projectRoot:o.cwd}),a=i=>{const l=n[i];return l===void 0?"—":p(l,o.cwd)};o.logger.info(` ${r.id.padEnd(15)} ${r.label.padEnd(24)} project: ${a("project").padEnd(30)} global: ${a("global")}`)}return{code:0,written:[]}},No=o=>o.docsOnly===!0&&o.localOnly===!0?"`--docs-only` and `--local-only` are mutually exclusive — pass at most one.":"`--local-only` was set but this directory is not a Lunora project (no `lunora/` directory and wrangler config).",Uo=(o,t,e)=>{const r=[];for(const n of o)for(const a of t){const i=n.format==="json"?xo(n,a,e):Eo(n,a,e);i!==void 0&&r.push({action:i.action,client:n.id,path:i.path,server:a.name})}return r},_o=o=>{const t=o.home??w(),e=o.platform??process.platform,r=U(o.clients,()=>$o({home:t,platform:e,projectRoot:o.cwd}),o.logger,"install");if(r===void 0)return{code:1,written:[]};if(r.length===0)return o.logger.error(`mcp install: no MCP client config found — name one explicitly, e.g. \`lunora mcp install claude-code\`. Known clients: ${C.join(", ")}.`),{code:1,written:[]};o.clients.length===0&&o.logger.info(`Detected ${String(r.length)} configured client(s): ${r.map(i=>i.label).join(", ")}.`);const n=Co(o);if(n.length===0)return o.logger.error(`mcp install: nothing to install — ${No(o)}`),{code:1,written:[]};const a=Uo(r,n,{...o,home:t,platform:e});return a.some(i=>i.action==="created"||i.action==="updated")&&o.logger.info("Restart your editor (or reload its MCP servers) to pick up the change."),{code:a.some(i=>i.action==="invalid")?1:0,written:a}},L=100,F=1e3,Mo=o=>({content:[{text:JSON.stringify(o,void 0,2),type:"text"}]}),Ao=o=>({content:[{text:o,type:"text"}]}),Lo=o=>{const t=typeof o=="string"?Number(o):o;return typeof t!="number"||!Number.isFinite(t)?L:Math.min(F,Math.max(1,Math.floor(t)))},Fo=(o,t)=>{const e=y(o,"utf8").split(`
|
|
7
|
+
`);return e.at(-1)===""&&e.pop(),e.slice(-t).join(`
|
|
8
|
+
`)},Po=o=>[{definition:{description:"Report whether a `lunora dev` server is running for this project, and its URL, studio URL, mode and uptime. Call this first when a query or mutation tool fails to connect.",inputSchema:{properties:{},type:"object"},name:"lunora_dev_status"},handle:async()=>{const t=b(o);return Mo(t===void 0?{hint:"Start it with `lunora dev` in the project directory.",running:!1}:{background:t.background===!0,logFile:t.logFile,mode:t.mode,pid:t.pid,running:!0,startedAt:t.startedAt,studioUrl:t.studioUrl,url:t.url})}},{definition:{description:"Return the tail of the running dev server's captured log — worker output, request errors, and stack traces. Only available when the server was started in the background (`lunora dev --background`); a foreground server prints to its own terminal.",inputSchema:{properties:{lines:{description:`How many trailing lines to return (default ${String(L)}, max ${String(F)})`,type:"number"}},type:"object"},name:"lunora_dev_logs"},handle:async t=>{const e=b(o);if(e===void 0)return{content:[{text:"no dev server is running — start one with `lunora dev`",type:"text"}],isError:!0};if(e.logFile===void 0||!u(e.logFile))return{content:[{text:"this dev server is not capturing its output to a file (it runs in the foreground) — restart it with `lunora dev --background` to make its logs readable here",type:"text"}],isError:!0};const r=Lo(t.lines),n=Fo(e.logFile,r);return Ao(n.length===0?"(the dev server log is empty)":n)}}],Jo=new Set(["127.0.0.1","::1","localhost"]),P=o=>{try{const{hostname:t}=new URL(o);return Jo.has(t.startsWith("[")?t.slice(1,-1):t)}catch{return!1}},S=o=>{const t=o.url??b(o.cwd)?.url;if(t===void 0||t.length===0)return;if(o.token!==void 0&&o.token.length>0)return{token:o.token,url:t};if(o.url!==void 0&&!P(o.url))return{url:t};const e=oo(o.cwd);return e===void 0||e.length===0?{url:t}:{token:e,url:t}},Wo=async o=>{await new Promise(t=>{const e=()=>{t()};o.onclose=e,process.stdin.once("close",e),process.stdin.once("end",e)})},Do=(o,t)=>{if(o===void 0)return"lunora mcp serve: ready (documentation tools). No dev server running yet — start `lunora dev` and the deployment tools start working, no restart needed.\n";if(o.token!==void 0)return`lunora mcp serve: ready — deployment at ${o.url}
|
|
9
|
+
`;const e=t.url!==void 0&&!P(t.url)?"the project's admin token was NOT sent to this non-local --url; pass --token to authenticate deliberately":"no LUNORA_ADMIN_TOKEN in the environment or .dev.vars; the deployment tools will be refused until one is set";return`lunora mcp serve: ready — deployment at ${o.url} (${e})
|
|
10
|
+
`},To=async(o,t=to)=>{const e=o.writeError??(a=>{process.stderr.write(a)});let r;try{r=await t({allowWrites:o.allowWrites===!0,deployment:()=>S(o),docs:o.noDocs===!0?!1:{...o.docsUrl===void 0?{}:{baseUrl:o.docsUrl}},extraTools:Po(o.cwd),version:o.version})}catch(a){const i=a instanceof Error?a.message:String(a);return e(`lunora mcp serve: failed to start — ${i}
|
|
11
|
+
`),{code:1,deployment:void 0}}const n=S(o);return e(Do(n,o)),await Wo(r),{code:0,deployment:n}},zo=o=>{const t=[];return o.localOnly!==!0&&t.push(_),o.docsOnly!==!0&&t.push(M),t},Io=(o,t,e,r)=>{const n=go({key:o.key,name:t,path:e}),a=p(e,r.cwd);return n.action==="removed"?r.logger.success(`${o.label}: removed "${t}" from ${a}.`):n.action==="invalid"&&r.logger.error(`${o.label}: ${a} is not valid JSON (${n.error??"unknown error"}) — left untouched.`),{action:n.action,path:e}},Ho=(o,t,e,r)=>{const n=N({key:o.key,name:t,path:e}),a=p(e,r.cwd);return n==="invalid"?(r.logger.error(`${o.label}: ${a} is not valid JSON — a real run would report it and exit non-zero.`),{action:"invalid",path:e}):n==="absent"?{action:"absent",path:e}:(r.logger.info(`${o.label}: would remove "${t}" from ${a}.`),{action:"removed",path:e})},Ko=(o,t,e)=>o.flatMap(r=>vo(r,t)).filter(bo).filter(r=>e===void 0||r.scope===e),qo=o=>{const t=o.home??w(),e=o.platform??process.platform,r=U(o.clients,()=>h,o.logger,"uninstall");if(r===void 0)return{code:1,removed:[]};const n=zo(o);if(n.length===0)return o.logger.error("mcp uninstall: nothing to remove — `--docs-only` and `--local-only` are mutually exclusive, pass at most one."),{code:1,removed:[]};const a=[],i={home:t,platform:e,projectRoot:o.cwd};for(const l of Ko(r,i,o.scope))for(const c of n){const f=o.print===!0?Ho(l.client,c,l.path,o):Io(l.client,c,l.path,o);f.action!=="absent"&&a.push({...f,client:l.client.id,server:c})}return a.length===0?o.logger.info("Nothing to remove — no Lunora MCP servers were configured in the clients checked."):o.print===!0&&o.logger.info("Nothing was changed — re-run without --print to remove these."),{code:a.some(l=>l.action==="invalid")?1:0,removed:a}},O=o=>o.global===!0?"global":o.project===!0?"project":void 0,Go=async(o,t)=>{const{code:e}=await To({allowWrites:t.allowWrites===!0,cwd:o,noDocs:t.docs===!1,version:J,...t.docsUrl===void 0?{}:{docsUrl:t.docsUrl},...t.token===void 0?{}:{token:t.token},...t.url===void 0?{}:{url:t.url}});return{code:e}},at=W(async({argument:o,cwd:t,logger:e,options:r})=>{const[n,...a]=o;if(n==="serve")return Go(t,r);if(n==="uninstall"){const i=O(r),{code:l}=qo({clients:a,cwd:t,docsOnly:r.docsOnly===!0,localOnly:r.localOnly===!0,logger:e,print:r.print===!0,...i===void 0?{}:{scope:i}});return{code:l}}if(n==="install"){if(r.list===!0)return Ro({cwd:t,logger:e});const i=O(r),{code:l}=_o({clients:a,cwd:t,docsOnly:r.docsOnly===!0,force:r.force===!0,localOnly:r.localOnly===!0,logger:e,print:r.print===!0,...i===void 0?{}:{scope:i},...r.docsUrl===void 0?{}:{docsUrl:r.docsUrl}});return{code:l}}return e.error("mcp: unknown subcommand. Usage: lunora mcp <install|uninstall|serve>"),{code:1}});export{at as execute};
|