create-interview-cockpit 0.15.0 → 0.16.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/App.tsx +3 -0
- package/template/client/src/components/CanvasLabModal.tsx +585 -0
- package/template/client/src/components/LabsPanel.tsx +63 -0
- package/template/client/src/components/Sidebar.tsx +12 -1
- package/template/client/src/components/WorkspaceSwitcher.tsx +36 -0
- package/template/client/src/reactLab.ts +1206 -0
- package/template/client/src/store.ts +24 -1
- package/template/client/src/types.ts +3 -1
- package/template/client/vite.config.ts +15 -8
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +4 -1
- package/template/server/src/index.ts +63 -6
- package/template/server/src/storage.ts +1 -0
|
@@ -224,7 +224,8 @@ interface Store {
|
|
|
224
224
|
| "infra"
|
|
225
225
|
| "react"
|
|
226
226
|
| "nextjs"
|
|
227
|
-
| "module-federation"
|
|
227
|
+
| "module-federation"
|
|
228
|
+
| "canvas",
|
|
228
229
|
) => Promise<import("./types").ContextFile>;
|
|
229
230
|
clearMessages: (questionId: string) => Promise<void>;
|
|
230
231
|
|
|
@@ -356,6 +357,13 @@ interface Store {
|
|
|
356
357
|
serverCode?: string,
|
|
357
358
|
serverLang?: string,
|
|
358
359
|
) => void;
|
|
360
|
+
|
|
361
|
+
// ── Canvas Lab ───────────────────────────────────────────────
|
|
362
|
+
showCanvasLab: boolean;
|
|
363
|
+
canvasLabInitialCode: string | null;
|
|
364
|
+
canvasLabInitialFileId: string | null;
|
|
365
|
+
openCanvasLab: (code?: string, fileId?: string) => void;
|
|
366
|
+
closeCanvasLab: () => void;
|
|
359
367
|
}
|
|
360
368
|
|
|
361
369
|
export const useStore = create<Store>((set, get) => ({
|
|
@@ -387,6 +395,9 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
387
395
|
showInfraLab: false,
|
|
388
396
|
runnerInitialInfra: null,
|
|
389
397
|
runnerInitialInfraFileId: null,
|
|
398
|
+
showCanvasLab: false,
|
|
399
|
+
canvasLabInitialCode: null,
|
|
400
|
+
canvasLabInitialFileId: null,
|
|
390
401
|
|
|
391
402
|
// ── Workspaces ───────────────────────────────────────────────
|
|
392
403
|
workspaces: [],
|
|
@@ -1076,6 +1087,18 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
1076
1087
|
openBrowserSecurityLab: () => set({ showBrowserSecurityLab: true }),
|
|
1077
1088
|
closeBrowserSecurityLab: () => set({ showBrowserSecurityLab: false }),
|
|
1078
1089
|
closeInfraLab: () => set({ showInfraLab: false }),
|
|
1090
|
+
openCanvasLab: (code?, fileId?) =>
|
|
1091
|
+
set({
|
|
1092
|
+
showCanvasLab: true,
|
|
1093
|
+
canvasLabInitialCode: code ?? null,
|
|
1094
|
+
canvasLabInitialFileId: fileId ?? null,
|
|
1095
|
+
}),
|
|
1096
|
+
closeCanvasLab: () =>
|
|
1097
|
+
set({
|
|
1098
|
+
showCanvasLab: false,
|
|
1099
|
+
canvasLabInitialCode: null,
|
|
1100
|
+
canvasLabInitialFileId: null,
|
|
1101
|
+
}),
|
|
1079
1102
|
|
|
1080
1103
|
fetchAiSettings: async () => {
|
|
1081
1104
|
const settings = await api.fetchAiSettings();
|
|
@@ -7,7 +7,8 @@ export type ContextFileOrigin =
|
|
|
7
7
|
| "infra"
|
|
8
8
|
| "react"
|
|
9
9
|
| "nextjs"
|
|
10
|
-
| "module-federation"
|
|
10
|
+
| "module-federation"
|
|
11
|
+
| "canvas";
|
|
11
12
|
|
|
12
13
|
export interface ContextFile {
|
|
13
14
|
id: string;
|
|
@@ -51,6 +52,7 @@ export interface WorkspaceMeta {
|
|
|
51
52
|
id: string;
|
|
52
53
|
name: string;
|
|
53
54
|
type: "local" | "google_drive";
|
|
55
|
+
questionSortOrder?: "name" | "createdAt";
|
|
54
56
|
driveConfig?: {
|
|
55
57
|
folderId: string;
|
|
56
58
|
folderName: string;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { defineConfig } from "vite";
|
|
1
|
+
import { defineConfig, loadEnv } from "vite";
|
|
2
2
|
import react from "@vitejs/plugin-react";
|
|
3
3
|
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export default defineConfig(({ mode }) => {
|
|
5
|
+
// Load the root .env (one level above client/)
|
|
6
|
+
const env = loadEnv(mode, "../", "");
|
|
7
|
+
const clientPort = parseInt(env.CLIENT_PORT || "5173", 10);
|
|
8
|
+
const serverPort = parseInt(env.PORT || "3001", 10);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
plugins: [react()],
|
|
12
|
+
server: {
|
|
13
|
+
port: clientPort,
|
|
14
|
+
proxy: {
|
|
15
|
+
"/api": `http://localhost:${serverPort}`,
|
|
16
|
+
},
|
|
10
17
|
},
|
|
11
|
-
}
|
|
18
|
+
};
|
|
12
19
|
});
|
package/template/cockpit.json
CHANGED
|
@@ -330,6 +330,7 @@ export async function syncWorkspace(
|
|
|
330
330
|
let codeContextFiles: string[] = [];
|
|
331
331
|
let codeAnnotations: storage.Question["codeAnnotations"] = {};
|
|
332
332
|
let parentTitle: string | null = null;
|
|
333
|
+
let restoredCreatedAt: string | null = null;
|
|
333
334
|
const restoredContextFiles: storage.ContextFile[] = [];
|
|
334
335
|
|
|
335
336
|
if (filename.endsWith(".json")) {
|
|
@@ -342,6 +343,7 @@ export async function syncWorkspace(
|
|
|
342
343
|
codeContextFiles = parsed.codeContextFiles || [];
|
|
343
344
|
codeAnnotations = parsed.codeAnnotations || {};
|
|
344
345
|
parentTitle = parsed.parentTitle || null;
|
|
346
|
+
if (parsed.createdAt) restoredCreatedAt = parsed.createdAt;
|
|
345
347
|
|
|
346
348
|
// Restore code snippet context files (user/ai origin)
|
|
347
349
|
if (
|
|
@@ -399,7 +401,7 @@ export async function syncWorkspace(
|
|
|
399
401
|
contextFiles: restoredContextFiles,
|
|
400
402
|
messages,
|
|
401
403
|
codeAnnotations,
|
|
402
|
-
createdAt: new Date().toISOString(),
|
|
404
|
+
createdAt: restoredCreatedAt ?? new Date().toISOString(),
|
|
403
405
|
};
|
|
404
406
|
await storage.saveQuestion(q);
|
|
405
407
|
result.filesImported++;
|
|
@@ -798,6 +800,7 @@ export async function exportWorkspace(
|
|
|
798
800
|
codeContextFiles: q.codeContextFiles,
|
|
799
801
|
codeAnnotations: q.codeAnnotations ?? {},
|
|
800
802
|
codeSnippets: contextFilesWithContent,
|
|
803
|
+
createdAt: q.createdAt,
|
|
801
804
|
},
|
|
802
805
|
null,
|
|
803
806
|
2,
|
|
@@ -743,11 +743,12 @@ app.post("/api/questions/:questionId/save-code-snippet", async (req, res) => {
|
|
|
743
743
|
origin !== "infra" &&
|
|
744
744
|
origin !== "react" &&
|
|
745
745
|
origin !== "nextjs" &&
|
|
746
|
-
origin !== "module-federation"
|
|
746
|
+
origin !== "module-federation" &&
|
|
747
|
+
origin !== "canvas"
|
|
747
748
|
) {
|
|
748
749
|
return res.status(400).json({
|
|
749
750
|
error:
|
|
750
|
-
"origin must be 'user', 'ai', 'sandbox', 'browser-security', 'infra', 'react', 'nextjs',
|
|
751
|
+
"origin must be 'user', 'ai', 'sandbox', 'browser-security', 'infra', 'react', 'nextjs', 'module-federation', or 'canvas'",
|
|
751
752
|
});
|
|
752
753
|
}
|
|
753
754
|
try {
|
|
@@ -2972,7 +2973,7 @@ app.post("/api/module-federation/start", async (req, res) => {
|
|
|
2972
2973
|
|
|
2973
2974
|
await runLoggedCommand(
|
|
2974
2975
|
npmCommand(),
|
|
2975
|
-
["install", "--no-audit", "--no-fund", "--
|
|
2976
|
+
["install", "--no-audit", "--no-fund", "--legacy-peer-deps"],
|
|
2976
2977
|
{
|
|
2977
2978
|
cwd: dir,
|
|
2978
2979
|
env: {
|
|
@@ -2983,12 +2984,36 @@ app.post("/api/module-federation/start", async (req, res) => {
|
|
|
2983
2984
|
logs,
|
|
2984
2985
|
);
|
|
2985
2986
|
|
|
2987
|
+
// Detect new modern Next.js MFE template types.
|
|
2988
|
+
const isMultiZones = typeof files["apps/zone-b/package.json"] === "string";
|
|
2989
|
+
const isMfRuntimeApi =
|
|
2990
|
+
typeof files["apps/mf-remote/package.json"] === "string";
|
|
2991
|
+
const isRspackShell =
|
|
2992
|
+
typeof files["apps/rspack-shell/package.json"] === "string";
|
|
2993
|
+
|
|
2986
2994
|
// Detect isolated 2-app pattern (host + mfe-auth, no profile/checkout).
|
|
2987
2995
|
const isIsolated =
|
|
2988
2996
|
typeof files["apps/mfe-auth/package.json"] === "string" &&
|
|
2989
2997
|
typeof files["apps/checkout/package.json"] !== "string";
|
|
2990
2998
|
|
|
2991
|
-
|
|
2999
|
+
// Detect Next.js MF pattern (shell + remote, no mfe-auth/checkout/profile).
|
|
3000
|
+
// Excludes Multi-Zones (zone-b) and MF Runtime API (mf-remote) which also use apps/shell.
|
|
3001
|
+
const isNextjsMf =
|
|
3002
|
+
typeof files["apps/shell/package.json"] === "string" &&
|
|
3003
|
+
!isMultiZones &&
|
|
3004
|
+
!isMfRuntimeApi &&
|
|
3005
|
+
typeof files["apps/mfe-auth/package.json"] !== "string" &&
|
|
3006
|
+
typeof files["apps/checkout/package.json"] !== "string";
|
|
3007
|
+
|
|
3008
|
+
const ports = await getDistinctPorts(
|
|
3009
|
+
isIsolated ||
|
|
3010
|
+
isNextjsMf ||
|
|
3011
|
+
isMultiZones ||
|
|
3012
|
+
isMfRuntimeApi ||
|
|
3013
|
+
isRspackShell
|
|
3014
|
+
? 2
|
|
3015
|
+
: 3,
|
|
3016
|
+
);
|
|
2992
3017
|
const [hostPort] = ports;
|
|
2993
3018
|
|
|
2994
3019
|
const appUrls: Record<string, string> = {
|
|
@@ -3001,7 +3026,32 @@ app.post("/api/module-federation/start", async (req, res) => {
|
|
|
3001
3026
|
npm_config_update_notifier: "false",
|
|
3002
3027
|
};
|
|
3003
3028
|
|
|
3004
|
-
if (
|
|
3029
|
+
if (isNextjsMf) {
|
|
3030
|
+
const [, remotePort] = ports;
|
|
3031
|
+
appUrls.remote = `http://localhost:${remotePort}`;
|
|
3032
|
+
spawnEnv.REMOTE_PORT = String(remotePort);
|
|
3033
|
+
// Next.js remotes put the entry at /_next/static/chunks/; webpack remotes put it at root.
|
|
3034
|
+
const isNextjsRemote =
|
|
3035
|
+
typeof files["apps/remote/next.config.js"] === "string";
|
|
3036
|
+
const remoteEntryPath = isNextjsRemote
|
|
3037
|
+
? "/_next/static/chunks/remoteEntry.js"
|
|
3038
|
+
: "/remoteEntry.js";
|
|
3039
|
+
spawnEnv.NEXT_PUBLIC_REMOTE_URL = `http://localhost:${remotePort}${remoteEntryPath}`;
|
|
3040
|
+
} else if (isMultiZones) {
|
|
3041
|
+
const [, remotePort] = ports;
|
|
3042
|
+
// Zone B serves everything under /store (basePath), so point the preview there.
|
|
3043
|
+
appUrls.zoneB = `http://localhost:${remotePort}/store`;
|
|
3044
|
+
spawnEnv.REMOTE_PORT = String(remotePort);
|
|
3045
|
+
} else if (isMfRuntimeApi) {
|
|
3046
|
+
const [, remotePort] = ports;
|
|
3047
|
+
appUrls.mfRemote = `http://localhost:${remotePort}`;
|
|
3048
|
+
spawnEnv.REMOTE_PORT = String(remotePort);
|
|
3049
|
+
spawnEnv.NEXT_PUBLIC_REMOTE_URL = `http://localhost:${remotePort}/remoteEntry.js`;
|
|
3050
|
+
} else if (isRspackShell) {
|
|
3051
|
+
const [, remotePort] = ports;
|
|
3052
|
+
appUrls.remote = `http://localhost:${remotePort}`;
|
|
3053
|
+
spawnEnv.REMOTE_PORT = String(remotePort);
|
|
3054
|
+
} else if (isIsolated) {
|
|
3005
3055
|
const [, mfeAuthPort] = ports;
|
|
3006
3056
|
appUrls.mfeAuth = `http://localhost:${mfeAuthPort}`;
|
|
3007
3057
|
spawnEnv.MFE_AUTH_PORT = String(mfeAuthPort);
|
|
@@ -3014,7 +3064,14 @@ app.post("/api/module-federation/start", async (req, res) => {
|
|
|
3014
3064
|
}
|
|
3015
3065
|
|
|
3016
3066
|
const readyPorts = new Set<string>();
|
|
3017
|
-
const requiredPorts =
|
|
3067
|
+
const requiredPorts =
|
|
3068
|
+
isIsolated ||
|
|
3069
|
+
isNextjsMf ||
|
|
3070
|
+
isMultiZones ||
|
|
3071
|
+
isMfRuntimeApi ||
|
|
3072
|
+
isRspackShell
|
|
3073
|
+
? 2
|
|
3074
|
+
: 3;
|
|
3018
3075
|
|
|
3019
3076
|
const child = spawn(npmCommand(), ["run", "dev"], {
|
|
3020
3077
|
cwd: dir,
|