@lunora/cli 1.0.0-alpha.1 → 1.0.0-alpha.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/bin.mjs +1 -1
- package/dist/index.d.mts +473 -344
- package/dist/index.d.ts +473 -344
- package/dist/index.mjs +10 -11
- package/dist/packem_chunks/handler.mjs +91 -7
- package/dist/packem_chunks/handler10.mjs +8 -14
- package/dist/packem_chunks/handler11.mjs +20 -189
- package/dist/packem_chunks/handler12.mjs +176 -115
- package/dist/packem_chunks/handler13.mjs +118 -52
- package/dist/packem_chunks/handler14.mjs +50 -43
- package/dist/packem_chunks/handler15.mjs +27 -6
- package/dist/packem_chunks/handler16.mjs +6 -3
- package/dist/packem_chunks/handler17.mjs +1 -1
- package/dist/packem_chunks/handler18.mjs +5 -7
- package/dist/packem_chunks/handler19.mjs +44 -5
- package/dist/packem_chunks/handler2.mjs +6 -4
- package/dist/packem_chunks/handler20.mjs +2 -2
- package/dist/packem_chunks/handler21.mjs +158 -267
- package/dist/packem_chunks/handler3.mjs +8 -4
- package/dist/packem_chunks/handler4.mjs +2 -2
- package/dist/packem_chunks/handler5.mjs +7 -4
- package/dist/packem_chunks/handler6.mjs +10 -7
- package/dist/packem_chunks/handler7.mjs +2 -2
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +330 -12
- package/dist/packem_chunks/planDevCommand.mjs +759 -121
- package/dist/packem_chunks/runCodegenCommand.mjs +5 -4
- package/dist/packem_chunks/runDeployCommand.mjs +137 -22
- package/dist/packem_chunks/runInitCommand.mjs +2053 -112
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +98 -51
- package/dist/packem_chunks/runResetCommand.mjs +4 -4
- package/dist/packem_chunks/runRpcCommand.mjs +3 -2
- package/dist/packem_shared/{COMMANDS-1V_KEx35.mjs → COMMANDS-BOJIDoVY.mjs} +154 -28
- package/dist/packem_shared/{DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs → DEFAULT_IMPORT_BATCH_SIZE-D0VOTerB.mjs} +7 -2
- package/dist/packem_shared/{api-spec-CtA6ilu4.mjs → api-spec-Bx0iKbxA.mjs} +3 -1
- package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-BS5ig822.mjs} +1 -1
- package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +31 -0
- package/dist/packem_shared/{command-BDXcJCCJ.mjs → command-lYnl4QyF.mjs} +6 -1
- package/dist/packem_shared/{runAddCommand-BZGkRnBs.mjs → commands-D5Yxt9VY.mjs} +331 -33
- package/dist/packem_shared/{createLogger-CHPNjFw2.mjs → createLogger-CIWSHrTL.mjs} +40 -8
- package/dist/packem_shared/{defaultSpawner-DxI3mebw.mjs → createRecordingSpawner-WuSn20kb.mjs} +23 -2
- package/dist/packem_shared/detect-package-manager-v4hHpQd0.mjs +62 -0
- package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
- package/dist/packem_shared/{insertSchemaExtension-BuzF6-t2.mjs → insertSchemaExtension-DAqbfr9Z.mjs} +15 -10
- package/dist/packem_shared/{open-url-Dfq6fAyT.mjs → open-url-4PBLY9X0.mjs} +3 -2
- package/dist/packem_shared/{output-format-7gyGR3h8.mjs → output-format-B4642rjE.mjs} +1 -1
- package/dist/packem_shared/{parseManifest--vZf2FY1.mjs → parseManifest-Dbp-Q2q3.mjs} +36 -9
- package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +9 -0
- package/dist/packem_shared/runAddCommand-D1hgfqFQ.mjs +4 -0
- package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
- package/dist/packem_shared/storage-BXU4ax4O.mjs +84 -0
- package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +658 -0
- package/dist/packem_shared/wrangler-secrets-Coni-mER.mjs +49 -0
- package/package.json +23 -17
- package/skills/lunora-quickstart/SKILL.md +26 -5
- package/skills/lunora-setup-storage/SKILL.md +7 -3
- package/dist/packem_shared/features-ocSSpZtS.mjs +0 -24
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createWriteStream, createReadStream } from 'node:fs';
|
|
2
2
|
import { unlink, stat } from 'node:fs/promises';
|
|
3
|
+
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
import { r as resolveAdminBaseUrl } from './admin-url-4UzT-CI4.mjs';
|
|
4
5
|
|
|
5
6
|
const EXPORT_ENDPOINT_PATH = "/_lunora/admin/export";
|
|
@@ -129,6 +130,10 @@ const resolveImportRequest = async (options) => {
|
|
|
129
130
|
options.logger.error("--prod requires an explicit --url (refusing to import to the implicit localhost worker)");
|
|
130
131
|
return void 0;
|
|
131
132
|
}
|
|
133
|
+
if (options.prod && options.yes !== true) {
|
|
134
|
+
options.logger.error("import --prod bulk-writes production. Re-run with --yes to confirm.");
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
132
137
|
const token = options.token ?? process.env["LUNORA_ADMIN_TOKEN"];
|
|
133
138
|
if (!token) {
|
|
134
139
|
options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
|
|
@@ -183,7 +188,7 @@ const runImportCommand = async (options) => {
|
|
|
183
188
|
});
|
|
184
189
|
if (!response.ok) {
|
|
185
190
|
const text = await response.text().catch(() => "<no body>");
|
|
186
|
-
throw new
|
|
191
|
+
throw new LunoraError("INTERNAL", `import batch failed (HTTP ${String(response.status)}): ${text}`);
|
|
187
192
|
}
|
|
188
193
|
const json = await response.json();
|
|
189
194
|
if (json.inserted) {
|
|
@@ -213,7 +218,7 @@ const runImportCommand = async (options) => {
|
|
|
213
218
|
parsedDocument = JSON.parse(trimmed);
|
|
214
219
|
} catch (error) {
|
|
215
220
|
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
-
throw new
|
|
221
|
+
throw new LunoraError("INTERNAL", `invalid JSON on line ${String(lineNumber)}: ${message}`, { cause: error });
|
|
217
222
|
}
|
|
218
223
|
batch.push(JSON.stringify({ doc: parsedDocument, table: options.table }));
|
|
219
224
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const API_SPEC_VALUES = ["both", "none", "openapi", "openrpc"];
|
|
2
4
|
const API_SPEC_HELP = API_SPEC_VALUES.join(" | ");
|
|
3
5
|
const parseApiSpec = (value) => {
|
|
@@ -7,7 +9,7 @@ const parseApiSpec = (value) => {
|
|
|
7
9
|
if (API_SPEC_VALUES.includes(value)) {
|
|
8
10
|
return value;
|
|
9
11
|
}
|
|
10
|
-
throw new
|
|
12
|
+
throw new LunoraError("INTERNAL", `invalid --api-spec "${value}" — expected one of: ${API_SPEC_HELP}`);
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export { API_SPEC_HELP as A, parseApiSpec as p };
|
package/dist/packem_shared/{buildRegistryIndex-BcYe607_.mjs → buildRegistryIndex-BS5ig822.mjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from '@visulima/path';
|
|
3
|
-
import parseManifest from './parseManifest
|
|
3
|
+
import parseManifest from './parseManifest-Dbp-Q2q3.mjs';
|
|
4
4
|
|
|
5
5
|
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/gu;
|
|
6
6
|
const stripControlChars = (value) => value === void 0 ? void 0 : value.replaceAll(CONTROL_CHARS, "");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findLunoraSolution } from '@lunora/codegen';
|
|
2
|
+
import { VisulimaError, renderError } from '@visulima/error';
|
|
3
|
+
|
|
4
|
+
const flattenSolutionBody = (solution) => solution.body.split("\n").filter((line) => !line.startsWith("```")).join("\n").replaceAll(/\*\*(.+?)\*\*/gu, "$1").replaceAll(/`([^`]+)`/gu, "$1");
|
|
5
|
+
const NO_STACK = { filterStacktrace: () => false, hideErrorCodeView: true };
|
|
6
|
+
const renderCodegenFailure = (error, reason) => {
|
|
7
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8
|
+
const solution = findLunoraSolution(message);
|
|
9
|
+
const rendered = new VisulimaError({
|
|
10
|
+
hint: solution ? [solution.header, "", flattenSolutionBody(solution)] : void 0,
|
|
11
|
+
message: reason === void 0 ? `codegen failed: ${message}` : `codegen failed (${reason}): ${message}`,
|
|
12
|
+
name: "CodegenError"
|
|
13
|
+
});
|
|
14
|
+
rendered.stack = "";
|
|
15
|
+
return renderError(rendered, NO_STACK);
|
|
16
|
+
};
|
|
17
|
+
const renderCodegenHint = (message) => {
|
|
18
|
+
const solution = findLunoraSolution(message);
|
|
19
|
+
if (solution === void 0) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
const rendered = new VisulimaError({
|
|
23
|
+
hint: [flattenSolutionBody(solution)],
|
|
24
|
+
message: solution.header,
|
|
25
|
+
name: "Hint"
|
|
26
|
+
});
|
|
27
|
+
rendered.stack = "";
|
|
28
|
+
return renderError(rendered, NO_STACK);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { renderCodegenHint as a, renderCodegenFailure as r };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createLogger } from './createLogger-
|
|
1
|
+
import { createLogger } from './createLogger-CIWSHrTL.mjs';
|
|
2
|
+
import { P as PromptCancelledError, a as PROMPT_CANCEL_EXIT_CODE } from './prompt-cancelled-APzX1Im-.mjs';
|
|
2
3
|
|
|
3
4
|
const defineHandler = (body) => async (toolbox) => {
|
|
4
5
|
const logger = createLogger();
|
|
@@ -6,6 +7,10 @@ const defineHandler = (body) => async (toolbox) => {
|
|
|
6
7
|
const { code } = await body({ argument: toolbox.argument, cwd: toolbox.process.cwd, logger, options: toolbox.options });
|
|
7
8
|
toolbox.process.exit(code);
|
|
8
9
|
} catch (error) {
|
|
10
|
+
if (error instanceof PromptCancelledError) {
|
|
11
|
+
toolbox.process.exit(PROMPT_CANCEL_EXIT_CODE);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
9
14
|
logger.error(error instanceof Error ? error.message : String(error));
|
|
10
15
|
toolbox.process.exit(1);
|
|
11
16
|
}
|
|
@@ -1,15 +1,169 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
-
import { join,
|
|
3
|
-
import { DEV_VARS_FILE, parseDevVariableEntries,
|
|
2
|
+
import { dirname, join, relative } from '@visulima/path';
|
|
3
|
+
import { DEV_VARS_FILE, parseDevVariableEntries, findWranglerFile, readWranglerJsonc } from '@lunora/config';
|
|
4
4
|
import { modify, applyEdits, parse } from 'jsonc-parser';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { LunoraError } from '@lunora/errors';
|
|
7
|
+
import { b as tuiConfirm } from './tui-prompts-BjEN8XgP.mjs';
|
|
8
|
+
import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BS5ig822.mjs';
|
|
9
|
+
import { insertSchemaExtension } from './insertSchemaExtension-DAqbfr9Z.mjs';
|
|
7
10
|
import { createHash } from 'node:crypto';
|
|
8
11
|
import { tmpdir } from 'node:os';
|
|
9
12
|
import { downloadTemplate } from 'giget';
|
|
10
|
-
import parseManifest from './parseManifest
|
|
13
|
+
import parseManifest from './parseManifest-Dbp-Q2q3.mjs';
|
|
11
14
|
|
|
12
|
-
const
|
|
15
|
+
const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
|
|
16
|
+
const STABLE_BRANCH = "main";
|
|
17
|
+
const PRERELEASE_CHANNEL_BRANCHES = /* @__PURE__ */ new Set(["alpha", "beta", "next"]);
|
|
18
|
+
const SAFE_REF = /^[\w./@-]+$/;
|
|
19
|
+
const isSafeRef = (ref) => !ref.includes("..") && SAFE_REF.test(ref);
|
|
20
|
+
const resolveCliVersion = () => {
|
|
21
|
+
try {
|
|
22
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
for (let index = 0; index < 6; index += 1) {
|
|
24
|
+
const candidate = join(directory, "package.json");
|
|
25
|
+
if (existsSync(candidate)) {
|
|
26
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
27
|
+
if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
|
|
28
|
+
return parsed.version;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const parent = dirname(directory);
|
|
32
|
+
if (parent === directory) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
directory = parent;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return "0.0.0";
|
|
40
|
+
};
|
|
41
|
+
const resolveVersionRef = (version) => {
|
|
42
|
+
if (version === "0.0.0") {
|
|
43
|
+
return DEFAULT_SOURCE_REF_FALLBACK;
|
|
44
|
+
}
|
|
45
|
+
const core = version.split("+")[0] ?? version;
|
|
46
|
+
const dashIndex = core.indexOf("-");
|
|
47
|
+
if (dashIndex !== -1) {
|
|
48
|
+
const [channel] = core.slice(dashIndex + 1).split(".");
|
|
49
|
+
if (channel !== void 0 && PRERELEASE_CHANNEL_BRANCHES.has(channel)) {
|
|
50
|
+
return channel;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return STABLE_BRANCH;
|
|
54
|
+
};
|
|
55
|
+
const resolveSourceRef = (ref) => {
|
|
56
|
+
if (ref !== void 0 && ref.length > 0) {
|
|
57
|
+
if (!isSafeRef(ref)) {
|
|
58
|
+
throw new LunoraError("INTERNAL", `invalid --ref "${ref}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);
|
|
59
|
+
}
|
|
60
|
+
return ref;
|
|
61
|
+
}
|
|
62
|
+
return resolveVersionRef(resolveCliVersion());
|
|
63
|
+
};
|
|
64
|
+
const SOURCE_REPO = "anolilab/lunora";
|
|
65
|
+
const COMMIT_SHA = /^[0-9a-f]{40}$/iu;
|
|
66
|
+
const SEMVER_BODY = String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+)?(?:\+[0-9A-Za-z.-]+)?`;
|
|
67
|
+
const LEADING_VERSION_TAG = new RegExp(String.raw`^v?${SEMVER_BODY}$`, "u");
|
|
68
|
+
const PACKAGE_VERSION_TAG = new RegExp(String.raw`^(?:@[\w.-]+\/)?[\w.-]+@${SEMVER_BODY}$`, "u");
|
|
69
|
+
const isImmutableRef = (ref) => COMMIT_SHA.test(ref) || LEADING_VERSION_TAG.test(ref) || PACKAGE_VERSION_TAG.test(ref);
|
|
70
|
+
const githubAuthHeaders = () => {
|
|
71
|
+
const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
|
|
72
|
+
return token !== void 0 && token.length > 0 ? { authorization: `Bearer ${token}` } : {};
|
|
73
|
+
};
|
|
74
|
+
const fetchBranchSha = async (branch) => {
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`https://api.github.com/repos/${SOURCE_REPO}/commits/${encodeURIComponent(branch)}`, {
|
|
77
|
+
headers: { accept: "application/vnd.github+json", "user-agent": "lunora-cli", ...githubAuthHeaders() },
|
|
78
|
+
signal: AbortSignal.timeout(1e4)
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
const body = await response.json();
|
|
84
|
+
return typeof body.sha === "string" && COMMIT_SHA.test(body.sha) ? body.sha : void 0;
|
|
85
|
+
} catch {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const resolvePinnedSourceRef = async (ref, logger) => {
|
|
90
|
+
const resolved = resolveSourceRef(ref);
|
|
91
|
+
if (isImmutableRef(resolved)) {
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
const sha = await fetchBranchSha(resolved);
|
|
95
|
+
if (sha === void 0) {
|
|
96
|
+
logger.warn(`could not pin ${SOURCE_REPO}#${resolved} to a commit — fetching the UNPINNED branch (set GITHUB_TOKEN if rate-limited).`);
|
|
97
|
+
return resolved;
|
|
98
|
+
}
|
|
99
|
+
logger.info(`pinned ${SOURCE_REPO}#${resolved} → ${sha}`);
|
|
100
|
+
return sha;
|
|
101
|
+
};
|
|
102
|
+
const STABLE_DIST_TAG = "latest";
|
|
103
|
+
const resolveDistTag = (version = resolveCliVersion()) => {
|
|
104
|
+
const ref = resolveVersionRef(version);
|
|
105
|
+
return ref === STABLE_BRANCH ? STABLE_DIST_TAG : ref;
|
|
106
|
+
};
|
|
107
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
108
|
+
const registryBase = () => {
|
|
109
|
+
const configured = process.env["npm_config_registry"];
|
|
110
|
+
const base = configured !== void 0 && configured.length > 0 ? configured : DEFAULT_REGISTRY;
|
|
111
|
+
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
112
|
+
};
|
|
113
|
+
const resolveTagVersion = async (packageName, tag) => {
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${registryBase()}/${packageName.replaceAll("/", "%2F")}`, {
|
|
116
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
117
|
+
signal: AbortSignal.timeout(1e4)
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
const packument = await response.json();
|
|
123
|
+
return packument["dist-tags"]?.[tag];
|
|
124
|
+
} catch {
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const resolveTagVersions = async (names, tag) => {
|
|
129
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
130
|
+
await Promise.all(
|
|
131
|
+
[...new Set(names)].map(async (name) => {
|
|
132
|
+
const version = await resolveTagVersion(name, tag);
|
|
133
|
+
if (version !== void 0) {
|
|
134
|
+
resolved.set(name, version);
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
return resolved;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const resolveDepRange = (range) => {
|
|
142
|
+
if (!range.startsWith("workspace:")) {
|
|
143
|
+
return range;
|
|
144
|
+
}
|
|
145
|
+
const rest = range.slice("workspace:".length);
|
|
146
|
+
if (rest === "" || rest === "*" || rest === "^" || rest === "~") {
|
|
147
|
+
return resolveDistTag();
|
|
148
|
+
}
|
|
149
|
+
return rest;
|
|
150
|
+
};
|
|
151
|
+
const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/ratelimit", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
|
|
152
|
+
const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|ratelimit|runtime|server|values)(\/[^'"]*)?\1/gu;
|
|
153
|
+
const projectUsesUmbrella = (projectRoot) => {
|
|
154
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
155
|
+
if (!existsSync(packageJsonPath)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
160
|
+
return parsed.dependencies?.lunorash !== void 0 || parsed.devDependencies?.lunorash !== void 0;
|
|
161
|
+
} catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const rewriteUmbrellaImports = (source) => source.replaceAll(UMBRELLA_IMPORT_RE, (_match, quote, base, subpath) => `${quote}lunorash/${base}${subpath ?? ""}${quote}`);
|
|
166
|
+
const applyDeps = (deps, projectRoot, logger, section = "dependencies", useUmbrella = false) => {
|
|
13
167
|
const entries = Object.entries(deps);
|
|
14
168
|
if (entries.length === 0) {
|
|
15
169
|
return [];
|
|
@@ -23,11 +177,15 @@ const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
|
|
|
23
177
|
const parsed = JSON.parse(text);
|
|
24
178
|
const added = [];
|
|
25
179
|
for (const [name, range] of entries) {
|
|
180
|
+
if (useUmbrella && UMBRELLA_REEXPORTED_DEPS.has(name)) {
|
|
181
|
+
logger.info(`dep provided by the lunorash umbrella, skipping: ${name}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
26
184
|
if (parsed.dependencies?.[name] !== void 0 || parsed.devDependencies?.[name] !== void 0) {
|
|
27
185
|
logger.info(`dep already present: ${name}`);
|
|
28
186
|
continue;
|
|
29
187
|
}
|
|
30
|
-
const edits = modify(text, [section, name], range, {
|
|
188
|
+
const edits = modify(text, [section, name], resolveDepRange(range), {
|
|
31
189
|
formattingOptions: { insertSpaces: true, tabSize: 4 }
|
|
32
190
|
});
|
|
33
191
|
text = applyEdits(text, edits);
|
|
@@ -148,14 +306,14 @@ const applyBindings = (bindings, projectRoot, logger) => {
|
|
|
148
306
|
}
|
|
149
307
|
return applied;
|
|
150
308
|
};
|
|
151
|
-
const applyItemResources = (manifest, cwd, logger) => {
|
|
309
|
+
const applyItemResources = (manifest, cwd, logger, useUmbrella = false) => {
|
|
152
310
|
const deps = [];
|
|
153
311
|
const bindings = [];
|
|
154
312
|
if (manifest.deps) {
|
|
155
|
-
deps.push(...applyDeps(manifest.deps, cwd, logger));
|
|
313
|
+
deps.push(...applyDeps(manifest.deps, cwd, logger, "dependencies", useUmbrella));
|
|
156
314
|
}
|
|
157
315
|
if (manifest.devDependencies) {
|
|
158
|
-
deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies"));
|
|
316
|
+
deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies", useUmbrella));
|
|
159
317
|
}
|
|
160
318
|
if (manifest.bindings) {
|
|
161
319
|
bindings.push(...applyBindings(manifest.bindings, cwd, logger));
|
|
@@ -187,8 +345,8 @@ const confirmDepMutation = async (items, options) => {
|
|
|
187
345
|
options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
|
|
188
346
|
return false;
|
|
189
347
|
}
|
|
190
|
-
const confirmer = options.confirm ??
|
|
191
|
-
const confirmed = await confirmer(`The requested items ${reasonText}. Continue
|
|
348
|
+
const confirmer = options.confirm ?? tuiConfirm;
|
|
349
|
+
const confirmed = await confirmer(`The requested items ${reasonText}. Continue?`);
|
|
192
350
|
if (!confirmed) {
|
|
193
351
|
options.logger.info("add: aborted");
|
|
194
352
|
}
|
|
@@ -267,7 +425,12 @@ const renderDiff = (oldText, newText) => {
|
|
|
267
425
|
return out;
|
|
268
426
|
};
|
|
269
427
|
|
|
270
|
-
const
|
|
428
|
+
const CODE_FILE_RE = /\.[cm]?[jt]sx?$/u;
|
|
429
|
+
const readItemFile = (itemDirectory, file, useUmbrella) => {
|
|
430
|
+
const source = readFileSync(join(itemDirectory, file.from), "utf8");
|
|
431
|
+
return useUmbrella && CODE_FILE_RE.test(file.to) ? rewriteUmbrellaImports(source) : source;
|
|
432
|
+
};
|
|
433
|
+
const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff, useUmbrella) => {
|
|
271
434
|
const schemaPath = join(projectRoot, "lunora", "schema.ts");
|
|
272
435
|
if (diff) {
|
|
273
436
|
logger.info(`~ would merge .extend(${itemKey}.extension) into lunora/schema.ts (and create ${file.to} if absent)`);
|
|
@@ -276,9 +439,13 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
|
|
|
276
439
|
const destinationPath = join(projectRoot, file.to);
|
|
277
440
|
if (!existsSync(destinationPath)) {
|
|
278
441
|
mkdirSync(dirname(destinationPath), { recursive: true });
|
|
279
|
-
writeFileSync(destinationPath,
|
|
442
|
+
writeFileSync(destinationPath, readItemFile(itemDirectory, file, useUmbrella), "utf8");
|
|
280
443
|
}
|
|
281
|
-
const
|
|
444
|
+
const baseModule = useUmbrella ? "lunorash/server" : "@lunora/server";
|
|
445
|
+
const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : `import { defineSchema } from "${baseModule}";
|
|
446
|
+
|
|
447
|
+
export const schema = defineSchema({});
|
|
448
|
+
`;
|
|
282
449
|
const result = insertSchemaExtension(existingSchema, itemKey);
|
|
283
450
|
if (result.ok) {
|
|
284
451
|
mkdirSync(dirname(schemaPath), { recursive: true });
|
|
@@ -291,11 +458,12 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
|
|
|
291
458
|
return { kind: "skipped", path: schemaPath };
|
|
292
459
|
}
|
|
293
460
|
if (result.reason === "invalid-identifier") {
|
|
294
|
-
throw new
|
|
461
|
+
throw new LunoraError(
|
|
462
|
+
"INTERNAL",
|
|
295
463
|
`schema-extension item "${itemKey}" is not a valid JS identifier — it is spliced into lunora/schema.ts as \`import { ${itemKey} }\` / \`.extend(${itemKey}.extension)\`. Rename the item to a valid identifier (no leading digit, no "-").`
|
|
296
464
|
);
|
|
297
465
|
}
|
|
298
|
-
throw new
|
|
466
|
+
throw new LunoraError("INTERNAL", `schema-extension merge failed for "${itemKey}": ${result.reason}`);
|
|
299
467
|
};
|
|
300
468
|
const previewWholeFile = (file, current, incoming, exists, logger) => {
|
|
301
469
|
const lines = renderDiff(current, incoming);
|
|
@@ -308,9 +476,9 @@ const previewWholeFile = (file, current, incoming, exists, logger) => {
|
|
|
308
476
|
logger.info(` ${line}`);
|
|
309
477
|
}
|
|
310
478
|
};
|
|
311
|
-
const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions) => {
|
|
479
|
+
const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella) => {
|
|
312
480
|
const destinationPath = join(projectRoot, file.to);
|
|
313
|
-
const incoming =
|
|
481
|
+
const incoming = readItemFile(itemDirectory, file, useUmbrella);
|
|
314
482
|
const exists = existsSync(destinationPath);
|
|
315
483
|
const current = exists ? readFileSync(destinationPath, "utf8") : "";
|
|
316
484
|
const write = (message) => {
|
|
@@ -348,11 +516,95 @@ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, l
|
|
|
348
516
|
logger.warn(`conflict: ${file.to} has local edits and an upstream update — wrote ${file.to}.new (use --overwrite to take theirs)`);
|
|
349
517
|
return { kind: "skipped", path: destinationPath };
|
|
350
518
|
};
|
|
351
|
-
const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}) => {
|
|
519
|
+
const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}, useUmbrella = false) => {
|
|
352
520
|
if (file.merge === "schema-extension") {
|
|
353
|
-
return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true);
|
|
521
|
+
return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true, useUmbrella);
|
|
354
522
|
}
|
|
355
|
-
return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions);
|
|
523
|
+
return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
|
|
524
|
+
};
|
|
525
|
+
const WORKER_ENTRY_FALLBACKS = ["src/server.ts", "src/server/index.ts", "src/server/index.tsx", "src/index.ts", "src/worker.ts"];
|
|
526
|
+
const readWranglerMain = (projectRoot) => {
|
|
527
|
+
const wranglerPath = findWranglerFile(projectRoot);
|
|
528
|
+
if (wranglerPath === void 0) {
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
const { parsed } = readWranglerJsonc(wranglerPath);
|
|
532
|
+
return typeof parsed?.main === "string" ? parsed.main : void 0;
|
|
533
|
+
};
|
|
534
|
+
const findWorkerEntry = (projectRoot) => {
|
|
535
|
+
const main = readWranglerMain(projectRoot);
|
|
536
|
+
const candidates = main === void 0 ? WORKER_ENTRY_FALLBACKS : [main, ...WORKER_ENTRY_FALLBACKS];
|
|
537
|
+
for (const candidate of candidates) {
|
|
538
|
+
const absolute = join(projectRoot, candidate);
|
|
539
|
+
if (!existsSync(absolute)) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const content = readFileSync(absolute, "utf8");
|
|
543
|
+
if (!content.includes("createShardDO(")) {
|
|
544
|
+
if (candidate === main) {
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
return { entryPath: absolute, main: candidate, source: content };
|
|
550
|
+
}
|
|
551
|
+
return void 0;
|
|
552
|
+
};
|
|
553
|
+
const computeRelativeSpecifier = (entryPath, projectRoot, moduleName) => {
|
|
554
|
+
const importPath = relative(dirname(entryPath), join(projectRoot, "lunora", moduleName)).replaceAll("\\", "/");
|
|
555
|
+
return importPath.startsWith(".") ? importPath : `./${importPath}`;
|
|
556
|
+
};
|
|
557
|
+
const logClassAFallback = (entrypointReexports, logger) => {
|
|
558
|
+
for (const reexport of entrypointReexports) {
|
|
559
|
+
const specifier = `./lunora/${reexport.module}.js`;
|
|
560
|
+
const instruction = `Add \`export * from "${specifier}"\` to your worker entry`;
|
|
561
|
+
const suffix = reexport.comment ? ` (${reexport.comment})` : "";
|
|
562
|
+
logger.warn(`${instruction}${suffix}`);
|
|
563
|
+
}
|
|
564
|
+
return 0;
|
|
565
|
+
};
|
|
566
|
+
const buildReexportLines = (entrypointReexports, entryPath, projectRoot, source) => {
|
|
567
|
+
const lines = [];
|
|
568
|
+
for (const reexport of entrypointReexports) {
|
|
569
|
+
const specifier = computeRelativeSpecifier(entryPath, projectRoot, reexport.module);
|
|
570
|
+
const escapedSpecifier = specifier.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
571
|
+
const existingRe = new RegExp(String.raw`export\s+\*\s+from\s+["']${escapedSpecifier}\.js["']`, "u");
|
|
572
|
+
if (existingRe.test(source)) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (reexport.comment) {
|
|
576
|
+
lines.push(`
|
|
577
|
+
// ${reexport.comment}`);
|
|
578
|
+
}
|
|
579
|
+
lines.push(`export * from "${specifier}.js";`);
|
|
580
|
+
}
|
|
581
|
+
return lines;
|
|
582
|
+
};
|
|
583
|
+
const applyEntrypointReexports = (entrypointReexports, projectRoot, logger, diff) => {
|
|
584
|
+
if (entrypointReexports.length === 0) {
|
|
585
|
+
return 0;
|
|
586
|
+
}
|
|
587
|
+
const entry = findWorkerEntry(projectRoot);
|
|
588
|
+
if (entry === void 0) {
|
|
589
|
+
return logClassAFallback(entrypointReexports, logger);
|
|
590
|
+
}
|
|
591
|
+
const linesToAppend = buildReexportLines(entrypointReexports, entry.entryPath, projectRoot, entry.source);
|
|
592
|
+
if (linesToAppend.length === 0) {
|
|
593
|
+
return 0;
|
|
594
|
+
}
|
|
595
|
+
if (diff) {
|
|
596
|
+
for (const line of linesToAppend) {
|
|
597
|
+
if (line !== "") {
|
|
598
|
+
logger.info(`~ entrypoint: ${line}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return linesToAppend.length;
|
|
602
|
+
}
|
|
603
|
+
const separator = entry.source.endsWith("\n") ? "" : "\n";
|
|
604
|
+
writeFileSync(entry.entryPath, `${entry.source}${separator}${linesToAppend.join("\n")}
|
|
605
|
+
`, "utf8");
|
|
606
|
+
logger.success(`wrote ${String(linesToAppend.length)} entrypoint re-export(s) to ${relative(projectRoot, entry.entryPath)}`);
|
|
607
|
+
return linesToAppend.length;
|
|
356
608
|
};
|
|
357
609
|
const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
358
610
|
const written = [];
|
|
@@ -360,15 +612,19 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
|
360
612
|
const depsAdded = [];
|
|
361
613
|
const bindingsApplied = [];
|
|
362
614
|
const lock = readLock(cwd);
|
|
615
|
+
const useUmbrella = projectUsesUmbrella(cwd);
|
|
363
616
|
for (const { directory, manifest } of items) {
|
|
364
617
|
for (const file of manifest.files) {
|
|
365
|
-
const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions);
|
|
618
|
+
const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
|
|
366
619
|
(outcome.kind === "written" ? written : skipped).push(outcome.path);
|
|
367
620
|
}
|
|
621
|
+
if (manifest.entrypointReexports !== void 0) {
|
|
622
|
+
applyEntrypointReexports(manifest.entrypointReexports, cwd, logger, reconcileOptions.diff === true);
|
|
623
|
+
}
|
|
368
624
|
if (reconcileOptions.diff) {
|
|
369
625
|
continue;
|
|
370
626
|
}
|
|
371
|
-
const applied = applyItemResources(manifest, cwd, logger);
|
|
627
|
+
const applied = applyItemResources(manifest, cwd, logger, useUmbrella);
|
|
372
628
|
depsAdded.push(...applied.deps);
|
|
373
629
|
bindingsApplied.push(...applied.bindings);
|
|
374
630
|
}
|
|
@@ -379,11 +635,11 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
|
379
635
|
};
|
|
380
636
|
|
|
381
637
|
const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/registry";
|
|
382
|
-
const DEFAULT_SOURCE_REF = "alpha";
|
|
383
638
|
const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
|
|
384
639
|
const assertSafeItemName = (name) => {
|
|
385
640
|
if (!VALID_ITEM_NAME.test(name)) {
|
|
386
|
-
throw new
|
|
641
|
+
throw new LunoraError(
|
|
642
|
+
"INTERNAL",
|
|
387
643
|
`invalid registry item name "${name}" — names must match ${VALID_ITEM_NAME.source} (letters, digits, "-", "_"; no path separators or "..")`
|
|
388
644
|
);
|
|
389
645
|
}
|
|
@@ -420,29 +676,39 @@ const fetchToStaging = async (remote, label, logger) => {
|
|
|
420
676
|
throw error;
|
|
421
677
|
}
|
|
422
678
|
};
|
|
679
|
+
const remoteRefCache = /* @__PURE__ */ new WeakMap();
|
|
680
|
+
const resolveRemoteRef = async (options) => {
|
|
681
|
+
const cached = remoteRefCache.get(options);
|
|
682
|
+
if (cached !== void 0) {
|
|
683
|
+
return cached;
|
|
684
|
+
}
|
|
685
|
+
const pending = options.source !== void 0 && options.source.length > 0 ? Promise.resolve(resolveSourceRef(options.ref)) : resolvePinnedSourceRef(options.ref, options.logger);
|
|
686
|
+
remoteRefCache.set(options, pending);
|
|
687
|
+
return pending;
|
|
688
|
+
};
|
|
423
689
|
const resolveItemDirectory = async (name, options) => {
|
|
424
690
|
assertSafeItemName(name);
|
|
425
691
|
if (options.from !== void 0) {
|
|
426
692
|
const directory = join(options.from, name);
|
|
427
693
|
if (!existsSync(directory)) {
|
|
428
|
-
throw new
|
|
694
|
+
throw new LunoraError("INTERNAL", `registry item not found in local source: ${directory}`);
|
|
429
695
|
}
|
|
430
696
|
return { cleanup: () => {
|
|
431
697
|
}, directory };
|
|
432
698
|
}
|
|
433
699
|
const base = options.source ?? DEFAULT_SOURCE_BASE;
|
|
434
|
-
return fetchToStaging(`${base}/${name}#${
|
|
700
|
+
return fetchToStaging(`${base}/${name}#${await resolveRemoteRef(options)}`, "item", options.logger);
|
|
435
701
|
};
|
|
436
702
|
const resolveRegistryRoot = async (options) => {
|
|
437
703
|
if (options.from !== void 0) {
|
|
438
704
|
if (!existsSync(options.from)) {
|
|
439
|
-
throw new
|
|
705
|
+
throw new LunoraError("INTERNAL", `registry root not found: ${options.from}`);
|
|
440
706
|
}
|
|
441
707
|
return { cleanup: () => {
|
|
442
708
|
}, root: options.from };
|
|
443
709
|
}
|
|
444
710
|
const base = options.source ?? DEFAULT_SOURCE_BASE;
|
|
445
|
-
const { cleanup, directory } = await fetchToStaging(`${base}#${
|
|
711
|
+
const { cleanup, directory } = await fetchToStaging(`${base}#${await resolveRemoteRef(options)}`, "registry", options.logger);
|
|
446
712
|
return { cleanup, root: directory };
|
|
447
713
|
};
|
|
448
714
|
const readManifest = (itemDirectory, name) => {
|
|
@@ -459,7 +725,7 @@ const resolvePlan = async (names, options) => {
|
|
|
459
725
|
return;
|
|
460
726
|
}
|
|
461
727
|
if (inProgress.has(name)) {
|
|
462
|
-
throw new
|
|
728
|
+
throw new LunoraError("INTERNAL", `cyclic registry dependency detected at "${name}"`);
|
|
463
729
|
}
|
|
464
730
|
inProgress.add(name);
|
|
465
731
|
const { cleanup, directory } = await resolveItemDirectory(name, options);
|
|
@@ -488,6 +754,26 @@ const resolvePlan = async (names, options) => {
|
|
|
488
754
|
const emptyResult = () => {
|
|
489
755
|
return { bindings: [], code: 0, deps: [], skipped: [], written: [] };
|
|
490
756
|
};
|
|
757
|
+
const setBindingField = (manifest, section, match, field, fieldValue) => {
|
|
758
|
+
if (!manifest.bindings) {
|
|
759
|
+
return manifest;
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
...manifest,
|
|
763
|
+
bindings: manifest.bindings.map((binding) => {
|
|
764
|
+
if (binding.path[0] !== section || !Array.isArray(binding.value)) {
|
|
765
|
+
return binding;
|
|
766
|
+
}
|
|
767
|
+
const entries = binding.value;
|
|
768
|
+
return {
|
|
769
|
+
...binding,
|
|
770
|
+
value: entries.map(
|
|
771
|
+
(entry) => typeof entry === "object" && entry !== null && entry[match.key] === match.value ? { ...entry, [field]: fieldValue } : entry
|
|
772
|
+
)
|
|
773
|
+
};
|
|
774
|
+
})
|
|
775
|
+
};
|
|
776
|
+
};
|
|
491
777
|
|
|
492
778
|
const printPlan = (logger, manifest) => {
|
|
493
779
|
const label = manifest.title ?? manifest.description;
|
|
@@ -508,6 +794,11 @@ const printPlan = (logger, manifest) => {
|
|
|
508
794
|
const valueSuffix = variable.secret ? " (secret)" : ` = ${JSON.stringify(variable.value ?? "")}`;
|
|
509
795
|
logger.info(` env ${variable.name}${valueSuffix}`);
|
|
510
796
|
}
|
|
797
|
+
for (const reexport of manifest.entrypointReexports ?? []) {
|
|
798
|
+
const specifier = `./lunora/${reexport.module}`;
|
|
799
|
+
const suffix = reexport.comment ? ` // ${reexport.comment}` : "";
|
|
800
|
+
logger.info(` entry ${specifier}${suffix}`);
|
|
801
|
+
}
|
|
511
802
|
};
|
|
512
803
|
const printJsonPlan = (items) => {
|
|
513
804
|
const planSnapshot = items.map(({ manifest }) => {
|
|
@@ -519,6 +810,9 @@ const printJsonPlan = (items) => {
|
|
|
519
810
|
}),
|
|
520
811
|
deps: Object.keys(manifest.deps ?? {}),
|
|
521
812
|
devDependencies: Object.keys(manifest.devDependencies ?? {}),
|
|
813
|
+
entrypointReexports: (manifest.entrypointReexports ?? []).map((reexport) => {
|
|
814
|
+
return { module: reexport.module, ...reexport.comment ? { comment: reexport.comment } : {} };
|
|
815
|
+
}),
|
|
522
816
|
envVars: (manifest.envVars ?? []).map((variable) => {
|
|
523
817
|
return { name: variable.name, ...variable.secret ? { secret: true } : { value: variable.value ?? "" } };
|
|
524
818
|
}),
|
|
@@ -593,8 +887,12 @@ const runAddCommand = async (options) => {
|
|
|
593
887
|
}
|
|
594
888
|
let cleanups = [];
|
|
595
889
|
try {
|
|
596
|
-
const { cleanups: planCleanups, items } = await resolvePlan(options.names, options);
|
|
890
|
+
const { cleanups: planCleanups, items: resolvedItems } = await resolvePlan(options.names, options);
|
|
597
891
|
cleanups = planCleanups;
|
|
892
|
+
const { transformManifest } = options;
|
|
893
|
+
const items = transformManifest ? resolvedItems.map((item) => {
|
|
894
|
+
return { ...item, manifest: transformManifest(item.manifest) };
|
|
895
|
+
}) : resolvedItems;
|
|
598
896
|
for (const { manifest } of items) {
|
|
599
897
|
printPlan(options.logger, manifest);
|
|
600
898
|
}
|
|
@@ -690,4 +988,4 @@ const runBuildIndexCommand = async (options) => {
|
|
|
690
988
|
return empty;
|
|
691
989
|
};
|
|
692
990
|
|
|
693
|
-
export {
|
|
991
|
+
export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveTagVersions as c, resolveSourceRef as d, resolvePinnedSourceRef as e, resolveDistTag as f, runListCommand as g, runAddCommand as r, setBindingField as s };
|