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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-interview-cockpit",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Scaffold a personal AI-powered interview prep cockpit",
5
5
  "type": "module",
6
6
  "bin": {
@@ -320,7 +320,14 @@ export async function saveCodeSnippet(
320
320
  code: string,
321
321
  language: string,
322
322
  label: string,
323
- origin: "user" | "ai" | "sandbox" | "infra" | "react" | "nextjs",
323
+ origin:
324
+ | "user"
325
+ | "ai"
326
+ | "sandbox"
327
+ | "infra"
328
+ | "react"
329
+ | "nextjs"
330
+ | "module-federation",
324
331
  ): Promise<ContextFile> {
325
332
  const res = await fetch(`${BASE}/questions/${questionId}/save-code-snippet`, {
326
333
  method: "POST",
@@ -538,7 +545,7 @@ export async function streamFrontendLabAsk(
538
545
  input: {
539
546
  messages: Array<{ role: "user" | "assistant"; content: string }>;
540
547
  workspace: Record<string, string>;
541
- labType: "react" | "nextjs";
548
+ labType: "react" | "nextjs" | "module-federation";
542
549
  questionId?: string;
543
550
  },
544
551
  onDelta: (chunk: string) => void,
@@ -779,6 +786,20 @@ export interface NextjsSandboxInfo {
779
786
  url: string;
780
787
  }
781
788
 
789
+ export interface ModuleFederationSandboxInfo {
790
+ id: string;
791
+ hostUrl: string;
792
+ appUrls: Record<string, string>;
793
+ }
794
+
795
+ export interface ModuleFederationSandboxStatus {
796
+ running: boolean;
797
+ ready?: boolean;
798
+ hostUrl?: string;
799
+ appUrls?: Record<string, string>;
800
+ logs?: string[];
801
+ }
802
+
782
803
  export async function startNextjsSandbox(
783
804
  files: Record<string, string>,
784
805
  ): Promise<NextjsSandboxInfo> {
@@ -810,3 +831,45 @@ export async function updateNextjsFiles(
810
831
  export async function stopNextjsSandbox(id: string): Promise<void> {
811
832
  await fetch(`${BASE}/nextjs/${id}`, { method: "DELETE" });
812
833
  }
834
+
835
+ export async function startModuleFederationSandbox(
836
+ files: Record<string, string>,
837
+ ): Promise<ModuleFederationSandboxInfo> {
838
+ const res = await fetch(`${BASE}/module-federation/start`, {
839
+ method: "POST",
840
+ headers: { "Content-Type": "application/json" },
841
+ body: JSON.stringify({ files }),
842
+ });
843
+ if (!res.ok) {
844
+ const body = await res.json().catch(() => ({}));
845
+ throw new Error(
846
+ (body as any).error ||
847
+ `Failed to start webpack module federation lab (${res.status})`,
848
+ );
849
+ }
850
+ return res.json();
851
+ }
852
+
853
+ export async function updateModuleFederationFiles(
854
+ id: string,
855
+ files: Record<string, string>,
856
+ ): Promise<void> {
857
+ const res = await fetch(`${BASE}/module-federation/${id}/update-files`, {
858
+ method: "POST",
859
+ headers: { "Content-Type": "application/json" },
860
+ body: JSON.stringify({ files }),
861
+ });
862
+ if (!res.ok) throw new Error(await res.text());
863
+ }
864
+
865
+ export async function fetchModuleFederationStatus(
866
+ id: string,
867
+ ): Promise<ModuleFederationSandboxStatus> {
868
+ const res = await fetch(`${BASE}/module-federation/${id}/status`);
869
+ if (!res.ok) throw new Error(await res.text());
870
+ return res.json();
871
+ }
872
+
873
+ export async function stopModuleFederationSandbox(id: string): Promise<void> {
874
+ await fetch(`${BASE}/module-federation/${id}`, { method: "DELETE" });
875
+ }
@@ -238,6 +238,7 @@ export default function CodeContextPanel() {
238
238
  openInfraLab,
239
239
  openReactLab,
240
240
  openNextLab,
241
+ openModuleFederationLab,
241
242
  removeQuestionFile,
242
243
  renameContextFile,
243
244
  } = useStore();
@@ -1144,6 +1145,116 @@ export default function CodeContextPanel() {
1144
1145
  </div>
1145
1146
  )}
1146
1147
 
1148
+ {/* ── Webpack Module Federation Labs section ───────────── */}
1149
+ {currentQuestion && (
1150
+ <div className="border-t border-slate-800 px-3 py-2">
1151
+ <div className="flex items-center justify-between mb-1">
1152
+ <div className="flex items-center gap-1">
1153
+ <Server className="w-3 h-3 text-emerald-400/70" />
1154
+ <span className="text-[10px] uppercase tracking-wider text-slate-600">
1155
+ Webpack MF Labs (
1156
+ {
1157
+ (currentQuestion.contextFiles || []).filter(
1158
+ (f) => f.origin === "module-federation",
1159
+ ).length
1160
+ }
1161
+ )
1162
+ </span>
1163
+ </div>
1164
+ <button
1165
+ onClick={() => openModuleFederationLab()}
1166
+ className="p-0.5 rounded text-slate-600 hover:text-emerald-400 hover:bg-slate-700 transition-colors"
1167
+ title="Open Webpack Module Federation Lab"
1168
+ >
1169
+ <Plus className="w-3.5 h-3.5" />
1170
+ </button>
1171
+ </div>
1172
+ <div className="space-y-0.5 max-h-32 overflow-y-auto">
1173
+ {(currentQuestion.contextFiles || [])
1174
+ .filter((f) => f.origin === "module-federation")
1175
+ .map((cf) => (
1176
+ <div
1177
+ key={cf.id}
1178
+ className="flex items-center gap-1 text-xs bg-emerald-500/10 border border-emerald-500/20 rounded px-1.5 py-1 group"
1179
+ >
1180
+ <span
1181
+ className="text-emerald-200 font-medium truncate flex-1"
1182
+ title={cf.label || cf.originalName}
1183
+ >
1184
+ {cf.label || cf.originalName}
1185
+ </span>
1186
+ <button
1187
+ onClick={async () => {
1188
+ try {
1189
+ const raw = await fetch(
1190
+ `/api/context-files/${cf.id}/content`,
1191
+ )
1192
+ .then((r) => r.json())
1193
+ .then((d) => d.content as string);
1194
+ const ext = JSON.parse(raw) as {
1195
+ clientType?: string;
1196
+ reactFiles?: Record<string, string>;
1197
+ reactActiveFile?: string;
1198
+ serverCode?: string;
1199
+ serverLang?: string;
1200
+ };
1201
+ if (
1202
+ ext?.clientType === "module-federation" &&
1203
+ ext.reactFiles
1204
+ ) {
1205
+ openModuleFederationLab(
1206
+ {
1207
+ version: 1,
1208
+ type: "module-federation",
1209
+ label:
1210
+ cf.label || "Webpack Module Federation Lab",
1211
+ activeFile:
1212
+ ext.reactActiveFile ??
1213
+ Object.keys(ext.reactFiles)[0] ??
1214
+ "apps/host/webpack.config.js",
1215
+ files: ext.reactFiles,
1216
+ },
1217
+ cf.id,
1218
+ ext.serverCode,
1219
+ ext.serverLang,
1220
+ );
1221
+ } else {
1222
+ const ws = parseFrontendLabWorkspace(raw);
1223
+ if (ws?.type === "module-federation") {
1224
+ openModuleFederationLab(ws, cf.id);
1225
+ }
1226
+ }
1227
+ } catch {
1228
+ /* ignore */
1229
+ }
1230
+ }}
1231
+ className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-slate-500 hover:text-emerald-400 transition-all"
1232
+ title="Open in Webpack Module Federation Lab"
1233
+ >
1234
+ <Play className="w-3 h-3" />
1235
+ </button>
1236
+ <button
1237
+ onClick={() =>
1238
+ removeQuestionFile(currentQuestion.id, cf.id)
1239
+ }
1240
+ className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-slate-500 hover:text-red-400 transition-all"
1241
+ title="Remove"
1242
+ >
1243
+ <Trash2 className="w-3 h-3" />
1244
+ </button>
1245
+ </div>
1246
+ ))}
1247
+ {(currentQuestion.contextFiles || []).filter(
1248
+ (f) => f.origin === "module-federation",
1249
+ ).length === 0 && (
1250
+ <p className="text-[10px] text-slate-700 italic">
1251
+ Save a webpack module federation lab to reopen it here
1252
+ </p>
1253
+ )}
1254
+ </div>
1255
+ </div>
1256
+ )}
1257
+
1147
1258
  {/* ── Notes section ────────────────────────────────────── */}
1148
1259
  <div className="border-t border-slate-800 px-3 py-2">
1149
1260
  <button