@lunora/vite 1.0.0-alpha.5 → 1.0.0-alpha.50

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.
@@ -1,9 +1,44 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { resolve, sep, join } from 'node:path';
2
+ import { join, resolve, sep, basename } from 'node:path';
3
3
  import { createCodegenProject, refreshCodegenProject, runCodegen, CodegenDiagnosticError } from '@lunora/codegen';
4
- import { inferLunoraBindings, reconcileWranglerBindings } from '@lunora/config';
4
+ import { findWranglerFile, LUNORA_CONFIG_FILE, readWranglerJsonc, WRANGLER_FILES, collectWranglerSecretVariables, inferLunoraBindings, reconcileWranglerBindings } from '@lunora/config';
5
+ import { isRunnableDevEnvironment } from 'vite';
5
6
  import { reconcileWranglerCrons } from './reconcileWranglerCrons-PxGwfCp_.mjs';
6
- import { L as LUNORA_TAG } from './log-Bgkv6QhO.mjs';
7
+ import LUNORA_API_UPDATED_EVENT from './LUNORA_API_UPDATED_EVENT-BrkWk3rr.mjs';
8
+ import { L as LUNORA_TAG, a as advisoryLine } from './log-BjO9EWah.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
+ };
7
42
 
8
43
  const DEBOUNCE_MS = 100;
9
44
  const TSCONFIG_VARIANT_RE = /[/\\]tsconfig\..+\.json$/u;
@@ -43,7 +78,13 @@ const runCodegenSafely = (options, logger, overlay, project) => {
43
78
  return void 0;
44
79
  }
45
80
  try {
46
- const result = runCodegen({ apiSpec: options.apiSpec, lunoraDirectory: options.schemaDir, project, projectRoot: options.projectRoot });
81
+ const result = runCodegen({
82
+ apiSpec: options.apiSpec,
83
+ lunoraDirectory: options.schemaDir,
84
+ project,
85
+ projectRoot: options.projectRoot,
86
+ wranglerVariables: collectWranglerSecretVariables(options.projectRoot)
87
+ });
47
88
  try {
48
89
  const reconciled = reconcileWranglerCrons(options.projectRoot, result.cronTriggers);
49
90
  if (reconciled.changed) {
@@ -56,7 +97,12 @@ const runCodegenSafely = (options, logger, overlay, project) => {
56
97
  logger.warn(`${LUNORA_TAG} cron trigger sync skipped: ${message}`);
57
98
  }
58
99
  for (const advisory of result.advisories) {
59
- logger.warn(`${LUNORA_TAG} schema advisory [${advisory.level}] ${advisory.name}: ${advisory.detail} — ${advisory.remediation}`);
100
+ const line = advisoryLine(advisory.level, advisory.name, advisory.detail, advisory.remediation);
101
+ if (advisory.level === "ERROR") {
102
+ logger.error(line);
103
+ } else {
104
+ logger.warn(line);
105
+ }
60
106
  }
61
107
  return resolve(result.outputDirectory);
62
108
  } catch (error) {
@@ -66,11 +112,36 @@ const runCodegenSafely = (options, logger, overlay, project) => {
66
112
  return void 0;
67
113
  }
68
114
  };
115
+ const notifyEnvironmentsAfterCodegen = (server, changedFile, clearErrorOverlay) => {
116
+ let clientEnvironment;
117
+ for (const [name, environment] of Object.entries(server.environments)) {
118
+ if (name === "client") {
119
+ clientEnvironment = environment;
120
+ continue;
121
+ }
122
+ if (isRunnableDevEnvironment(environment)) {
123
+ continue;
124
+ }
125
+ environment.hot.send({ path: "*", triggeredBy: changedFile, type: "full-reload" });
126
+ }
127
+ if (clientEnvironment === void 0) {
128
+ server.hot.send({ type: "full-reload" });
129
+ return;
130
+ }
131
+ if (clearErrorOverlay) {
132
+ clientEnvironment.hot.send({ type: "full-reload" });
133
+ return;
134
+ }
135
+ clientEnvironment.hot.send({ event: LUNORA_API_UPDATED_EVENT, type: "custom" });
136
+ };
69
137
  const codegenPlugin = (options) => {
70
138
  const absoluteSchemaDirectory = resolve(options.projectRoot, options.schemaDir);
71
139
  let absoluteGeneratedDirectory = resolve(options.projectRoot, options.generatedDir);
72
140
  let debounceTimer;
73
141
  let devServer;
142
+ let configFingerprint;
143
+ let configBaselineSettled = false;
144
+ let restartInFlight = false;
74
145
  return {
75
146
  async buildStart() {
76
147
  const logger = {
@@ -94,10 +165,16 @@ const codegenPlugin = (options) => {
94
165
  type: "error"
95
166
  });
96
167
  });
168
+ if (devServer !== void 0) {
169
+ configFingerprint = computeConfigFingerprint(options.projectRoot);
170
+ configBaselineSettled = true;
171
+ }
97
172
  },
98
173
  configureServer(server) {
99
174
  devServer = server;
100
175
  server.watcher.add(absoluteSchemaDirectory);
176
+ configFingerprint = computeConfigFingerprint(options.projectRoot);
177
+ configBaselineSettled = false;
101
178
  const serverLogger = {
102
179
  error: (message) => {
103
180
  server.config.logger.error(message);
@@ -109,8 +186,10 @@ const codegenPlugin = (options) => {
109
186
  server.config.logger.warn(message);
110
187
  }
111
188
  };
189
+ let hadErrorOverlay = false;
112
190
  const overlay = {
113
191
  onError(error, message) {
192
+ hadErrorOverlay = true;
114
193
  const loc = error instanceof CodegenDiagnosticError ? { column: error.column, file: error.file, line: error.line } : void 0;
115
194
  const overlayMessage = loc === void 0 ? `[lunora] codegen failed: ${message}
116
195
  (see terminal for full stack trace and file location)` : `[lunora] codegen failed: ${message}`;
@@ -128,19 +207,8 @@ const codegenPlugin = (options) => {
128
207
  let closed = false;
129
208
  let cachedProject;
130
209
  const invalidateGenerated = () => {
131
- const environments = server.environments;
132
- const graphs = [];
133
- if (environments !== void 0) {
134
- for (const environment of Object.values(environments)) {
135
- if (environment.moduleGraph !== void 0) {
136
- graphs.push(environment.moduleGraph);
137
- }
138
- }
139
- }
140
- if (graphs.length === 0) {
141
- graphs.push(server.moduleGraph);
142
- }
143
- for (const graph of graphs) {
210
+ for (const environment of Object.values(server.environments)) {
211
+ const graph = environment.moduleGraph;
144
212
  for (const moduleEntry of graph.idToModuleMap.values()) {
145
213
  if (moduleEntry.id && isInside(moduleEntry.id, absoluteGeneratedDirectory)) {
146
214
  graph.invalidateModule(moduleEntry);
@@ -186,12 +254,55 @@ const codegenPlugin = (options) => {
186
254
  }
187
255
  absoluteGeneratedDirectory = outputDirectory;
188
256
  invalidateGenerated();
189
- server.hot.send({ type: "full-reload" });
257
+ notifyEnvironmentsAfterCodegen(server, normalized, hadErrorOverlay);
258
+ hadErrorOverlay = false;
190
259
  }, DEBOUNCE_MS);
191
260
  };
192
261
  server.watcher.on("add", onChange);
193
262
  server.watcher.on("change", onChange);
194
263
  server.watcher.on("unlink", onChange);
264
+ const configWatchPaths = /* @__PURE__ */ new Set([
265
+ ...WRANGLER_FILES.map((name) => resolve(options.projectRoot, name)),
266
+ resolve(options.projectRoot, LUNORA_CONFIG_FILE)
267
+ ]);
268
+ for (const configPath of configWatchPaths) {
269
+ server.watcher.add(configPath);
270
+ }
271
+ const onConfigChange = (file) => {
272
+ if (!configWatchPaths.has(resolve(file))) {
273
+ return;
274
+ }
275
+ if (restartInFlight) {
276
+ return;
277
+ }
278
+ const nextFingerprint = computeConfigFingerprint(options.projectRoot);
279
+ if (!configBaselineSettled) {
280
+ configFingerprint = nextFingerprint;
281
+ return;
282
+ }
283
+ if (configFingerprint === void 0) {
284
+ configFingerprint = nextFingerprint;
285
+ return;
286
+ }
287
+ if (nextFingerprint === configFingerprint) {
288
+ return;
289
+ }
290
+ configFingerprint = nextFingerprint;
291
+ restartInFlight = true;
292
+ server.config.logger.info(`${LUNORA_TAG} ${basename(resolve(file))} changed — restarting dev server`);
293
+ Promise.resolve(server.restart()).finally(() => {
294
+ restartInFlight = false;
295
+ }).catch((error) => {
296
+ const message = error instanceof Error ? error.message : String(error);
297
+ server.config.logger.error(
298
+ `${LUNORA_TAG} dev server restart failed: ${message} — keeping the running server; re-save your config to retry.`
299
+ );
300
+ server.hot.send({ err: { loc: void 0, message: `[lunora] dev server restart failed: ${message}`, stack: "" }, type: "error" });
301
+ });
302
+ };
303
+ server.watcher.on("add", onConfigChange);
304
+ server.watcher.on("change", onConfigChange);
305
+ server.watcher.on("unlink", onConfigChange);
195
306
  return () => {
196
307
  server.httpServer?.once("close", () => {
197
308
  closed = true;
@@ -203,6 +314,9 @@ const codegenPlugin = (options) => {
203
314
  server.watcher.off("add", onChange);
204
315
  server.watcher.off("change", onChange);
205
316
  server.watcher.off("unlink", onChange);
317
+ server.watcher.off("add", onConfigChange);
318
+ server.watcher.off("change", onConfigChange);
319
+ server.watcher.off("unlink", onConfigChange);
206
320
  });
207
321
  };
208
322
  },
@@ -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 };
@@ -0,0 +1,78 @@
1
+ import { claimDevServerState, DEV_LOG_FILE_ENV, DEV_DAEMON_ENV, DEV_HANDOFF_ENV, clearDevServerState } from '@lunora/config';
2
+ import { l as lunoraLine } from './log-BjO9EWah.mjs';
3
+
4
+ const TRAILING_SLASH = /\/$/;
5
+ const resolveLocalUrl = (server) => {
6
+ const resolved = server.resolvedUrls?.local[0];
7
+ if (resolved !== void 0) {
8
+ return resolved.replace(TRAILING_SLASH, "");
9
+ }
10
+ const address = server.httpServer?.address();
11
+ if (address !== null && typeof address === "object") {
12
+ return `http://localhost:${String(address.port)}`;
13
+ }
14
+ return void 0;
15
+ };
16
+ const devStatePlugin = (options) => {
17
+ let recorded = false;
18
+ return {
19
+ apply: "serve",
20
+ configureServer(server) {
21
+ const root = options.projectRoot;
22
+ const record = () => {
23
+ if (recorded) {
24
+ return;
25
+ }
26
+ const url = resolveLocalUrl(server);
27
+ if (url === void 0) {
28
+ return;
29
+ }
30
+ const handoffPid = Number(process.env[DEV_HANDOFF_ENV]);
31
+ const claim = claimDevServerState(
32
+ root,
33
+ {
34
+ background: process.env[DEV_DAEMON_ENV] === "1",
35
+ logFile: process.env[DEV_LOG_FILE_ENV],
36
+ mode: "vite",
37
+ pid: process.pid,
38
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
39
+ url
40
+ },
41
+ Number.isInteger(handoffPid) && handoffPid > 0 ? { supersedePid: handoffPid } : void 0
42
+ );
43
+ if (!claim.ok) {
44
+ if (claim.existing !== void 0) {
45
+ server.config.logger.warn(
46
+ lunoraLine(
47
+ `another dev server is already recorded at ${claim.existing.url} (pid ${String(claim.existing.pid)}) — leaving .lunora/dev.json untouched`
48
+ )
49
+ );
50
+ }
51
+ return;
52
+ }
53
+ recorded = true;
54
+ };
55
+ server.httpServer?.once("close", () => {
56
+ if (recorded) {
57
+ clearDevServerState(root, process.pid);
58
+ recorded = false;
59
+ }
60
+ });
61
+ return () => {
62
+ if (typeof server.printUrls === "function") {
63
+ const printUrls = server.printUrls.bind(server);
64
+ server.printUrls = () => {
65
+ printUrls();
66
+ record();
67
+ };
68
+ }
69
+ server.httpServer?.once("listening", () => {
70
+ setTimeout(record, 0);
71
+ });
72
+ };
73
+ },
74
+ name: "lunora:dev-state"
75
+ };
76
+ };
77
+
78
+ export { devStatePlugin as default };
@@ -0,0 +1,19 @@
1
+ import { ensureDevVariables, createConfirm, fillDevSecrets } from '@lunora/config';
2
+ import { l as lunoraLine } from './log-BjO9EWah.mjs';
3
+
4
+ const devVariablesPlugin = (options) => {
5
+ return {
6
+ apply: "serve",
7
+ async configResolved() {
8
+ const info = (message) => {
9
+ console.info(lunoraLine(message));
10
+ };
11
+ await ensureDevVariables({ confirm: createConfirm("[lunora] "), cwd: options.projectRoot, info });
12
+ fillDevSecrets({ cwd: options.projectRoot, info });
13
+ },
14
+ enforce: "pre",
15
+ name: "lunora:dev-vars"
16
+ };
17
+ };
18
+
19
+ export { devVariablesPlugin as default };
@@ -0,0 +1,8 @@
1
+ import { paintBadge, BADGES } from '@lunora/config';
2
+
3
+ const LUNORA_TAG = paintBadge(BADGES.lunora);
4
+ const lunoraLine = (message) => `${LUNORA_TAG} ${message}`;
5
+ const ADVISORY_BADGE = { ERROR: BADGES.error, INFO: BADGES.info, WARN: BADGES.warn };
6
+ const advisoryLine = (level, name, detail, remediation) => `${paintBadge(ADVISORY_BADGE[level])} ${name}: ${detail} — ${remediation}`;
7
+
8
+ export { LUNORA_TAG as L, advisoryLine as a, lunoraLine as l };
@@ -1,4 +1,4 @@
1
- import { LUNORA_EVENT_SOURCE, formatLunoraEvent } from '@lunora/config';
1
+ import { detectAiAgent, LUNORA_EVENT_SOURCE, formatLunoraEvent } from '@lunora/config';
2
2
 
3
3
  const LUNORA_MARKER = `"source":"${LUNORA_EVENT_SOURCE}"`;
4
4
  const ANSI = {
@@ -35,6 +35,16 @@ const patchStream = (stream) => {
35
35
  stream.write = original;
36
36
  };
37
37
  };
38
+ const wantRawJsonLogs = () => {
39
+ const flag = process.env.LUNORA_LOG_JSON;
40
+ if (flag === "1" || flag === "true") {
41
+ return true;
42
+ }
43
+ if (flag === "0" || flag === "false") {
44
+ return false;
45
+ }
46
+ return detectAiAgent() !== void 0;
47
+ };
38
48
  const logStreamPlugin = () => {
39
49
  let restore;
40
50
  return {
@@ -43,6 +53,9 @@ const logStreamPlugin = () => {
43
53
  if (restore) {
44
54
  return;
45
55
  }
56
+ if (wantRawJsonLogs()) {
57
+ return;
58
+ }
46
59
  const restoreStdout = patchStream(process.stdout);
47
60
  const restoreStderr = patchStream(process.stderr);
48
61
  restore = () => {
@@ -0,0 +1,17 @@
1
+ import { findLunoraSolution } from '@lunora/codegen';
2
+
3
+ const lunoraSolutionFinder = {
4
+ // Synchronous body wrapped in `Promise.resolve` rather than `async`: the
5
+ // overlay's `handle` contract is promise-returning, but `require-await` is on
6
+ // for src and there's nothing to await here.
7
+ handle: (error) => {
8
+ const message = typeof error.message === "string" ? error.message : "";
9
+ const solution = findLunoraSolution(message);
10
+ return Promise.resolve(solution ? { body: solution.body, header: solution.header } : void 0);
11
+ },
12
+ name: "lunora",
13
+ priority: 100
14
+ };
15
+ const lunoraSolutionFinders = [lunoraSolutionFinder];
16
+
17
+ export { lunoraSolutionFinder, lunoraSolutionFinders };
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { validateWranglerProject, readWranglerJsonc } from '@lunora/config';
3
- import { l as lunoraLine } from './log-Bgkv6QhO.mjs';
3
+ import { LunoraError } from '@lunora/errors';
4
+ import { l as lunoraLine } from './log-BjO9EWah.mjs';
4
5
 
5
6
  const isLocalImagePath = (image) => image.startsWith("./") || image.startsWith("../") || image.startsWith("/") || image.includes("Dockerfile");
6
7
  const probeDocker = () => {
@@ -41,7 +42,8 @@ const wranglerValidatorPlugin = (options) => {
41
42
  schemaDir: options.schemaDir
42
43
  });
43
44
  if (!result.wranglerPath) {
44
- throw new Error(
45
+ throw new LunoraError(
46
+ "INTERNAL",
45
47
  [
46
48
  "[lunora] wrangler.jsonc not found.",
47
49
  ` searched in: ${options.projectRoot}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/vite",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.50",
4
4
  "description": "The Lunora Vite plugin: codegen, type sync, wrangler validation, and an error overlay over @cloudflare/vite-plugin",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -25,7 +25,7 @@
25
25
  "directory": "packages/vite"
26
26
  },
27
27
  "files": [
28
- "dist",
28
+ "./dist",
29
29
  "__assets__",
30
30
  "README.md",
31
31
  "LICENSE.md"
@@ -45,16 +45,17 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@cloudflare/vite-plugin": "1.42.0",
49
- "@lunora/codegen": "1.0.0-alpha.3",
50
- "@lunora/config": "1.0.0-alpha.5",
48
+ "@cloudflare/vite-plugin": "1.42.3",
49
+ "@lunora/codegen": "1.0.0-alpha.32",
50
+ "@lunora/config": "1.0.0-alpha.50",
51
+ "@lunora/errors": "1.0.0-alpha.1",
51
52
  "@visulima/vite-overlay": "2.0.0-alpha.35",
52
53
  "jsonc-parser": "^3.3.1",
53
54
  "ts-morph": "^28.0.0"
54
55
  },
55
56
  "peerDependencies": {
56
- "@lunora/studio": "1.0.0-alpha.3",
57
- "vite": "^8.0.16"
57
+ "@lunora/studio": "1.0.0-alpha.35",
58
+ "vite": "^8.1.0"
58
59
  },
59
60
  "peerDependenciesMeta": {
60
61
  "@lunora/studio": {
@@ -1,21 +0,0 @@
1
- import { ensureDevVariables, createConfirm } from '@lunora/config';
2
- import { l as lunoraLine } from './log-Bgkv6QhO.mjs';
3
-
4
- const devVariablesPlugin = (options) => {
5
- return {
6
- apply: "serve",
7
- async configResolved() {
8
- await ensureDevVariables({
9
- confirm: createConfirm("[lunora] "),
10
- cwd: options.projectRoot,
11
- info: (message) => {
12
- console.info(lunoraLine(message));
13
- }
14
- });
15
- },
16
- enforce: "pre",
17
- name: "lunora:dev-vars"
18
- };
19
- };
20
-
21
- export { devVariablesPlugin as default };
@@ -1,6 +0,0 @@
1
- import { paintBadge, BADGES } from '@lunora/config';
2
-
3
- const LUNORA_TAG = paintBadge(BADGES.lunora);
4
- const lunoraLine = (message) => `${LUNORA_TAG} ${message}`;
5
-
6
- export { LUNORA_TAG as L, lunoraLine as l };