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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InfraLabWorkspace } from "./types";
|
|
2
|
+
import { ENTERPRISE_LOCAL_AUTH_LAB } from "./enterpriseLocalLab";
|
|
2
3
|
|
|
3
4
|
const DEFAULT_INFRA_FILES: Record<string, string> = {
|
|
4
5
|
"provider.tf": `terraform {
|
|
@@ -39,15 +40,370 @@ export const DEFAULT_INFRA_LAB: InfraLabWorkspace = {
|
|
|
39
40
|
files: DEFAULT_INFRA_FILES,
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
export const DOCKER_DEEP_DIVE_LAB: InfraLabWorkspace = {
|
|
44
|
+
version: 1,
|
|
45
|
+
label: "Docker Deep Dive Lab",
|
|
46
|
+
provider: "docker",
|
|
47
|
+
executionMode: "docker",
|
|
48
|
+
activeFile: "compose.yaml",
|
|
49
|
+
files: {
|
|
50
|
+
"README.md": `# Docker Deep Dive Lab
|
|
51
|
+
|
|
52
|
+
This lab is a small production-shaped Docker system:
|
|
53
|
+
|
|
54
|
+
- **api** is a Node/Express service built from the local Dockerfile.
|
|
55
|
+
- **redis** is an off-the-shelf image with a named volume.
|
|
56
|
+
- **worker** reuses the same image as the api but runs a different command.
|
|
57
|
+
- **labnet** demonstrates Compose service discovery: the api connects to Redis at \`redis:6379\`.
|
|
58
|
+
- **port 4288** publishes the api from the container to your host browser.
|
|
59
|
+
|
|
60
|
+
## First run
|
|
61
|
+
|
|
62
|
+
Run these in the Console tab, one command at a time:
|
|
63
|
+
|
|
64
|
+
1. \`docker version\` — confirm the Docker client and engine can talk.
|
|
65
|
+
2. \`docker compose up -d --build\` — build the api image and start all services.
|
|
66
|
+
3. \`docker compose ps\` — compare services, containers, health, and published ports.
|
|
67
|
+
4. \`curl -s http://localhost:4288/api/ping\` — hit the container through the host port.
|
|
68
|
+
5. \`docker images\` — find the image Compose built from this Dockerfile.
|
|
69
|
+
6. \`docker logs docker-deep-dive-api\` — read stdout/stderr from the api container.
|
|
70
|
+
7. \`docker compose exec -T api node -e "console.log(process.env.REDIS_URL)"\` — run a process inside the container.
|
|
71
|
+
8. \`docker volume ls\` — notice the Redis data volume.
|
|
72
|
+
9. \`docker compose down\` — stop/remove containers and the lab network.
|
|
73
|
+
10. \`docker compose down -v\` — also remove the Redis volume when you want a clean slate.
|
|
74
|
+
|
|
75
|
+
The console intentionally disables shell pipes/operators. Use direct commands instead of chaining.
|
|
76
|
+
|
|
77
|
+
## Experiments that teach the deep parts
|
|
78
|
+
|
|
79
|
+
- Change \`LAB_MESSAGE\` in compose.yaml, run \`docker compose up -d --build\`, then hit \`/api/ping\`.
|
|
80
|
+
- Change the Dockerfile order and rebuild to see which layers are cached.
|
|
81
|
+
- Add an environment variable to the worker only, then compare \`docker inspect docker-deep-dive-api\` and \`docker inspect docker-deep-dive-worker\`.
|
|
82
|
+
- Change the host port from \`4288:3000\` to another port and observe how host networking differs from container networking.
|
|
83
|
+
- Run \`docker compose down\`, start again, and verify Redis data survives because the named volume remains.
|
|
84
|
+
- Run \`docker compose down -v\`, start again, and verify the Redis data resets.
|
|
85
|
+
`,
|
|
86
|
+
".dockerignore": `node_modules
|
|
87
|
+
npm-debug.log
|
|
88
|
+
.git
|
|
89
|
+
.env
|
|
90
|
+
coverage
|
|
91
|
+
dist
|
|
92
|
+
`,
|
|
93
|
+
Dockerfile: `# syntax=docker/dockerfile:1.7
|
|
94
|
+
|
|
95
|
+
# Senior/interview insight: copy dependency manifests before source files so
|
|
96
|
+
# Docker can reuse the dependency layer when only application code changes.
|
|
97
|
+
FROM node:20-alpine AS deps
|
|
98
|
+
WORKDIR /app
|
|
99
|
+
COPY package*.json ./
|
|
100
|
+
RUN npm install --omit=dev
|
|
101
|
+
|
|
102
|
+
# Senior/interview insight: keep runtime images small and run as a non-root
|
|
103
|
+
# user so a compromised app has less power inside the container.
|
|
104
|
+
FROM node:20-alpine AS runtime
|
|
105
|
+
ENV NODE_ENV=production
|
|
106
|
+
ENV PORT=3000
|
|
107
|
+
WORKDIR /app
|
|
108
|
+
RUN addgroup -S nodeapp && adduser -S nodeapp -G nodeapp
|
|
109
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
110
|
+
COPY package*.json ./
|
|
111
|
+
COPY src ./src
|
|
112
|
+
USER nodeapp
|
|
113
|
+
EXPOSE 3000
|
|
114
|
+
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + '/api/ping').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
|
115
|
+
CMD ["node", "src/server.js"]
|
|
116
|
+
`,
|
|
117
|
+
"compose.yaml": `name: interview-cockpit-docker-lab
|
|
118
|
+
|
|
119
|
+
services:
|
|
120
|
+
api:
|
|
121
|
+
build:
|
|
122
|
+
context: .
|
|
123
|
+
dockerfile: Dockerfile
|
|
124
|
+
image: interview-cockpit/docker-deep-dive-api:local
|
|
125
|
+
container_name: docker-deep-dive-api
|
|
126
|
+
ports:
|
|
127
|
+
- "4288:3000"
|
|
128
|
+
environment:
|
|
129
|
+
PORT: "3000"
|
|
130
|
+
REDIS_URL: redis://redis:6379
|
|
131
|
+
LAB_MESSAGE: "Edit me in compose.yaml, rebuild, then hit /api/ping"
|
|
132
|
+
depends_on:
|
|
133
|
+
redis:
|
|
134
|
+
condition: service_healthy
|
|
135
|
+
healthcheck:
|
|
136
|
+
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/ping').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
|
|
137
|
+
interval: 10s
|
|
138
|
+
timeout: 3s
|
|
139
|
+
retries: 5
|
|
140
|
+
networks:
|
|
141
|
+
- labnet
|
|
142
|
+
|
|
143
|
+
worker:
|
|
144
|
+
image: interview-cockpit/docker-deep-dive-api:local
|
|
145
|
+
container_name: docker-deep-dive-worker
|
|
146
|
+
command: ["node", "src/worker.js"]
|
|
147
|
+
environment:
|
|
148
|
+
REDIS_URL: redis://redis:6379
|
|
149
|
+
WORKER_NAME: compose-worker
|
|
150
|
+
depends_on:
|
|
151
|
+
redis:
|
|
152
|
+
condition: service_healthy
|
|
153
|
+
api:
|
|
154
|
+
condition: service_started
|
|
155
|
+
networks:
|
|
156
|
+
- labnet
|
|
157
|
+
|
|
158
|
+
redis:
|
|
159
|
+
image: redis:7-alpine
|
|
160
|
+
container_name: docker-deep-dive-redis
|
|
161
|
+
command: ["redis-server", "--appendonly", "yes"]
|
|
162
|
+
volumes:
|
|
163
|
+
- redis-data:/data
|
|
164
|
+
healthcheck:
|
|
165
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
166
|
+
interval: 5s
|
|
167
|
+
timeout: 3s
|
|
168
|
+
retries: 10
|
|
169
|
+
networks:
|
|
170
|
+
- labnet
|
|
171
|
+
|
|
172
|
+
volumes:
|
|
173
|
+
redis-data:
|
|
174
|
+
|
|
175
|
+
networks:
|
|
176
|
+
labnet:
|
|
177
|
+
`,
|
|
178
|
+
"package.json": `{
|
|
179
|
+
"name": "docker-deep-dive-api",
|
|
180
|
+
"version": "1.0.0",
|
|
181
|
+
"private": true,
|
|
182
|
+
"type": "module",
|
|
183
|
+
"scripts": {
|
|
184
|
+
"start": "node src/server.js",
|
|
185
|
+
"worker": "node src/worker.js"
|
|
186
|
+
},
|
|
187
|
+
"dependencies": {
|
|
188
|
+
"@redis/client": "^1.6.1",
|
|
189
|
+
"cors": "^2.8.5",
|
|
190
|
+
"express": "^4.18.3"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
`,
|
|
194
|
+
"src/server.js": `import os from "node:os";
|
|
195
|
+
import express from "express";
|
|
196
|
+
import cors from "cors";
|
|
197
|
+
import { createClient } from "@redis/client";
|
|
198
|
+
|
|
199
|
+
const app = express();
|
|
200
|
+
const port = Number(process.env.PORT || 3000);
|
|
201
|
+
const startedAt = new Date();
|
|
202
|
+
let redis;
|
|
203
|
+
let redisReady = false;
|
|
204
|
+
|
|
205
|
+
app.use(cors());
|
|
206
|
+
app.use(express.json());
|
|
207
|
+
|
|
208
|
+
async function getRedis() {
|
|
209
|
+
if (redisReady) return redis;
|
|
210
|
+
if (!redis) {
|
|
211
|
+
redis = createClient({ url: process.env.REDIS_URL });
|
|
212
|
+
redis.on("error", (error) => {
|
|
213
|
+
redisReady = false;
|
|
214
|
+
console.error("redis error", error.message);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (!redis.isOpen) await redis.connect();
|
|
218
|
+
redisReady = true;
|
|
219
|
+
return redis;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function readRedis(key) {
|
|
223
|
+
try {
|
|
224
|
+
const client = await getRedis();
|
|
225
|
+
return { ok: true, value: await client.get(key) };
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return { ok: false, error: error.message };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
app.get("/", (_req, res) => {
|
|
232
|
+
res.type("html").send(\`<!doctype html>
|
|
233
|
+
<html>
|
|
234
|
+
<head>
|
|
235
|
+
<meta charset="utf-8" />
|
|
236
|
+
<title>Docker Deep Dive API</title>
|
|
237
|
+
<style>
|
|
238
|
+
body { margin: 0; font-family: ui-sans-serif, system-ui; background: #020617; color: #dbeafe; }
|
|
239
|
+
main { max-width: 860px; margin: 0 auto; padding: 40px 24px; }
|
|
240
|
+
button, a { border: 1px solid #155e75; border-radius: 999px; background: #083344; color: #a5f3fc; padding: 10px 14px; text-decoration: none; cursor: pointer; }
|
|
241
|
+
pre { background: #0f172a; border: 1px solid #1e293b; border-radius: 16px; padding: 16px; overflow: auto; }
|
|
242
|
+
.grid { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; }
|
|
243
|
+
.muted { color: #94a3b8; }
|
|
244
|
+
</style>
|
|
245
|
+
</head>
|
|
246
|
+
<body>
|
|
247
|
+
<main>
|
|
248
|
+
<p class="muted">Container hostname: \${os.hostname()}</p>
|
|
249
|
+
<h1>Docker Deep Dive API</h1>
|
|
250
|
+
<p>This page is served from inside the api container and published to your host on port 4288.</p>
|
|
251
|
+
<div class="grid">
|
|
252
|
+
<button onclick="hit('/api/ping')">GET /api/ping</button>
|
|
253
|
+
<button onclick="hit('/api/redis/increment/browser')">Increment Redis counter</button>
|
|
254
|
+
<button onclick="setCache()">Set cache value</button>
|
|
255
|
+
<button onclick="hit('/api/cache/browser-demo')">Read cache value</button>
|
|
256
|
+
</div>
|
|
257
|
+
<pre id="out">Click a button to call the API.</pre>
|
|
258
|
+
</main>
|
|
259
|
+
<script>
|
|
260
|
+
async function hit(path, init) {
|
|
261
|
+
const res = await fetch(path, init);
|
|
262
|
+
document.querySelector('#out').textContent = JSON.stringify(await res.json(), null, 2);
|
|
263
|
+
}
|
|
264
|
+
function setCache() {
|
|
265
|
+
return hit('/api/cache/browser-demo', {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: { 'content-type': 'application/json' },
|
|
268
|
+
body: JSON.stringify({ value: 'saved from the browser at ' + new Date().toISOString() })
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
</script>
|
|
272
|
+
</body>
|
|
273
|
+
</html>\`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
app.get("/api/ping", async (_req, res) => {
|
|
277
|
+
let hits = null;
|
|
278
|
+
let redisError = null;
|
|
279
|
+
try {
|
|
280
|
+
const client = await getRedis();
|
|
281
|
+
hits = await client.incr("api:pings");
|
|
282
|
+
} catch (error) {
|
|
283
|
+
redisError = error.message;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
res.json({
|
|
287
|
+
ok: true,
|
|
288
|
+
message: process.env.LAB_MESSAGE || "hello from inside the container",
|
|
289
|
+
container: {
|
|
290
|
+
hostname: os.hostname(),
|
|
291
|
+
pid: process.pid,
|
|
292
|
+
uptimeSeconds: Math.round(process.uptime()),
|
|
293
|
+
startedAt: startedAt.toISOString(),
|
|
294
|
+
},
|
|
295
|
+
environment: {
|
|
296
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
297
|
+
PORT: process.env.PORT,
|
|
298
|
+
REDIS_URL: process.env.REDIS_URL,
|
|
299
|
+
},
|
|
300
|
+
redis: {
|
|
301
|
+
connected: redisReady,
|
|
302
|
+
pingCount: hits,
|
|
303
|
+
error: redisError,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
app.get("/api/cache/:key", async (req, res) => {
|
|
309
|
+
const result = await readRedis(\`cache:\${req.params.key}\`);
|
|
310
|
+
res.status(result.ok ? 200 : 503).json(result);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
app.post("/api/cache/:key", async (req, res) => {
|
|
314
|
+
try {
|
|
315
|
+
const client = await getRedis();
|
|
316
|
+
const value = req.body?.value ?? \`stored at \${new Date().toISOString()}\`;
|
|
317
|
+
await client.set(\`cache:\${req.params.key}\`, String(value));
|
|
318
|
+
res.json({ ok: true, key: req.params.key, value });
|
|
319
|
+
} catch (error) {
|
|
320
|
+
res.status(503).json({ ok: false, error: error.message });
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
app.delete("/api/cache/:key", async (req, res) => {
|
|
325
|
+
try {
|
|
326
|
+
const client = await getRedis();
|
|
327
|
+
const deleted = await client.del(\`cache:\${req.params.key}\`);
|
|
328
|
+
res.json({ ok: true, key: req.params.key, deleted });
|
|
329
|
+
} catch (error) {
|
|
330
|
+
res.status(503).json({ ok: false, error: error.message });
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
app.get("/api/redis/increment/:key", async (req, res) => {
|
|
335
|
+
try {
|
|
336
|
+
const client = await getRedis();
|
|
337
|
+
const value = await client.incr(\`counter:\${req.params.key}\`);
|
|
338
|
+
res.json({ ok: true, key: req.params.key, value });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
res.status(503).json({ ok: false, error: error.message });
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
app.get("/api/headers", (req, res) => {
|
|
345
|
+
res.json({ ok: true, headers: req.headers });
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
app.listen(port, "0.0.0.0", () => {
|
|
349
|
+
console.log(\`api listening on 0.0.0.0:\${port}\`);
|
|
350
|
+
});
|
|
351
|
+
`,
|
|
352
|
+
"src/worker.js": `import { createClient } from "@redis/client";
|
|
353
|
+
|
|
354
|
+
const redisUrl = process.env.REDIS_URL;
|
|
355
|
+
const workerName = process.env.WORKER_NAME || "worker";
|
|
356
|
+
const client = createClient({ url: redisUrl });
|
|
357
|
+
|
|
358
|
+
client.on("error", (error) => {
|
|
359
|
+
console.error(\`[\${workerName}] redis error\`, error.message);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await client.connect();
|
|
363
|
+
console.log(\`[\${workerName}] connected to \${redisUrl}\`);
|
|
364
|
+
|
|
365
|
+
setInterval(async () => {
|
|
366
|
+
const ticks = await client.incr("worker:ticks");
|
|
367
|
+
console.log(\`[\${workerName}] tick \${ticks}\`);
|
|
368
|
+
}, 5000);
|
|
369
|
+
`,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
function refreshLegacyEnterpriseAuthFiles(
|
|
374
|
+
source: InfraLabWorkspace,
|
|
375
|
+
files: Record<string, string>,
|
|
376
|
+
): Record<string, string> {
|
|
377
|
+
const main = files["main.tf"] ?? "";
|
|
378
|
+
const isLegacyDockerProviderBuild =
|
|
379
|
+
source.label === ENTERPRISE_LOCAL_AUTH_LAB.label &&
|
|
380
|
+
source.provider === "docker" &&
|
|
381
|
+
main.includes('resource "docker_image" "claims_api"') &&
|
|
382
|
+
main.includes('resource "docker_image" "bff"') &&
|
|
383
|
+
main.includes("docker_image.bff.image_id");
|
|
384
|
+
|
|
385
|
+
if (!isLegacyDockerProviderBuild) return files;
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
...files,
|
|
389
|
+
"README.md":
|
|
390
|
+
ENTERPRISE_LOCAL_AUTH_LAB.files["README.md"] ?? files["README.md"],
|
|
391
|
+
"variables.tf":
|
|
392
|
+
ENTERPRISE_LOCAL_AUTH_LAB.files["variables.tf"] ?? files["variables.tf"],
|
|
393
|
+
"main.tf": ENTERPRISE_LOCAL_AUTH_LAB.files["main.tf"] ?? files["main.tf"],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
42
397
|
export function cloneInfraLabWorkspace(
|
|
43
398
|
workspace?: InfraLabWorkspace | null,
|
|
44
399
|
): InfraLabWorkspace {
|
|
45
400
|
const source = workspace ?? DEFAULT_INFRA_LAB;
|
|
46
401
|
// Only seed defaults when the source has no files of its own
|
|
47
|
-
const
|
|
402
|
+
const sourceFiles =
|
|
48
403
|
source.files && Object.keys(source.files).length > 0
|
|
49
404
|
? { ...source.files }
|
|
50
405
|
: { ...DEFAULT_INFRA_FILES };
|
|
406
|
+
const files = refreshLegacyEnterpriseAuthFiles(source, sourceFiles);
|
|
51
407
|
const activeFile = files[source.activeFile]
|
|
52
408
|
? source.activeFile
|
|
53
409
|
: (Object.keys(files)[0] ?? "main.tf");
|
|
@@ -55,9 +411,13 @@ export function cloneInfraLabWorkspace(
|
|
|
55
411
|
return {
|
|
56
412
|
version: 1,
|
|
57
413
|
label: source.label || DEFAULT_INFRA_LAB.label,
|
|
58
|
-
provider: "aws",
|
|
414
|
+
provider: source.provider === "docker" ? "docker" : "aws",
|
|
59
415
|
executionMode:
|
|
60
|
-
source.executionMode === "
|
|
416
|
+
source.executionMode === "docker"
|
|
417
|
+
? "docker"
|
|
418
|
+
: source.executionMode === "plan-only"
|
|
419
|
+
? "plan-only"
|
|
420
|
+
: "localstack",
|
|
61
421
|
activeFile,
|
|
62
422
|
files,
|
|
63
423
|
};
|
|
@@ -65,13 +425,21 @@ export function cloneInfraLabWorkspace(
|
|
|
65
425
|
|
|
66
426
|
export function getInfraLabFileOrder(workspace: InfraLabWorkspace): string[] {
|
|
67
427
|
const preferred = [
|
|
428
|
+
"README.md",
|
|
429
|
+
"compose.yaml",
|
|
430
|
+
"docker-compose.yml",
|
|
431
|
+
"docker-compose.yaml",
|
|
432
|
+
"Dockerfile",
|
|
433
|
+
".dockerignore",
|
|
434
|
+
"package.json",
|
|
435
|
+
"src/server.js",
|
|
436
|
+
"src/worker.js",
|
|
68
437
|
"main.tf",
|
|
69
438
|
"provider.tf",
|
|
70
439
|
"variables.tf",
|
|
71
440
|
"terraform.tfvars",
|
|
72
441
|
"outputs.tf",
|
|
73
442
|
"locals.tf",
|
|
74
|
-
"README.md",
|
|
75
443
|
];
|
|
76
444
|
const extras = Object.keys(workspace.files)
|
|
77
445
|
.filter((name) => !preferred.includes(name))
|
|
@@ -109,9 +477,13 @@ export function parseInfraLabWorkspace(raw: string): InfraLabWorkspace | null {
|
|
|
109
477
|
typeof parsed.label === "string" && parsed.label.trim()
|
|
110
478
|
? parsed.label.trim()
|
|
111
479
|
: DEFAULT_INFRA_LAB.label,
|
|
112
|
-
provider: "aws",
|
|
480
|
+
provider: parsed.provider === "docker" ? "docker" : "aws",
|
|
113
481
|
executionMode:
|
|
114
|
-
parsed.executionMode === "
|
|
482
|
+
parsed.executionMode === "docker"
|
|
483
|
+
? "docker"
|
|
484
|
+
: parsed.executionMode === "plan-only"
|
|
485
|
+
? "plan-only"
|
|
486
|
+
: "localstack",
|
|
115
487
|
activeFile:
|
|
116
488
|
typeof parsed.activeFile === "string"
|
|
117
489
|
? parsed.activeFile
|