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
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type { GithubActionsLabWorkspace } from "./types";
|
|
2
|
+
|
|
3
|
+
// ─── Default Lab Template ────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// A self-contained playground that mirrors how GitHub Actions actually works:
|
|
6
|
+
// - one workflow under .github/workflows/ci.yml
|
|
7
|
+
// - a local composite action under .github/actions/say-hello/action.yml
|
|
8
|
+
// (referenced via `uses: ./.github/actions/say-hello`)
|
|
9
|
+
// - a public marketplace action via `uses: actions/checkout@v4`
|
|
10
|
+
// - a tiny script the workflow runs to prove the workspace was checked out
|
|
11
|
+
//
|
|
12
|
+
// Run it with the lab's Run button. `act` will pull `actions/checkout`
|
|
13
|
+
// from GitHub the first time, then execute everything inside Docker locally.
|
|
14
|
+
|
|
15
|
+
const DEFAULT_FILES: Record<string, string> = {
|
|
16
|
+
"README.md": `# GitHub Actions Local Playground
|
|
17
|
+
|
|
18
|
+
This lab runs your workflows on your own machine using [\`act\`](https://github.com/nektos/act),
|
|
19
|
+
the same tool GitHub recommends for testing Actions locally.
|
|
20
|
+
|
|
21
|
+
## First-time setup
|
|
22
|
+
|
|
23
|
+
You need two things on your host:
|
|
24
|
+
|
|
25
|
+
1. **Docker** — already required by other labs.
|
|
26
|
+
2. **act** — install once:
|
|
27
|
+
- macOS: \`brew install act\`
|
|
28
|
+
- Linux: \`curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash\`
|
|
29
|
+
|
|
30
|
+
The first run will pull a runner image (~500 MB). Subsequent runs are fast.
|
|
31
|
+
|
|
32
|
+
## How to use this lab
|
|
33
|
+
|
|
34
|
+
- The left tree shows the workspace, exactly like a Git checkout.
|
|
35
|
+
- Workflows live under \`.github/workflows/\` — \`act\` discovers them automatically.
|
|
36
|
+
- Local actions live under \`.github/actions/<name>/action.yml\` — reference them with \`uses: ./.github/actions/<name>\`.
|
|
37
|
+
- Public actions like \`actions/checkout@v4\` are pulled from GitHub on first use.
|
|
38
|
+
- Pick an event (push, pull_request, workflow_dispatch), pick a workflow, and click **Run**.
|
|
39
|
+
- The right pane streams the live log, just like the Actions tab on GitHub.
|
|
40
|
+
|
|
41
|
+
## What this template demonstrates
|
|
42
|
+
|
|
43
|
+
- A **multi-job workflow** with \`needs:\` for ordering
|
|
44
|
+
- A **local composite action** invoked via \`uses: ./.github/actions/...\`
|
|
45
|
+
- A **marketplace action** invoked via \`uses: actions/checkout@v4\`
|
|
46
|
+
- **Workflow inputs** with \`workflow_dispatch\`
|
|
47
|
+
- **Job outputs** + **\${{ steps.x.outputs.y }}** expressions
|
|
48
|
+
- A **matrix** strategy across Node versions
|
|
49
|
+
|
|
50
|
+
## Useful console commands
|
|
51
|
+
|
|
52
|
+
The Console tab on the right also supports:
|
|
53
|
+
|
|
54
|
+
act -l # list jobs in all workflows
|
|
55
|
+
act -W .github/workflows/ci.yml -l
|
|
56
|
+
act push # simulate a push event
|
|
57
|
+
act pull_request # simulate a pull_request event
|
|
58
|
+
act workflow_dispatch -j greet # run a single job
|
|
59
|
+
act -n # dry run (no Docker needed)
|
|
60
|
+
|
|
61
|
+
\`act\` flags are sandboxed; only flags listed above are allowed.
|
|
62
|
+
`,
|
|
63
|
+
|
|
64
|
+
".github/workflows/ci.yml": `name: CI
|
|
65
|
+
|
|
66
|
+
# Trigger on push, PRs, and a manual button (workflow_dispatch).
|
|
67
|
+
# Inputs let you test how reusable parameters flow through the run.
|
|
68
|
+
on:
|
|
69
|
+
push:
|
|
70
|
+
pull_request:
|
|
71
|
+
workflow_dispatch:
|
|
72
|
+
inputs:
|
|
73
|
+
greeting:
|
|
74
|
+
description: "Greeting to print"
|
|
75
|
+
required: false
|
|
76
|
+
default: "Hello from local act!"
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
# Job 1 — uses a local composite action you can edit in this lab.
|
|
80
|
+
greet:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
outputs:
|
|
83
|
+
stamp: \${{ steps.hello.outputs.stamp }}
|
|
84
|
+
steps:
|
|
85
|
+
- name: Checkout repo
|
|
86
|
+
uses: actions/checkout@v4
|
|
87
|
+
|
|
88
|
+
- name: Say hello (local composite action)
|
|
89
|
+
id: hello
|
|
90
|
+
uses: ./.github/actions/say-hello
|
|
91
|
+
with:
|
|
92
|
+
name: \${{ github.event.inputs.greeting || 'world' }}
|
|
93
|
+
|
|
94
|
+
- name: Show output from the composite action
|
|
95
|
+
run: echo "Composite action returned stamp=\${{ steps.hello.outputs.stamp }}"
|
|
96
|
+
|
|
97
|
+
# Job 2 — depends on greet, runs a matrix of Node versions.
|
|
98
|
+
build:
|
|
99
|
+
needs: greet
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
strategy:
|
|
102
|
+
fail-fast: false
|
|
103
|
+
matrix:
|
|
104
|
+
node-version: [18, 20]
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
|
|
108
|
+
- name: Show inherited output
|
|
109
|
+
run: echo "Previous job said \${{ needs.greet.outputs.stamp }}"
|
|
110
|
+
|
|
111
|
+
- name: Print Node version inside the runner
|
|
112
|
+
run: node --version
|
|
113
|
+
|
|
114
|
+
- name: Run the workspace script
|
|
115
|
+
run: node scripts/build.js \${{ matrix.node-version }}
|
|
116
|
+
`,
|
|
117
|
+
|
|
118
|
+
".github/actions/say-hello/action.yml": `# This is a LOCAL composite action.
|
|
119
|
+
# The workflow references it with: uses: ./.github/actions/say-hello
|
|
120
|
+
#
|
|
121
|
+
# Composite actions are how you package reusable step sequences without
|
|
122
|
+
# publishing to the Marketplace. They take inputs, run steps, and return outputs.
|
|
123
|
+
|
|
124
|
+
name: "Say Hello"
|
|
125
|
+
description: "Prints a greeting and emits a stamp output."
|
|
126
|
+
|
|
127
|
+
inputs:
|
|
128
|
+
name:
|
|
129
|
+
description: "Who to greet"
|
|
130
|
+
required: true
|
|
131
|
+
default: "world"
|
|
132
|
+
|
|
133
|
+
outputs:
|
|
134
|
+
stamp:
|
|
135
|
+
description: "ISO timestamp of when the action ran"
|
|
136
|
+
value: \${{ steps.gen.outputs.stamp }}
|
|
137
|
+
|
|
138
|
+
runs:
|
|
139
|
+
using: "composite"
|
|
140
|
+
steps:
|
|
141
|
+
- name: Greet
|
|
142
|
+
shell: bash
|
|
143
|
+
run: echo "Hello, \${{ inputs.name }}!"
|
|
144
|
+
|
|
145
|
+
- name: Generate output
|
|
146
|
+
id: gen
|
|
147
|
+
shell: bash
|
|
148
|
+
run: echo "stamp=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
|
|
149
|
+
`,
|
|
150
|
+
|
|
151
|
+
"scripts/build.js": `// Tiny script the workflow runs to prove your repo files are checked out
|
|
152
|
+
// inside the runner container.
|
|
153
|
+
const nodeMajor = process.argv[2] || "unknown";
|
|
154
|
+
console.log(\`[build] running on Node \${nodeMajor}\`);
|
|
155
|
+
console.log(\`[build] cwd = \${process.cwd()}\`);
|
|
156
|
+
console.log("[build] workspace files visible to the job:");
|
|
157
|
+
require("fs").readdirSync(".").forEach((f) => console.log(" -", f));
|
|
158
|
+
`,
|
|
159
|
+
|
|
160
|
+
".actrc": `# Default flags act will pick up from the workspace.
|
|
161
|
+
# Pinning the runner image keeps installs reproducible across machines.
|
|
162
|
+
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
|
163
|
+
--container-architecture linux/amd64
|
|
164
|
+
`,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const DEFAULT_GHA_LAB: GithubActionsLabWorkspace = {
|
|
168
|
+
version: 1,
|
|
169
|
+
label: "GitHub Actions Playground",
|
|
170
|
+
activeFile: ".github/workflows/ci.yml",
|
|
171
|
+
defaultEvent: "push",
|
|
172
|
+
defaultWorkflow: ".github/workflows/ci.yml",
|
|
173
|
+
files: DEFAULT_FILES,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ─── Helpers (mirror infraLab.ts API surface) ────────────────────────────
|
|
177
|
+
|
|
178
|
+
export function cloneGhaLabWorkspace(
|
|
179
|
+
workspace?: GithubActionsLabWorkspace | null,
|
|
180
|
+
): GithubActionsLabWorkspace {
|
|
181
|
+
const source = workspace ?? DEFAULT_GHA_LAB;
|
|
182
|
+
const sourceFiles =
|
|
183
|
+
source.files && Object.keys(source.files).length > 0
|
|
184
|
+
? { ...source.files }
|
|
185
|
+
: { ...DEFAULT_FILES };
|
|
186
|
+
const activeFile = sourceFiles[source.activeFile]
|
|
187
|
+
? source.activeFile
|
|
188
|
+
: (Object.keys(sourceFiles)[0] ?? ".github/workflows/ci.yml");
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
version: 1,
|
|
192
|
+
label: source.label || DEFAULT_GHA_LAB.label,
|
|
193
|
+
activeFile,
|
|
194
|
+
defaultEvent: source.defaultEvent || "push",
|
|
195
|
+
defaultWorkflow:
|
|
196
|
+
source.defaultWorkflow && sourceFiles[source.defaultWorkflow]
|
|
197
|
+
? source.defaultWorkflow
|
|
198
|
+
: Object.keys(sourceFiles).find((f) =>
|
|
199
|
+
f.startsWith(".github/workflows/"),
|
|
200
|
+
) || ".github/workflows/ci.yml",
|
|
201
|
+
files: sourceFiles,
|
|
202
|
+
// Preserve optional UX flags so they round-trip through save/load.
|
|
203
|
+
...(source.includeRunHistoryInContext
|
|
204
|
+
? { includeRunHistoryInContext: true }
|
|
205
|
+
: {}),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getGhaLabFileOrder(
|
|
210
|
+
workspace: GithubActionsLabWorkspace,
|
|
211
|
+
): string[] {
|
|
212
|
+
const all = Object.keys(workspace.files);
|
|
213
|
+
// Surface workflows first, then composite actions, then scripts, then rest.
|
|
214
|
+
const workflows = all
|
|
215
|
+
.filter((p) => p.startsWith(".github/workflows/"))
|
|
216
|
+
.sort();
|
|
217
|
+
const actions = all
|
|
218
|
+
.filter((p) => p.startsWith(".github/actions/") && !workflows.includes(p))
|
|
219
|
+
.sort();
|
|
220
|
+
const scripts = all
|
|
221
|
+
.filter(
|
|
222
|
+
(p) =>
|
|
223
|
+
(p.startsWith("scripts/") || p === "package.json") &&
|
|
224
|
+
!workflows.includes(p) &&
|
|
225
|
+
!actions.includes(p),
|
|
226
|
+
)
|
|
227
|
+
.sort();
|
|
228
|
+
const rest = all
|
|
229
|
+
.filter(
|
|
230
|
+
(p) =>
|
|
231
|
+
!workflows.includes(p) && !actions.includes(p) && !scripts.includes(p),
|
|
232
|
+
)
|
|
233
|
+
.sort((a, b) => {
|
|
234
|
+
if (a === "README.md") return -1;
|
|
235
|
+
if (b === "README.md") return 1;
|
|
236
|
+
return a.localeCompare(b);
|
|
237
|
+
});
|
|
238
|
+
return [...rest, ...workflows, ...actions, ...scripts];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function listWorkflowPaths(
|
|
242
|
+
workspace: GithubActionsLabWorkspace,
|
|
243
|
+
): string[] {
|
|
244
|
+
return Object.keys(workspace.files)
|
|
245
|
+
.filter((p) => /^\.github\/workflows\/.+\.(ya?ml)$/i.test(p))
|
|
246
|
+
.sort();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function serializeGhaLabWorkspace(
|
|
250
|
+
workspace: GithubActionsLabWorkspace,
|
|
251
|
+
): string {
|
|
252
|
+
return JSON.stringify(cloneGhaLabWorkspace(workspace), null, 2);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function parseGhaLabWorkspace(
|
|
256
|
+
raw: string,
|
|
257
|
+
): GithubActionsLabWorkspace | null {
|
|
258
|
+
try {
|
|
259
|
+
const parsed = JSON.parse(raw) as Partial<GithubActionsLabWorkspace> & {
|
|
260
|
+
files?: Record<string, unknown>;
|
|
261
|
+
};
|
|
262
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
263
|
+
if (!parsed.files || typeof parsed.files !== "object") return null;
|
|
264
|
+
const files = Object.fromEntries(
|
|
265
|
+
Object.entries(parsed.files).filter(
|
|
266
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
if (Object.keys(files).length === 0) return null;
|
|
270
|
+
return cloneGhaLabWorkspace({
|
|
271
|
+
version: 1,
|
|
272
|
+
label:
|
|
273
|
+
typeof parsed.label === "string" && parsed.label.trim()
|
|
274
|
+
? parsed.label.trim()
|
|
275
|
+
: DEFAULT_GHA_LAB.label,
|
|
276
|
+
activeFile:
|
|
277
|
+
typeof parsed.activeFile === "string"
|
|
278
|
+
? parsed.activeFile
|
|
279
|
+
: DEFAULT_GHA_LAB.activeFile,
|
|
280
|
+
defaultEvent:
|
|
281
|
+
typeof parsed.defaultEvent === "string" ? parsed.defaultEvent : "push",
|
|
282
|
+
defaultWorkflow:
|
|
283
|
+
typeof parsed.defaultWorkflow === "string"
|
|
284
|
+
? parsed.defaultWorkflow
|
|
285
|
+
: DEFAULT_GHA_LAB.defaultWorkflow,
|
|
286
|
+
files,
|
|
287
|
+
...(parsed.includeRunHistoryInContext === true
|
|
288
|
+
? { includeRunHistoryInContext: true }
|
|
289
|
+
: {}),
|
|
290
|
+
});
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|