create-interview-cockpit 0.13.0 → 0.15.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.
@@ -726,6 +726,7 @@ app.post("/api/questions/:questionId/save-code-snippet", async (req, res) => {
726
726
  | "user"
727
727
  | "ai"
728
728
  | "sandbox"
729
+ | "browser-security"
729
730
  | "infra"
730
731
  | "react"
731
732
  | "nextjs"
@@ -738,6 +739,7 @@ app.post("/api/questions/:questionId/save-code-snippet", async (req, res) => {
738
739
  origin !== "user" &&
739
740
  origin !== "ai" &&
740
741
  origin !== "sandbox" &&
742
+ origin !== "browser-security" &&
741
743
  origin !== "infra" &&
742
744
  origin !== "react" &&
743
745
  origin !== "nextjs" &&
@@ -745,7 +747,7 @@ app.post("/api/questions/:questionId/save-code-snippet", async (req, res) => {
745
747
  ) {
746
748
  return res.status(400).json({
747
749
  error:
748
- "origin must be 'user', 'ai', 'sandbox', 'infra', 'react', 'nextjs', or 'module-federation'",
750
+ "origin must be 'user', 'ai', 'sandbox', 'browser-security', 'infra', 'react', 'nextjs', or 'module-federation'",
749
751
  });
750
752
  }
751
753
  try {
@@ -2589,17 +2591,19 @@ function getModuleFederationCommandEnv(
2589
2591
  sandbox: ModuleFederationSandboxEntry,
2590
2592
  ): NodeJS.ProcessEnv {
2591
2593
  const hostPort = new URL(sandbox.appUrls.host).port;
2592
- const profilePort = new URL(sandbox.appUrls.profile).port;
2593
- const checkoutPort = new URL(sandbox.appUrls.checkout).port;
2594
-
2595
- return {
2594
+ const env: NodeJS.ProcessEnv = {
2596
2595
  ...process.env,
2597
2596
  HOST_PORT: hostPort,
2598
- PROFILE_PORT: profilePort,
2599
- CHECKOUT_PORT: checkoutPort,
2600
2597
  MF_SANDBOX_ID: sandbox.id,
2601
2598
  npm_config_update_notifier: "false",
2602
2599
  };
2600
+ if (sandbox.appUrls.profile)
2601
+ env.PROFILE_PORT = new URL(sandbox.appUrls.profile).port;
2602
+ if (sandbox.appUrls.checkout)
2603
+ env.CHECKOUT_PORT = new URL(sandbox.appUrls.checkout).port;
2604
+ if (sandbox.appUrls.mfeAuth)
2605
+ env.MFE_AUTH_PORT = new URL(sandbox.appUrls.mfeAuth).port;
2606
+ return env;
2603
2607
  }
2604
2608
 
2605
2609
  async function runStreamedCommand(
@@ -2883,6 +2887,51 @@ app.post("/api/nextjs/:id/update-files", async (req, res) => {
2883
2887
  res.json({ ok: true });
2884
2888
  });
2885
2889
 
2890
+ app.post("/api/nextjs/:id/command-stream", async (req, res) => {
2891
+ const sb = nextSandboxes.get(req.params.id);
2892
+
2893
+ res.setHeader("Content-Type", "text/event-stream");
2894
+ res.setHeader("Cache-Control", "no-cache");
2895
+ res.setHeader("Connection", "keep-alive");
2896
+ res.flushHeaders();
2897
+
2898
+ const send = (payload: unknown) => {
2899
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
2900
+ };
2901
+
2902
+ if (!sb) {
2903
+ send({ type: "error", error: "Sandbox not found" });
2904
+ res.end();
2905
+ return;
2906
+ }
2907
+
2908
+ const { command } = req.body as { command?: string };
2909
+ if (typeof command !== "string" || !command.trim()) {
2910
+ send({ type: "error", error: "command is required" });
2911
+ res.end();
2912
+ return;
2913
+ }
2914
+
2915
+ try {
2916
+ const parsed = parseReactLabCommand(command);
2917
+ send({ type: "output", kind: "info", text: `$ ${command.trim()}\n` });
2918
+ await runStreamedCommand(
2919
+ npmCommand(),
2920
+ parsed.args,
2921
+ {
2922
+ cwd: sb.dir,
2923
+ env: { ...process.env, npm_config_update_notifier: "false" },
2924
+ },
2925
+ ({ kind, text }) => send({ type: "output", kind, text }),
2926
+ );
2927
+ send({ type: "complete" });
2928
+ } catch (error: any) {
2929
+ send({ type: "error", error: error?.message || "Command failed" });
2930
+ }
2931
+
2932
+ res.end();
2933
+ });
2934
+
2886
2935
  app.get("/api/nextjs/:id/status", (req, res) => {
2887
2936
  const sb = nextSandboxes.get(req.params.id);
2888
2937
  if (!sb) return res.json({ running: false });
@@ -2934,24 +2983,42 @@ app.post("/api/module-federation/start", async (req, res) => {
2934
2983
  logs,
2935
2984
  );
2936
2985
 
2937
- const [hostPort, profilePort, checkoutPort] = await getDistinctPorts(3);
2938
- const appUrls = {
2986
+ // Detect isolated 2-app pattern (host + mfe-auth, no profile/checkout).
2987
+ const isIsolated =
2988
+ typeof files["apps/mfe-auth/package.json"] === "string" &&
2989
+ typeof files["apps/checkout/package.json"] !== "string";
2990
+
2991
+ const ports = await getDistinctPorts(isIsolated ? 2 : 3);
2992
+ const [hostPort] = ports;
2993
+
2994
+ const appUrls: Record<string, string> = {
2939
2995
  host: `http://localhost:${hostPort}`,
2940
- profile: `http://localhost:${profilePort}`,
2941
- checkout: `http://localhost:${checkoutPort}`,
2942
2996
  };
2997
+ const spawnEnv: NodeJS.ProcessEnv = {
2998
+ ...process.env,
2999
+ HOST_PORT: String(hostPort),
3000
+ MF_SANDBOX_ID: id,
3001
+ npm_config_update_notifier: "false",
3002
+ };
3003
+
3004
+ if (isIsolated) {
3005
+ const [, mfeAuthPort] = ports;
3006
+ appUrls.mfeAuth = `http://localhost:${mfeAuthPort}`;
3007
+ spawnEnv.MFE_AUTH_PORT = String(mfeAuthPort);
3008
+ } else {
3009
+ const [, profilePort, checkoutPort] = ports;
3010
+ appUrls.profile = `http://localhost:${profilePort}`;
3011
+ appUrls.checkout = `http://localhost:${checkoutPort}`;
3012
+ spawnEnv.PROFILE_PORT = String(profilePort);
3013
+ spawnEnv.CHECKOUT_PORT = String(checkoutPort);
3014
+ }
3015
+
2943
3016
  const readyPorts = new Set<string>();
3017
+ const requiredPorts = isIsolated ? 2 : 3;
2944
3018
 
2945
3019
  const child = spawn(npmCommand(), ["run", "dev"], {
2946
3020
  cwd: dir,
2947
- env: {
2948
- ...process.env,
2949
- HOST_PORT: String(hostPort),
2950
- PROFILE_PORT: String(profilePort),
2951
- CHECKOUT_PORT: String(checkoutPort),
2952
- MF_SANDBOX_ID: id,
2953
- npm_config_update_notifier: "false",
2954
- },
3021
+ env: spawnEnv,
2955
3022
  });
2956
3023
 
2957
3024
  const entry: ModuleFederationSandboxEntry = {
@@ -2966,11 +3033,10 @@ app.post("/api/module-federation/start", async (req, res) => {
2966
3033
  };
2967
3034
 
2968
3035
  const markReady = (text: string) => {
2969
- if (text.includes(`localhost:${hostPort}`)) readyPorts.add("host");
2970
- if (text.includes(`localhost:${profilePort}`)) readyPorts.add("profile");
2971
- if (text.includes(`localhost:${checkoutPort}`))
2972
- readyPorts.add("checkout");
2973
- if (readyPorts.size === 3) {
3036
+ for (const [key, url] of Object.entries(appUrls)) {
3037
+ if (text.includes(new URL(url).host)) readyPorts.add(key);
3038
+ }
3039
+ if (readyPorts.size >= requiredPorts) {
2974
3040
  entry.ready = true;
2975
3041
  }
2976
3042
  };
@@ -59,6 +59,7 @@ export interface ContextFile {
59
59
  /** Distinguishes how this file was added. 'upload' = user-uploaded doc,
60
60
  * 'user' = code saved from Code Runner, 'ai' = AI-generated code block,
61
61
  * 'sandbox' = paired server+client sandbox saved as JSON,
62
+ * 'browser-security' = paired server+client security lab saved as JSON,
62
63
  * 'infra' = Terraform-style infra lab workspace saved as JSON,
63
64
  * 'react' = React + TypeScript lab workspace,
64
65
  * 'nextjs' = Next.js App Router lab workspace,
@@ -68,6 +69,7 @@ export interface ContextFile {
68
69
  | "ai"
69
70
  | "upload"
70
71
  | "sandbox"
72
+ | "browser-security"
71
73
  | "infra"
72
74
  | "react"
73
75
  | "nextjs"