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.
- package/package.json +1 -1
- package/template/client/src/api.ts +39 -0
- package/template/client/src/browserSecurityTemplates.ts +3242 -0
- package/template/client/src/components/BrowserSecurityLabModal.tsx +1510 -0
- package/template/client/src/components/CodeRunnerModal.tsx +406 -55
- package/template/client/src/components/LabsPanel.tsx +123 -12
- package/template/client/src/components/LinkedConvosPicker.tsx +121 -63
- package/template/client/src/components/Sidebar.tsx +113 -0
- package/template/client/src/reactLab.ts +408 -0
- package/template/client/src/store.ts +15 -1
- package/template/client/src/types.ts +2 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +2 -0
- package/template/server/src/index.ts +90 -24
- package/template/server/src/storage.ts +2 -0
|
@@ -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
|
|
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
|
-
|
|
2938
|
-
const
|
|
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
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
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"
|