@lunora/vite 1.0.0-alpha.81 → 1.0.0-alpha.83
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/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.mjs +3 -173
- package/dist/packem_shared/CLASS_A_WIRING-QlSvg7Ku.mjs +33 -0
- package/dist/packem_shared/DEV_WORKER_ENV_VALUE-CdVg3e_J.mjs +1 -0
- package/dist/packem_shared/LUNORA_API_UPDATED_EVENT-CO2Rv2re.mjs +1 -0
- package/dist/packem_shared/STUDIO_PATH-CCACdfgp.mjs +1 -0
- package/dist/packem_shared/WORKER_STARTUP_HINT-Dsm2vG6Q.mjs +5 -0
- package/dist/packem_shared/checkLunoraProxy-n_jVuOU-.mjs +1 -0
- package/dist/packem_shared/codegenPlugin-Cw1lSUC0.mjs +3 -0
- package/dist/packem_shared/containerLogsPlugin-QOsfYAYL.mjs +1 -0
- package/dist/packem_shared/devStatePlugin-CMRBp9zZ.mjs +1 -0
- package/dist/packem_shared/devVariablesPlugin-0SvpKjRJ.mjs +1 -0
- package/dist/packem_shared/log-DTdeAemA.mjs +1 -0
- package/dist/packem_shared/logStreamPlugin-CFlQAr0R.mjs +3 -0
- package/dist/packem_shared/lunoraSolutionFinder-Dl5pUZK2.mjs +1 -0
- package/dist/packem_shared/planViteRemoteBindings-BDAdqq6D.mjs +1 -0
- package/dist/packem_shared/server-close-C8zGVydc.mjs +1 -0
- package/dist/packem_shared/wranglerValidatorPlugin-CHmNBKbO.mjs +3 -0
- package/package.json +3 -3
- package/dist/packem_shared/CLASS_A_WIRING-T1RohRZQ.mjs +0 -120
- package/dist/packem_shared/DEV_WORKER_ENV_VALUE-Coo6bgVz.mjs +0 -36
- package/dist/packem_shared/LUNORA_API_UPDATED_EVENT-BrkWk3rr.mjs +0 -3
- package/dist/packem_shared/STUDIO_PATH-U0eIaW1t.mjs +0 -236
- package/dist/packem_shared/WORKER_STARTUP_HINT-DhsXUW8k.mjs +0 -81
- package/dist/packem_shared/codegenPlugin-B8c38rky.mjs +0 -347
- package/dist/packem_shared/containerLogsPlugin-DMssU3wb.mjs +0 -50
- package/dist/packem_shared/devStatePlugin-DdjfMrhx.mjs +0 -84
- package/dist/packem_shared/devVariablesPlugin-CDNSnvOP.mjs +0 -19
- package/dist/packem_shared/log-BjO9EWah.mjs +0 -8
- package/dist/packem_shared/logStreamPlugin-DeFzd_pC.mjs +0 -81
- package/dist/packem_shared/lunoraSolutionFinder-BKEAiUJP.mjs +0 -17
- package/dist/packem_shared/planViteRemoteBindings-CRU90Bip.mjs +0 -66
- package/dist/packem_shared/server-close-DKDp7YTU.mjs +0 -16
- package/dist/packem_shared/wranglerValidatorPlugin-C5VWOD7N.mjs +0 -68
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { detectAgentRules } from '@lunora/config';
|
|
2
|
-
import { handleSeedRequest, handleSchemaEditRequest, handlePolicyScaffoldRequest, SEED_ENDPOINT, SCHEMA_EDIT_ENDPOINT, POLICY_SCAFFOLD_ENDPOINT, serveJsonHandler, isStandaloneModulePath, renderStudioHtml, resolveAdminToken, studioAssetsStamp, loadStudioAssets, readStandaloneAsset, assetContentType } from '@lunora/config/studio-host';
|
|
3
|
-
|
|
4
|
-
const STUDIO_PATH = "/__lunora";
|
|
5
|
-
const STUDIO_SCRIPT_PATH = `${STUDIO_PATH}/studio.js`;
|
|
6
|
-
const STUDIO_STYLE_PATH = `${STUDIO_PATH}/styles.css`;
|
|
7
|
-
const LEADING_SLASH = /^\//;
|
|
8
|
-
const TRAILING_SLASH = /\/$/;
|
|
9
|
-
const JSON_ENDPOINT_HANDLERS = {
|
|
10
|
-
[POLICY_SCAFFOLD_ENDPOINT]: handlePolicyScaffoldRequest,
|
|
11
|
-
[SCHEMA_EDIT_ENDPOINT]: handleSchemaEditRequest,
|
|
12
|
-
[SEED_ENDPOINT]: handleSeedRequest
|
|
13
|
-
};
|
|
14
|
-
const STATE_CHANGING_ENDPOINTS = new Set(Object.keys(JSON_ENDPOINT_HANDLERS));
|
|
15
|
-
const headerValue = (raw) => {
|
|
16
|
-
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
17
|
-
return typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
18
|
-
};
|
|
19
|
-
const isLoopbackAddress = (remoteAddress) => {
|
|
20
|
-
if (remoteAddress === void 0 || remoteAddress === "") {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
const address = remoteAddress.toLowerCase();
|
|
24
|
-
const v4 = address.startsWith("::ffff:") ? address.slice(7) : address;
|
|
25
|
-
if (v4 === "::1") {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
return v4.startsWith("127.");
|
|
29
|
-
};
|
|
30
|
-
const hostnameOf = (host) => {
|
|
31
|
-
if (host === void 0) {
|
|
32
|
-
return void 0;
|
|
33
|
-
}
|
|
34
|
-
if (host.startsWith("[")) {
|
|
35
|
-
const close = host.indexOf("]");
|
|
36
|
-
return close === -1 ? host.slice(1) : host.slice(1, close);
|
|
37
|
-
}
|
|
38
|
-
const colon = host.indexOf(":");
|
|
39
|
-
return colon === -1 ? host : host.slice(0, colon);
|
|
40
|
-
};
|
|
41
|
-
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["0.0.0.0", "127.0.0.1", "::1", "localhost"]);
|
|
42
|
-
const transportRejectionReason = (request) => {
|
|
43
|
-
if (!isLoopbackAddress(request.socket?.remoteAddress ?? void 0)) {
|
|
44
|
-
return "Lunora studio is only available on loopback connections in dev.";
|
|
45
|
-
}
|
|
46
|
-
const host = hostnameOf(headerValue(request.headers?.host));
|
|
47
|
-
if (host !== void 0 && !LOOPBACK_HOSTS.has(host)) {
|
|
48
|
-
return "Lunora studio rejects a non-localhost Host header in dev.";
|
|
49
|
-
}
|
|
50
|
-
const FORWARDING_HEADERS = ["x-forwarded-for", "x-forwarded-host", "x-forwarded-proto", "forwarded"];
|
|
51
|
-
if (FORWARDING_HEADERS.some((name) => request.headers?.[name] !== void 0)) {
|
|
52
|
-
return "Lunora studio refuses a proxied (X-Forwarded-*) request in dev.";
|
|
53
|
-
}
|
|
54
|
-
return void 0;
|
|
55
|
-
};
|
|
56
|
-
const originRejectionReason = (headers) => {
|
|
57
|
-
const secFetchSite = headerValue(headers["sec-fetch-site"]);
|
|
58
|
-
if (secFetchSite !== void 0) {
|
|
59
|
-
return secFetchSite === "same-origin" || secFetchSite === "same-site" || secFetchSite === "none" ? void 0 : "cross-origin request rejected";
|
|
60
|
-
}
|
|
61
|
-
const origin = headerValue(headers.origin);
|
|
62
|
-
if (origin === void 0 || origin === "null") {
|
|
63
|
-
return void 0;
|
|
64
|
-
}
|
|
65
|
-
let originHost;
|
|
66
|
-
try {
|
|
67
|
-
originHost = new URL(origin).host.toLowerCase();
|
|
68
|
-
} catch {
|
|
69
|
-
return "invalid origin header";
|
|
70
|
-
}
|
|
71
|
-
const host = headerValue(headers.host);
|
|
72
|
-
return host === void 0 || originHost !== host ? "cross-origin request rejected" : void 0;
|
|
73
|
-
};
|
|
74
|
-
const csrfRejectionReason = (request) => {
|
|
75
|
-
const headers = request.headers ?? {};
|
|
76
|
-
const originReason = originRejectionReason(headers);
|
|
77
|
-
if (originReason !== void 0) {
|
|
78
|
-
return originReason;
|
|
79
|
-
}
|
|
80
|
-
const method = (request.method ?? "GET").toUpperCase();
|
|
81
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
82
|
-
const contentType = headerValue(headers["content-type"]);
|
|
83
|
-
if (!contentType?.startsWith("application/json")) {
|
|
84
|
-
return "content-type must be application/json";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return void 0;
|
|
88
|
-
};
|
|
89
|
-
const sendOk = (response, body, contentType) => {
|
|
90
|
-
response.statusCode = 200;
|
|
91
|
-
response.setHeader("Content-Type", contentType);
|
|
92
|
-
response.end(body);
|
|
93
|
-
};
|
|
94
|
-
const buildStudioUrl = (input) => {
|
|
95
|
-
const path = STUDIO_PATH.replace(LEADING_SLASH, "");
|
|
96
|
-
if (input.resolvedLocal !== void 0 && input.resolvedLocal !== "") {
|
|
97
|
-
const origin = input.resolvedLocal.endsWith("/") ? input.resolvedLocal.slice(0, -1) : input.resolvedLocal;
|
|
98
|
-
return `${origin}/${path}`;
|
|
99
|
-
}
|
|
100
|
-
const base = input.base === void 0 || input.base === "/" ? "" : input.base.replace(TRAILING_SLASH, "");
|
|
101
|
-
if (input.address === void 0 || typeof input.address === "string") {
|
|
102
|
-
return `http://localhost:5173${base}/${path}`;
|
|
103
|
-
}
|
|
104
|
-
const host = input.address.address === "::" || input.address.address === "0.0.0.0" ? "localhost" : input.address.address;
|
|
105
|
-
const bracketed = host.includes(":") ? `[${host}]` : host;
|
|
106
|
-
return `http://${bracketed}:${String(input.address.port)}${base}/${path}`;
|
|
107
|
-
};
|
|
108
|
-
const pathnameOf = (url) => {
|
|
109
|
-
try {
|
|
110
|
-
return new URL(url, "http://localhost").pathname.replace(TRAILING_SLASH, "");
|
|
111
|
-
} catch {
|
|
112
|
-
return url;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const createStudioHandler = (server, isNonLoopbackBind) => {
|
|
116
|
-
let assets;
|
|
117
|
-
let assetsStamp;
|
|
118
|
-
let html;
|
|
119
|
-
const projectRoot = server.config.root ?? process.cwd();
|
|
120
|
-
const serveStaticAsset = (pathname, request, response) => {
|
|
121
|
-
const stamp = studioAssetsStamp();
|
|
122
|
-
if (assets === void 0 || stamp !== assetsStamp) {
|
|
123
|
-
assets = loadStudioAssets(server.config.logger);
|
|
124
|
-
assetsStamp = stamp;
|
|
125
|
-
}
|
|
126
|
-
if (assets === void 0) {
|
|
127
|
-
response.statusCode = 501;
|
|
128
|
-
response.setHeader("Content-Type", "text/plain");
|
|
129
|
-
response.end("Lunora studio assets not found — install and build @lunora/studio.");
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const isStyle = pathname === STUDIO_STYLE_PATH;
|
|
133
|
-
const fileName = pathname.slice(pathname.lastIndexOf("/") + 1);
|
|
134
|
-
const etag = stamp === void 0 ? void 0 : `W/"${fileName}-${String(stamp)}"`;
|
|
135
|
-
response.setHeader("Cache-Control", "no-cache");
|
|
136
|
-
if (etag !== void 0) {
|
|
137
|
-
response.setHeader("ETag", etag);
|
|
138
|
-
if (headerValue(request.headers?.["if-none-match"]) === etag.toLowerCase()) {
|
|
139
|
-
response.statusCode = 304;
|
|
140
|
-
response.end();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (isStyle) {
|
|
145
|
-
sendOk(response, assets.styles, "text/css; charset=utf-8");
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const bytes = readStandaloneAsset(fileName);
|
|
149
|
-
if (bytes === void 0) {
|
|
150
|
-
response.statusCode = 404;
|
|
151
|
-
response.setHeader("Content-Type", "text/plain");
|
|
152
|
-
response.end("Not found");
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
sendOk(response, bytes, assetContentType(fileName));
|
|
156
|
-
};
|
|
157
|
-
return (request, response, next) => {
|
|
158
|
-
const pathname = pathnameOf(request.url ?? "");
|
|
159
|
-
if (pathname !== STUDIO_PATH && !pathname.startsWith(`${STUDIO_PATH}/`)) {
|
|
160
|
-
next();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (isNonLoopbackBind || transportRejectionReason(request) !== void 0) {
|
|
164
|
-
response.statusCode = 403;
|
|
165
|
-
response.setHeader("Content-Type", "text/plain");
|
|
166
|
-
response.end("Lunora studio is only available on loopback hosts in dev.");
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (STATE_CHANGING_ENDPOINTS.has(pathname)) {
|
|
170
|
-
const csrf = csrfRejectionReason(request);
|
|
171
|
-
if (csrf !== void 0) {
|
|
172
|
-
response.statusCode = 403;
|
|
173
|
-
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
174
|
-
response.end(JSON.stringify({ error: csrf, ok: false }));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const jsonHandler = JSON_ENDPOINT_HANDLERS[pathname];
|
|
179
|
-
if (jsonHandler !== void 0) {
|
|
180
|
-
serveJsonHandler(request, response, jsonHandler, projectRoot);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (pathname === STUDIO_STYLE_PATH || isStandaloneModulePath(pathname)) {
|
|
184
|
-
serveStaticAsset(pathname, request, response);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
html ??= renderStudioHtml({
|
|
188
|
-
adminToken: resolveAdminToken(projectRoot),
|
|
189
|
-
basePath: STUDIO_PATH,
|
|
190
|
-
// Loopback-only dev route (it 403s on a non-loopback bind), so the
|
|
191
|
-
// developer owns the data — let them edit rows, run-as a user, and edit
|
|
192
|
-
// the schema by default.
|
|
193
|
-
dataEditable: true,
|
|
194
|
-
rulesInstalled: detectAgentRules(projectRoot).installed,
|
|
195
|
-
runAsIdentity: true,
|
|
196
|
-
schemaEditable: true,
|
|
197
|
-
scriptSrc: STUDIO_SCRIPT_PATH,
|
|
198
|
-
styleHref: STUDIO_STYLE_PATH
|
|
199
|
-
});
|
|
200
|
-
sendOk(response, html, "text/html; charset=utf-8");
|
|
201
|
-
};
|
|
202
|
-
};
|
|
203
|
-
const studioPlugin = () => {
|
|
204
|
-
return {
|
|
205
|
-
apply: "serve",
|
|
206
|
-
configureServer(server) {
|
|
207
|
-
const configuredHost = server.config.server?.host;
|
|
208
|
-
const isNonLoopbackBind = configuredHost !== void 0 && configuredHost !== false && configuredHost !== "localhost" && configuredHost !== "127.0.0.1" && configuredHost !== "::1";
|
|
209
|
-
server.middlewares.use(createStudioHandler(server, isNonLoopbackBind));
|
|
210
|
-
return () => {
|
|
211
|
-
const announce = () => {
|
|
212
|
-
const url = buildStudioUrl({
|
|
213
|
-
address: server.httpServer?.address() ?? void 0,
|
|
214
|
-
base: server.config.base,
|
|
215
|
-
resolvedLocal: server.resolvedUrls?.local[0]
|
|
216
|
-
});
|
|
217
|
-
server.config.logger.info(` \x1B[32m➜\x1B[39m \x1B[1mLunora\x1B[22m: \x1B[36m${url}\x1B[39m`);
|
|
218
|
-
};
|
|
219
|
-
if (typeof server.printUrls === "function") {
|
|
220
|
-
const printUrls = server.printUrls.bind(server);
|
|
221
|
-
server.printUrls = () => {
|
|
222
|
-
printUrls();
|
|
223
|
-
announce();
|
|
224
|
-
};
|
|
225
|
-
} else if (server.httpServer?.listening === true) {
|
|
226
|
-
announce();
|
|
227
|
-
} else {
|
|
228
|
-
server.httpServer?.once("listening", announce);
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
},
|
|
232
|
-
name: "lunora:studio"
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
export { STUDIO_PATH, STUDIO_SCRIPT_PATH, STUDIO_STYLE_PATH, buildStudioUrl, studioPlugin };
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const WORKER_STARTUP_HINT = [
|
|
2
|
-
"",
|
|
3
|
-
" ┌─ Lunora ──────────────────────────────────────────────────────────────",
|
|
4
|
-
" │ Your Worker entry threw while loading, so the dev server couldn't read",
|
|
5
|
-
" │ its exports. The TypeError above comes from inside the Cloudflare",
|
|
6
|
-
" │ runtime and hides the file that actually failed. It is almost always:",
|
|
7
|
-
" │",
|
|
8
|
-
" │ • A circular import in lunora/. A query/mutation/action module ran at",
|
|
9
|
-
" │ the top level before `v`/`query`/`mutation` finished initializing,",
|
|
10
|
-
" │ so they were `undefined` (hence `reading 'string'`/`'id'`/…).",
|
|
11
|
-
" │ → Import `v`/`query`/`mutation` only from `lunora/_generated/server`,",
|
|
12
|
-
" │ and don't import one lunora/ function module from another at the",
|
|
13
|
-
" │ top level.",
|
|
14
|
-
" │",
|
|
15
|
-
" │ • Stale or missing generated files.",
|
|
16
|
-
" │ → Re-run `lunora codegen`, then restart the dev server.",
|
|
17
|
-
" │",
|
|
18
|
-
" │ Tip: check the lunora/ files you edited most recently — the throw is at",
|
|
19
|
-
" │ their module top level.",
|
|
20
|
-
" └───────────────────────────────────────────────────────────────────────"
|
|
21
|
-
].join("\n");
|
|
22
|
-
const RUNNER_WORKER_RE = /runner-worker[/\\]index\.js/u;
|
|
23
|
-
const EXPORT_TYPES_PROBE_RE = /getWorkerEntryExportTypes/u;
|
|
24
|
-
const isWorkerEntryEvalError = (error) => {
|
|
25
|
-
if (!(error instanceof Error)) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
const haystack = `${error.stack ?? ""}
|
|
29
|
-
${error.message}`;
|
|
30
|
-
return RUNNER_WORKER_RE.test(haystack) || EXPORT_TYPES_PROBE_RE.test(haystack);
|
|
31
|
-
};
|
|
32
|
-
const HINTED = /* @__PURE__ */ Symbol.for("lunora.workerStartupHintApplied");
|
|
33
|
-
const augmentWorkerStartupError = (error) => {
|
|
34
|
-
if (!isWorkerEntryEvalError(error)) {
|
|
35
|
-
return error;
|
|
36
|
-
}
|
|
37
|
-
const flagged = error;
|
|
38
|
-
if (flagged[HINTED]) {
|
|
39
|
-
return error;
|
|
40
|
-
}
|
|
41
|
-
if (Object.isFrozen(flagged) || Object.isSealed(flagged)) {
|
|
42
|
-
return error;
|
|
43
|
-
}
|
|
44
|
-
flagged[HINTED] = true;
|
|
45
|
-
flagged.message = `${flagged.message}
|
|
46
|
-
${WORKER_STARTUP_HINT}`;
|
|
47
|
-
if (typeof flagged.stack === "string") {
|
|
48
|
-
flagged.stack = `${flagged.stack}
|
|
49
|
-
${WORKER_STARTUP_HINT}`;
|
|
50
|
-
}
|
|
51
|
-
return error;
|
|
52
|
-
};
|
|
53
|
-
const wrapHookFunction = (function_) => async (...arguments_) => {
|
|
54
|
-
try {
|
|
55
|
-
return await function_(...arguments_);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
throw augmentWorkerStartupError(error);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const wrapHook = (hook) => {
|
|
61
|
-
if (typeof hook === "function") {
|
|
62
|
-
return wrapHookFunction(hook);
|
|
63
|
-
}
|
|
64
|
-
if (hook !== null && typeof hook === "object" && "handler" in hook && typeof hook["handler"] === "function") {
|
|
65
|
-
return { ...hook, handler: wrapHookFunction(hook.handler) };
|
|
66
|
-
}
|
|
67
|
-
return hook;
|
|
68
|
-
};
|
|
69
|
-
const WRAPPED_HOOKS = ["configureServer", "buildStart"];
|
|
70
|
-
const withWorkerStartupHint = (plugins) => plugins.map((plugin) => {
|
|
71
|
-
let next = plugin;
|
|
72
|
-
for (const hookName of WRAPPED_HOOKS) {
|
|
73
|
-
const hook = plugin[hookName];
|
|
74
|
-
if (hook !== void 0) {
|
|
75
|
-
next = { ...next, [hookName]: wrapHook(hook) };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return next;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export { WORKER_STARTUP_HINT, augmentWorkerStartupError, isWorkerEntryEvalError, withWorkerStartupHint };
|
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join, resolve, sep, basename } from 'node:path';
|
|
3
|
-
import { createCodegenProject, refreshCodegenProject, runCodegen, CodegenDiagnosticError } from '@lunora/codegen';
|
|
4
|
-
import { findWranglerFile, LUNORA_CONFIG_FILE, readWranglerJsonc, WRANGLER_FILES, collectWranglerSecretVariables, inferLunoraBindings, reconcileWranglerBindings, reconcileWranglerCrons, reconcileWranglerCompatibilityDate } from '@lunora/config';
|
|
5
|
-
import { isRunnableDevEnvironment } from 'vite';
|
|
6
|
-
import LUNORA_API_UPDATED_EVENT from './LUNORA_API_UPDATED_EVENT-BrkWk3rr.mjs';
|
|
7
|
-
import { L as LUNORA_TAG, a as advisoryLine } from './log-BjO9EWah.mjs';
|
|
8
|
-
import { r as runPendingClose, a as registerDevServerClose } from './server-close-DKDp7YTU.mjs';
|
|
9
|
-
|
|
10
|
-
const fingerprintJsonc = (filePath, normalize) => {
|
|
11
|
-
try {
|
|
12
|
-
const { parsed, text } = readWranglerJsonc(filePath);
|
|
13
|
-
if (parsed === void 0) {
|
|
14
|
-
return `raw:${text}`;
|
|
15
|
-
}
|
|
16
|
-
return JSON.stringify(normalize ? normalize(parsed) : parsed);
|
|
17
|
-
} catch {
|
|
18
|
-
return "unreadable";
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const stripCodegenOwnedCrons = (parsed) => {
|
|
22
|
-
const clone = { ...parsed };
|
|
23
|
-
const { triggers } = clone;
|
|
24
|
-
if (triggers !== null && typeof triggers === "object") {
|
|
25
|
-
const restTriggers = { ...triggers };
|
|
26
|
-
delete restTriggers.crons;
|
|
27
|
-
if (Object.keys(restTriggers).length > 0) {
|
|
28
|
-
clone.triggers = restTriggers;
|
|
29
|
-
} else {
|
|
30
|
-
delete clone.triggers;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return clone;
|
|
34
|
-
};
|
|
35
|
-
const computeConfigFingerprint = (projectRoot) => {
|
|
36
|
-
const wranglerFile = findWranglerFile(projectRoot);
|
|
37
|
-
const wranglerPart = wranglerFile === void 0 ? "absent" : fingerprintJsonc(wranglerFile, stripCodegenOwnedCrons);
|
|
38
|
-
const lunoraConfigPath = join(projectRoot, LUNORA_CONFIG_FILE);
|
|
39
|
-
const lunoraPart = existsSync(lunoraConfigPath) ? fingerprintJsonc(lunoraConfigPath) : "absent";
|
|
40
|
-
return `${wranglerPart}\0${lunoraPart}`;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const DEBOUNCE_MS = 100;
|
|
44
|
-
const TSCONFIG_VARIANT_RE = /[/\\]tsconfig\..+\.json$/u;
|
|
45
|
-
const formatExportGapOverlay = (gaps) => {
|
|
46
|
-
const lines = gaps.map((gap) => ` • ${gap.kind} "${gap.exportName}" — class ${gap.className} is not exported by your worker entry.`);
|
|
47
|
-
const hints = [...new Set(gaps.map((gap) => gap.module))].map((module) => ` export * from "./lunora/_generated/${module}";`);
|
|
48
|
-
return [
|
|
49
|
-
`[lunora] ${String(gaps.length)} declared ${gaps.length === 1 ? "binding is" : "bindings are"} not exported by your worker entry — \`wrangler deploy\` will fail.`,
|
|
50
|
-
...lines,
|
|
51
|
-
"",
|
|
52
|
-
"Add to your worker entry:",
|
|
53
|
-
...hints
|
|
54
|
-
].join("\n");
|
|
55
|
-
};
|
|
56
|
-
const reconcileBindingsSafely = async (options, logger, onExportGaps) => {
|
|
57
|
-
try {
|
|
58
|
-
const inferred = await inferLunoraBindings({ projectRoot: options.projectRoot, schemaDir: options.schemaDir });
|
|
59
|
-
const reconciled = reconcileWranglerBindings(options.projectRoot, inferred);
|
|
60
|
-
if (reconciled.changed) {
|
|
61
|
-
logger.info?.(`${LUNORA_TAG} inferred bindings → ${reconciled.added.join(", ")} (written to ${reconciled.wranglerPath ?? "wrangler.jsonc"})`);
|
|
62
|
-
}
|
|
63
|
-
for (const warning of reconciled.warnings) {
|
|
64
|
-
logger.warn(`${LUNORA_TAG} ${warning}`);
|
|
65
|
-
}
|
|
66
|
-
if (reconciled.exportGaps.length > 0) {
|
|
67
|
-
onExportGaps?.(reconciled.exportGaps);
|
|
68
|
-
}
|
|
69
|
-
} catch (error) {
|
|
70
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
-
logger.warn(`${LUNORA_TAG} binding inference skipped: ${message}`);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
const reconcileWranglerExtras = (projectRoot, cronTriggers, logger) => {
|
|
75
|
-
try {
|
|
76
|
-
const reconciled = reconcileWranglerCrons(projectRoot, cronTriggers);
|
|
77
|
-
if (reconciled.changed) {
|
|
78
|
-
logger.info?.(`${LUNORA_TAG} synced ${cronTriggers.length.toFixed(0)} cron trigger(s) into ${reconciled.wranglerPath ?? "wrangler.jsonc"}`);
|
|
79
|
-
}
|
|
80
|
-
} catch (cronError) {
|
|
81
|
-
const message = cronError instanceof Error ? cronError.message : String(cronError);
|
|
82
|
-
logger.warn(`${LUNORA_TAG} cron trigger sync skipped: ${message}`);
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
const reconciled = reconcileWranglerCompatibilityDate(projectRoot);
|
|
86
|
-
if (reconciled.changed) {
|
|
87
|
-
logger.info?.(
|
|
88
|
-
`${LUNORA_TAG} bumped compatibility_date to ${reconciled.date ?? "unknown"} (Workers Cache enabled) → ${reconciled.wranglerPath ?? "wrangler.jsonc"}`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
} catch (dateError) {
|
|
92
|
-
const message = dateError instanceof Error ? dateError.message : String(dateError);
|
|
93
|
-
logger.warn(`${LUNORA_TAG} compatibility date sync skipped: ${message}`);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const runCodegenSafely = (options, logger, overlay, project) => {
|
|
97
|
-
const schemaPath = join(options.projectRoot, options.schemaDir, "schema.ts");
|
|
98
|
-
if (!existsSync(schemaPath)) {
|
|
99
|
-
logger.warn(`${LUNORA_TAG} schema.ts not found at ${schemaPath} — codegen skipped`);
|
|
100
|
-
return void 0;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
const result = runCodegen({
|
|
104
|
-
apiSpec: options.apiSpec,
|
|
105
|
-
lunoraDirectory: options.schemaDir,
|
|
106
|
-
project,
|
|
107
|
-
projectRoot: options.projectRoot,
|
|
108
|
-
wranglerVariables: collectWranglerSecretVariables(options.projectRoot)
|
|
109
|
-
});
|
|
110
|
-
reconcileWranglerExtras(options.projectRoot, result.cronTriggers, logger);
|
|
111
|
-
for (const advisory of result.advisories) {
|
|
112
|
-
const line = advisoryLine(advisory.level, advisory.name, advisory.detail, advisory.remediation);
|
|
113
|
-
if (advisory.level === "ERROR") {
|
|
114
|
-
logger.error(line);
|
|
115
|
-
} else {
|
|
116
|
-
logger.warn(line);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return resolve(result.outputDirectory);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
-
logger.error(`${LUNORA_TAG} codegen failed: ${message}`);
|
|
123
|
-
overlay?.onError(error, message);
|
|
124
|
-
return void 0;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
const notifyEnvironmentsAfterCodegen = (server, changedFile, clearErrorOverlay) => {
|
|
128
|
-
let clientEnvironment;
|
|
129
|
-
for (const [name, environment] of Object.entries(server.environments)) {
|
|
130
|
-
if (name === "client") {
|
|
131
|
-
clientEnvironment = environment;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (isRunnableDevEnvironment(environment)) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
environment.hot.send({ path: "*", triggeredBy: changedFile, type: "full-reload" });
|
|
138
|
-
}
|
|
139
|
-
if (clientEnvironment === void 0) {
|
|
140
|
-
server.hot.send({ type: "full-reload" });
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (clearErrorOverlay) {
|
|
144
|
-
clientEnvironment.hot.send({ type: "full-reload" });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
clientEnvironment.hot.send({ event: LUNORA_API_UPDATED_EVENT, type: "custom" });
|
|
148
|
-
};
|
|
149
|
-
const codegenPlugin = (options) => {
|
|
150
|
-
const absoluteSchemaDirectory = resolve(options.projectRoot, options.schemaDir);
|
|
151
|
-
let absoluteGeneratedDirectory = resolve(options.projectRoot, options.generatedDir);
|
|
152
|
-
let devServer;
|
|
153
|
-
const pendingMiddlewareTeardowns = /* @__PURE__ */ new Map();
|
|
154
|
-
let configFingerprint;
|
|
155
|
-
let configBaselineSettled = false;
|
|
156
|
-
let restartInFlight = false;
|
|
157
|
-
return {
|
|
158
|
-
async buildStart() {
|
|
159
|
-
const logger = {
|
|
160
|
-
error: (message) => {
|
|
161
|
-
console.error(message);
|
|
162
|
-
},
|
|
163
|
-
info: (message) => {
|
|
164
|
-
console.info(message);
|
|
165
|
-
},
|
|
166
|
-
warn: (message) => {
|
|
167
|
-
console.warn(message);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
const outputDirectory = runCodegenSafely(options, logger);
|
|
171
|
-
if (outputDirectory !== void 0) {
|
|
172
|
-
absoluteGeneratedDirectory = outputDirectory;
|
|
173
|
-
}
|
|
174
|
-
await reconcileBindingsSafely(options, logger, (gaps) => {
|
|
175
|
-
devServer?.hot.send({
|
|
176
|
-
err: { loc: void 0, message: formatExportGapOverlay(gaps), stack: "" },
|
|
177
|
-
type: "error"
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
if (devServer !== void 0) {
|
|
181
|
-
configFingerprint = computeConfigFingerprint(options.projectRoot);
|
|
182
|
-
configBaselineSettled = true;
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
configureServer(server) {
|
|
186
|
-
devServer = server;
|
|
187
|
-
server.watcher.add(absoluteSchemaDirectory);
|
|
188
|
-
configFingerprint = computeConfigFingerprint(options.projectRoot);
|
|
189
|
-
configBaselineSettled = false;
|
|
190
|
-
const serverLogger = {
|
|
191
|
-
error: (message) => {
|
|
192
|
-
server.config.logger.error(message);
|
|
193
|
-
},
|
|
194
|
-
info: (message) => {
|
|
195
|
-
server.config.logger.info(message);
|
|
196
|
-
},
|
|
197
|
-
warn: (message) => {
|
|
198
|
-
server.config.logger.warn(message);
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
let hadErrorOverlay = false;
|
|
202
|
-
const overlay = {
|
|
203
|
-
onError(error, message) {
|
|
204
|
-
hadErrorOverlay = true;
|
|
205
|
-
const loc = error instanceof CodegenDiagnosticError ? { column: error.column, file: error.file, line: error.line } : void 0;
|
|
206
|
-
const overlayMessage = loc === void 0 ? `[lunora] codegen failed: ${message}
|
|
207
|
-
(see terminal for full stack trace and file location)` : `[lunora] codegen failed: ${message}`;
|
|
208
|
-
devServer?.hot.send({
|
|
209
|
-
err: {
|
|
210
|
-
loc,
|
|
211
|
-
message: overlayMessage,
|
|
212
|
-
stack: error instanceof Error ? error.stack ?? "" : ""
|
|
213
|
-
},
|
|
214
|
-
type: "error"
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
const isInside = (path, directory) => path === directory || path.startsWith(directory + sep);
|
|
219
|
-
let closed = false;
|
|
220
|
-
let debounceTimer;
|
|
221
|
-
let cachedProject;
|
|
222
|
-
const invalidateGenerated = () => {
|
|
223
|
-
for (const environment of Object.values(server.environments)) {
|
|
224
|
-
const graph = environment.moduleGraph;
|
|
225
|
-
for (const moduleEntry of graph.idToModuleMap.values()) {
|
|
226
|
-
if (moduleEntry.id && isInside(moduleEntry.id, absoluteGeneratedDirectory)) {
|
|
227
|
-
graph.invalidateModule(moduleEntry);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
const onChange = (file) => {
|
|
233
|
-
const normalized = resolve(file);
|
|
234
|
-
if (!isInside(normalized, absoluteSchemaDirectory)) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (isInside(normalized, absoluteGeneratedDirectory)) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (normalized.endsWith(`${sep}tsconfig.json`) || TSCONFIG_VARIANT_RE.test(normalized)) {
|
|
241
|
-
cachedProject = void 0;
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (!normalized.endsWith(".ts")) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (normalized.includes(`${sep}__tests__${sep}`) || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (debounceTimer) {
|
|
251
|
-
clearTimeout(debounceTimer);
|
|
252
|
-
}
|
|
253
|
-
debounceTimer = setTimeout(() => {
|
|
254
|
-
debounceTimer = void 0;
|
|
255
|
-
if (closed) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (cachedProject === void 0) {
|
|
259
|
-
cachedProject = createCodegenProject(absoluteSchemaDirectory);
|
|
260
|
-
} else {
|
|
261
|
-
refreshCodegenProject(cachedProject, absoluteSchemaDirectory);
|
|
262
|
-
}
|
|
263
|
-
const outputDirectory = runCodegenSafely(options, serverLogger, overlay, cachedProject);
|
|
264
|
-
if (outputDirectory === void 0) {
|
|
265
|
-
cachedProject = void 0;
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
absoluteGeneratedDirectory = outputDirectory;
|
|
269
|
-
invalidateGenerated();
|
|
270
|
-
notifyEnvironmentsAfterCodegen(server, normalized, hadErrorOverlay);
|
|
271
|
-
hadErrorOverlay = false;
|
|
272
|
-
}, DEBOUNCE_MS);
|
|
273
|
-
};
|
|
274
|
-
server.watcher.on("add", onChange);
|
|
275
|
-
server.watcher.on("change", onChange);
|
|
276
|
-
server.watcher.on("unlink", onChange);
|
|
277
|
-
const configWatchPaths = /* @__PURE__ */ new Set([
|
|
278
|
-
...WRANGLER_FILES.map((name) => resolve(options.projectRoot, name)),
|
|
279
|
-
resolve(options.projectRoot, LUNORA_CONFIG_FILE)
|
|
280
|
-
]);
|
|
281
|
-
for (const configPath of configWatchPaths) {
|
|
282
|
-
server.watcher.add(configPath);
|
|
283
|
-
}
|
|
284
|
-
const onConfigChange = (file) => {
|
|
285
|
-
if (!configWatchPaths.has(resolve(file))) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (restartInFlight) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const nextFingerprint = computeConfigFingerprint(options.projectRoot);
|
|
292
|
-
if (!configBaselineSettled) {
|
|
293
|
-
configFingerprint = nextFingerprint;
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (configFingerprint === void 0) {
|
|
297
|
-
configFingerprint = nextFingerprint;
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (nextFingerprint === configFingerprint) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
configFingerprint = nextFingerprint;
|
|
304
|
-
restartInFlight = true;
|
|
305
|
-
server.config.logger.info(`${LUNORA_TAG} ${basename(resolve(file))} changed — restarting dev server`);
|
|
306
|
-
Promise.resolve(server.restart()).finally(() => {
|
|
307
|
-
restartInFlight = false;
|
|
308
|
-
}).catch((error) => {
|
|
309
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
310
|
-
server.config.logger.error(
|
|
311
|
-
`${LUNORA_TAG} dev server restart failed: ${message} — keeping the running server; re-save your config to retry.`
|
|
312
|
-
);
|
|
313
|
-
server.hot.send({ err: { loc: void 0, message: `[lunora] dev server restart failed: ${message}`, stack: "" }, type: "error" });
|
|
314
|
-
});
|
|
315
|
-
};
|
|
316
|
-
server.watcher.on("add", onConfigChange);
|
|
317
|
-
server.watcher.on("change", onConfigChange);
|
|
318
|
-
server.watcher.on("unlink", onConfigChange);
|
|
319
|
-
const teardown = () => {
|
|
320
|
-
if (closed) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
closed = true;
|
|
324
|
-
cachedProject = void 0;
|
|
325
|
-
if (debounceTimer) {
|
|
326
|
-
clearTimeout(debounceTimer);
|
|
327
|
-
debounceTimer = void 0;
|
|
328
|
-
}
|
|
329
|
-
server.watcher.off("add", onChange);
|
|
330
|
-
server.watcher.off("change", onChange);
|
|
331
|
-
server.watcher.off("unlink", onChange);
|
|
332
|
-
server.watcher.off("add", onConfigChange);
|
|
333
|
-
server.watcher.off("change", onConfigChange);
|
|
334
|
-
server.watcher.off("unlink", onConfigChange);
|
|
335
|
-
};
|
|
336
|
-
return () => {
|
|
337
|
-
registerDevServerClose(server, pendingMiddlewareTeardowns, teardown);
|
|
338
|
-
};
|
|
339
|
-
},
|
|
340
|
-
buildEnd() {
|
|
341
|
-
runPendingClose(pendingMiddlewareTeardowns, this.environment);
|
|
342
|
-
},
|
|
343
|
-
name: "lunora:codegen"
|
|
344
|
-
};
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
export { codegenPlugin as default };
|