@lunora/vite 1.0.0-alpha.7 → 1.0.0-alpha.71
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/README.md +15 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +335 -248
- package/dist/index.d.ts +335 -248
- package/dist/index.mjs +44 -25
- package/dist/packem_shared/{CLASS_A_WIRING-CZVcjgKo.mjs → CLASS_A_WIRING-T1RohRZQ.mjs} +22 -8
- package/dist/packem_shared/LUNORA_API_UPDATED_EVENT-BrkWk3rr.mjs +3 -0
- package/dist/packem_shared/{STUDIO_PATH-5ppCdBHa.mjs → STUDIO_PATH-U0eIaW1t.mjs} +32 -6
- package/dist/packem_shared/codegenPlugin-B8c38rky.mjs +347 -0
- package/dist/packem_shared/containerLogsPlugin-DMssU3wb.mjs +50 -0
- package/dist/packem_shared/devStatePlugin-DdjfMrhx.mjs +84 -0
- package/dist/packem_shared/devVariablesPlugin-CDNSnvOP.mjs +19 -0
- package/dist/packem_shared/{logStreamPlugin-CqvZ17kd.mjs → logStreamPlugin-DeFzd_pC.mjs} +30 -10
- package/dist/packem_shared/lunoraSolutionFinder-BKEAiUJP.mjs +17 -0
- package/dist/packem_shared/{planViteRemoteBindings-QN5ncUS1.mjs → planViteRemoteBindings-CRU90Bip.mjs} +21 -5
- package/dist/packem_shared/server-close-DKDp7YTU.mjs +16 -0
- package/dist/packem_shared/{wranglerValidatorPlugin-CEoJEghS.mjs → wranglerValidatorPlugin-C5VWOD7N.mjs} +3 -1
- package/package.json +9 -8
- package/dist/packem_shared/codegenPlugin-MuvbqAP8.mjs +0 -218
- package/dist/packem_shared/devVariablesPlugin-CVjkQay7.mjs +0 -21
- package/dist/packem_shared/reconcileWranglerCrons-PxGwfCp_.mjs +0 -29
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import { cloudflare } from '@cloudflare/vite-plugin';
|
|
2
3
|
import errorOverlayPlugin from '@visulima/vite-overlay';
|
|
3
4
|
import { detectAgentRules, claimAgentRulesHint, AGENT_RULES_HINT, detectFramework } from '@lunora/config';
|
|
4
|
-
export { detectFramework } from '@lunora/config';
|
|
5
|
+
export { detectFramework, reconcileWranglerCrons } from '@lunora/config';
|
|
5
6
|
import { l as lunoraLine } from './packem_shared/log-BjO9EWah.mjs';
|
|
6
|
-
import codegenPlugin from './packem_shared/codegenPlugin-
|
|
7
|
-
import
|
|
7
|
+
import codegenPlugin from './packem_shared/codegenPlugin-B8c38rky.mjs';
|
|
8
|
+
import containerLogsPlugin from './packem_shared/containerLogsPlugin-DMssU3wb.mjs';
|
|
9
|
+
import devStatePlugin from './packem_shared/devStatePlugin-DdjfMrhx.mjs';
|
|
10
|
+
import devVariablesPlugin from './packem_shared/devVariablesPlugin-CDNSnvOP.mjs';
|
|
8
11
|
import { createCommandProbe, withDevWorkerEnv } from './packem_shared/DEV_WORKER_ENV_VALUE-Coo6bgVz.mjs';
|
|
9
12
|
export { DEV_WORKER_ENV_VALUE, DEV_WORKER_ENV_VAR } from './packem_shared/DEV_WORKER_ENV_VALUE-Coo6bgVz.mjs';
|
|
10
|
-
import { frameworkComposePlugin } from './packem_shared/CLASS_A_WIRING-
|
|
11
|
-
export { CLASS_A_WIRING, LUNORA_WORKER_VIRTUAL_ID, buildWorkerEntrySource, isAutoComposable } from './packem_shared/CLASS_A_WIRING-
|
|
12
|
-
import logStreamPlugin from './packem_shared/logStreamPlugin-
|
|
13
|
-
import { planViteRemoteBindings, remoteBindingsCleanupPlugin,
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import { frameworkComposePlugin } from './packem_shared/CLASS_A_WIRING-T1RohRZQ.mjs';
|
|
14
|
+
export { CLASS_A_WIRING, LUNORA_WORKER_VIRTUAL_ID, buildWorkerEntrySource, isAutoComposable } from './packem_shared/CLASS_A_WIRING-T1RohRZQ.mjs';
|
|
15
|
+
import logStreamPlugin from './packem_shared/logStreamPlugin-DeFzd_pC.mjs';
|
|
16
|
+
import { planViteRemoteBindings, remoteBindingsCleanupPlugin, remoteBindingsConfigPlugin } from './packem_shared/planViteRemoteBindings-CRU90Bip.mjs';
|
|
17
|
+
export { withRemoteBindings } from './packem_shared/planViteRemoteBindings-CRU90Bip.mjs';
|
|
18
|
+
import { lunoraSolutionFinders } from './packem_shared/lunoraSolutionFinder-BKEAiUJP.mjs';
|
|
19
|
+
export { lunoraSolutionFinder } from './packem_shared/lunoraSolutionFinder-BKEAiUJP.mjs';
|
|
20
|
+
import { studioPlugin } from './packem_shared/STUDIO_PATH-U0eIaW1t.mjs';
|
|
21
|
+
export { STUDIO_PATH, buildStudioUrl } from './packem_shared/STUDIO_PATH-U0eIaW1t.mjs';
|
|
16
22
|
import { withWorkerStartupHint } from './packem_shared/WORKER_STARTUP_HINT-DhsXUW8k.mjs';
|
|
17
23
|
export { WORKER_STARTUP_HINT, augmentWorkerStartupError, isWorkerEntryEvalError } from './packem_shared/WORKER_STARTUP_HINT-DhsXUW8k.mjs';
|
|
18
|
-
import { wranglerValidatorPlugin } from './packem_shared/wranglerValidatorPlugin-
|
|
19
|
-
export {
|
|
24
|
+
import { wranglerValidatorPlugin } from './packem_shared/wranglerValidatorPlugin-C5VWOD7N.mjs';
|
|
25
|
+
export { default as LUNORA_API_UPDATED_EVENT } from './packem_shared/LUNORA_API_UPDATED_EVENT-BrkWk3rr.mjs';
|
|
20
26
|
|
|
21
27
|
const agentRulesHintPlugin = (options) => {
|
|
22
28
|
return {
|
|
@@ -81,6 +87,19 @@ const frameworkDetectPlugin = (options, context) => {
|
|
|
81
87
|
};
|
|
82
88
|
};
|
|
83
89
|
|
|
90
|
+
const resolveOverlayOption = (overlay) => {
|
|
91
|
+
if (overlay === false) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const userOverlay = overlay === true || overlay === void 0 ? {} : overlay;
|
|
95
|
+
return {
|
|
96
|
+
...userOverlay,
|
|
97
|
+
// After the spread + nullish-coalesce so an explicit `undefined` from the
|
|
98
|
+
// user doesn't erase Lunora's default (the spread would otherwise win).
|
|
99
|
+
forwardedConsoleMethods: userOverlay.forwardedConsoleMethods ?? ["error", "warn"],
|
|
100
|
+
solutionFinders: [...lunoraSolutionFinders, ...userOverlay.solutionFinders ?? []]
|
|
101
|
+
};
|
|
102
|
+
};
|
|
84
103
|
const resolveOptions = (options) => {
|
|
85
104
|
const input = options ?? {};
|
|
86
105
|
const schemaDirectory = input.schemaDir ?? "lunora";
|
|
@@ -92,20 +111,13 @@ const resolveOptions = (options) => {
|
|
|
92
111
|
} else {
|
|
93
112
|
cloudflareOption = input.cloudflare;
|
|
94
113
|
}
|
|
95
|
-
let overlayOption;
|
|
96
|
-
if (input.overlay === false) {
|
|
97
|
-
overlayOption = false;
|
|
98
|
-
} else if (input.overlay === true || input.overlay === void 0) {
|
|
99
|
-
overlayOption = {};
|
|
100
|
-
} else {
|
|
101
|
-
overlayOption = input.overlay;
|
|
102
|
-
}
|
|
103
114
|
return {
|
|
115
|
+
allowUnauthenticatedShardAccess: input.allowUnauthenticatedShardAccess ?? false,
|
|
104
116
|
apiSpec: input.apiSpec ?? "openapi",
|
|
105
117
|
cloudflare: cloudflareOption,
|
|
106
118
|
studio: input.studio ?? true,
|
|
107
119
|
generatedDir: input.generatedDir ?? `${schemaDirectory}/_generated`,
|
|
108
|
-
overlay:
|
|
120
|
+
overlay: resolveOverlayOption(input.overlay),
|
|
109
121
|
projectRoot: input.projectRoot ?? process.cwd(),
|
|
110
122
|
schemaDir: schemaDirectory,
|
|
111
123
|
validateWrangler: input.validateWrangler ?? true
|
|
@@ -128,6 +140,9 @@ const lunora = (options) => {
|
|
|
128
140
|
devVariablesPlugin(resolved),
|
|
129
141
|
codegenPlugin(resolved),
|
|
130
142
|
logStreamPlugin(),
|
|
143
|
+
// Registers the running dev server in `.lunora/dev.json` so
|
|
144
|
+
// `lunora dev --background|stop|status|logs` manage Vite projects too.
|
|
145
|
+
devStatePlugin(resolved),
|
|
131
146
|
agentRulesHintPlugin(resolved)
|
|
132
147
|
];
|
|
133
148
|
if (resolved.studio) {
|
|
@@ -140,15 +155,19 @@ const lunora = (options) => {
|
|
|
140
155
|
plugins.push(errorOverlayPlugin(resolved.overlay));
|
|
141
156
|
}
|
|
142
157
|
if (resolved.cloudflare !== false) {
|
|
158
|
+
plugins.push(containerLogsPlugin(resolved));
|
|
143
159
|
const remotePlan = planViteRemoteBindings({ projectRoot: resolved.projectRoot });
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
const cloudflareOptions = withDevWorkerEnv(resolved.cloudflare, isServe);
|
|
161
|
+
if (remotePlan.enabled) {
|
|
162
|
+
if (remotePlan.configPath !== void 0) {
|
|
163
|
+
plugins.push(remoteBindingsCleanupPlugin(remotePlan.cleanup));
|
|
164
|
+
}
|
|
165
|
+
plugins.push(remoteBindingsConfigPlugin(cloudflareOptions, remotePlan));
|
|
146
166
|
}
|
|
147
|
-
const cloudflareOptions = withRemoteBindings(withDevWorkerEnv(resolved.cloudflare, isServe), isServe, remotePlan);
|
|
148
167
|
plugins.push(...withWorkerStartupHint(cloudflare(cloudflareOptions)));
|
|
149
168
|
}
|
|
150
169
|
return plugins;
|
|
151
170
|
};
|
|
152
|
-
const VERSION = "
|
|
171
|
+
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
153
172
|
|
|
154
|
-
export { VERSION, codegenPlugin, createCommandProbe, devVariablesPlugin, frameworkComposePlugin, logStreamPlugin, lunora, planViteRemoteBindings, remoteBindingsCleanupPlugin, studioPlugin, withDevWorkerEnv,
|
|
173
|
+
export { VERSION, codegenPlugin, containerLogsPlugin, createCommandProbe, devStatePlugin, devVariablesPlugin, frameworkComposePlugin, logStreamPlugin, lunora, lunoraSolutionFinders, planViteRemoteBindings, remoteBindingsCleanupPlugin, remoteBindingsConfigPlugin, resolveOverlayOption, studioPlugin, withDevWorkerEnv, withWorkerStartupHint, wranglerValidatorPlugin };
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve, join } from 'node:path';
|
|
3
|
+
import { LunoraError } from '@lunora/errors';
|
|
3
4
|
|
|
4
5
|
const LUNORA_WORKER_VIRTUAL_ID = "virtual:lunora/worker";
|
|
5
6
|
const RESOLVED_VIRTUAL_PREFIX = "\0";
|
|
6
7
|
const RESOLVED_LUNORA_WORKER_ID = `${RESOLVED_VIRTUAL_PREFIX}${LUNORA_WORKER_VIRTUAL_ID}`;
|
|
8
|
+
const CLIENT_WORKER_STUB = `// @lunora/vite — the composed worker entry is worker-only and unavailable in the client environment.
|
|
9
|
+
export default {};
|
|
10
|
+
`;
|
|
7
11
|
const TRAILING_SLASH = /\/$/;
|
|
8
12
|
const CLASS_A_WIRING = {
|
|
9
13
|
"react-router": {
|
|
@@ -44,23 +48,24 @@ const projectUsesUmbrella = (projectRoot) => {
|
|
|
44
48
|
return false;
|
|
45
49
|
}
|
|
46
50
|
};
|
|
47
|
-
const buildWorkerEntrySource = (framework, generatedImportBase, hasContainers = false, useUmbrella = false) => {
|
|
51
|
+
const buildWorkerEntrySource = (framework, generatedImportBase, hasContainers = false, useUmbrella = false, allowUnauthenticatedShardAccess = false) => {
|
|
48
52
|
const wiring = CLASS_A_WIRING[framework];
|
|
49
53
|
if (wiring === void 0) {
|
|
50
|
-
throw new
|
|
54
|
+
throw new LunoraError("INTERNAL", `[lunora] no class-A worker wiring for framework "${framework}"`);
|
|
51
55
|
}
|
|
56
|
+
const base = generatedImportBase.replaceAll("\\", "/");
|
|
52
57
|
const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
|
|
53
58
|
const containersReexport = hasContainers ? `
|
|
54
|
-
export * from "${
|
|
59
|
+
export * from "${base}/containers";
|
|
55
60
|
` : "";
|
|
56
61
|
return `// Generated by @lunora/vite — class-A worker composition (PLAN4 M2).
|
|
57
62
|
// Do not edit: emitted from the detected framework (${framework}). Point your
|
|
58
63
|
// wrangler \`main\` here (or re-export it) instead of hand-wiring createWorker.
|
|
59
64
|
import { composeWorker } from "${runtimeModule}";
|
|
60
65
|
${wiring.imports}
|
|
61
|
-
import { LUNORA_FUNCTIONS } from "${
|
|
62
|
-
import { openApiSpec } from "${
|
|
63
|
-
import { createShardDO } from "${
|
|
66
|
+
import { LUNORA_FUNCTIONS } from "${base}/functions";
|
|
67
|
+
import { openApiSpec } from "${base}/openapi";
|
|
68
|
+
import { createShardDO } from "${base}/shard";
|
|
64
69
|
|
|
65
70
|
export const ShardDO = createShardDO();
|
|
66
71
|
${containersReexport}
|
|
@@ -69,7 +74,7 @@ let worker;
|
|
|
69
74
|
|
|
70
75
|
export default {
|
|
71
76
|
async fetch(request, env, context) {
|
|
72
|
-
worker ??= composeWorker({
|
|
77
|
+
worker ??= composeWorker({${allowUnauthenticatedShardAccess ? "\n allowUnauthenticatedShardAccess: true," : ""}
|
|
73
78
|
functions: LUNORA_FUNCTIONS,
|
|
74
79
|
httpRouter: ${wiring.handler},
|
|
75
80
|
openApiSpec,
|
|
@@ -88,8 +93,17 @@ const frameworkComposePlugin = (options, context) => {
|
|
|
88
93
|
return {
|
|
89
94
|
load(id) {
|
|
90
95
|
if (id === RESOLVED_LUNORA_WORKER_ID && isWorkerVirtualActive(context) && context.framework !== void 0) {
|
|
96
|
+
if (this.environment.name === "client") {
|
|
97
|
+
return CLIENT_WORKER_STUB;
|
|
98
|
+
}
|
|
91
99
|
const hasContainers = existsSync(join(generatedImportBase, "containers.ts"));
|
|
92
|
-
return buildWorkerEntrySource(
|
|
100
|
+
return buildWorkerEntrySource(
|
|
101
|
+
context.framework.framework,
|
|
102
|
+
generatedImportBase,
|
|
103
|
+
hasContainers,
|
|
104
|
+
useUmbrella,
|
|
105
|
+
options.allowUnauthenticatedShardAccess
|
|
106
|
+
);
|
|
93
107
|
}
|
|
94
108
|
return void 0;
|
|
95
109
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectAgentRules } from '@lunora/config';
|
|
2
|
-
import { handleSeedRequest, handleSchemaEditRequest, handlePolicyScaffoldRequest, SEED_ENDPOINT, SCHEMA_EDIT_ENDPOINT, POLICY_SCAFFOLD_ENDPOINT, serveJsonHandler, renderStudioHtml, resolveAdminToken, studioAssetsStamp, loadStudioAssets } from '@lunora/config/studio-host';
|
|
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
3
|
|
|
4
4
|
const STUDIO_PATH = "/__lunora";
|
|
5
5
|
const STUDIO_SCRIPT_PATH = `${STUDIO_PATH}/studio.js`;
|
|
@@ -47,6 +47,10 @@ const transportRejectionReason = (request) => {
|
|
|
47
47
|
if (host !== void 0 && !LOOPBACK_HOSTS.has(host)) {
|
|
48
48
|
return "Lunora studio rejects a non-localhost Host header in dev.";
|
|
49
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
|
+
}
|
|
50
54
|
return void 0;
|
|
51
55
|
};
|
|
52
56
|
const originRejectionReason = (headers) => {
|
|
@@ -113,7 +117,7 @@ const createStudioHandler = (server, isNonLoopbackBind) => {
|
|
|
113
117
|
let assetsStamp;
|
|
114
118
|
let html;
|
|
115
119
|
const projectRoot = server.config.root ?? process.cwd();
|
|
116
|
-
const serveStaticAsset = (pathname, response) => {
|
|
120
|
+
const serveStaticAsset = (pathname, request, response) => {
|
|
117
121
|
const stamp = studioAssetsStamp();
|
|
118
122
|
if (assets === void 0 || stamp !== assetsStamp) {
|
|
119
123
|
assets = loadStudioAssets(server.config.logger);
|
|
@@ -125,8 +129,30 @@ const createStudioHandler = (server, isNonLoopbackBind) => {
|
|
|
125
129
|
response.end("Lunora studio assets not found — install and build @lunora/studio.");
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
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));
|
|
130
156
|
};
|
|
131
157
|
return (request, response, next) => {
|
|
132
158
|
const pathname = pathnameOf(request.url ?? "");
|
|
@@ -154,8 +180,8 @@ const createStudioHandler = (server, isNonLoopbackBind) => {
|
|
|
154
180
|
serveJsonHandler(request, response, jsonHandler, projectRoot);
|
|
155
181
|
return;
|
|
156
182
|
}
|
|
157
|
-
if (pathname ===
|
|
158
|
-
serveStaticAsset(pathname, response);
|
|
183
|
+
if (pathname === STUDIO_STYLE_PATH || isStandaloneModulePath(pathname)) {
|
|
184
|
+
serveStaticAsset(pathname, request, response);
|
|
159
185
|
return;
|
|
160
186
|
}
|
|
161
187
|
html ??= renderStudioHtml({
|
|
@@ -0,0 +1,347 @@
|
|
|
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 };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { discoverContainerInfo, streamContainerLogs } from '@lunora/config';
|
|
2
|
+
import { l as lunoraLine } from './log-BjO9EWah.mjs';
|
|
3
|
+
|
|
4
|
+
const containerLogsPlugin = (options) => {
|
|
5
|
+
let handle;
|
|
6
|
+
const closeHandle = () => {
|
|
7
|
+
handle?.close();
|
|
8
|
+
handle = void 0;
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
apply: "serve",
|
|
12
|
+
// Fires on `server.close()` for both the normal and middleware-mode dev
|
|
13
|
+
// servers, so the Docker poll loop is torn down even when there is no
|
|
14
|
+
// `httpServer` to listen on (middleware mode).
|
|
15
|
+
buildEnd() {
|
|
16
|
+
closeHandle();
|
|
17
|
+
},
|
|
18
|
+
configureServer(server) {
|
|
19
|
+
if (handle || process.env.LUNORA_CONTAINER_LOGS === "0") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const discovery = discoverContainerInfo(options.projectRoot, options.schemaDir);
|
|
23
|
+
const containers = discovery.containers.map((container) => {
|
|
24
|
+
return { className: container.className, exportName: container.exportName };
|
|
25
|
+
});
|
|
26
|
+
if (containers.length === 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const { logger } = server.config;
|
|
30
|
+
handle = streamContainerLogs({
|
|
31
|
+
containers,
|
|
32
|
+
onLine: (line) => {
|
|
33
|
+
const text = lunoraLine(`container:${line.name} ${line.text}`);
|
|
34
|
+
if (line.level === "error") {
|
|
35
|
+
logger.warn(text);
|
|
36
|
+
} else {
|
|
37
|
+
logger.info(text);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
onUnavailable: (message) => {
|
|
41
|
+
logger.warn(lunoraLine(`container: Docker engine unreachable — container logs unavailable (${message})`));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
server.httpServer?.once("close", closeHandle);
|
|
45
|
+
},
|
|
46
|
+
name: "lunora:container-logs"
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { containerLogsPlugin as default };
|