create-interview-cockpit 0.17.3 → 0.19.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/api.ts +184 -8
- package/template/client/src/components/GhaHistoryPanel.tsx +194 -0
- package/template/client/src/components/GhaJobsPanel.tsx +432 -0
- package/template/client/src/components/GithubActionsLabModal.tsx +1048 -0
- package/template/client/src/components/InfraLabModal.tsx +993 -262
- package/template/client/src/components/LabsPanel.tsx +71 -5
- package/template/client/src/components/Sidebar.tsx +603 -60
- package/template/client/src/components/WorkspaceSwitcher.tsx +4 -0
- package/template/client/src/enterpriseLocalLab.ts +921 -0
- package/template/client/src/githubActionsLab.ts +294 -0
- package/template/client/src/infraLab.ts +378 -6
- package/template/client/src/reactLab.ts +409 -0
- package/template/client/src/store.ts +130 -10
- package/template/client/src/types.ts +33 -3
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/gha-runner.ts +793 -0
- package/template/server/src/google-drive.ts +542 -149
- package/template/server/src/index.ts +327 -10
- package/template/server/src/infra-runner.ts +321 -30
- package/template/server/src/storage.ts +3 -1
|
@@ -4,7 +4,7 @@ import { randomUUID } from "crypto";
|
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import * as storage from "./storage.js";
|
|
6
6
|
|
|
7
|
-
type InfraExecutionMode = "plan-only" | "localstack";
|
|
7
|
+
type InfraExecutionMode = "plan-only" | "localstack" | "docker";
|
|
8
8
|
export type InfraRunAction = "validate" | "plan" | "command";
|
|
9
9
|
type InfraRunStatus = "completed" | "failed";
|
|
10
10
|
type OutputKind = "stdout" | "stderr" | "info";
|
|
@@ -12,7 +12,7 @@ type OutputKind = "stdout" | "stderr" | "info";
|
|
|
12
12
|
interface InfraLabWorkspace {
|
|
13
13
|
version: 1;
|
|
14
14
|
label: string;
|
|
15
|
-
provider: "aws";
|
|
15
|
+
provider: "aws" | "docker";
|
|
16
16
|
executionMode: InfraExecutionMode;
|
|
17
17
|
activeFile: string;
|
|
18
18
|
files: Record<string, string>;
|
|
@@ -54,7 +54,7 @@ export interface InfraRunListItem {
|
|
|
54
54
|
startedAt: string;
|
|
55
55
|
completedAt: string;
|
|
56
56
|
durationMs: number;
|
|
57
|
-
provider: "aws";
|
|
57
|
+
provider: "aws" | "docker";
|
|
58
58
|
executionMode: InfraExecutionMode;
|
|
59
59
|
diagnostics: InfraDiagnostic[];
|
|
60
60
|
planSummary?: InfraPlanSummary;
|
|
@@ -82,17 +82,21 @@ interface RunCommandResult {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
interface ParsedCommand {
|
|
85
|
+
executable: string;
|
|
85
86
|
args: string[];
|
|
86
87
|
subcommand: string;
|
|
87
88
|
action: InfraRunAction;
|
|
89
|
+
command: string;
|
|
88
90
|
displayCommand: string;
|
|
89
91
|
planOutputFile?: string;
|
|
92
|
+
tool: "terraform" | "docker" | "local";
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
const MAX_FILE_COUNT = 24;
|
|
93
96
|
const MAX_TOTAL_SOURCE_BYTES = 500_000;
|
|
94
97
|
const MAX_LOG_CHARS = 200_000;
|
|
95
98
|
const SOURCE_MANIFEST = ".infra-source-files.json";
|
|
99
|
+
const SHELL_OPERATORS = new Set(["|", "||", "&&", ";", ">", ">>", "<", "<<"]);
|
|
96
100
|
const ALLOWED_SUBCOMMANDS = new Set([
|
|
97
101
|
"fmt",
|
|
98
102
|
"init",
|
|
@@ -106,6 +110,56 @@ const ALLOWED_SUBCOMMANDS = new Set([
|
|
|
106
110
|
"version",
|
|
107
111
|
"providers",
|
|
108
112
|
]);
|
|
113
|
+
const ALLOWED_DOCKER_SUBCOMMANDS = new Set([
|
|
114
|
+
"version",
|
|
115
|
+
"info",
|
|
116
|
+
"context",
|
|
117
|
+
"ps",
|
|
118
|
+
"run",
|
|
119
|
+
"exec",
|
|
120
|
+
"create",
|
|
121
|
+
"container",
|
|
122
|
+
"image",
|
|
123
|
+
"images",
|
|
124
|
+
"history",
|
|
125
|
+
"network",
|
|
126
|
+
"volume",
|
|
127
|
+
"logs",
|
|
128
|
+
"inspect",
|
|
129
|
+
"port",
|
|
130
|
+
"top",
|
|
131
|
+
"stats",
|
|
132
|
+
"rm",
|
|
133
|
+
"rmi",
|
|
134
|
+
"stop",
|
|
135
|
+
"start",
|
|
136
|
+
"restart",
|
|
137
|
+
"build",
|
|
138
|
+
"pull",
|
|
139
|
+
"tag",
|
|
140
|
+
"compose",
|
|
141
|
+
"system",
|
|
142
|
+
]);
|
|
143
|
+
const ALLOWED_LOCAL_COMMANDS = new Set(["pwd", "ls", "cat", "curl"]);
|
|
144
|
+
const DISALLOWED_DOCKER_FLAGS = new Set([
|
|
145
|
+
"--privileged",
|
|
146
|
+
"--pid=host",
|
|
147
|
+
"--ipc=host",
|
|
148
|
+
"--uts=host",
|
|
149
|
+
"--userns=host",
|
|
150
|
+
"--network=host",
|
|
151
|
+
"--net=host",
|
|
152
|
+
]);
|
|
153
|
+
const DISALLOWED_CURL_FLAGS = new Set([
|
|
154
|
+
"-o",
|
|
155
|
+
"--output",
|
|
156
|
+
"-O",
|
|
157
|
+
"--remote-name",
|
|
158
|
+
"--config",
|
|
159
|
+
"-K",
|
|
160
|
+
"-T",
|
|
161
|
+
"--upload-file",
|
|
162
|
+
]);
|
|
109
163
|
|
|
110
164
|
function getInfraRunsDir(): string {
|
|
111
165
|
return path.resolve(storage.getContextFilesDir(), "..", "infra-runs");
|
|
@@ -182,9 +236,13 @@ function parseWorkspace(input: unknown): InfraLabWorkspace {
|
|
|
182
236
|
typeof candidate.label === "string" && candidate.label.trim()
|
|
183
237
|
? candidate.label.trim()
|
|
184
238
|
: "Infrastructure Lab",
|
|
185
|
-
provider: "aws",
|
|
239
|
+
provider: candidate.provider === "docker" ? "docker" : "aws",
|
|
186
240
|
executionMode:
|
|
187
|
-
candidate.executionMode === "
|
|
241
|
+
candidate.executionMode === "docker"
|
|
242
|
+
? "docker"
|
|
243
|
+
: candidate.executionMode === "plan-only"
|
|
244
|
+
? "plan-only"
|
|
245
|
+
: "localstack",
|
|
188
246
|
activeFile:
|
|
189
247
|
typeof candidate.activeFile === "string" && files[candidate.activeFile]
|
|
190
248
|
? candidate.activeFile
|
|
@@ -275,12 +333,91 @@ function extractPlanOutputFile(args: string[]): string | undefined {
|
|
|
275
333
|
return undefined;
|
|
276
334
|
}
|
|
277
335
|
|
|
278
|
-
function
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
281
|
-
throw new Error(
|
|
336
|
+
function assertNoShellOperators(tokens: string[]): void {
|
|
337
|
+
const operator = tokens.find((token) => SHELL_OPERATORS.has(token));
|
|
338
|
+
if (operator) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Shell operator '${operator}' is not supported. Run one command at a time.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function hasForceFlag(args: string[]): boolean {
|
|
346
|
+
return args.some(
|
|
347
|
+
(arg) =>
|
|
348
|
+
arg === "-f" ||
|
|
349
|
+
arg === "--force" ||
|
|
350
|
+
arg.startsWith("--force=") ||
|
|
351
|
+
(/^-[A-Za-z]+$/.test(arg) && arg.includes("f")),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function assertSafeLocalPathArgs(args: string[], label: string): void {
|
|
356
|
+
for (const arg of args) {
|
|
357
|
+
if (!arg || arg.startsWith("-") || arg === ".") continue;
|
|
358
|
+
assertSafeRelativePath(arg, label);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function assertSafeDockerArgs(args: string[]): void {
|
|
363
|
+
const disallowed = args.find((arg) => DISALLOWED_DOCKER_FLAGS.has(arg));
|
|
364
|
+
if (disallowed) {
|
|
365
|
+
throw new Error(`${disallowed} is disabled in the lab console`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const subcommand = args[0];
|
|
369
|
+
const composeSubcommand = subcommand === "compose" ? args[1] : undefined;
|
|
370
|
+
|
|
371
|
+
if (subcommand === "compose" && composeSubcommand === "up") {
|
|
372
|
+
const detached = args.includes("-d") || args.includes("--detach");
|
|
373
|
+
if (!detached) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"Use docker compose up -d in this console so the command does not stay attached forever",
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const followsLogs = args.some(
|
|
381
|
+
(arg) =>
|
|
382
|
+
arg === "-f" || arg === "--follow" || /^-[A-Za-z]*f[A-Za-z]*$/.test(arg),
|
|
383
|
+
);
|
|
384
|
+
if ((subcommand === "logs" || composeSubcommand === "logs") && followsLogs) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
"Follow-mode logs are disabled; run docker logs without -f",
|
|
387
|
+
);
|
|
282
388
|
}
|
|
283
389
|
|
|
390
|
+
if (subcommand === "stats" && !args.includes("--no-stream")) {
|
|
391
|
+
throw new Error("Use docker stats --no-stream in this console");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function assertSafeCurlArgs(args: string[]): void {
|
|
396
|
+
const disallowed = args.find((arg) => DISALLOWED_CURL_FLAGS.has(arg));
|
|
397
|
+
if (disallowed) {
|
|
398
|
+
throw new Error(`${disallowed} is disabled in the lab console`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const urls = args.filter((arg) => /^https?:\/\//i.test(arg));
|
|
402
|
+
if (urls.length === 0) {
|
|
403
|
+
throw new Error("curl requires a localhost http:// or https:// URL");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const url of urls) {
|
|
407
|
+
if (
|
|
408
|
+
!/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?(?:\/|$)/i.test(
|
|
409
|
+
url,
|
|
410
|
+
)
|
|
411
|
+
) {
|
|
412
|
+
throw new Error("curl is limited to localhost URLs in the lab console");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function parseTerraformCommand(
|
|
418
|
+
command: string,
|
|
419
|
+
tokens: string[],
|
|
420
|
+
): ParsedCommand {
|
|
284
421
|
const args = tokens[0] === "terraform" ? tokens.slice(1) : [...tokens];
|
|
285
422
|
if (args.length === 0) {
|
|
286
423
|
throw new Error("Type a Terraform subcommand after 'terraform'");
|
|
@@ -289,7 +426,7 @@ function parseCommand(command: string): ParsedCommand {
|
|
|
289
426
|
const subcommand = args[0];
|
|
290
427
|
if (!ALLOWED_SUBCOMMANDS.has(subcommand)) {
|
|
291
428
|
throw new Error(
|
|
292
|
-
"
|
|
429
|
+
"Supported Terraform commands: fmt, init, validate, plan, show, apply, destroy, output, state, version, providers",
|
|
293
430
|
);
|
|
294
431
|
}
|
|
295
432
|
|
|
@@ -312,7 +449,9 @@ function parseCommand(command: string): ParsedCommand {
|
|
|
312
449
|
assertSafeRelativePath(planOutputFile, "Plan output file");
|
|
313
450
|
}
|
|
314
451
|
|
|
452
|
+
const normalizedCommand = `terraform ${args.join(" ")}`;
|
|
315
453
|
return {
|
|
454
|
+
executable: "terraform",
|
|
316
455
|
args,
|
|
317
456
|
subcommand,
|
|
318
457
|
action:
|
|
@@ -321,30 +460,149 @@ function parseCommand(command: string): ParsedCommand {
|
|
|
321
460
|
: subcommand === "plan"
|
|
322
461
|
? "plan"
|
|
323
462
|
: "command",
|
|
324
|
-
|
|
463
|
+
command: normalizedCommand,
|
|
464
|
+
displayCommand: `$ ${normalizedCommand}\n`,
|
|
325
465
|
planOutputFile,
|
|
466
|
+
tool: "terraform",
|
|
326
467
|
};
|
|
327
468
|
}
|
|
328
469
|
|
|
329
|
-
|
|
470
|
+
function parseDockerCommand(command: string, tokens: string[]): ParsedCommand {
|
|
471
|
+
const args = tokens.slice(1);
|
|
472
|
+
if (args.length === 0) {
|
|
473
|
+
throw new Error("Type a Docker subcommand after 'docker'");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (
|
|
477
|
+
args.some(
|
|
478
|
+
(arg) =>
|
|
479
|
+
arg === "-H" ||
|
|
480
|
+
arg.startsWith("-H=") ||
|
|
481
|
+
arg === "--host" ||
|
|
482
|
+
arg.startsWith("--host="),
|
|
483
|
+
)
|
|
484
|
+
) {
|
|
485
|
+
throw new Error("Changing the Docker host is disabled in this console");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const subcommand = args[0];
|
|
489
|
+
if (!ALLOWED_DOCKER_SUBCOMMANDS.has(subcommand)) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
"Supported Docker commands include: version, info, context, ps, run, exec, images, network, volume, container, logs, inspect, port, stats, rm, stop, start, build, pull, tag, compose, system",
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
assertSafeDockerArgs(args);
|
|
496
|
+
|
|
497
|
+
if (args.includes("prune") && !hasForceFlag(args)) {
|
|
498
|
+
throw new Error("Docker prune commands require --force or -f");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
executable: "docker",
|
|
503
|
+
args,
|
|
504
|
+
subcommand:
|
|
505
|
+
subcommand === "compose" && args[1] ? `compose ${args[1]}` : subcommand,
|
|
506
|
+
action: "command",
|
|
507
|
+
command: command.trim(),
|
|
508
|
+
displayCommand: `$ ${command.trim()}\n`,
|
|
509
|
+
tool: "docker",
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function parseLocalCommand(command: string, tokens: string[]): ParsedCommand {
|
|
514
|
+
const executable = tokens[0];
|
|
515
|
+
const args = tokens.slice(1);
|
|
516
|
+
|
|
517
|
+
if (!ALLOWED_LOCAL_COMMANDS.has(executable)) {
|
|
518
|
+
throw new Error("Supported local commands: pwd, ls, cat, curl");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (executable === "curl") {
|
|
522
|
+
assertSafeCurlArgs(args);
|
|
523
|
+
return {
|
|
524
|
+
executable,
|
|
525
|
+
args,
|
|
526
|
+
subcommand: executable,
|
|
527
|
+
action: "command",
|
|
528
|
+
command: command.trim(),
|
|
529
|
+
displayCommand: `$ ${command.trim()}\n`,
|
|
530
|
+
tool: "local",
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (executable === "pwd" && args.length > 0) {
|
|
535
|
+
throw new Error("pwd does not accept arguments in this console");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (executable === "ls") {
|
|
539
|
+
assertSafeLocalPathArgs(args, "ls path");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (executable === "cat") {
|
|
543
|
+
const fileArgs = args.filter((arg) => !arg.startsWith("-"));
|
|
544
|
+
if (fileArgs.length === 0) {
|
|
545
|
+
throw new Error("cat requires a workspace-relative file path");
|
|
546
|
+
}
|
|
547
|
+
assertSafeLocalPathArgs(fileArgs, "cat path");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
executable,
|
|
552
|
+
args,
|
|
553
|
+
subcommand: executable,
|
|
554
|
+
action: "command",
|
|
555
|
+
command: command.trim(),
|
|
556
|
+
displayCommand: `$ ${command.trim()}\n`,
|
|
557
|
+
tool: "local",
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function parseCommand(command: string): ParsedCommand {
|
|
562
|
+
const tokens = splitCommand(command);
|
|
563
|
+
if (tokens.length === 0) {
|
|
564
|
+
throw new Error("Type a command to run");
|
|
565
|
+
}
|
|
566
|
+
assertNoShellOperators(tokens);
|
|
567
|
+
|
|
568
|
+
if (tokens[0] === "terraform" || ALLOWED_SUBCOMMANDS.has(tokens[0])) {
|
|
569
|
+
return parseTerraformCommand(command, tokens);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (tokens[0] === "docker") {
|
|
573
|
+
return parseDockerCommand(command, tokens);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (ALLOWED_LOCAL_COMMANDS.has(tokens[0])) {
|
|
577
|
+
return parseLocalCommand(command, tokens);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
throw new Error(
|
|
581
|
+
"Supported commands: terraform, docker, pwd, ls, cat, curl. Shell operators are disabled.",
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function runWorkspaceCommand(
|
|
330
586
|
cwd: string,
|
|
587
|
+
executable: string,
|
|
331
588
|
args: string[],
|
|
332
589
|
executionMode: InfraExecutionMode,
|
|
590
|
+
displayCommand: string,
|
|
333
591
|
onChunk?: (chunk: { kind: OutputKind; text: string }) => void,
|
|
592
|
+
extraEnv: NodeJS.ProcessEnv = {},
|
|
334
593
|
): Promise<RunCommandResult> {
|
|
335
594
|
return await new Promise<RunCommandResult>((resolve) => {
|
|
336
595
|
const stdout: string[] = [];
|
|
337
596
|
const stderr: string[] = [];
|
|
338
|
-
const commandLine = `$ terraform ${args.join(" ")}\n`;
|
|
339
597
|
let settled = false;
|
|
340
598
|
|
|
341
|
-
onChunk?.({ kind: "info", text:
|
|
599
|
+
onChunk?.({ kind: "info", text: displayCommand });
|
|
342
600
|
|
|
343
|
-
const child = spawn(
|
|
601
|
+
const child = spawn(executable, args, {
|
|
344
602
|
cwd,
|
|
345
603
|
env: {
|
|
346
604
|
...getDefaultEnv(executionMode),
|
|
347
|
-
|
|
605
|
+
...extraEnv,
|
|
348
606
|
},
|
|
349
607
|
});
|
|
350
608
|
|
|
@@ -365,13 +623,13 @@ async function runTerraformCommand(
|
|
|
365
623
|
onChunk?.({ kind: "stderr", text });
|
|
366
624
|
});
|
|
367
625
|
child.on("error", (error) => {
|
|
368
|
-
const message =
|
|
626
|
+
const message = `${executable} launch failed: ${error.message}\n`;
|
|
369
627
|
onChunk?.({ kind: "stderr", text: message });
|
|
370
628
|
finish({
|
|
371
629
|
exitCode: 1,
|
|
372
630
|
stdout: "",
|
|
373
631
|
stderr: message,
|
|
374
|
-
combined: `${
|
|
632
|
+
combined: `${displayCommand}${message}`,
|
|
375
633
|
});
|
|
376
634
|
});
|
|
377
635
|
child.on("close", (code) => {
|
|
@@ -381,12 +639,48 @@ async function runTerraformCommand(
|
|
|
381
639
|
exitCode: typeof code === "number" ? code : 1,
|
|
382
640
|
stdout: out,
|
|
383
641
|
stderr: err,
|
|
384
|
-
combined: `${
|
|
642
|
+
combined: `${displayCommand}${out}${err}`,
|
|
385
643
|
});
|
|
386
644
|
});
|
|
387
645
|
});
|
|
388
646
|
}
|
|
389
647
|
|
|
648
|
+
async function runTerraformCommand(
|
|
649
|
+
cwd: string,
|
|
650
|
+
args: string[],
|
|
651
|
+
executionMode: InfraExecutionMode,
|
|
652
|
+
onChunk?: (chunk: { kind: OutputKind; text: string }) => void,
|
|
653
|
+
): Promise<RunCommandResult> {
|
|
654
|
+
return runWorkspaceCommand(
|
|
655
|
+
cwd,
|
|
656
|
+
"terraform",
|
|
657
|
+
args,
|
|
658
|
+
executionMode,
|
|
659
|
+
`$ terraform ${args.join(" ")}\n`,
|
|
660
|
+
onChunk,
|
|
661
|
+
{ TF_DATA_DIR: path.join(cwd, ".tfdata") },
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function runParsedCommand(
|
|
666
|
+
cwd: string,
|
|
667
|
+
parsed: ParsedCommand,
|
|
668
|
+
executionMode: InfraExecutionMode,
|
|
669
|
+
onChunk?: (chunk: { kind: OutputKind; text: string }) => void,
|
|
670
|
+
): Promise<RunCommandResult> {
|
|
671
|
+
return runWorkspaceCommand(
|
|
672
|
+
cwd,
|
|
673
|
+
parsed.executable,
|
|
674
|
+
parsed.args,
|
|
675
|
+
executionMode,
|
|
676
|
+
parsed.displayCommand,
|
|
677
|
+
onChunk,
|
|
678
|
+
parsed.tool === "terraform"
|
|
679
|
+
? { TF_DATA_DIR: path.join(cwd, ".tfdata") }
|
|
680
|
+
: {},
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
390
684
|
function toDiagnostics(value: unknown): InfraDiagnostic[] {
|
|
391
685
|
if (!value || typeof value !== "object") return [];
|
|
392
686
|
const diagnostics = (value as { diagnostics?: unknown }).diagnostics;
|
|
@@ -879,7 +1173,7 @@ export async function runInfraAction(input: {
|
|
|
879
1173
|
startedAt,
|
|
880
1174
|
completedAt,
|
|
881
1175
|
durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
|
|
882
|
-
provider:
|
|
1176
|
+
provider: workspace.provider,
|
|
883
1177
|
executionMode: workspace.executionMode,
|
|
884
1178
|
diagnostics,
|
|
885
1179
|
...(planSummary ? { planSummary } : {}),
|
|
@@ -941,9 +1235,9 @@ export async function streamInfraCommand(input: {
|
|
|
941
1235
|
input.onMessage?.(message);
|
|
942
1236
|
};
|
|
943
1237
|
|
|
944
|
-
const commandResult = await
|
|
1238
|
+
const commandResult = await runParsedCommand(
|
|
945
1239
|
workspaceDir,
|
|
946
|
-
parsed
|
|
1240
|
+
parsed,
|
|
947
1241
|
workspace.executionMode,
|
|
948
1242
|
(chunk) => {
|
|
949
1243
|
logs = appendLog(logs, chunk.text);
|
|
@@ -953,13 +1247,10 @@ export async function streamInfraCommand(input: {
|
|
|
953
1247
|
|
|
954
1248
|
if (commandResult.exitCode !== 0) {
|
|
955
1249
|
status = "failed";
|
|
956
|
-
error = describeRunError(
|
|
957
|
-
commandResult,
|
|
958
|
-
`terraform ${parsed.subcommand} failed`,
|
|
959
|
-
);
|
|
1250
|
+
error = describeRunError(commandResult, `${parsed.command} failed`);
|
|
960
1251
|
}
|
|
961
1252
|
|
|
962
|
-
if (parsed.subcommand === "validate") {
|
|
1253
|
+
if (parsed.tool === "terraform" && parsed.subcommand === "validate") {
|
|
963
1254
|
const validation = await collectValidationArtifacts(
|
|
964
1255
|
workspaceDir,
|
|
965
1256
|
workspace.executionMode,
|
|
@@ -970,7 +1261,7 @@ export async function streamInfraCommand(input: {
|
|
|
970
1261
|
artifacts.push(...validation.artifacts);
|
|
971
1262
|
}
|
|
972
1263
|
|
|
973
|
-
if (parsed.subcommand === "plan") {
|
|
1264
|
+
if (parsed.tool === "terraform" && parsed.subcommand === "plan") {
|
|
974
1265
|
artifacts.push(
|
|
975
1266
|
await writeArtifact(
|
|
976
1267
|
artifactsDir,
|
|
@@ -1012,12 +1303,12 @@ export async function streamInfraCommand(input: {
|
|
|
1012
1303
|
...(input.questionId ? { questionId: input.questionId } : {}),
|
|
1013
1304
|
label: input.label?.trim() || workspace.label,
|
|
1014
1305
|
action: parsed.action,
|
|
1015
|
-
command:
|
|
1306
|
+
command: parsed.command,
|
|
1016
1307
|
status,
|
|
1017
1308
|
startedAt,
|
|
1018
1309
|
completedAt,
|
|
1019
1310
|
durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
|
|
1020
|
-
provider:
|
|
1311
|
+
provider: workspace.provider,
|
|
1021
1312
|
executionMode: workspace.executionMode,
|
|
1022
1313
|
diagnostics,
|
|
1023
1314
|
...(planSummary ? { planSummary } : {}),
|
|
@@ -73,7 +73,9 @@ export interface ContextFile {
|
|
|
73
73
|
| "infra"
|
|
74
74
|
| "react"
|
|
75
75
|
| "nextjs"
|
|
76
|
-
| "module-federation"
|
|
76
|
+
| "module-federation"
|
|
77
|
+
| "canvas"
|
|
78
|
+
| "github-actions";
|
|
77
79
|
/** Language hint for code snippets (e.g. 'typescript', 'javascript'). */
|
|
78
80
|
language?: string;
|
|
79
81
|
/** Short display label for code snippets. */
|