@caryhu/codemine-forge 0.0.1-alpha.1 → 0.0.1-alpha.2

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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createForgePreviewDocument = void 0;
3
+ exports.createForgePreviewErrorOverlayDocument = exports.createForgePreviewDocument = void 0;
4
4
  const DEFAULT_HTML_SHELL = [
5
5
  "<!doctype html>",
6
6
  "<html>",
@@ -15,6 +15,9 @@ const DEFAULT_HTML_SHELL = [
15
15
  "</html>",
16
16
  ].join("\n");
17
17
  const PREVIEW_MODULE_SPECIFIER_PREFIX = "__forge_preview__/";
18
+ const PREVIEW_ERROR_OVERLAY_START = "<!-- forge-preview-error-overlay:start -->";
19
+ const PREVIEW_ERROR_OVERLAY_END = "<!-- forge-preview-error-overlay:end -->";
20
+ const PREVIEW_RUNTIME_ERROR_CLIENT_ID = "forge-preview-runtime-error-client";
18
21
  function createForgePreviewDocument(options) {
19
22
  const htmlShell = options.htmlShell ?? DEFAULT_HTML_SHELL;
20
23
  const browserBundle = options.buildResult.browserBundle;
@@ -26,6 +29,7 @@ function createForgePreviewDocument(options) {
26
29
  : previewModuleSpecifiers.get(browserBundle.entryOutputPath) ??
27
30
  browserBundle.entryOutputPath;
28
31
  const metadata = {
32
+ adapter: options.adapter,
29
33
  browserBundle: browserBundle === undefined
30
34
  ? undefined
31
35
  : {
@@ -58,6 +62,7 @@ function createForgePreviewDocument(options) {
58
62
  previewImportMap === undefined
59
63
  ? ""
60
64
  : `<script type="importmap" id="forge-browser-bundle-importmap">${escapeJsonForScript(JSON.stringify(previewImportMap))}</script>`,
65
+ browserBundle === undefined ? "" : createPreviewRuntimeErrorClientScript(),
61
66
  browserBundle === undefined
62
67
  ? ""
63
68
  : `<script type="module" id="forge-browser-bundle-entry">import ${JSON.stringify(previewEntrySpecifier)};</script>`,
@@ -72,6 +77,16 @@ function createForgePreviewDocument(options) {
72
77
  };
73
78
  }
74
79
  exports.createForgePreviewDocument = createForgePreviewDocument;
80
+ function createForgePreviewErrorOverlayDocument(previewDocument, diagnostics) {
81
+ if (diagnostics.length === 0) {
82
+ return previewDocument;
83
+ }
84
+ return {
85
+ ...previewDocument,
86
+ html: injectBeforeBodyEnd(removePreviewErrorOverlay(previewDocument.html), createPreviewErrorOverlay(diagnostics)),
87
+ };
88
+ }
89
+ exports.createForgePreviewErrorOverlayDocument = createForgePreviewErrorOverlayDocument;
75
90
  function createPreviewImportMap(outputFiles, previewModuleSpecifiers) {
76
91
  const importMapFile = outputFiles.find((file) => file.contentType === "application/importmap+json");
77
92
  if (importMapFile === undefined) {
@@ -130,12 +145,199 @@ function injectBeforeBodyEnd(html, injection) {
130
145
  }
131
146
  return `${html}\n${injection}`;
132
147
  }
148
+ function createPreviewRuntimeErrorClientScript() {
149
+ return [
150
+ `<script id="${PREVIEW_RUNTIME_ERROR_CLIENT_ID}">`,
151
+ "(function(){",
152
+ ' var overlaySelector = "[data-forge-preview-runtime-error-overlay]";',
153
+ ' var styleSelector = "style[data-forge-preview-runtime-error-overlay-style]";',
154
+ " function stringify(value){",
155
+ ' if (value instanceof Error) return value.message || value.name || "Runtime error";',
156
+ ' if (typeof value === "string") return value;',
157
+ " if (value === undefined) return \"undefined\";",
158
+ " if (value === null) return \"null\";",
159
+ " try {",
160
+ " var json = JSON.stringify(value);",
161
+ " if (typeof json === \"string\") return json;",
162
+ " } catch (error) {}",
163
+ " return String(value);",
164
+ " }",
165
+ " function stackFor(value){",
166
+ " return value instanceof Error && typeof value.stack === \"string\" && value.stack.length > 0",
167
+ " ? value.stack",
168
+ " : undefined;",
169
+ " }",
170
+ " function formatLocation(event){",
171
+ " var parts = [];",
172
+ " if (typeof event.filename === \"string\" && event.filename.length > 0) parts.push(event.filename);",
173
+ " if (typeof event.lineno === \"number\" && event.lineno > 0) parts.push(\"line \" + event.lineno);",
174
+ " if (typeof event.colno === \"number\" && event.colno > 0) parts.push(\"column \" + event.colno);",
175
+ " return parts.length > 0 ? parts.join(\" · \") : undefined;",
176
+ " }",
177
+ " function createTextElement(tagName, attributeName, text){",
178
+ " var element = document.createElement(tagName);",
179
+ " element.setAttribute(attributeName, \"\");",
180
+ " element.textContent = text;",
181
+ " return element;",
182
+ " }",
183
+ " function ensureStyle(){",
184
+ " if (document.querySelector(styleSelector)) return;",
185
+ " var style = document.createElement(\"style\");",
186
+ " style.setAttribute(\"data-forge-preview-runtime-error-overlay-style\", \"\");",
187
+ " style.textContent = [",
188
+ ' "[data-forge-preview-runtime-error-overlay]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding:32px 16px;background:rgba(15,23,42,.72);color:#f8fafc;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\'Segoe UI\',sans-serif;box-sizing:border-box;}",',
189
+ ' "[data-forge-preview-runtime-error-overlay] *{box-sizing:border-box;}",',
190
+ ' "[data-forge-preview-runtime-error-overlay-panel]{width:min(760px,100%);max-height:calc(100vh - 64px);overflow:auto;border:1px solid rgba(248,113,113,.45);border-radius:8px;background:#111827;box-shadow:0 24px 80px rgba(0,0,0,.42);padding:20px;}",',
191
+ ' "[data-forge-preview-runtime-error-overlay-kicker]{margin:0 0 6px;color:#fca5a5;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;}",',
192
+ ' "[data-forge-preview-runtime-error-overlay-title]{margin:0 0 16px;font-size:20px;line-height:1.25;font-weight:700;}",',
193
+ ' "[data-forge-preview-runtime-error-overlay-message]{margin:0 0 12px;color:#f8fafc;font-size:14px;line-height:1.5;}",',
194
+ ' "[data-forge-preview-runtime-error-overlay-detail]{margin:0;color:#cbd5e1;white-space:pre-wrap;font-family:ui-monospace,SFMono-Regular,Consolas,\'Liberation Mono\',monospace;font-size:12px;line-height:1.45;}",',
195
+ " ].join(\"\");",
196
+ " (document.head || document.documentElement).appendChild(style);",
197
+ " }",
198
+ " function showOverlay(diagnostic){",
199
+ " ensureStyle();",
200
+ " var existing = document.querySelector(overlaySelector);",
201
+ " if (existing && existing.parentNode) existing.parentNode.removeChild(existing);",
202
+ " var overlay = document.createElement(\"div\");",
203
+ " overlay.setAttribute(\"data-forge-preview-runtime-error-overlay\", \"\");",
204
+ " overlay.setAttribute(\"role\", \"alert\");",
205
+ " overlay.setAttribute(\"aria-live\", \"assertive\");",
206
+ " var panel = document.createElement(\"section\");",
207
+ " panel.setAttribute(\"data-forge-preview-runtime-error-overlay-panel\", \"\");",
208
+ " panel.appendChild(createTextElement(\"p\", \"data-forge-preview-runtime-error-overlay-kicker\", \"Forge preview\"));",
209
+ " panel.appendChild(createTextElement(\"h1\", \"data-forge-preview-runtime-error-overlay-title\", diagnostic.title));",
210
+ " panel.appendChild(createTextElement(\"p\", \"data-forge-preview-runtime-error-overlay-message\", diagnostic.message));",
211
+ " if (diagnostic.detail) {",
212
+ " panel.appendChild(createTextElement(\"pre\", \"data-forge-preview-runtime-error-overlay-detail\", diagnostic.detail));",
213
+ " }",
214
+ " overlay.appendChild(panel);",
215
+ " (document.body || document.documentElement).appendChild(overlay);",
216
+ " }",
217
+ " window.addEventListener(\"error\", function(event){",
218
+ " showOverlay({",
219
+ " title: \"Preview runtime error\",",
220
+ " message: event.error === undefined || event.error === null",
221
+ " ? event.message || \"Runtime error\"",
222
+ " : stringify(event.error),",
223
+ " detail: stackFor(event.error) || formatLocation(event),",
224
+ " });",
225
+ " event.preventDefault();",
226
+ " });",
227
+ " window.addEventListener(\"unhandledrejection\", function(event){",
228
+ " showOverlay({",
229
+ " title: \"Unhandled promise rejection\",",
230
+ " message: stringify(event.reason),",
231
+ " detail: stackFor(event.reason),",
232
+ " });",
233
+ " event.preventDefault();",
234
+ " });",
235
+ "})();",
236
+ "</script>",
237
+ ].join("\n");
238
+ }
239
+ function createPreviewErrorOverlay(diagnostics) {
240
+ const items = diagnostics.map(createPreviewErrorOverlayItem).join("\n");
241
+ return [
242
+ PREVIEW_ERROR_OVERLAY_START,
243
+ '<style data-forge-preview-error-overlay>',
244
+ "[data-forge-preview-error-overlay]{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding:32px 16px;background:rgba(15,23,42,.72);color:#f8fafc;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;box-sizing:border-box;}",
245
+ "[data-forge-preview-error-overlay] *{box-sizing:border-box;}",
246
+ "[data-forge-preview-error-overlay-panel]{width:min(760px,100%);max-height:calc(100vh - 64px);overflow:auto;border:1px solid rgba(248,113,113,.45);border-radius:8px;background:#111827;box-shadow:0 24px 80px rgba(0,0,0,.42);padding:20px;}",
247
+ "[data-forge-preview-error-overlay-kicker]{margin:0 0 6px;color:#fca5a5;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;}",
248
+ "[data-forge-preview-error-overlay-title]{margin:0 0 16px;font-size:20px;line-height:1.25;font-weight:700;}",
249
+ "[data-forge-preview-error-overlay-list]{display:grid;gap:12px;margin:0;padding:0;list-style:none;}",
250
+ "[data-forge-preview-error-overlay-item]{display:grid;gap:6px;border-top:1px solid rgba(148,163,184,.22);padding-top:12px;}",
251
+ "[data-forge-preview-error-overlay-code]{width:max-content;border-radius:4px;background:rgba(248,113,113,.16);color:#fecaca;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',monospace;font-size:12px;}",
252
+ "[data-forge-preview-error-overlay-message]{margin:0;color:#f8fafc;font-size:14px;line-height:1.5;}",
253
+ "[data-forge-preview-error-overlay-detail]{margin:0;color:#cbd5e1;font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',monospace;font-size:12px;line-height:1.45;}",
254
+ "</style>",
255
+ '<div data-forge-preview-error-overlay role="alert" aria-live="assertive">',
256
+ ' <section data-forge-preview-error-overlay-panel>',
257
+ ' <p data-forge-preview-error-overlay-kicker>Forge preview</p>',
258
+ ' <h1 data-forge-preview-error-overlay-title>Preview refresh failed</h1>',
259
+ ` <ul data-forge-preview-error-overlay-list>${items}</ul>`,
260
+ " </section>",
261
+ "</div>",
262
+ PREVIEW_ERROR_OVERLAY_END,
263
+ ].join("\n");
264
+ }
265
+ function createPreviewErrorOverlayItem(diagnostic) {
266
+ const detail = formatDiagnosticDetail(diagnostic);
267
+ return [
268
+ " <li data-forge-preview-error-overlay-item>",
269
+ ` <code data-forge-preview-error-overlay-code>${escapeHtml(diagnostic.code)}</code>`,
270
+ ` <p data-forge-preview-error-overlay-message>${escapeHtml(diagnostic.message)}</p>`,
271
+ detail === undefined
272
+ ? ""
273
+ : ` <p data-forge-preview-error-overlay-detail>${escapeHtml(detail)}</p>`,
274
+ " </li>",
275
+ ]
276
+ .filter((line) => line.length > 0)
277
+ .join("\n");
278
+ }
279
+ function formatDiagnosticDetail(diagnostic) {
280
+ const parts = [
281
+ getStringDiagnosticProperty(diagnostic, "path"),
282
+ getStringDiagnosticProperty(diagnostic, "importer"),
283
+ getStringDiagnosticProperty(diagnostic, "importSpecifier"),
284
+ getStringDiagnosticProperty(diagnostic, "dependencyName"),
285
+ getStringDiagnosticProperty(diagnostic, "requestedRange"),
286
+ getStringDiagnosticProperty(diagnostic, "script"),
287
+ getStringDiagnosticProperty(diagnostic, "sessionId"),
288
+ formatLimitDiagnostic(diagnostic),
289
+ ].filter((part) => part !== undefined && part.length > 0);
290
+ return parts.length === 0 ? undefined : parts.join(" · ");
291
+ }
292
+ function getStringDiagnosticProperty(diagnostic, key) {
293
+ const value = diagnostic[key];
294
+ return typeof value === "string" ? value : undefined;
295
+ }
296
+ function formatLimitDiagnostic(diagnostic) {
297
+ const limit = diagnostic.limit;
298
+ if (typeof limit !== "object" || limit === null) {
299
+ return undefined;
300
+ }
301
+ const name = limit.name;
302
+ const actual = limit.actual;
303
+ const maximum = limit.maximum;
304
+ if (typeof name !== "string" ||
305
+ typeof actual !== "number" ||
306
+ typeof maximum !== "number") {
307
+ return undefined;
308
+ }
309
+ return `${name}: ${actual}/${maximum}`;
310
+ }
311
+ function removePreviewErrorOverlay(html) {
312
+ return html.replace(new RegExp(`${escapeRegExp(PREVIEW_ERROR_OVERLAY_START)}[\\s\\S]*?${escapeRegExp(PREVIEW_ERROR_OVERLAY_END)}\\n?`, "g"), "");
313
+ }
133
314
  function removeProjectModuleScripts(html) {
134
315
  return html.replace(/<script\b(?=[^>]*\btype\s*=\s*["']module["'])[^>]*>[\s\S]*?<\/script\s*>/gi, "");
135
316
  }
136
317
  function escapeJsonForScript(json) {
137
318
  return json.replace(/</g, "\\u003c");
138
319
  }
320
+ function escapeHtml(value) {
321
+ return value.replace(/[&<>"']/g, (character) => {
322
+ switch (character) {
323
+ case "&":
324
+ return "&amp;";
325
+ case "<":
326
+ return "&lt;";
327
+ case ">":
328
+ return "&gt;";
329
+ case '"':
330
+ return "&quot;";
331
+ case "'":
332
+ return "&#39;";
333
+ default:
334
+ return character;
335
+ }
336
+ });
337
+ }
338
+ function escapeRegExp(value) {
339
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
340
+ }
139
341
  function toBase64(bytes) {
140
342
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
141
343
  let output = "";
@@ -14,10 +14,17 @@ export declare class DefaultDevServerPreview implements ForgeDevServerPreview {
14
14
  stop(sessionId: string): Promise<ForgeDevSessionStopResult>;
15
15
  getSession(sessionId: string): ForgeDevSession | undefined;
16
16
  watch(listener: ForgeDevServerPreviewEventListener): ForgeUnsubscribe;
17
- private validateDevScript;
17
+ private readDevManifest;
18
18
  private failStart;
19
19
  private handleFileEvent;
20
+ private handleWorkspaceChange;
21
+ private wasCssPathBundled;
22
+ private wasWorkspaceModuleBundled;
23
+ private performStyleUpdate;
24
+ private performModuleUpdate;
20
25
  private performRebuild;
26
+ private completeSuccessfulRefresh;
27
+ private completeFailedRebuild;
21
28
  private createPreviewDocument;
22
29
  private allocateSessionId;
23
30
  private emit;
@@ -34,10 +34,15 @@ class DefaultDevServerPreview {
34
34
  },
35
35
  };
36
36
  this.sessions.set(sessionId, record);
37
- const devScriptDiagnostics = await this.validateDevScript();
38
- if (devScriptDiagnostics.length > 0) {
39
- return this.failStart(record, devScriptDiagnostics, "dev-script");
37
+ const devManifestResult = await this.readDevManifest();
38
+ if (!devManifestResult.success) {
39
+ return this.failStart(record, devManifestResult.diagnostics, "dev-script");
40
40
  }
41
+ record.adapter = selectDevServerAdapter(devManifestResult.manifest);
42
+ record.session = {
43
+ ...record.session,
44
+ adapter: record.adapter,
45
+ };
41
46
  const buildResult = await this.options.buildPipeline.build({
42
47
  entry: options.entry,
43
48
  signal: options.signal,
@@ -45,15 +50,19 @@ class DefaultDevServerPreview {
45
50
  if (!buildResult.success) {
46
51
  return this.failStart(record, buildResult.diagnostics, "initial-build", buildResult);
47
52
  }
48
- const previewDocument = await this.createPreviewDocument(sessionId, previewUrl, 1, buildResult);
53
+ const previewDocument = await this.createPreviewDocument(sessionId, previewUrl, 1, buildResult, record.adapter);
54
+ record.lastSuccessfulBuildResult = buildResult;
55
+ record.lastSuccessfulPreviewDocument = previewDocument;
49
56
  const session = {
50
57
  ...record.session,
51
58
  status: "running",
59
+ adapter: record.adapter,
52
60
  revision: 1,
53
61
  updatedAt: this.now(),
54
62
  diagnostics: buildResult.diagnostics,
55
63
  buildResult,
56
64
  previewDocument,
65
+ errorOverlayDocument: undefined,
57
66
  };
58
67
  record.session = session;
59
68
  record.unsubscribe = this.options.fileSystem.watch((event) => {
@@ -88,6 +97,7 @@ class DefaultDevServerPreview {
88
97
  status: "stopped",
89
98
  updatedAt: this.now(),
90
99
  diagnostics: [],
100
+ errorOverlayDocument: undefined,
91
101
  };
92
102
  record.session = session;
93
103
  this.emit({ type: "dev:session-stopped", session });
@@ -106,21 +116,34 @@ class DefaultDevServerPreview {
106
116
  this.listeners.delete(listener);
107
117
  };
108
118
  }
109
- async validateDevScript() {
119
+ async readDevManifest() {
110
120
  try {
111
121
  const manifest = await (0, package_resolution_1.readPackageManifest)(this.options.fileSystem);
112
122
  const devScript = manifest.scripts.dev;
113
123
  if (devScript === undefined) {
114
- return [(0, diagnostics_1.createMissingDevScriptDiagnostic)()];
124
+ return {
125
+ success: false,
126
+ diagnostics: [(0, diagnostics_1.createMissingDevScriptDiagnostic)()],
127
+ };
115
128
  }
116
129
  if (!isSupportedViteDevScript(devScript)) {
117
- return [(0, diagnostics_1.createUnsupportedDevScriptDiagnostic)(devScript)];
130
+ return {
131
+ success: false,
132
+ diagnostics: [(0, diagnostics_1.createUnsupportedDevScriptDiagnostic)(devScript)],
133
+ };
118
134
  }
119
- return [];
135
+ return {
136
+ success: true,
137
+ diagnostics: [],
138
+ manifest,
139
+ };
120
140
  }
121
141
  catch (error) {
122
142
  if (error instanceof package_resolution_1.ForgePackageResolutionError) {
123
- return [error.diagnostic];
143
+ return {
144
+ success: false,
145
+ diagnostics: [error.diagnostic],
146
+ };
124
147
  }
125
148
  throw error;
126
149
  }
@@ -154,13 +177,59 @@ class DefaultDevServerPreview {
154
177
  return;
155
178
  }
156
179
  const previous = record.rebuildQueue ?? Promise.resolve(undefined);
157
- const next = previous.then(() => this.performRebuild(record, change.reason, record.entry));
180
+ const next = previous.then(() => this.handleWorkspaceChange(record, event, change.reason));
158
181
  record.rebuildQueue = next;
159
182
  void next.catch(() => {
160
183
  record.rebuildQueue = undefined;
161
184
  });
162
185
  }
163
- async performRebuild(record, reason, entry, signal) {
186
+ async handleWorkspaceChange(record, event, reason) {
187
+ const cssPath = getUpdatedCssPath(event);
188
+ if (cssPath !== undefined &&
189
+ record.session.status === "running" &&
190
+ this.wasCssPathBundled(record, cssPath)) {
191
+ await this.performStyleUpdate(record, cssPath, reason);
192
+ return;
193
+ }
194
+ const scriptPath = getUpdatedScriptModulePath(event);
195
+ if (scriptPath !== undefined &&
196
+ record.session.status === "running" &&
197
+ this.wasWorkspaceModuleBundled(record, scriptPath)) {
198
+ return this.performModuleUpdate(record, scriptPath, reason);
199
+ }
200
+ return this.performRebuild(record, reason, record.entry);
201
+ }
202
+ wasCssPathBundled(record, path) {
203
+ const styleId = createForgeCssStyleId(path);
204
+ return (record.lastSuccessfulBuildResult?.outputFiles.some((file) => file.contentType === "text/javascript" &&
205
+ file.content.includes(styleId)) ?? false);
206
+ }
207
+ wasWorkspaceModuleBundled(record, path) {
208
+ return (record.lastSuccessfulBuildResult?.browserBundle?.modules.some((moduleRecord) => moduleRecord.origin === "workspace" &&
209
+ moduleRecord.sourcePath === path) ?? false);
210
+ }
211
+ async performStyleUpdate(record, sourcePath, reason) {
212
+ const content = await this.options.fileSystem.readText(sourcePath);
213
+ const session = {
214
+ ...record.session,
215
+ status: "running",
216
+ updatedAt: this.now(),
217
+ errorOverlayDocument: undefined,
218
+ };
219
+ record.session = session;
220
+ this.emit({
221
+ type: "preview:style-updated",
222
+ session,
223
+ styleUpdate: {
224
+ content,
225
+ contentType: "text/css",
226
+ sourcePath,
227
+ styleId: createForgeCssStyleId(sourcePath),
228
+ },
229
+ reason,
230
+ });
231
+ }
232
+ async performModuleUpdate(record, sourcePath, reason) {
164
233
  if (record.session.status === "stopped") {
165
234
  const diagnostic = (0, diagnostics_1.createSessionStoppedDiagnostic)(record.session.id);
166
235
  return {
@@ -169,37 +238,74 @@ class DefaultDevServerPreview {
169
238
  diagnostics: [diagnostic],
170
239
  };
171
240
  }
241
+ const previousBuildResult = record.lastSuccessfulBuildResult;
172
242
  record.session = {
173
243
  ...record.session,
174
244
  status: "rebuilding",
175
245
  updatedAt: this.now(),
246
+ errorOverlayDocument: undefined,
176
247
  };
177
- const buildResult = await this.options.buildPipeline.build({ entry, signal });
248
+ const buildResult = await this.options.buildPipeline.build({
249
+ entry: record.entry,
250
+ });
178
251
  if (!buildResult.success) {
179
- const session = {
180
- ...record.session,
181
- status: "failed",
182
- updatedAt: this.now(),
183
- diagnostics: buildResult.diagnostics,
184
- buildResult,
185
- };
186
- record.session = session;
187
- this.emit({
188
- type: "dev:session-failed",
189
- session,
190
- diagnostics: buildResult.diagnostics,
191
- buildResult,
192
- reason,
193
- });
252
+ return this.completeFailedRebuild(record, reason, buildResult);
253
+ }
254
+ const moduleUpdate = createModuleUpdate(previousBuildResult, buildResult, sourcePath);
255
+ if (moduleUpdate === undefined) {
256
+ return this.completeSuccessfulRefresh(record, reason, buildResult);
257
+ }
258
+ record.lastSuccessfulBuildResult = buildResult;
259
+ const session = {
260
+ ...record.session,
261
+ status: "running",
262
+ updatedAt: this.now(),
263
+ diagnostics: buildResult.diagnostics,
264
+ buildResult,
265
+ previewDocument: record.lastSuccessfulPreviewDocument ?? record.session.previewDocument,
266
+ errorOverlayDocument: undefined,
267
+ };
268
+ record.session = session;
269
+ this.emit({
270
+ type: "preview:module-updated",
271
+ session,
272
+ moduleUpdate,
273
+ buildResult,
274
+ reason,
275
+ });
276
+ return {
277
+ success: true,
278
+ session,
279
+ diagnostics: buildResult.diagnostics,
280
+ buildResult,
281
+ };
282
+ }
283
+ async performRebuild(record, reason, entry, signal) {
284
+ if (record.session.status === "stopped") {
285
+ const diagnostic = (0, diagnostics_1.createSessionStoppedDiagnostic)(record.session.id);
194
286
  return {
195
287
  success: false,
196
- session,
197
- diagnostics: buildResult.diagnostics,
198
- buildResult,
288
+ session: record.session,
289
+ diagnostics: [diagnostic],
199
290
  };
200
291
  }
292
+ record.session = {
293
+ ...record.session,
294
+ status: "rebuilding",
295
+ updatedAt: this.now(),
296
+ errorOverlayDocument: undefined,
297
+ };
298
+ const buildResult = await this.options.buildPipeline.build({ entry, signal });
299
+ if (!buildResult.success) {
300
+ return this.completeFailedRebuild(record, reason, buildResult);
301
+ }
302
+ return this.completeSuccessfulRefresh(record, reason, buildResult);
303
+ }
304
+ async completeSuccessfulRefresh(record, reason, buildResult) {
201
305
  const revision = record.session.revision + 1;
202
- const previewDocument = await this.createPreviewDocument(record.session.id, record.session.previewUrl, revision, buildResult);
306
+ const previewDocument = await this.createPreviewDocument(record.session.id, record.session.previewUrl, revision, buildResult, record.adapter);
307
+ record.lastSuccessfulBuildResult = buildResult;
308
+ record.lastSuccessfulPreviewDocument = previewDocument;
203
309
  const session = {
204
310
  ...record.session,
205
311
  status: "running",
@@ -208,6 +314,7 @@ class DefaultDevServerPreview {
208
314
  diagnostics: buildResult.diagnostics,
209
315
  buildResult,
210
316
  previewDocument,
317
+ errorOverlayDocument: undefined,
211
318
  };
212
319
  record.session = session;
213
320
  this.emit({
@@ -223,11 +330,41 @@ class DefaultDevServerPreview {
223
330
  buildResult,
224
331
  };
225
332
  }
226
- async createPreviewDocument(sessionId, previewUrl, revision, buildResult) {
333
+ completeFailedRebuild(record, reason, buildResult) {
334
+ const previousPreviewDocument = record.lastSuccessfulPreviewDocument ?? record.session.previewDocument;
335
+ const errorOverlayDocument = previousPreviewDocument === undefined
336
+ ? undefined
337
+ : (0, preview_document_1.createForgePreviewErrorOverlayDocument)(previousPreviewDocument, buildResult.diagnostics);
338
+ const session = {
339
+ ...record.session,
340
+ status: "failed",
341
+ updatedAt: this.now(),
342
+ diagnostics: buildResult.diagnostics,
343
+ buildResult,
344
+ previewDocument: previousPreviewDocument,
345
+ errorOverlayDocument,
346
+ };
347
+ record.session = session;
348
+ this.emit({
349
+ type: "dev:session-failed",
350
+ session,
351
+ diagnostics: buildResult.diagnostics,
352
+ buildResult,
353
+ reason,
354
+ });
355
+ return {
356
+ success: false,
357
+ session,
358
+ diagnostics: buildResult.diagnostics,
359
+ buildResult,
360
+ };
361
+ }
362
+ async createPreviewDocument(sessionId, previewUrl, revision, buildResult, adapter) {
227
363
  const htmlShell = (await this.options.fileSystem.exists("/index.html"))
228
364
  ? await this.options.fileSystem.readText("/index.html")
229
365
  : undefined;
230
366
  return (0, preview_document_1.createForgePreviewDocument)({
367
+ adapter,
231
368
  buildResult,
232
369
  htmlShell,
233
370
  previewUrl,
@@ -269,6 +406,30 @@ exports.isSupportedViteDevScript = isSupportedViteDevScript;
269
406
  function createPreviewUrl(sessionId) {
270
407
  return `/preview/${encodeURIComponent(sessionId)}/`;
271
408
  }
409
+ function selectDevServerAdapter(manifest) {
410
+ const dependencies = {
411
+ ...manifest.dependencies,
412
+ ...manifest.devDependencies,
413
+ };
414
+ if ("react" in dependencies || "react-dom" in dependencies) {
415
+ return createDevServerAdapterMetadata("vite-react", "react");
416
+ }
417
+ if ("vue" in dependencies) {
418
+ return createDevServerAdapterMetadata("vite-vue", "vue");
419
+ }
420
+ return createDevServerAdapterMetadata("vite-vanilla", "vanilla");
421
+ }
422
+ function createDevServerAdapterMetadata(name, framework) {
423
+ return {
424
+ framework,
425
+ hmrMode: "hmr-lite",
426
+ name,
427
+ preservesState: false,
428
+ };
429
+ }
430
+ function createForgeCssStyleId(path) {
431
+ return `forge-css:${path}`;
432
+ }
272
433
  function describeFileChange(event) {
273
434
  const path = event.type === "file:renamed" ? event.to : event.path;
274
435
  if (path.startsWith("/.forge/")) {
@@ -278,3 +439,56 @@ function describeFileChange(event) {
278
439
  reason: `${event.type}:${path}`,
279
440
  };
280
441
  }
442
+ function getUpdatedCssPath(event) {
443
+ return event.type === "file:updated" &&
444
+ event.path.endsWith(".css") &&
445
+ !event.path.endsWith(".module.css")
446
+ ? event.path
447
+ : undefined;
448
+ }
449
+ function getUpdatedScriptModulePath(event) {
450
+ return event.type === "file:updated" && isHmrScriptPath(event.path)
451
+ ? event.path
452
+ : undefined;
453
+ }
454
+ function createModuleUpdate(previousBuildResult, buildResult, sourcePath) {
455
+ const previousBundle = previousBuildResult?.browserBundle;
456
+ const nextBundle = buildResult.browserBundle;
457
+ if (previousBundle === undefined ||
458
+ nextBundle === undefined ||
459
+ previousBundle.entry === sourcePath ||
460
+ nextBundle.entry === sourcePath ||
461
+ !sameOutputPaths(previousBundle.outputPaths, nextBundle.outputPaths)) {
462
+ return undefined;
463
+ }
464
+ const previousModule = findWorkspaceModule(previousBundle.modules, sourcePath);
465
+ const nextModule = findWorkspaceModule(nextBundle.modules, sourcePath);
466
+ if (previousModule === undefined ||
467
+ nextModule === undefined ||
468
+ previousModule.outputPath !== nextModule.outputPath) {
469
+ return undefined;
470
+ }
471
+ const outputFile = buildResult.outputFiles.find((file) => file.path === nextModule.outputPath &&
472
+ file.contentType === "text/javascript");
473
+ return outputFile === undefined
474
+ ? undefined
475
+ : {
476
+ content: outputFile.content,
477
+ contentType: "text/javascript",
478
+ outputPath: nextModule.outputPath,
479
+ sourcePath,
480
+ };
481
+ }
482
+ function findWorkspaceModule(modules, sourcePath) {
483
+ return modules.find((moduleRecord) => moduleRecord.origin === "workspace" &&
484
+ moduleRecord.sourcePath === sourcePath);
485
+ }
486
+ function sameOutputPaths(left, right) {
487
+ const sortedLeft = [...left].sort();
488
+ const sortedRight = [...right].sort();
489
+ return (sortedLeft.length === sortedRight.length &&
490
+ sortedLeft.every((path, index) => path === sortedRight[index]));
491
+ }
492
+ function isHmrScriptPath(path) {
493
+ return /\.(?:jsx?|tsx?)$/i.test(path);
494
+ }