create-interview-cockpit 0.6.0 → 0.7.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.
@@ -1,5 +1,7 @@
1
1
  import type { FrontendLabWorkspace } from "./types";
2
2
 
3
+ export type FrontendLabType = FrontendLabWorkspace["type"];
4
+
3
5
  // ── Default file contents ────────────────────────────────────────────────────
4
6
 
5
7
  const REACT_DEFAULT_FILES: Record<string, string> = {
@@ -207,6 +209,457 @@ export interface User {
207
209
  `,
208
210
  };
209
211
 
212
+ const MODULE_FEDERATION_DEFAULT_FILES: Record<string, string> = {
213
+ "README.md": `# Webpack Module Federation Lab
214
+
215
+ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
216
+
217
+ ## What is here
218
+
219
+ - package.json runs three apps together: a host plus two remotes
220
+ - apps/host consumes federated modules from the remotes
221
+ - apps/profile exposes a profile widget
222
+ - apps/checkout exposes a checkout widget
223
+
224
+ ## Good experiments
225
+
226
+ 1. Break one remote URL in apps/host/webpack.config.js and see how the host fails.
227
+ 2. Rename an exposed module in a remote without updating the host import.
228
+ 3. Stop sharing React as a singleton and inspect the runtime behavior.
229
+ 4. Add a new remote by copying an existing app and wiring it into the host.
230
+
231
+ ## Notes
232
+
233
+ - Ports are injected by the lab runner through environment variables.
234
+ - If you change package.json, restart the webpack lab so dependencies/scripts are re-read.
235
+ `,
236
+ "package.json": `{
237
+ "name": "webpack-module-federation-lab",
238
+ "private": true,
239
+ "scripts": {
240
+ "dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm run dev:host' 'npm run dev:profile' 'npm run dev:checkout'",
241
+ "dev:host": "webpack serve --config apps/host/webpack.config.js",
242
+ "dev:profile": "webpack serve --config apps/profile/webpack.config.js",
243
+ "dev:checkout": "webpack serve --config apps/checkout/webpack.config.js",
244
+ "build": "npm run build:host && npm run build:profile && npm run build:checkout",
245
+ "build:host": "webpack --config apps/host/webpack.config.js",
246
+ "build:profile": "webpack --config apps/profile/webpack.config.js",
247
+ "build:checkout": "webpack --config apps/checkout/webpack.config.js"
248
+ },
249
+ "dependencies": {
250
+ "react": "^19.0.0",
251
+ "react-dom": "^19.0.0"
252
+ },
253
+ "devDependencies": {
254
+ "concurrently": "^9.2.1",
255
+ "esbuild": "^0.28.0",
256
+ "esbuild-loader": "^4.4.3",
257
+ "html-webpack-plugin": "^5.6.7",
258
+ "webpack": "^5.106.2",
259
+ "webpack-cli": "^7.0.2",
260
+ "webpack-dev-server": "^5.2.3"
261
+ }
262
+ }
263
+ `,
264
+ "apps/host/public/index.html": `<!doctype html>
265
+ <html lang="en">
266
+ <head>
267
+ <meta charset="utf-8" />
268
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
269
+ <title>Host App</title>
270
+ </head>
271
+ <body>
272
+ <div id="root"></div>
273
+ </body>
274
+ </html>
275
+ `,
276
+ "apps/host/src/index.jsx": `import("./bootstrap");
277
+ `,
278
+ "apps/host/src/bootstrap.jsx": `import React from "react";
279
+ import { createRoot } from "react-dom/client";
280
+ import App from "./App";
281
+
282
+ const root = createRoot(document.getElementById("root"));
283
+
284
+ root.render(
285
+ <React.StrictMode>
286
+ <App />
287
+ </React.StrictMode>,
288
+ );
289
+ `,
290
+ "apps/host/src/App.jsx": `import React, { Suspense } from "react";
291
+
292
+ const ProfileCard = React.lazy(() => import("profile/ProfileCard"));
293
+ const CheckoutPanel = React.lazy(() => import("checkout/CheckoutPanel"));
294
+
295
+ function RemoteBoundary({ title, children }) {
296
+ return (
297
+ <div
298
+ style={{
299
+ border: "1px solid #cbd5e1",
300
+ borderRadius: "0.75rem",
301
+ padding: "1rem",
302
+ background: "#fff",
303
+ }}
304
+ >
305
+ <div style={{ fontSize: "0.8rem", color: "#64748b", marginBottom: "0.75rem" }}>
306
+ {title}
307
+ </div>
308
+ <Suspense fallback={<p style={{ color: "#64748b" }}>Loading remote...</p>}>
309
+ {children}
310
+ </Suspense>
311
+ </div>
312
+ );
313
+ }
314
+
315
+ export default function App() {
316
+ return (
317
+ <main
318
+ style={{
319
+ minHeight: "100vh",
320
+ margin: 0,
321
+ padding: "2rem",
322
+ background: "linear-gradient(135deg, #e0f2fe 0%, #f8fafc 50%, #fef3c7 100%)",
323
+ fontFamily: "ui-sans-serif, system-ui, sans-serif",
324
+ }}
325
+ >
326
+ <div style={{ maxWidth: "1100px", margin: "0 auto" }}>
327
+ <div style={{ marginBottom: "1.5rem" }}>
328
+ <p style={{ margin: 0, color: "#0369a1", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
329
+ Webpack 5 Host
330
+ </p>
331
+ <h1 style={{ margin: "0.35rem 0 0", fontSize: "2rem", color: "#0f172a" }}>
332
+ Module Federation Playground
333
+ </h1>
334
+ <p style={{ color: "#475569", maxWidth: "52rem" }}>
335
+ The host renders two independently built remotes. Change a remote expose, shared dependency,
336
+ or URL in the webpack configs to see the same failures you would hit in a real setup.
337
+ </p>
338
+ </div>
339
+
340
+ <section
341
+ style={{
342
+ display: "grid",
343
+ gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
344
+ gap: "1rem",
345
+ }}
346
+ >
347
+ <RemoteBoundary title="Remote: profile/ProfileCard">
348
+ <ProfileCard />
349
+ </RemoteBoundary>
350
+ <RemoteBoundary title="Remote: checkout/CheckoutPanel">
351
+ <CheckoutPanel />
352
+ </RemoteBoundary>
353
+ </section>
354
+ </div>
355
+ </main>
356
+ );
357
+ }
358
+ `,
359
+ "apps/host/webpack.config.js": `const path = require("path");
360
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
361
+ const { ModuleFederationPlugin } = require("webpack").container;
362
+
363
+ const hostPort = Number(process.env.HOST_PORT || 3100);
364
+ const profilePort = Number(process.env.PROFILE_PORT || 3101);
365
+ const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
366
+
367
+ module.exports = {
368
+ mode: "development",
369
+ entry: path.resolve(__dirname, "./src/index.jsx"),
370
+ output: {
371
+ publicPath: "http://localhost:" + hostPort + "/",
372
+ clean: true,
373
+ },
374
+ resolve: {
375
+ extensions: [".js", ".jsx"],
376
+ },
377
+ module: {
378
+ rules: [
379
+ {
380
+ test: /\\.(js|jsx)$/,
381
+ exclude: /node_modules/,
382
+ use: {
383
+ loader: "esbuild-loader",
384
+ options: {
385
+ loader: "jsx",
386
+ jsx: "automatic",
387
+ target: "es2020",
388
+ },
389
+ },
390
+ },
391
+ ],
392
+ },
393
+ devServer: {
394
+ port: hostPort,
395
+ historyApiFallback: true,
396
+ hot: true,
397
+ headers: {
398
+ "Access-Control-Allow-Origin": "*",
399
+ },
400
+ },
401
+ plugins: [
402
+ new ModuleFederationPlugin({
403
+ name: "host",
404
+ remotes: {
405
+ profile: "profile@http://localhost:" + profilePort + "/remoteEntry.js",
406
+ checkout: "checkout@http://localhost:" + checkoutPort + "/remoteEntry.js",
407
+ },
408
+ shared: {
409
+ react: { singleton: true, requiredVersion: false },
410
+ "react-dom": { singleton: true, requiredVersion: false },
411
+ },
412
+ }),
413
+ new HtmlWebpackPlugin({
414
+ template: path.resolve(__dirname, "./public/index.html"),
415
+ }),
416
+ ],
417
+ };
418
+ `,
419
+ "apps/profile/public/index.html": `<!doctype html>
420
+ <html lang="en">
421
+ <head>
422
+ <meta charset="utf-8" />
423
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
424
+ <title>Profile Remote</title>
425
+ </head>
426
+ <body>
427
+ <div id="root"></div>
428
+ </body>
429
+ </html>
430
+ `,
431
+ "apps/profile/src/index.jsx": `import("./bootstrap");
432
+ `,
433
+ "apps/profile/src/bootstrap.jsx": `import React from "react";
434
+ import { createRoot } from "react-dom/client";
435
+ import App from "./App";
436
+
437
+ const root = createRoot(document.getElementById("root"));
438
+
439
+ root.render(
440
+ <React.StrictMode>
441
+ <App />
442
+ </React.StrictMode>,
443
+ );
444
+ `,
445
+ "apps/profile/src/App.jsx": `import React from "react";
446
+ import ProfileCard from "./ProfileCard";
447
+
448
+ export default function App() {
449
+ return (
450
+ <main style={{ padding: "2rem", fontFamily: "ui-sans-serif, system-ui, sans-serif", background: "#f8fafc", minHeight: "100vh" }}>
451
+ <p style={{ margin: 0, color: "#7c3aed", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
452
+ Remote App
453
+ </p>
454
+ <h1 style={{ margin: "0.35rem 0 1rem", color: "#1e293b" }}>Profile</h1>
455
+ <ProfileCard />
456
+ </main>
457
+ );
458
+ }
459
+ `,
460
+ "apps/profile/src/ProfileCard.jsx": `import React from "react";
461
+
462
+ export default function ProfileCard() {
463
+ return (
464
+ <section>
465
+ <h2 style={{ marginTop: 0, color: "#1e293b" }}>Federated profile card</h2>
466
+ <p style={{ color: "#475569" }}>
467
+ This component is exposed from the profile remote and consumed by the host at runtime.
468
+ </p>
469
+ <dl style={{ display: "grid", gridTemplateColumns: "max-content 1fr", gap: "0.5rem 1rem", margin: 0 }}>
470
+ <dt style={{ color: "#64748b" }}>Owner</dt>
471
+ <dd style={{ margin: 0, color: "#0f172a" }}>Composable Platform Team</dd>
472
+ <dt style={{ color: "#64748b" }}>Build</dt>
473
+ <dd style={{ margin: 0, color: "#0f172a" }}>profile/ProfileCard</dd>
474
+ </dl>
475
+ </section>
476
+ );
477
+ }
478
+ `,
479
+ "apps/profile/webpack.config.js": `const path = require("path");
480
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
481
+ const { ModuleFederationPlugin } = require("webpack").container;
482
+
483
+ const profilePort = Number(process.env.PROFILE_PORT || 3101);
484
+
485
+ module.exports = {
486
+ mode: "development",
487
+ entry: path.resolve(__dirname, "./src/index.jsx"),
488
+ output: {
489
+ publicPath: "http://localhost:" + profilePort + "/",
490
+ clean: true,
491
+ },
492
+ resolve: {
493
+ extensions: [".js", ".jsx"],
494
+ },
495
+ module: {
496
+ rules: [
497
+ {
498
+ test: /\\.(js|jsx)$/,
499
+ exclude: /node_modules/,
500
+ use: {
501
+ loader: "esbuild-loader",
502
+ options: {
503
+ loader: "jsx",
504
+ jsx: "automatic",
505
+ target: "es2020",
506
+ },
507
+ },
508
+ },
509
+ ],
510
+ },
511
+ devServer: {
512
+ port: profilePort,
513
+ hot: true,
514
+ headers: {
515
+ "Access-Control-Allow-Origin": "*",
516
+ },
517
+ },
518
+ plugins: [
519
+ new ModuleFederationPlugin({
520
+ name: "profile",
521
+ filename: "remoteEntry.js",
522
+ exposes: {
523
+ "./ProfileCard": path.resolve(__dirname, "./src/ProfileCard.jsx"),
524
+ },
525
+ shared: {
526
+ react: { singleton: true, requiredVersion: false },
527
+ "react-dom": { singleton: true, requiredVersion: false },
528
+ },
529
+ }),
530
+ new HtmlWebpackPlugin({
531
+ template: path.resolve(__dirname, "./public/index.html"),
532
+ }),
533
+ ],
534
+ };
535
+ `,
536
+ "apps/checkout/public/index.html": `<!doctype html>
537
+ <html lang="en">
538
+ <head>
539
+ <meta charset="utf-8" />
540
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
541
+ <title>Checkout Remote</title>
542
+ </head>
543
+ <body>
544
+ <div id="root"></div>
545
+ </body>
546
+ </html>
547
+ `,
548
+ "apps/checkout/src/index.jsx": `import("./bootstrap");
549
+ `,
550
+ "apps/checkout/src/bootstrap.jsx": `import React from "react";
551
+ import { createRoot } from "react-dom/client";
552
+ import App from "./App";
553
+
554
+ const root = createRoot(document.getElementById("root"));
555
+
556
+ root.render(
557
+ <React.StrictMode>
558
+ <App />
559
+ </React.StrictMode>,
560
+ );
561
+ `,
562
+ "apps/checkout/src/App.jsx": `import React from "react";
563
+ import CheckoutPanel from "./CheckoutPanel";
564
+
565
+ export default function App() {
566
+ return (
567
+ <main style={{ padding: "2rem", fontFamily: "ui-sans-serif, system-ui, sans-serif", background: "#fff7ed", minHeight: "100vh" }}>
568
+ <p style={{ margin: 0, color: "#ea580c", fontSize: "0.8rem", letterSpacing: "0.08em", textTransform: "uppercase" }}>
569
+ Remote App
570
+ </p>
571
+ <h1 style={{ margin: "0.35rem 0 1rem", color: "#7c2d12" }}>Checkout</h1>
572
+ <CheckoutPanel />
573
+ </main>
574
+ );
575
+ }
576
+ `,
577
+ "apps/checkout/src/CheckoutPanel.jsx": `import React from "react";
578
+
579
+ export default function CheckoutPanel() {
580
+ return (
581
+ <section>
582
+ <h2 style={{ marginTop: 0, color: "#7c2d12" }}>Federated checkout panel</h2>
583
+ <p style={{ color: "#9a3412" }}>
584
+ This remote can evolve independently from the host as long as the public module contract stays stable.
585
+ </p>
586
+ <button
587
+ type="button"
588
+ style={{
589
+ border: 0,
590
+ borderRadius: "999px",
591
+ background: "#fb923c",
592
+ color: "#431407",
593
+ padding: "0.65rem 1rem",
594
+ fontWeight: 700,
595
+ cursor: "pointer",
596
+ }}
597
+ >
598
+ Ship order
599
+ </button>
600
+ </section>
601
+ );
602
+ }
603
+ `,
604
+ "apps/checkout/webpack.config.js": `const path = require("path");
605
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
606
+ const { ModuleFederationPlugin } = require("webpack").container;
607
+
608
+ const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
609
+
610
+ module.exports = {
611
+ mode: "development",
612
+ entry: path.resolve(__dirname, "./src/index.jsx"),
613
+ output: {
614
+ publicPath: "http://localhost:" + checkoutPort + "/",
615
+ clean: true,
616
+ },
617
+ resolve: {
618
+ extensions: [".js", ".jsx"],
619
+ },
620
+ module: {
621
+ rules: [
622
+ {
623
+ test: /\\.(js|jsx)$/,
624
+ exclude: /node_modules/,
625
+ use: {
626
+ loader: "esbuild-loader",
627
+ options: {
628
+ loader: "jsx",
629
+ jsx: "automatic",
630
+ target: "es2020",
631
+ },
632
+ },
633
+ },
634
+ ],
635
+ },
636
+ devServer: {
637
+ port: checkoutPort,
638
+ hot: true,
639
+ headers: {
640
+ "Access-Control-Allow-Origin": "*",
641
+ },
642
+ },
643
+ plugins: [
644
+ new ModuleFederationPlugin({
645
+ name: "checkout",
646
+ filename: "remoteEntry.js",
647
+ exposes: {
648
+ "./CheckoutPanel": path.resolve(__dirname, "./src/CheckoutPanel.jsx"),
649
+ },
650
+ shared: {
651
+ react: { singleton: true, requiredVersion: false },
652
+ "react-dom": { singleton: true, requiredVersion: false },
653
+ },
654
+ }),
655
+ new HtmlWebpackPlugin({
656
+ template: path.resolve(__dirname, "./public/index.html"),
657
+ }),
658
+ ],
659
+ };
660
+ `,
661
+ };
662
+
210
663
  // ── Lab workspace constructors ────────────────────────────────────────────────
211
664
 
212
665
  export const DEFAULT_REACT_LAB: FrontendLabWorkspace = {
@@ -225,13 +678,23 @@ export const DEFAULT_NEXTJS_LAB: FrontendLabWorkspace = {
225
678
  files: NEXTJS_DEFAULT_FILES,
226
679
  };
227
680
 
228
- export function defaultForType(type: "react" | "nextjs"): FrontendLabWorkspace {
229
- return type === "nextjs" ? DEFAULT_NEXTJS_LAB : DEFAULT_REACT_LAB;
681
+ export const DEFAULT_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
682
+ version: 1,
683
+ label: "Webpack Module Federation Lab",
684
+ type: "module-federation",
685
+ activeFile: "apps/host/src/App.jsx",
686
+ files: MODULE_FEDERATION_DEFAULT_FILES,
687
+ };
688
+
689
+ export function defaultForType(type: FrontendLabType): FrontendLabWorkspace {
690
+ if (type === "nextjs") return DEFAULT_NEXTJS_LAB;
691
+ if (type === "module-federation") return DEFAULT_MODULE_FEDERATION_LAB;
692
+ return DEFAULT_REACT_LAB;
230
693
  }
231
694
 
232
695
  export function cloneFrontendLabWorkspace(
233
696
  workspace?: FrontendLabWorkspace | null,
234
- type?: "react" | "nextjs",
697
+ type?: FrontendLabType,
235
698
  ): FrontendLabWorkspace {
236
699
  const resolvedType = workspace?.type ?? type ?? "react";
237
700
  const defaults = defaultForType(resolvedType);
@@ -276,8 +739,12 @@ export function parseFrontendLabWorkspace(
276
739
  );
277
740
  if (Object.keys(files).length === 0) return null;
278
741
 
279
- const type: "react" | "nextjs" =
280
- parsed.type === "nextjs" ? "nextjs" : "react";
742
+ const type: FrontendLabType =
743
+ parsed.type === "nextjs"
744
+ ? "nextjs"
745
+ : parsed.type === "module-federation"
746
+ ? "module-federation"
747
+ : "react";
281
748
 
282
749
  return cloneFrontendLabWorkspace({
283
750
  version: 1,
@@ -304,6 +771,11 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
304
771
  ? "app/page.tsx"
305
772
  : Object.keys(workspace.files)[0];
306
773
  }
774
+ if (workspace.type === "module-federation") {
775
+ return workspace.files["apps/host/src/App.jsx"]
776
+ ? "apps/host/src/App.jsx"
777
+ : Object.keys(workspace.files)[0];
778
+ }
307
779
  return workspace.files["App.tsx"]
308
780
  ? "App.tsx"
309
781
  : Object.keys(workspace.files)[0];
@@ -313,6 +785,17 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
313
785
  export function getFrontendLabFileOrder(
314
786
  workspace: FrontendLabWorkspace,
315
787
  ): string[] {
788
+ if (workspace.type === "module-federation") {
789
+ const preferred = ["README.md", "package.json"];
790
+ const rest = Object.keys(workspace.files)
791
+ .filter((name) => !preferred.includes(name))
792
+ .sort((a, b) => {
793
+ const ad = a.split("/").length;
794
+ const bd = b.split("/").length;
795
+ return ad !== bd ? ad - bd : a.localeCompare(b);
796
+ });
797
+ return preferred.filter((name) => workspace.files[name]).concat(rest);
798
+ }
316
799
  const allFiles = Object.keys(workspace.files).sort((a, b) => {
317
800
  // Sort by folder depth first, then alphabetically
318
801
  const ad = a.split("/").length;
@@ -212,7 +212,14 @@ interface Store {
212
212
  code: string,
213
213
  language: string,
214
214
  label: string,
215
- origin: "user" | "ai" | "sandbox" | "infra" | "react" | "nextjs",
215
+ origin:
216
+ | "user"
217
+ | "ai"
218
+ | "sandbox"
219
+ | "infra"
220
+ | "react"
221
+ | "nextjs"
222
+ | "module-federation",
216
223
  ) => Promise<import("./types").ContextFile>;
217
224
  clearMessages: (questionId: string) => Promise<void>;
218
225
 
@@ -273,7 +280,7 @@ interface Store {
273
280
  clientLang: string;
274
281
  fileId?: string;
275
282
  /** If set, the client panel opens in React or Next.js preview mode instead of script mode */
276
- clientType?: "script" | "react" | "nextjs";
283
+ clientType?: "script" | "react" | "nextjs" | "module-federation";
277
284
  reactFiles?: Record<string, string> | null;
278
285
  reactActiveFile?: string | null;
279
286
  } | null;
@@ -287,7 +294,7 @@ interface Store {
287
294
  clientLang: string,
288
295
  fileId?: string,
289
296
  opts?: {
290
- clientType?: "script" | "react" | "nextjs";
297
+ clientType?: "script" | "react" | "nextjs" | "module-federation";
291
298
  reactFiles?: Record<string, string>;
292
299
  reactActiveFile?: string;
293
300
  },
@@ -311,7 +318,7 @@ interface Store {
311
318
  openInfraLab: (workspace?: InfraLabWorkspace, fileId?: string) => void;
312
319
  closeInfraLab: () => void;
313
320
 
314
- // ── Frontend Labs (React / Next.js) — open inside the sandbox ──
321
+ // ── Frontend Labs (React / Next.js / Module Federation) — open inside the sandbox ──
315
322
  openReactLab: (
316
323
  workspace?: FrontendLabWorkspace,
317
324
  fileId?: string,
@@ -324,6 +331,12 @@ interface Store {
324
331
  serverCode?: string,
325
332
  serverLang?: string,
326
333
  ) => void;
334
+ openModuleFederationLab: (
335
+ workspace?: FrontendLabWorkspace,
336
+ fileId?: string,
337
+ serverCode?: string,
338
+ serverLang?: string,
339
+ ) => void;
327
340
  }
328
341
 
329
342
  export const useStore = create<Store>((set, get) => ({
@@ -959,6 +972,24 @@ export const useStore = create<Store>((set, get) => ({
959
972
  },
960
973
  runnerInitialFileId: null,
961
974
  }),
975
+ openModuleFederationLab: (workspace?, fileId?, serverCode?, serverLang?) =>
976
+ set({
977
+ showCodeRunner: true,
978
+ showInfraLab: false,
979
+ runnerInitialCode: "",
980
+ runnerInitialLanguage: "typescript",
981
+ runnerInitialSandbox: {
982
+ serverCode: serverCode ?? DEFAULT_SERVER_CODE,
983
+ serverLang: serverLang ?? "typescript",
984
+ clientCode: "",
985
+ clientLang: "javascript",
986
+ fileId,
987
+ clientType: "module-federation",
988
+ reactFiles: workspace?.files ?? null,
989
+ reactActiveFile: workspace?.activeFile ?? null,
990
+ },
991
+ runnerInitialFileId: null,
992
+ }),
962
993
 
963
994
  overwriteContextFileContent: async (questionId, fileId, content) => {
964
995
  await api.overwriteContextFileContent(questionId, fileId, content);
@@ -5,7 +5,8 @@ export type ContextFileOrigin =
5
5
  | "sandbox"
6
6
  | "infra"
7
7
  | "react"
8
- | "nextjs";
8
+ | "nextjs"
9
+ | "module-federation";
9
10
 
10
11
  export interface ContextFile {
11
12
  id: string;
@@ -28,7 +29,7 @@ export interface FrontendLabWorkspace {
28
29
  version: 1;
29
30
  label: string;
30
31
  /** Determines defaults, entry file, and file-tree conventions. */
31
- type: "react" | "nextjs";
32
+ type: "react" | "nextjs" | "module-federation";
32
33
  activeFile: string;
33
34
  files: Record<string, string>;
34
35
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.4.0"
2
+ "version": "0.5.0"
3
3
  }
@@ -360,6 +360,7 @@ export async function syncWorkspace(
360
360
  cs.origin === "sandbox" ||
361
361
  cs.origin === "react" ||
362
362
  cs.origin === "nextjs" ||
363
+ cs.origin === "module-federation" ||
363
364
  cs.origin === "infra")
364
365
  ) {
365
366
  try {
@@ -770,6 +771,7 @@ export async function exportWorkspace(
770
771
  cf.origin === "sandbox" ||
771
772
  cf.origin === "react" ||
772
773
  cf.origin === "nextjs" ||
774
+ cf.origin === "module-federation" ||
773
775
  cf.origin === "infra"
774
776
  ) {
775
777
  try {