create-interview-cockpit 0.17.3 → 0.18.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.
@@ -0,0 +1,287 @@
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
+ };
203
+ }
204
+
205
+ export function getGhaLabFileOrder(
206
+ workspace: GithubActionsLabWorkspace,
207
+ ): string[] {
208
+ const all = Object.keys(workspace.files);
209
+ // Surface workflows first, then composite actions, then scripts, then rest.
210
+ const workflows = all
211
+ .filter((p) => p.startsWith(".github/workflows/"))
212
+ .sort();
213
+ const actions = all
214
+ .filter((p) => p.startsWith(".github/actions/") && !workflows.includes(p))
215
+ .sort();
216
+ const scripts = all
217
+ .filter(
218
+ (p) =>
219
+ (p.startsWith("scripts/") || p === "package.json") &&
220
+ !workflows.includes(p) &&
221
+ !actions.includes(p),
222
+ )
223
+ .sort();
224
+ const rest = all
225
+ .filter(
226
+ (p) =>
227
+ !workflows.includes(p) && !actions.includes(p) && !scripts.includes(p),
228
+ )
229
+ .sort((a, b) => {
230
+ if (a === "README.md") return -1;
231
+ if (b === "README.md") return 1;
232
+ return a.localeCompare(b);
233
+ });
234
+ return [...rest, ...workflows, ...actions, ...scripts];
235
+ }
236
+
237
+ export function listWorkflowPaths(
238
+ workspace: GithubActionsLabWorkspace,
239
+ ): string[] {
240
+ return Object.keys(workspace.files)
241
+ .filter((p) => /^\.github\/workflows\/.+\.(ya?ml)$/i.test(p))
242
+ .sort();
243
+ }
244
+
245
+ export function serializeGhaLabWorkspace(
246
+ workspace: GithubActionsLabWorkspace,
247
+ ): string {
248
+ return JSON.stringify(cloneGhaLabWorkspace(workspace), null, 2);
249
+ }
250
+
251
+ export function parseGhaLabWorkspace(
252
+ raw: string,
253
+ ): GithubActionsLabWorkspace | null {
254
+ try {
255
+ const parsed = JSON.parse(raw) as Partial<GithubActionsLabWorkspace> & {
256
+ files?: Record<string, unknown>;
257
+ };
258
+ if (!parsed || typeof parsed !== "object") return null;
259
+ if (!parsed.files || typeof parsed.files !== "object") return null;
260
+ const files = Object.fromEntries(
261
+ Object.entries(parsed.files).filter(
262
+ (entry): entry is [string, string] => typeof entry[1] === "string",
263
+ ),
264
+ );
265
+ if (Object.keys(files).length === 0) return null;
266
+ return cloneGhaLabWorkspace({
267
+ version: 1,
268
+ label:
269
+ typeof parsed.label === "string" && parsed.label.trim()
270
+ ? parsed.label.trim()
271
+ : DEFAULT_GHA_LAB.label,
272
+ activeFile:
273
+ typeof parsed.activeFile === "string"
274
+ ? parsed.activeFile
275
+ : DEFAULT_GHA_LAB.activeFile,
276
+ defaultEvent:
277
+ typeof parsed.defaultEvent === "string" ? parsed.defaultEvent : "push",
278
+ defaultWorkflow:
279
+ typeof parsed.defaultWorkflow === "string"
280
+ ? parsed.defaultWorkflow
281
+ : DEFAULT_GHA_LAB.defaultWorkflow,
282
+ files,
283
+ });
284
+ } catch {
285
+ return null;
286
+ }
287
+ }