create-interview-cockpit 0.7.0 → 0.8.0
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/package.json +1 -1
- package/template/client/src/api.ts +96 -1
- package/template/client/src/components/CodeRunnerModal.tsx +545 -65
- package/template/client/src/reactLab.ts +38 -8
- package/template/client/tsconfig.tsbuildinfo +30 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/index.ts +376 -0
|
@@ -237,14 +237,14 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
|
|
|
237
237
|
"name": "webpack-module-federation-lab",
|
|
238
238
|
"private": true,
|
|
239
239
|
"scripts": {
|
|
240
|
-
"dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm run dev
|
|
241
|
-
"dev:host": "
|
|
242
|
-
"dev:profile": "
|
|
243
|
-
"dev:checkout": "
|
|
244
|
-
"build": "npm run build
|
|
245
|
-
"build:host": "
|
|
246
|
-
"build:profile": "
|
|
247
|
-
"build:checkout": "
|
|
240
|
+
"dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm --prefix apps/host run dev' 'npm --prefix apps/profile run dev' 'npm --prefix apps/checkout run dev'",
|
|
241
|
+
"dev:host": "npm --prefix apps/host run dev",
|
|
242
|
+
"dev:profile": "npm --prefix apps/profile run dev",
|
|
243
|
+
"dev:checkout": "npm --prefix apps/checkout run dev",
|
|
244
|
+
"build": "npm --prefix apps/host run build && npm --prefix apps/profile run build && npm --prefix apps/checkout run build",
|
|
245
|
+
"build:host": "npm --prefix apps/host run build",
|
|
246
|
+
"build:profile": "npm --prefix apps/profile run build",
|
|
247
|
+
"build:checkout": "npm --prefix apps/checkout run build"
|
|
248
248
|
},
|
|
249
249
|
"dependencies": {
|
|
250
250
|
"react": "^19.0.0",
|
|
@@ -272,6 +272,15 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
|
|
|
272
272
|
<div id="root"></div>
|
|
273
273
|
</body>
|
|
274
274
|
</html>
|
|
275
|
+
`,
|
|
276
|
+
"apps/host/package.json": `{
|
|
277
|
+
"name": "@mf-lab/host",
|
|
278
|
+
"private": true,
|
|
279
|
+
"scripts": {
|
|
280
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
281
|
+
"build": "webpack --config webpack.config.js"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
275
284
|
`,
|
|
276
285
|
"apps/host/src/index.jsx": `import("./bootstrap");
|
|
277
286
|
`,
|
|
@@ -368,6 +377,7 @@ module.exports = {
|
|
|
368
377
|
mode: "development",
|
|
369
378
|
entry: path.resolve(__dirname, "./src/index.jsx"),
|
|
370
379
|
output: {
|
|
380
|
+
path: path.resolve(__dirname, "./dist"),
|
|
371
381
|
publicPath: "http://localhost:" + hostPort + "/",
|
|
372
382
|
clean: true,
|
|
373
383
|
},
|
|
@@ -427,6 +437,15 @@ module.exports = {
|
|
|
427
437
|
<div id="root"></div>
|
|
428
438
|
</body>
|
|
429
439
|
</html>
|
|
440
|
+
`,
|
|
441
|
+
"apps/profile/package.json": `{
|
|
442
|
+
"name": "@mf-lab/profile",
|
|
443
|
+
"private": true,
|
|
444
|
+
"scripts": {
|
|
445
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
446
|
+
"build": "webpack --config webpack.config.js"
|
|
447
|
+
}
|
|
448
|
+
}
|
|
430
449
|
`,
|
|
431
450
|
"apps/profile/src/index.jsx": `import("./bootstrap");
|
|
432
451
|
`,
|
|
@@ -486,6 +505,7 @@ module.exports = {
|
|
|
486
505
|
mode: "development",
|
|
487
506
|
entry: path.resolve(__dirname, "./src/index.jsx"),
|
|
488
507
|
output: {
|
|
508
|
+
path: path.resolve(__dirname, "./dist"),
|
|
489
509
|
publicPath: "http://localhost:" + profilePort + "/",
|
|
490
510
|
clean: true,
|
|
491
511
|
},
|
|
@@ -544,6 +564,15 @@ module.exports = {
|
|
|
544
564
|
<div id="root"></div>
|
|
545
565
|
</body>
|
|
546
566
|
</html>
|
|
567
|
+
`,
|
|
568
|
+
"apps/checkout/package.json": `{
|
|
569
|
+
"name": "@mf-lab/checkout",
|
|
570
|
+
"private": true,
|
|
571
|
+
"scripts": {
|
|
572
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
573
|
+
"build": "webpack --config webpack.config.js"
|
|
574
|
+
}
|
|
575
|
+
}
|
|
547
576
|
`,
|
|
548
577
|
"apps/checkout/src/index.jsx": `import("./bootstrap");
|
|
549
578
|
`,
|
|
@@ -611,6 +640,7 @@ module.exports = {
|
|
|
611
640
|
mode: "development",
|
|
612
641
|
entry: path.resolve(__dirname, "./src/index.jsx"),
|
|
613
642
|
output: {
|
|
643
|
+
path: path.resolve(__dirname, "./dist"),
|
|
614
644
|
publicPath: "http://localhost:" + checkoutPort + "/",
|
|
615
645
|
clean: true,
|
|
616
646
|
},
|
|
@@ -1 +1,30 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"root": [
|
|
3
|
+
"./src/app.tsx",
|
|
4
|
+
"./src/api.ts",
|
|
5
|
+
"./src/main.tsx",
|
|
6
|
+
"./src/store.ts",
|
|
7
|
+
"./src/types.ts",
|
|
8
|
+
"./src/vite-env.d.ts",
|
|
9
|
+
"./src/components/aisettingsmodal.tsx",
|
|
10
|
+
"./src/components/annotationdialog.tsx",
|
|
11
|
+
"./src/components/chatmessage.tsx",
|
|
12
|
+
"./src/components/chatview.tsx",
|
|
13
|
+
"./src/components/codecontextpanel.tsx",
|
|
14
|
+
"./src/components/codelineannotationpopup.tsx",
|
|
15
|
+
"./src/components/coderunnermodal.tsx",
|
|
16
|
+
"./src/components/docrefmodal.tsx",
|
|
17
|
+
"./src/components/fileattachments.tsx",
|
|
18
|
+
"./src/components/filepickermodal.tsx",
|
|
19
|
+
"./src/components/fileviewermodal.tsx",
|
|
20
|
+
"./src/components/linkedconvospicker.tsx",
|
|
21
|
+
"./src/components/markdownrenderer.tsx",
|
|
22
|
+
"./src/components/mermaiddiagram.tsx",
|
|
23
|
+
"./src/components/plotembed.tsx",
|
|
24
|
+
"./src/components/sidebar.tsx",
|
|
25
|
+
"./src/components/textannotator.tsx",
|
|
26
|
+
"./src/components/vizcraftembed.tsx",
|
|
27
|
+
"./src/components/workspaceswitcher.tsx"
|
|
28
|
+
],
|
|
29
|
+
"version": "5.9.3"
|
|
30
|
+
}
|
package/template/cockpit.json
CHANGED
|
@@ -2369,6 +2369,261 @@ async function runLoggedCommand(
|
|
|
2369
2369
|
});
|
|
2370
2370
|
}
|
|
2371
2371
|
|
|
2372
|
+
type ModuleFederationCommandOutputKind = "stdout" | "stderr" | "info";
|
|
2373
|
+
|
|
2374
|
+
const MODULE_FEDERATION_SHELL_META_TOKENS = new Set([
|
|
2375
|
+
"&&",
|
|
2376
|
+
"||",
|
|
2377
|
+
";",
|
|
2378
|
+
"|",
|
|
2379
|
+
">",
|
|
2380
|
+
">>",
|
|
2381
|
+
"<",
|
|
2382
|
+
"2>",
|
|
2383
|
+
"&",
|
|
2384
|
+
]);
|
|
2385
|
+
|
|
2386
|
+
function splitShellLikeCommand(command: string): string[] {
|
|
2387
|
+
const tokens: string[] = [];
|
|
2388
|
+
let current = "";
|
|
2389
|
+
let quote: '"' | "'" | null = null;
|
|
2390
|
+
let escape = false;
|
|
2391
|
+
|
|
2392
|
+
for (const char of command.trim()) {
|
|
2393
|
+
if (escape) {
|
|
2394
|
+
current += char;
|
|
2395
|
+
escape = false;
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
if (char === "\\") {
|
|
2400
|
+
escape = true;
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
if (quote) {
|
|
2405
|
+
if (char === quote) {
|
|
2406
|
+
quote = null;
|
|
2407
|
+
} else {
|
|
2408
|
+
current += char;
|
|
2409
|
+
}
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
if (char === '"' || char === "'") {
|
|
2414
|
+
quote = char;
|
|
2415
|
+
continue;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
if (/\s/.test(char)) {
|
|
2419
|
+
if (current) {
|
|
2420
|
+
tokens.push(current);
|
|
2421
|
+
current = "";
|
|
2422
|
+
}
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
current += char;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
if (escape) current += "\\";
|
|
2430
|
+
if (quote) throw new Error("Command has an unclosed quote");
|
|
2431
|
+
if (current) tokens.push(current);
|
|
2432
|
+
return tokens;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function normalizeSandboxRelativePath(filePath: string, label: string): string {
|
|
2436
|
+
const normalized = filePath.replace(/\\/g, "/").trim();
|
|
2437
|
+
if (!normalized || normalized === ".") {
|
|
2438
|
+
return "";
|
|
2439
|
+
}
|
|
2440
|
+
if (path.isAbsolute(normalized)) {
|
|
2441
|
+
throw new Error(`${label} must be relative to the webpack lab root`);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
2445
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
2446
|
+
throw new Error(`${label} must stay inside the webpack lab root`);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
return parts.join("/");
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function isPathInside(root: string, target: string): boolean {
|
|
2453
|
+
const relative = path.relative(root, target);
|
|
2454
|
+
return (
|
|
2455
|
+
relative === "" ||
|
|
2456
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
function parseModuleFederationCommand(command: string): {
|
|
2461
|
+
args: string[];
|
|
2462
|
+
displayCommand: string;
|
|
2463
|
+
} {
|
|
2464
|
+
const tokens = splitShellLikeCommand(command);
|
|
2465
|
+
if (tokens.length < 3 || tokens[0] !== "npm" || tokens[1] !== "run") {
|
|
2466
|
+
throw new Error(
|
|
2467
|
+
"Only npm run <script> commands are allowed in the webpack lab console",
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
if (tokens.some((token) => MODULE_FEDERATION_SHELL_META_TOKENS.has(token))) {
|
|
2472
|
+
throw new Error(
|
|
2473
|
+
"Shell operators are not supported here. Run one npm command at a time.",
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
if (
|
|
2478
|
+
tokens.some(
|
|
2479
|
+
(token) => token === "--prefix" || token.startsWith("--prefix="),
|
|
2480
|
+
)
|
|
2481
|
+
) {
|
|
2482
|
+
throw new Error(
|
|
2483
|
+
"Use the folder selector instead of npm --prefix in the webpack lab console.",
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
return {
|
|
2488
|
+
args: tokens.slice(1),
|
|
2489
|
+
displayCommand: `$ ${tokens.join(" ")}`,
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
async function resolveModuleFederationCommandCwd(
|
|
2494
|
+
sandboxDir: string,
|
|
2495
|
+
cwd: string | undefined,
|
|
2496
|
+
): Promise<{ normalized: string; fullPath: string }> {
|
|
2497
|
+
const normalized = normalizeSandboxRelativePath(
|
|
2498
|
+
cwd ?? "",
|
|
2499
|
+
"Command directory",
|
|
2500
|
+
);
|
|
2501
|
+
const fullPath = path.resolve(sandboxDir, normalized || ".");
|
|
2502
|
+
|
|
2503
|
+
if (!isPathInside(path.resolve(sandboxDir), fullPath)) {
|
|
2504
|
+
throw new Error(
|
|
2505
|
+
"Command directory must stay inside the webpack lab sandbox",
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
const stats = await fs.stat(fullPath).catch(() => null);
|
|
2510
|
+
if (!stats?.isDirectory()) {
|
|
2511
|
+
throw new Error(`Command directory not found: ${normalized || "."}`);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
return { normalized, fullPath };
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function getModuleFederationCommandEnv(
|
|
2518
|
+
sandbox: ModuleFederationSandboxEntry,
|
|
2519
|
+
): NodeJS.ProcessEnv {
|
|
2520
|
+
const hostPort = new URL(sandbox.appUrls.host).port;
|
|
2521
|
+
const profilePort = new URL(sandbox.appUrls.profile).port;
|
|
2522
|
+
const checkoutPort = new URL(sandbox.appUrls.checkout).port;
|
|
2523
|
+
|
|
2524
|
+
return {
|
|
2525
|
+
...process.env,
|
|
2526
|
+
HOST_PORT: hostPort,
|
|
2527
|
+
PROFILE_PORT: profilePort,
|
|
2528
|
+
CHECKOUT_PORT: checkoutPort,
|
|
2529
|
+
npm_config_update_notifier: "false",
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
async function runStreamedCommand(
|
|
2534
|
+
command: string,
|
|
2535
|
+
args: string[],
|
|
2536
|
+
options: { cwd: string; env?: NodeJS.ProcessEnv },
|
|
2537
|
+
onChunk: (payload: {
|
|
2538
|
+
kind: ModuleFederationCommandOutputKind;
|
|
2539
|
+
text: string;
|
|
2540
|
+
}) => void,
|
|
2541
|
+
): Promise<void> {
|
|
2542
|
+
await new Promise<void>((resolve, reject) => {
|
|
2543
|
+
const stdout: string[] = [];
|
|
2544
|
+
const stderr: string[] = [];
|
|
2545
|
+
const child = spawn(command, args, {
|
|
2546
|
+
cwd: options.cwd,
|
|
2547
|
+
env: options.env,
|
|
2548
|
+
});
|
|
2549
|
+
|
|
2550
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
2551
|
+
const text = chunk.toString().replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
2552
|
+
stdout.push(text);
|
|
2553
|
+
onChunk({ kind: "stdout", text });
|
|
2554
|
+
});
|
|
2555
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
2556
|
+
const text = chunk.toString().replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
2557
|
+
stderr.push(text);
|
|
2558
|
+
onChunk({ kind: "stderr", text });
|
|
2559
|
+
});
|
|
2560
|
+
child.on("error", (error) => {
|
|
2561
|
+
reject(new Error(`${command} launch failed: ${error.message}`));
|
|
2562
|
+
});
|
|
2563
|
+
child.on("close", (code) => {
|
|
2564
|
+
if (code === 0) {
|
|
2565
|
+
resolve();
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
reject(
|
|
2570
|
+
new Error(
|
|
2571
|
+
stderr.join("").trim() ||
|
|
2572
|
+
stdout.join("").trim() ||
|
|
2573
|
+
`${command} ${args.join(" ")} exited with code ${String(code)}`,
|
|
2574
|
+
),
|
|
2575
|
+
);
|
|
2576
|
+
});
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
async function walkAllFiles(dir: string, prefix = ""): Promise<string[]> {
|
|
2581
|
+
const entries = await fs
|
|
2582
|
+
.readdir(dir, { withFileTypes: true })
|
|
2583
|
+
.catch(() => []);
|
|
2584
|
+
const files: string[] = [];
|
|
2585
|
+
|
|
2586
|
+
for (const entry of entries) {
|
|
2587
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2588
|
+
if (entry.isDirectory()) {
|
|
2589
|
+
files.push(...(await walkAllFiles(path.join(dir, entry.name), rel)));
|
|
2590
|
+
} else {
|
|
2591
|
+
files.push(rel);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
return files;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
async function listModuleFederationGeneratedFiles(
|
|
2599
|
+
dir: string,
|
|
2600
|
+
): Promise<string[]> {
|
|
2601
|
+
const files: string[] = [];
|
|
2602
|
+
|
|
2603
|
+
const visit = async (currentDir: string, prefix = "") => {
|
|
2604
|
+
const entries = await fs
|
|
2605
|
+
.readdir(currentDir, { withFileTypes: true })
|
|
2606
|
+
.catch(() => []);
|
|
2607
|
+
|
|
2608
|
+
for (const entry of entries) {
|
|
2609
|
+
if (entry.name === "node_modules") continue;
|
|
2610
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2611
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
2612
|
+
|
|
2613
|
+
if (entry.isDirectory()) {
|
|
2614
|
+
if (entry.name === "dist") {
|
|
2615
|
+
files.push(...(await walkAllFiles(fullPath, rel)));
|
|
2616
|
+
} else {
|
|
2617
|
+
await visit(fullPath, rel);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
await visit(dir);
|
|
2624
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2372
2627
|
async function getDistinctPorts(count: number): Promise<number[]> {
|
|
2373
2628
|
const ports = new Set<number>();
|
|
2374
2629
|
while (ports.size < count) {
|
|
@@ -2722,6 +2977,8 @@ app.get("/api/module-federation/:id/status", (req, res) => {
|
|
|
2722
2977
|
return res.json({ running: false });
|
|
2723
2978
|
}
|
|
2724
2979
|
|
|
2980
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2981
|
+
|
|
2725
2982
|
res.json({
|
|
2726
2983
|
running: true,
|
|
2727
2984
|
ready: sandbox.ready,
|
|
@@ -2731,6 +2988,125 @@ app.get("/api/module-federation/:id/status", (req, res) => {
|
|
|
2731
2988
|
});
|
|
2732
2989
|
});
|
|
2733
2990
|
|
|
2991
|
+
app.post("/api/module-federation/:id/command-stream", async (req, res) => {
|
|
2992
|
+
const sandbox = moduleFederationSandboxes.get(req.params.id);
|
|
2993
|
+
|
|
2994
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
2995
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
2996
|
+
res.setHeader("Connection", "keep-alive");
|
|
2997
|
+
res.flushHeaders();
|
|
2998
|
+
|
|
2999
|
+
const send = (payload: unknown) => {
|
|
3000
|
+
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
3001
|
+
};
|
|
3002
|
+
|
|
3003
|
+
if (!sandbox) {
|
|
3004
|
+
send({ type: "error", error: "Sandbox not found" });
|
|
3005
|
+
res.end();
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
const { command, cwd } = req.body as { command?: string; cwd?: string };
|
|
3010
|
+
|
|
3011
|
+
if (typeof command !== "string" || !command.trim()) {
|
|
3012
|
+
send({ type: "error", error: "command is required" });
|
|
3013
|
+
res.end();
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
try {
|
|
3018
|
+
const parsed = parseModuleFederationCommand(command);
|
|
3019
|
+
const resolvedCwd = await resolveModuleFederationCommandCwd(
|
|
3020
|
+
sandbox.dir,
|
|
3021
|
+
cwd,
|
|
3022
|
+
);
|
|
3023
|
+
|
|
3024
|
+
send({
|
|
3025
|
+
type: "output",
|
|
3026
|
+
kind: "info",
|
|
3027
|
+
text: `cwd: ${resolvedCwd.normalized || "."}\n${parsed.displayCommand}\n`,
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
await runStreamedCommand(
|
|
3031
|
+
npmCommand(),
|
|
3032
|
+
parsed.args,
|
|
3033
|
+
{
|
|
3034
|
+
cwd: resolvedCwd.fullPath,
|
|
3035
|
+
env: getModuleFederationCommandEnv(sandbox),
|
|
3036
|
+
},
|
|
3037
|
+
({ kind, text }) => {
|
|
3038
|
+
send({ type: "output", kind, text });
|
|
3039
|
+
},
|
|
3040
|
+
);
|
|
3041
|
+
|
|
3042
|
+
send({ type: "complete" });
|
|
3043
|
+
} catch (error: any) {
|
|
3044
|
+
send({
|
|
3045
|
+
type: "error",
|
|
3046
|
+
error: error?.message || "Failed to run webpack lab command",
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
res.end();
|
|
3051
|
+
});
|
|
3052
|
+
|
|
3053
|
+
app.get("/api/module-federation/:id/generated-files", async (req, res) => {
|
|
3054
|
+
const sandbox = moduleFederationSandboxes.get(req.params.id);
|
|
3055
|
+
if (!sandbox) {
|
|
3056
|
+
return res.status(404).json({ error: "Sandbox not found" });
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
res.setHeader("Cache-Control", "no-store");
|
|
3060
|
+
|
|
3061
|
+
try {
|
|
3062
|
+
const files = await listModuleFederationGeneratedFiles(sandbox.dir);
|
|
3063
|
+
res.json({ files });
|
|
3064
|
+
} catch (error: any) {
|
|
3065
|
+
res.status(500).json({
|
|
3066
|
+
error: error?.message || "Failed to list webpack lab generated files",
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
});
|
|
3070
|
+
|
|
3071
|
+
app.get("/api/module-federation/:id/generated-file", async (req, res) => {
|
|
3072
|
+
const sandbox = moduleFederationSandboxes.get(req.params.id);
|
|
3073
|
+
if (!sandbox) {
|
|
3074
|
+
return res.status(404).json({ error: "Sandbox not found" });
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
res.setHeader("Cache-Control", "no-store");
|
|
3078
|
+
|
|
3079
|
+
const filePath =
|
|
3080
|
+
typeof req.query.path === "string" ? req.query.path : undefined;
|
|
3081
|
+
if (!filePath) {
|
|
3082
|
+
return res.status(400).json({ error: "path is required" });
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
try {
|
|
3086
|
+
const normalized = normalizeSandboxRelativePath(
|
|
3087
|
+
filePath,
|
|
3088
|
+
"Generated file path",
|
|
3089
|
+
);
|
|
3090
|
+
if (!/(^|\/)dist\//.test(normalized)) {
|
|
3091
|
+
return res.status(400).json({
|
|
3092
|
+
error: "Only generated dist files can be read here",
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
const fullPath = path.resolve(sandbox.dir, normalized);
|
|
3097
|
+
if (!isPathInside(path.resolve(sandbox.dir), fullPath)) {
|
|
3098
|
+
return res.status(403).json({ error: "Access denied" });
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
3102
|
+
res.json({ path: normalized, content });
|
|
3103
|
+
} catch (error: any) {
|
|
3104
|
+
res.status(500).json({
|
|
3105
|
+
error: error?.message || "Failed to read webpack lab generated file",
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
|
|
2734
3110
|
app.delete("/api/module-federation/:id", async (req, res) => {
|
|
2735
3111
|
const sandbox = moduleFederationSandboxes.get(req.params.id);
|
|
2736
3112
|
if (sandbox) {
|