@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.
- package/dist/build-pipeline/browser-bundle.d.ts +2 -1
- package/dist/build-pipeline/browser-bundle.js +384 -24
- package/dist/build-pipeline/diagnostics.js +1 -1
- package/dist/build-pipeline/index.d.ts +1 -1
- package/dist/build-pipeline/pipeline.js +28 -30
- package/dist/build-pipeline/types.d.ts +18 -0
- package/dist/build-pipeline/vite-config.d.ts +8 -0
- package/dist/build-pipeline/vite-config.js +215 -0
- package/dist/dev-server-preview/index.d.ts +2 -2
- package/dist/dev-server-preview/index.js +2 -1
- package/dist/dev-server-preview/preview-document.d.ts +2 -1
- package/dist/dev-server-preview/preview-document.js +203 -1
- package/dist/dev-server-preview/server.d.ts +8 -1
- package/dist/dev-server-preview/server.js +246 -32
- package/dist/dev-server-preview/types.d.ts +38 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -2
- package/package.json +2 -2
|
@@ -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 "&";
|
|
325
|
+
case "<":
|
|
326
|
+
return "<";
|
|
327
|
+
case ">":
|
|
328
|
+
return ">";
|
|
329
|
+
case '"':
|
|
330
|
+
return """;
|
|
331
|
+
case "'":
|
|
332
|
+
return "'";
|
|
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
|
|
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
|
|
38
|
-
if (
|
|
39
|
-
return this.failStart(record,
|
|
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
|
|
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
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
diagnostics: [(0, diagnostics_1.createMissingDevScriptDiagnostic)()],
|
|
127
|
+
};
|
|
115
128
|
}
|
|
116
129
|
if (!isSupportedViteDevScript(devScript)) {
|
|
117
|
-
return
|
|
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
|
|
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.
|
|
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
|
|
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({
|
|
248
|
+
const buildResult = await this.options.buildPipeline.build({
|
|
249
|
+
entry: record.entry,
|
|
250
|
+
});
|
|
178
251
|
if (!buildResult.success) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|