@quikit/quikpilot-runner 7.0.2
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/README.md +27 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +7 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @quikit/quikpilot-runner
|
|
2
|
+
|
|
3
|
+
QuikPilot's CI runner. Invoked automatically by the `.github/workflows/quikpilot.yml` file QuikPilot
|
|
4
|
+
installs in an onboarded repository — it fetches the run's work packet, runs your setup/lint/test
|
|
5
|
+
commands, reports results to QuikTest, enforces the denied-path guardrail, commits, pushes, and
|
|
6
|
+
reports back to QuikTrack.
|
|
7
|
+
|
|
8
|
+
This package is not meant to be installed or run by hand. It exists so a fix to QuikPilot's runner
|
|
9
|
+
logic ships as a new package version rather than a new pull request into every onboarded repository
|
|
10
|
+
— the workflow file itself lives in your repository (QuikIT cannot update it directly), so it stays a
|
|
11
|
+
thin, ~25-line launcher that invokes an exact, pinned version of this package via `npx`.
|
|
12
|
+
|
|
13
|
+
It carries no secrets and needs none of your credentials: it authenticates to QuikTrack and to GitHub
|
|
14
|
+
using short-lived tokens exchanged at runtime, never a value baked into the workflow file or this
|
|
15
|
+
package.
|
|
16
|
+
|
|
17
|
+
## Versioning and support
|
|
18
|
+
|
|
19
|
+
Every workflow file pins an **exact** version of this package (`npx --yes
|
|
20
|
+
@quikit/quikpilot-runner@<version>`, never a range or `latest`) — a repository never picks up a new
|
|
21
|
+
release on its own. Older published versions are not removed from npm and keep working as long as a
|
|
22
|
+
repository's workflow file keeps pinning them; upgrading to a newer version only happens through a new
|
|
23
|
+
pull request into that repository (QuikTrack's "re-bootstrap" flow), never silently.
|
|
24
|
+
|
|
25
|
+
Releases follow [SemVer](https://semver.org/): a major version bump means the workflow file's own
|
|
26
|
+
shape changed (inputs, permissions) and requires a new pull request regardless of this package's
|
|
27
|
+
version; minor/patch releases are runner-logic fixes safe to pick up on the next re-bootstrap.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/callback.ts
|
|
27
|
+
async function reportBack(params) {
|
|
28
|
+
const { callbackUrl, runToken, status, branch } = params;
|
|
29
|
+
const body = { status };
|
|
30
|
+
if (branch) body.branch = branch;
|
|
31
|
+
const res = await fetch(callbackUrl, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { Authorization: `Bearer ${runToken}`, "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify(body)
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(`Report back to QuikTrack failed: ${res.status} ${res.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/commands.ts
|
|
42
|
+
var import_node_child_process = require("node:child_process");
|
|
43
|
+
function runCommand(command, label) {
|
|
44
|
+
if (!command) return;
|
|
45
|
+
try {
|
|
46
|
+
(0, import_node_child_process.execSync)(command, { shell: "/bin/bash", stdio: "inherit" });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error(`${label} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function runTestCommand(command) {
|
|
52
|
+
if (!command) return "success";
|
|
53
|
+
try {
|
|
54
|
+
(0, import_node_child_process.execSync)(command, { shell: "/bin/bash", stdio: "inherit" });
|
|
55
|
+
return "success";
|
|
56
|
+
} catch {
|
|
57
|
+
return "failure";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/git.ts
|
|
62
|
+
var import_node_child_process2 = require("node:child_process");
|
|
63
|
+
function git(args, extraEnv = {}) {
|
|
64
|
+
return (0, import_node_child_process2.execFileSync)("git", args, {
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
env: { ...process.env, ...extraEnv }
|
|
67
|
+
}).trim();
|
|
68
|
+
}
|
|
69
|
+
function currentBranch() {
|
|
70
|
+
return git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
71
|
+
}
|
|
72
|
+
function checkoutTargetBranch(targetBranch) {
|
|
73
|
+
if (!targetBranch || targetBranch === currentBranch()) return;
|
|
74
|
+
git(["fetch", "origin", targetBranch]);
|
|
75
|
+
git(["checkout", targetBranch]);
|
|
76
|
+
}
|
|
77
|
+
function addAll() {
|
|
78
|
+
git(["add", "-A"]);
|
|
79
|
+
}
|
|
80
|
+
function diffCachedNamesMatching(pattern) {
|
|
81
|
+
const out = git(["diff", "--cached", "--name-only", "--", `:(glob)${pattern}`]);
|
|
82
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
83
|
+
}
|
|
84
|
+
function hasStagedChanges() {
|
|
85
|
+
try {
|
|
86
|
+
(0, import_node_child_process2.execFileSync)("git", ["diff", "--cached", "--quiet"]);
|
|
87
|
+
return false;
|
|
88
|
+
} catch {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function configureCommitIdentity() {
|
|
93
|
+
git(["config", "user.name", "quikpilot[bot]"]);
|
|
94
|
+
git(["config", "user.email", "quikpilot@users.noreply.github.com"]);
|
|
95
|
+
}
|
|
96
|
+
function checkoutNewBranch(branch) {
|
|
97
|
+
git(["checkout", "-b", branch]);
|
|
98
|
+
}
|
|
99
|
+
function commit(message) {
|
|
100
|
+
git(["commit", "-m", message]);
|
|
101
|
+
}
|
|
102
|
+
function clearCheckoutExtraHeader() {
|
|
103
|
+
try {
|
|
104
|
+
git(["config", "--unset-all", "http.https://github.com/.extraheader"]);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
const status = err.status;
|
|
107
|
+
if (status !== 5) throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function push(branch, installationToken) {
|
|
111
|
+
clearCheckoutExtraHeader();
|
|
112
|
+
const basicAuth = Buffer.from(`x-access-token:${installationToken}`).toString("base64");
|
|
113
|
+
git(["push", "--set-upstream", "origin", branch], {
|
|
114
|
+
GIT_CONFIG_COUNT: "1",
|
|
115
|
+
GIT_CONFIG_KEY_0: "http.https://github.com/.extraheader",
|
|
116
|
+
GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${basicAuth}`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/commit.ts
|
|
121
|
+
function commitAndPush(branch, runId, installationToken) {
|
|
122
|
+
if (!hasStagedChanges()) return { changed: false };
|
|
123
|
+
if (!branch) {
|
|
124
|
+
throw new Error("packet carried no branch name");
|
|
125
|
+
}
|
|
126
|
+
configureCommitIdentity();
|
|
127
|
+
checkoutNewBranch(branch);
|
|
128
|
+
commit(`QuikPilot Phase 0 skeleton (run ${runId})`);
|
|
129
|
+
push(branch, installationToken);
|
|
130
|
+
return { changed: true, branch };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/env.ts
|
|
134
|
+
function required(name, value) {
|
|
135
|
+
if (!value) {
|
|
136
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
function readRunnerEnv(env = process.env) {
|
|
141
|
+
return {
|
|
142
|
+
runId: required("RUN_ID", env.RUN_ID),
|
|
143
|
+
callbackUrl: required("CALLBACK_URL", env.CALLBACK_URL),
|
|
144
|
+
packetUrl: required("PACKET_URL", env.PACKET_URL),
|
|
145
|
+
tokenUrl: required("TOKEN_URL", env.TOKEN_URL),
|
|
146
|
+
installationTokenUrl: required("INSTALLATION_TOKEN_URL", env.INSTALLATION_TOKEN_URL),
|
|
147
|
+
githubRunId: required("GITHUB_RUN_ID", env.GITHUB_RUN_ID),
|
|
148
|
+
githubServerUrl: env.GITHUB_SERVER_URL ?? "https://github.com",
|
|
149
|
+
githubRepository: required("GITHUB_REPOSITORY", env.GITHUB_REPOSITORY)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/guardrail.ts
|
|
154
|
+
function checkGuardrail(deniedPaths) {
|
|
155
|
+
addAll();
|
|
156
|
+
const violations = deniedPaths.flatMap((pattern) => diffCachedNamesMatching(pattern));
|
|
157
|
+
if (violations.length > 0) {
|
|
158
|
+
throw new Error(`QuikPilot touched denied paths:
|
|
159
|
+
${violations.join("\n")}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/installationToken.ts
|
|
164
|
+
async function exchangeForInstallationToken(installationTokenUrl, runToken) {
|
|
165
|
+
const res = await fetch(installationTokenUrl, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { Authorization: `Bearer ${runToken}` }
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
throw new Error(`Exchange installation token failed: ${res.status} ${res.statusText}`);
|
|
171
|
+
}
|
|
172
|
+
const body = await res.json();
|
|
173
|
+
if (body.success !== true) {
|
|
174
|
+
throw new Error("Exchange installation token failed: response did not carry success:true");
|
|
175
|
+
}
|
|
176
|
+
return body.data.token;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/oidc.ts
|
|
180
|
+
var QUIKPILOT_OIDC_AUDIENCE = "quiktrack-quikpilot";
|
|
181
|
+
async function fetchGithubOidcToken(env = process.env) {
|
|
182
|
+
const requestUrl = required(
|
|
183
|
+
"ACTIONS_ID_TOKEN_REQUEST_URL",
|
|
184
|
+
env.ACTIONS_ID_TOKEN_REQUEST_URL
|
|
185
|
+
);
|
|
186
|
+
const requestToken = required(
|
|
187
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN",
|
|
188
|
+
env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
189
|
+
);
|
|
190
|
+
const separator = requestUrl.includes("?") ? "&" : "?";
|
|
191
|
+
const url = `${requestUrl}${separator}audience=${encodeURIComponent(QUIKPILOT_OIDC_AUDIENCE)}`;
|
|
192
|
+
const res = await fetch(url, {
|
|
193
|
+
headers: { Authorization: `Bearer ${requestToken}` }
|
|
194
|
+
});
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
throw new Error(`Fetch GitHub OIDC token failed: ${res.status} ${res.statusText}`);
|
|
197
|
+
}
|
|
198
|
+
const body = await res.json();
|
|
199
|
+
if (!body.value) {
|
|
200
|
+
throw new Error("GitHub OIDC token response carried no value");
|
|
201
|
+
}
|
|
202
|
+
return body.value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/packet.ts
|
|
206
|
+
async function fetchPacket(packetUrl, runToken, githubRunId) {
|
|
207
|
+
const url = `${packetUrl}?githubRunId=${encodeURIComponent(githubRunId)}`;
|
|
208
|
+
const res = await fetch(url, {
|
|
209
|
+
headers: { Authorization: `Bearer ${runToken}` }
|
|
210
|
+
});
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
throw new Error(`Fetch work packet failed: ${res.status} ${res.statusText}`);
|
|
213
|
+
}
|
|
214
|
+
const body = await res.json();
|
|
215
|
+
if (body.success !== true) {
|
|
216
|
+
throw new Error("Fetch work packet failed: response did not carry success:true");
|
|
217
|
+
}
|
|
218
|
+
return body.data;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/phase0.ts
|
|
222
|
+
var import_node_fs = require("node:fs");
|
|
223
|
+
function writePhase0Marker(runId) {
|
|
224
|
+
(0, import_node_fs.mkdirSync)(".quikpilot", { recursive: true });
|
|
225
|
+
(0, import_node_fs.appendFileSync)(".quikpilot/phase0-marker.txt", `QuikPilot Phase 0 skeleton marker for run ${runId}
|
|
226
|
+
`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/quiktest.ts
|
|
230
|
+
var import_node_fs2 = require("node:fs");
|
|
231
|
+
var import_node_path = require("node:path");
|
|
232
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
233
|
+
function walkForJUnitByName(dir) {
|
|
234
|
+
let entries;
|
|
235
|
+
try {
|
|
236
|
+
entries = (0, import_node_fs2.readdirSync)(dir);
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
242
|
+
const full = (0, import_node_path.join)(dir, entry);
|
|
243
|
+
let stat;
|
|
244
|
+
try {
|
|
245
|
+
stat = (0, import_node_fs2.statSync)(full);
|
|
246
|
+
} catch {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (stat.isDirectory()) {
|
|
250
|
+
const found = walkForJUnitByName(full);
|
|
251
|
+
if (found) return found;
|
|
252
|
+
} else if (/^junit.*\.xml$/.test(entry)) {
|
|
253
|
+
return full;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
function firstXmlIn(dir, namePattern) {
|
|
259
|
+
let entries;
|
|
260
|
+
try {
|
|
261
|
+
entries = (0, import_node_fs2.readdirSync)(dir);
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const match = entries.find((e) => namePattern.test(e));
|
|
266
|
+
return match ? (0, import_node_path.join)(dir, match) : null;
|
|
267
|
+
}
|
|
268
|
+
function findJUnitReport(cwd = process.cwd()) {
|
|
269
|
+
return walkForJUnitByName(cwd) ?? firstXmlIn((0, import_node_path.join)(cwd, "test-results"), /\.xml$/) ?? firstXmlIn((0, import_node_path.join)(cwd, "reports"), /^junit.*\.xml$/) ?? firstXmlIn((0, import_node_path.join)(cwd, "target", "surefire-reports"), /\.xml$/);
|
|
270
|
+
}
|
|
271
|
+
function buildCiUrl(serverUrl, repository, githubRunId) {
|
|
272
|
+
return `${serverUrl}/${repository}/actions/runs/${githubRunId}`;
|
|
273
|
+
}
|
|
274
|
+
async function reportToQuikTest(params) {
|
|
275
|
+
const { resultsUrl, runToken, githubServerUrl, githubRepository, githubRunId, cwd } = params;
|
|
276
|
+
if (!resultsUrl) {
|
|
277
|
+
console.log("packet.quikTest is null \u2014 this run has no QuikTest run to report into.");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const report = findJUnitReport(cwd);
|
|
281
|
+
if (!report) {
|
|
282
|
+
console.warn("::warning::QuikPilot found no JUnit report \u2014 no results reported to QuikTest.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
console.log(`reporting ${report}`);
|
|
287
|
+
const ciUrl = buildCiUrl(githubServerUrl, githubRepository, githubRunId);
|
|
288
|
+
const { readFileSync } = await import("node:fs");
|
|
289
|
+
const body = readFileSync(report);
|
|
290
|
+
const url = `${resultsUrl}?ciUrl=${encodeURIComponent(ciUrl)}`;
|
|
291
|
+
const res = await fetch(url, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: { Authorization: `Bearer ${runToken}`, "Content-Type": "text/xml" },
|
|
294
|
+
body
|
|
295
|
+
});
|
|
296
|
+
if (!res.ok) {
|
|
297
|
+
throw new Error(`QuikTest report POST failed: ${res.status} ${res.statusText}`);
|
|
298
|
+
}
|
|
299
|
+
(0, import_node_fs2.rmSync)(report, { force: true });
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.warn(`::warning::QuikPilot failed to report results to QuikTest: ${err instanceof Error ? err.message : String(err)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/tokenExchange.ts
|
|
306
|
+
async function exchangeForRunToken(tokenUrl, oidcToken) {
|
|
307
|
+
const res = await fetch(tokenUrl, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: { Authorization: `Bearer ${oidcToken}` }
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) {
|
|
312
|
+
throw new Error(`Exchange run token failed: ${res.status} ${res.statusText}`);
|
|
313
|
+
}
|
|
314
|
+
const body = await res.json();
|
|
315
|
+
if (body.success !== true) {
|
|
316
|
+
throw new Error("Exchange run token failed: response did not carry success:true");
|
|
317
|
+
}
|
|
318
|
+
return body.data.runToken;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/run.ts
|
|
322
|
+
async function run(env = readRunnerEnv()) {
|
|
323
|
+
let hardFailure = false;
|
|
324
|
+
let packet;
|
|
325
|
+
let branch;
|
|
326
|
+
let runToken;
|
|
327
|
+
try {
|
|
328
|
+
runToken = await exchangeForRunToken(env.tokenUrl, await fetchGithubOidcToken());
|
|
329
|
+
packet = await fetchPacket(env.packetUrl, runToken, env.githubRunId);
|
|
330
|
+
checkoutTargetBranch(packet.targetBranch);
|
|
331
|
+
writePhase0Marker(env.runId);
|
|
332
|
+
runCommand(packet.commands.setup, "Setup");
|
|
333
|
+
runCommand(packet.commands.lint, "Lint");
|
|
334
|
+
runTestCommand(packet.commands.test);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
hardFailure = true;
|
|
337
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
338
|
+
}
|
|
339
|
+
if (!runToken) {
|
|
340
|
+
console.error("No run token \u2014 the OIDC exchange failed before QuikTrack could be reached.");
|
|
341
|
+
return { hardFailure: true };
|
|
342
|
+
}
|
|
343
|
+
await reportToQuikTest({
|
|
344
|
+
resultsUrl: packet?.quikTest?.resultsUrl,
|
|
345
|
+
runToken,
|
|
346
|
+
githubServerUrl: env.githubServerUrl,
|
|
347
|
+
githubRepository: env.githubRepository,
|
|
348
|
+
githubRunId: env.githubRunId
|
|
349
|
+
});
|
|
350
|
+
if (!hardFailure && packet) {
|
|
351
|
+
try {
|
|
352
|
+
checkGuardrail(packet.guardrails.deniedPaths);
|
|
353
|
+
const installationToken = await exchangeForInstallationToken(
|
|
354
|
+
env.installationTokenUrl,
|
|
355
|
+
runToken
|
|
356
|
+
);
|
|
357
|
+
const result = commitAndPush(packet.branch, env.runId, installationToken);
|
|
358
|
+
branch = result.branch;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
hardFailure = true;
|
|
361
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
await reportBack({
|
|
365
|
+
callbackUrl: env.callbackUrl,
|
|
366
|
+
runToken,
|
|
367
|
+
status: hardFailure ? "failed" : "completed",
|
|
368
|
+
branch
|
|
369
|
+
});
|
|
370
|
+
return { hardFailure };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/index.ts
|
|
374
|
+
run().then(({ hardFailure }) => {
|
|
375
|
+
process.exitCode = hardFailure ? 1 : 0;
|
|
376
|
+
}).catch((err) => {
|
|
377
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
378
|
+
process.exitCode = 1;
|
|
379
|
+
});
|
|
380
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/callback.ts", "../src/commands.ts", "../src/git.ts", "../src/commit.ts", "../src/env.ts", "../src/guardrail.ts", "../src/installationToken.ts", "../src/oidc.ts", "../src/packet.ts", "../src/phase0.ts", "../src/quiktest.ts", "../src/tokenExchange.ts", "../src/run.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export type RunStatus = \"completed\" | \"failed\";\n\n/** Always fires, regardless of what failed upstream \u2014 a run that fails silently is worse than one\n * that fails loudly. `branch` is omitted entirely (not sent as null/empty) when no push happened,\n * matching the original `jq ... | with_entries(select(.value != null and .value != \"\"))`. */\nexport async function reportBack(params: {\n callbackUrl: string;\n runToken: string;\n status: RunStatus;\n branch?: string;\n}): Promise<void> {\n const { callbackUrl, runToken, status, branch } = params;\n const body: Record<string, string> = { status };\n if (branch) body.branch = branch;\n const res = await fetch(callbackUrl, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${runToken}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n throw new Error(`Report back to QuikTrack failed: ${res.status} ${res.statusText}`);\n }\n}\n", "import { execSync } from \"node:child_process\";\n\n/** Runs a packet-supplied command through bash \u2014 same interpreter GitHub's own `run:` steps used,\n * so a command written against that shell (e.g. `set -o pipefail`-reliant) behaves the same way.\n * A null/empty command is a no-op, matching the original `[ -n \"$CMD\" ]` guard. */\nexport function runCommand(command: string | null, label: string): void {\n if (!command) return;\n try {\n execSync(command, { shell: \"/bin/bash\", stdio: \"inherit\" });\n } catch (err) {\n throw new Error(`${label} failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nexport type TestOutcome = \"success\" | \"failure\";\n\n/** The Test step alone is soft \u2014 a failing suite must not hide the rest of the run (guardrail,\n * commit, push) from happening, and must not flip the final callback to `failed`. QuikTest owns\n * per-test outcomes; this is only ever logged, never reported. */\nexport function runTestCommand(command: string | null): TestOutcome {\n if (!command) return \"success\";\n try {\n execSync(command, { shell: \"/bin/bash\", stdio: \"inherit\" });\n return \"success\";\n } catch {\n return \"failure\";\n }\n}\n", "import { execFileSync } from \"node:child_process\";\n\n/** Thin wrapper around `execFileSync` \u2014 args passed as an array, never through a shell, so a\n * branch name or guardrail pattern can never be interpreted as shell syntax. `extraEnv` layers\n * on top of the inherited environment; every existing call site is unaffected since it defaults\n * to empty. */\nfunction git(args: string[], extraEnv: NodeJS.ProcessEnv = {}): string {\n return execFileSync(\"git\", args, {\n encoding: \"utf8\",\n env: { ...process.env, ...extraEnv },\n }).trim();\n}\n\nexport function currentBranch(): string {\n return git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n}\n\n/** Only fetches + checks out when the packet names a target branch different from the one the\n * workflow_dispatch already put us on \u2014 dispatch always runs against the default branch (the\n * only place `workflow_dispatch` can find the file), but the PR base may differ. */\nexport function checkoutTargetBranch(targetBranch: string | null): void {\n if (!targetBranch || targetBranch === currentBranch()) return;\n git([\"fetch\", \"origin\", targetBranch]);\n git([\"checkout\", targetBranch]);\n}\n\nexport function addAll(): void {\n git([\"add\", \"-A\"]);\n}\n\n/** `:(glob)` pathspec \u2014 same semantics `**` needs that a hand-rolled glob-to-regex would risk\n * getting wrong. */\nexport function diffCachedNamesMatching(pattern: string): string[] {\n const out = git([\"diff\", \"--cached\", \"--name-only\", \"--\", `:(glob)${pattern}`]);\n return out ? out.split(\"\\n\").filter(Boolean) : [];\n}\n\nexport function hasStagedChanges(): boolean {\n try {\n execFileSync(\"git\", [\"diff\", \"--cached\", \"--quiet\"]);\n return false;\n } catch {\n return true;\n }\n}\n\nexport function configureCommitIdentity(): void {\n git([\"config\", \"user.name\", \"quikpilot[bot]\"]);\n git([\"config\", \"user.email\", \"quikpilot@users.noreply.github.com\"]);\n}\n\nexport function checkoutNewBranch(branch: string): void {\n git([\"checkout\", \"-b\", branch]);\n}\n\nexport function commit(message: string): void {\n git([\"commit\", \"-m\", message]);\n}\n\n/**\n * Push, authenticated with a repo-scoped GitHub installation token (QUIKTR-804, D16) rather than\n * whatever ambient credential checkout configured. `x-access-token:<token>` Basic-auth, GitHub's\n * documented scheme for installation tokens over HTTPS git.\n *\n * Carried via `GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_0`/`GIT_CONFIG_VALUE_0` env vars rather than a\n * `-c http.extraheader=...` argv flag or a `.git/config` write: env vars are not visible to `ps`,\n * argv is \u2014 the same class of exposure `packet.ts`'s own header comment already avoids for this\n * runner's other credential. Scoped to this one invocation only; never persisted.\n *\n * QUIKTR-943 rehearsal, 7.0.1 \u2014 the key MUST be `http.https://github.com/.extraheader`\n * (URL-scoped), not the unscoped `http.extraheader`. `actions/checkout` persists its own\n * credential under the URL-scoped key, pointing at the job's ambient token (contents: read since\n * QUIKTR-804 dropped it). Git prefers the more specific URL-scoped key, so an unscoped header here\n * silently loses to checkout's read-only one \u2014 the push then fails with \"denied to\n * github-actions[bot]\" even though this token itself has write access and the exchange succeeded.\n *\n * QUIKTR-943 rehearsal, 7.0.2 \u2014 url-scoping alone re-broke the push a second way:\n * `http.<url>.extraheader` is multi-valued (git appends, it doesn't replace), so checkout's\n * `.git/config` entry and this call's env-var entry are BOTH sent, and GitHub 400s with\n * `Duplicate header: \"Authorization\"`. `--unset-all` clears checkout's entry from `.git/config`\n * first, so our env-var header (scoped to this one invocation, never written to disk) is the only\n * one on the wire. Tolerates the key already being absent (exit 5) \u2014 nothing to clear is not a\n * failure, and a workflow that ever runs with `persist-credentials: false` shouldn't crash here.\n */\nfunction clearCheckoutExtraHeader(): void {\n try {\n git([\"config\", \"--unset-all\", \"http.https://github.com/.extraheader\"]);\n } catch (err: unknown) {\n const status = (err as { status?: number }).status;\n if (status !== 5) throw err;\n }\n}\n\nexport function push(branch: string, installationToken: string): void {\n clearCheckoutExtraHeader();\n const basicAuth = Buffer.from(`x-access-token:${installationToken}`).toString(\"base64\");\n git([\"push\", \"--set-upstream\", \"origin\", branch], {\n GIT_CONFIG_COUNT: \"1\",\n GIT_CONFIG_KEY_0: \"http.https://github.com/.extraheader\",\n GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${basicAuth}`,\n });\n}\n", "import {\n checkoutNewBranch,\n commit,\n configureCommitIdentity,\n hasStagedChanges,\n push,\n} from \"./git\";\n\nexport interface CommitResult {\n changed: boolean;\n branch?: string;\n}\n\n/** Runs regardless of the Test step's outcome \u2014 a failing suite must not hide the generated diff\n * from a reviewer (the whole reason Test alone is a soft failure). Only reached when Setup, Lint,\n * and the guardrail check all passed. The branch name comes from the packet, not `runId`: QuikTrack\n * already computed and stored it, and two sources for one value will eventually disagree.\n * `installationToken` (QUIKTR-804) is the repo-scoped GitHub credential the push authenticates\n * with \u2014 see git.ts's `push` for why. */\nexport function commitAndPush(\n branch: string | null,\n runId: string,\n installationToken: string,\n): CommitResult {\n if (!hasStagedChanges()) return { changed: false };\n if (!branch) {\n throw new Error(\"packet carried no branch name\");\n }\n configureCommitIdentity();\n checkoutNewBranch(branch);\n commit(`QuikPilot Phase 0 skeleton (run ${runId})`);\n push(branch, installationToken);\n return { changed: true, branch };\n}\n", "/** The `workflow_dispatch` inputs QuikTrack's `dispatch.ts` sends, unchanged by QUIKTR-572 \u2014 the\n * generated workflow file still passes them through as job-level env vars under these exact\n * names. QUIKTR-805: `runToken`/`RUN_TOKEN` (a bearer, visible in the Actions UI as a dispatch\n * input) was replaced with `tokenUrl`/`TOKEN_URL` \u2014 the runner now exchanges a GitHub OIDC ID\n * token there for a run token at runtime; see `oidc.ts` and `tokenExchange.ts`. QUIKTR-804 adds\n * `installationTokenUrl`/`INSTALLATION_TOKEN_URL` \u2014 the run token is exchanged there for a\n * repo-scoped GitHub push credential; see `installationToken.ts`. */\nexport interface RunnerEnv {\n runId: string;\n callbackUrl: string;\n packetUrl: string;\n tokenUrl: string;\n installationTokenUrl: string;\n /** GitHub Actions built-ins, read directly from `process.env` rather than threaded through here\n * individually. */\n githubRunId: string;\n githubServerUrl: string;\n githubRepository: string;\n}\n\nexport function required(name: string, value: string | undefined): string {\n if (!value) {\n throw new Error(`Missing required environment variable: ${name}`);\n }\n return value;\n}\n\nexport function readRunnerEnv(env: NodeJS.ProcessEnv = process.env): RunnerEnv {\n return {\n runId: required(\"RUN_ID\", env.RUN_ID),\n callbackUrl: required(\"CALLBACK_URL\", env.CALLBACK_URL),\n packetUrl: required(\"PACKET_URL\", env.PACKET_URL),\n tokenUrl: required(\"TOKEN_URL\", env.TOKEN_URL),\n installationTokenUrl: required(\"INSTALLATION_TOKEN_URL\", env.INSTALLATION_TOKEN_URL),\n githubRunId: required(\"GITHUB_RUN_ID\", env.GITHUB_RUN_ID),\n githubServerUrl: env.GITHUB_SERVER_URL ?? \"https://github.com\",\n githubRepository: required(\"GITHUB_REPOSITORY\", env.GITHUB_REPOSITORY),\n };\n}\n", "import { addAll, diffCachedNamesMatching } from \"./git\";\n\n/** Stages everything, then refuses to proceed if any staged file matches a denied-path glob.\n * Public and inspectable on purpose (Suyash, 2026-09-10): moving this into a published package\n * doesn't hide the enforcement \u2014 it's the same check, just no longer duplicated per customer repo. */\nexport function checkGuardrail(deniedPaths: string[]): void {\n addAll();\n const violations = deniedPaths.flatMap((pattern) => diffCachedNamesMatching(pattern));\n if (violations.length > 0) {\n throw new Error(`QuikPilot touched denied paths:\\n${violations.join(\"\\n\")}`);\n }\n}\n", "interface InstallationTokenResponse {\n success: boolean;\n data: { token: string; expiresAt: string };\n}\n\n/** Exchange the QuikPilot run token for a repo-scoped GitHub installation token (QUIKTR-804) \u2014\n * mirrors `tokenExchange.ts`'s `exchangeForRunToken` shape: bearer auth, `{success,data}`\n * envelope, same error-message style. */\nexport async function exchangeForInstallationToken(\n installationTokenUrl: string,\n runToken: string,\n): Promise<string> {\n const res = await fetch(installationTokenUrl, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${runToken}` },\n });\n if (!res.ok) {\n throw new Error(`Exchange installation token failed: ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as InstallationTokenResponse;\n if (body.success !== true) {\n throw new Error(\"Exchange installation token failed: response did not carry success:true\");\n }\n return body.data.token;\n}\n", "import { required } from \"./env\";\n\n/** Must equal `apps/quiktrack/lib/services/github/oidc.ts`'s own copy of this literal \u2014 that app\n * verifies the token this package requests. Duplicated because this package is published outside\n * the monorepo and cannot import from the app; same reasoning `packet.ts`'s header gives for\n * duplicating `PilotPacket`'s shape. */\nconst QUIKPILOT_OIDC_AUDIENCE = \"quiktrack-quikpilot\";\n\ninterface OidcTokenResponse {\n value: string;\n}\n\n/** Request a GitHub Actions OIDC ID token \u2014 the runner's proof of identity, exchanged at\n * `TOKEN_URL` for a QuikPilot run token (QUIKTR-805). `ACTIONS_ID_TOKEN_REQUEST_URL` and\n * `ACTIONS_ID_TOKEN_REQUEST_TOKEN` are ambient GitHub Actions env vars, present only because the\n * generated workflow declares `permissions: id-token: write` \u2014 no dispatch input carries them. */\nexport async function fetchGithubOidcToken(\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const requestUrl = required(\n \"ACTIONS_ID_TOKEN_REQUEST_URL\",\n env.ACTIONS_ID_TOKEN_REQUEST_URL,\n );\n const requestToken = required(\n \"ACTIONS_ID_TOKEN_REQUEST_TOKEN\",\n env.ACTIONS_ID_TOKEN_REQUEST_TOKEN,\n );\n const separator = requestUrl.includes(\"?\") ? \"&\" : \"?\";\n const url = `${requestUrl}${separator}audience=${encodeURIComponent(QUIKPILOT_OIDC_AUDIENCE)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${requestToken}` },\n });\n if (!res.ok) {\n throw new Error(`Fetch GitHub OIDC token failed: ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as OidcTokenResponse;\n if (!body.value) {\n throw new Error(\"GitHub OIDC token response carried no value\");\n }\n return body.value;\n}\n", "/** Mirrors `apps/quiktrack/lib/services/pilot/packet.ts`'s `PilotPacket` shape. Duplicated rather\n * than imported \u2014 this package is published outside the monorepo and cannot depend on the app. */\nexport interface PilotPacket {\n runId: string;\n issueKey: string | null;\n branch: string | null;\n targetBranch: string | null;\n commands: {\n setup: string | null;\n test: string | null;\n e2e: string | null;\n lint: string | null;\n };\n guardrails: {\n deniedPaths: string[];\n maxHealIterations: number;\n maxRunUsd: string;\n maxWallClockMinutes: number;\n };\n quikTest: { testRunId: string; resultsUrl: string } | null;\n}\n\ninterface PacketResponse {\n success: boolean;\n data: PilotPacket;\n}\n\n/** GET the run's work packet. Bearer auth via the run token, matching the header curl sent on\n * stdin (`--config -`) so it never showed up in `ps` \u2014 an HTTP client's header option is already\n * invisible to `ps`, so no equivalent trick is needed here. */\nexport async function fetchPacket(\n packetUrl: string,\n runToken: string,\n githubRunId: string,\n): Promise<PilotPacket> {\n const url = `${packetUrl}?githubRunId=${encodeURIComponent(githubRunId)}`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${runToken}` },\n });\n if (!res.ok) {\n throw new Error(`Fetch work packet failed: ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as PacketResponse;\n if (body.success !== true) {\n throw new Error(\"Fetch work packet failed: response did not carry success:true\");\n }\n return body.data;\n}\n", "import { appendFileSync, mkdirSync } from \"node:fs\";\n\n/** Phase 0 has no agent. This proves the loop end to end without touching real source, which would\n * risk breaking the build for a reason nobody would connect back to QuikPilot. Verbatim carry-over\n * from the inlined workflow \u2014 real codegen is separate, not-yet-built work. */\nexport function writePhase0Marker(runId: string): void {\n mkdirSync(\".quikpilot\", { recursive: true });\n appendFileSync(\".quikpilot/phase0-marker.txt\", `QuikPilot Phase 0 skeleton marker for run ${runId}\\n`);\n}\n", "import { readdirSync, rmSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst SKIP_DIRS = new Set([\"node_modules\", \".git\"]);\n\nfunction walkForJUnitByName(dir: string): string | null {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return null;\n }\n for (const entry of entries) {\n if (SKIP_DIRS.has(entry)) continue;\n const full = join(dir, entry);\n let stat;\n try {\n stat = statSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n const found = walkForJUnitByName(full);\n if (found) return found;\n } else if (/^junit.*\\.xml$/.test(entry)) {\n return full;\n }\n }\n return null;\n}\n\nfunction firstXmlIn(dir: string, namePattern: RegExp): string | null {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return null;\n }\n const match = entries.find((e) => namePattern.test(e));\n return match ? join(dir, match) : null;\n}\n\n/** QtPilotConfig has no column for the report path, so this is a convention over a short list of\n * common locations rather than a configurable setting. A project that writes its report elsewhere\n * reports nothing and finds out from the log \u2014 a missing artefact must not fail the run. Checked\n * in the same priority the original `find` clause listed them, though exact traversal order was\n * never guaranteed there either. */\nexport function findJUnitReport(cwd = process.cwd()): string | null {\n return (\n walkForJUnitByName(cwd) ??\n firstXmlIn(join(cwd, \"test-results\"), /\\.xml$/) ??\n firstXmlIn(join(cwd, \"reports\"), /^junit.*\\.xml$/) ??\n firstXmlIn(join(cwd, \"target\", \"surefire-reports\"), /\\.xml$/)\n );\n}\n\nfunction buildCiUrl(serverUrl: string, repository: string, githubRunId: string): string {\n return `${serverUrl}/${repository}/actions/runs/${githubRunId}`;\n}\n\n/** Best-effort \u2014 every branch here is designed to never throw, matching the bash step's own\n * `if: always()` + internally tolerant logic (null resultsUrl and \"no report found\" are both the\n * common path in Phase 0, not edge cases \u2014 D19). Deletes the report after a successful POST so the\n * guardrail's `git add -A` never commits a CI artefact into the customer's pull request. */\nexport async function reportToQuikTest(params: {\n resultsUrl: string | null | undefined;\n runToken: string;\n githubServerUrl: string;\n githubRepository: string;\n githubRunId: string;\n cwd?: string;\n}): Promise<void> {\n const { resultsUrl, runToken, githubServerUrl, githubRepository, githubRunId, cwd } = params;\n if (!resultsUrl) {\n console.log(\"packet.quikTest is null \u2014 this run has no QuikTest run to report into.\");\n return;\n }\n const report = findJUnitReport(cwd);\n if (!report) {\n console.warn(\"::warning::QuikPilot found no JUnit report \u2014 no results reported to QuikTest.\");\n return;\n }\n try {\n console.log(`reporting ${report}`);\n const ciUrl = buildCiUrl(githubServerUrl, githubRepository, githubRunId);\n const { readFileSync } = await import(\"node:fs\");\n const body = readFileSync(report);\n const url = `${resultsUrl}?ciUrl=${encodeURIComponent(ciUrl)}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${runToken}`, \"Content-Type\": \"text/xml\" },\n body,\n });\n if (!res.ok) {\n throw new Error(`QuikTest report POST failed: ${res.status} ${res.statusText}`);\n }\n rmSync(report, { force: true });\n } catch (err) {\n console.warn(`::warning::QuikPilot failed to report results to QuikTest: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n", "interface TokenExchangeResponse {\n success: boolean;\n data: { runToken: string; expiresAt: string };\n}\n\n/** Exchange a GitHub OIDC ID token for a QuikPilot run token (QUIKTR-805) \u2014 mirrors `packet.ts`'s\n * `fetchPacket` shape: bearer auth, `{success,data}` envelope, same error-message style. */\nexport async function exchangeForRunToken(tokenUrl: string, oidcToken: string): Promise<string> {\n const res = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${oidcToken}` },\n });\n if (!res.ok) {\n throw new Error(`Exchange run token failed: ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as TokenExchangeResponse;\n if (body.success !== true) {\n throw new Error(\"Exchange run token failed: response did not carry success:true\");\n }\n return body.data.runToken;\n}\n", "import { reportBack } from \"./callback\";\nimport { runCommand, runTestCommand } from \"./commands\";\nimport { commitAndPush } from \"./commit\";\nimport { readRunnerEnv } from \"./env\";\nimport { checkGuardrail } from \"./guardrail\";\nimport { checkoutTargetBranch } from \"./git\";\nimport { exchangeForInstallationToken } from \"./installationToken\";\nimport { fetchGithubOidcToken } from \"./oidc\";\nimport { fetchPacket, type PilotPacket } from \"./packet\";\nimport { writePhase0Marker } from \"./phase0\";\nimport { reportToQuikTest } from \"./quiktest\";\nimport { exchangeForRunToken } from \"./tokenExchange\";\n\n/**\n * QUIKTR-572 \u2014 the run sequence, preserving the original inlined workflow's error semantics exactly:\n * Fetch packet / Checkout branch / Setup / Lint / Guardrail are hard failures that skip everything\n * remaining except the QuikTest report and the final callback (both ran under `if: always()`\n * before). Test alone is soft \u2014 its failure never blocks Guardrail/Commit/Push and never flips the\n * final callback to `failed` (QuikTest owns per-test outcomes; captured here only for the log).\n *\n * QUIKTR-805 \u2014 the very first thing the run does is exchange a GitHub OIDC ID token for a run\n * token (no QuikPilot credential exists before this). It is folded into the same top-level try as\n * every other hard failure, with one deliberate exception: if THIS step fails, there is no run\n * token to report failure with either, so reportToQuikTest/reportBack are skipped rather than\n * called with nothing to authenticate \u2014 the job's own log is the only record, and the run's\n * existing DISPATCHING-expiry sweep is what notices it (same accepted fallback dispatch.ts's own\n * docblock documents for its \"RESIDUAL WINDOW\" crash case).\n *\n * QUIKTR-804 \u2014 immediately before pushing, the run token (already in hand by this point) is\n * exchanged for a repo-scoped GitHub installation token, which authenticates the push itself\n * instead of the ambient `github.token`. Folded into the EXISTING Guardrail/Commit/Push try block\n * below rather than given its own \u2014 a failure here is just another commit/push hard failure, with\n * no new edge case: unlike the OIDC exchange above, the run token already exists, so\n * reportToQuikTest/reportBack fire normally either way.\n */\nexport async function run(env = readRunnerEnv()): Promise<{ hardFailure: boolean }> {\n let hardFailure = false;\n let packet: PilotPacket | undefined;\n let branch: string | undefined;\n let runToken: string | undefined;\n\n try {\n runToken = await exchangeForRunToken(env.tokenUrl, await fetchGithubOidcToken());\n packet = await fetchPacket(env.packetUrl, runToken, env.githubRunId);\n checkoutTargetBranch(packet.targetBranch);\n writePhase0Marker(env.runId);\n runCommand(packet.commands.setup, \"Setup\");\n runCommand(packet.commands.lint, \"Lint\");\n runTestCommand(packet.commands.test);\n } catch (err) {\n hardFailure = true;\n console.error(err instanceof Error ? err.message : String(err));\n }\n\n if (!runToken) {\n console.error(\"No run token \u2014 the OIDC exchange failed before QuikTrack could be reached.\");\n return { hardFailure: true };\n }\n\n await reportToQuikTest({\n resultsUrl: packet?.quikTest?.resultsUrl,\n runToken,\n githubServerUrl: env.githubServerUrl,\n githubRepository: env.githubRepository,\n githubRunId: env.githubRunId,\n });\n\n if (!hardFailure && packet) {\n try {\n checkGuardrail(packet.guardrails.deniedPaths);\n const installationToken = await exchangeForInstallationToken(\n env.installationTokenUrl,\n runToken,\n );\n const result = commitAndPush(packet.branch, env.runId, installationToken);\n branch = result.branch;\n } catch (err) {\n hardFailure = true;\n console.error(err instanceof Error ? err.message : String(err));\n }\n }\n\n await reportBack({\n callbackUrl: env.callbackUrl,\n runToken,\n status: hardFailure ? \"failed\" : \"completed\",\n branch,\n });\n\n return { hardFailure };\n}\n", "import { run } from \"./run\";\n\nrun()\n .then(({ hardFailure }) => {\n process.exitCode = hardFailure ? 1 : 0;\n })\n .catch((err) => {\n // reportBack itself throwing is the one failure mode `run()` can't recover from internally \u2014\n // still exit non-zero so the raw Actions UI shows the run as failed.\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n });\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,eAAsB,WAAW,QAKf;AAChB,QAAM,EAAE,aAAa,UAAU,QAAQ,OAAO,IAAI;AAClD,QAAM,OAA+B,EAAE,OAAO;AAC9C,MAAI,OAAQ,MAAK,SAAS;AAC1B,QAAM,MAAM,MAAM,MAAM,aAAa;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS,EAAE,eAAe,UAAU,QAAQ,IAAI,gBAAgB,mBAAmB;AAAA,IACnF,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,oCAAoC,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EACpF;AACF;;;ACtBA,gCAAyB;AAKlB,SAAS,WAAW,SAAwB,OAAqB;AACtE,MAAI,CAAC,QAAS;AACd,MAAI;AACF,4CAAS,SAAS,EAAE,OAAO,aAAa,OAAO,UAAU,CAAC;AAAA,EAC5D,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,GAAG,KAAK,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACxF;AACF;AAOO,SAAS,eAAe,SAAqC;AAClE,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,4CAAS,SAAS,EAAE,OAAO,aAAa,OAAO,UAAU,CAAC;AAC1D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC3BA,IAAAA,6BAA6B;AAM7B,SAAS,IAAI,MAAgB,WAA8B,CAAC,GAAW;AACrE,aAAO,yCAAa,OAAO,MAAM;AAAA,IAC/B,UAAU;AAAA,IACV,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,SAAS;AAAA,EACrC,CAAC,EAAE,KAAK;AACV;AAEO,SAAS,gBAAwB;AACtC,SAAO,IAAI,CAAC,aAAa,gBAAgB,MAAM,CAAC;AAClD;AAKO,SAAS,qBAAqB,cAAmC;AACtE,MAAI,CAAC,gBAAgB,iBAAiB,cAAc,EAAG;AACvD,MAAI,CAAC,SAAS,UAAU,YAAY,CAAC;AACrC,MAAI,CAAC,YAAY,YAAY,CAAC;AAChC;AAEO,SAAS,SAAe;AAC7B,MAAI,CAAC,OAAO,IAAI,CAAC;AACnB;AAIO,SAAS,wBAAwB,SAA2B;AACjE,QAAM,MAAM,IAAI,CAAC,QAAQ,YAAY,eAAe,MAAM,UAAU,OAAO,EAAE,CAAC;AAC9E,SAAO,MAAM,IAAI,MAAM,IAAI,EAAE,OAAO,OAAO,IAAI,CAAC;AAClD;AAEO,SAAS,mBAA4B;AAC1C,MAAI;AACF,iDAAa,OAAO,CAAC,QAAQ,YAAY,SAAS,CAAC;AACnD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,0BAAgC;AAC9C,MAAI,CAAC,UAAU,aAAa,gBAAgB,CAAC;AAC7C,MAAI,CAAC,UAAU,cAAc,oCAAoC,CAAC;AACpE;AAEO,SAAS,kBAAkB,QAAsB;AACtD,MAAI,CAAC,YAAY,MAAM,MAAM,CAAC;AAChC;AAEO,SAAS,OAAO,SAAuB;AAC5C,MAAI,CAAC,UAAU,MAAM,OAAO,CAAC;AAC/B;AA2BA,SAAS,2BAAiC;AACxC,MAAI;AACF,QAAI,CAAC,UAAU,eAAe,sCAAsC,CAAC;AAAA,EACvE,SAAS,KAAc;AACrB,UAAM,SAAU,IAA4B;AAC5C,QAAI,WAAW,EAAG,OAAM;AAAA,EAC1B;AACF;AAEO,SAAS,KAAK,QAAgB,mBAAiC;AACpE,2BAAyB;AACzB,QAAM,YAAY,OAAO,KAAK,kBAAkB,iBAAiB,EAAE,EAAE,SAAS,QAAQ;AACtF,MAAI,CAAC,QAAQ,kBAAkB,UAAU,MAAM,GAAG;AAAA,IAChD,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,oBAAoB,wBAAwB,SAAS;AAAA,EACvD,CAAC;AACH;;;AClFO,SAAS,cACd,QACA,OACA,mBACc;AACd,MAAI,CAAC,iBAAiB,EAAG,QAAO,EAAE,SAAS,MAAM;AACjD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,0BAAwB;AACxB,oBAAkB,MAAM;AACxB,SAAO,mCAAmC,KAAK,GAAG;AAClD,OAAK,QAAQ,iBAAiB;AAC9B,SAAO,EAAE,SAAS,MAAM,OAAO;AACjC;;;ACbO,SAAS,SAAS,MAAc,OAAmC;AACxE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EAClE;AACA,SAAO;AACT;AAEO,SAAS,cAAc,MAAyB,QAAQ,KAAgB;AAC7E,SAAO;AAAA,IACL,OAAO,SAAS,UAAU,IAAI,MAAM;AAAA,IACpC,aAAa,SAAS,gBAAgB,IAAI,YAAY;AAAA,IACtD,WAAW,SAAS,cAAc,IAAI,UAAU;AAAA,IAChD,UAAU,SAAS,aAAa,IAAI,SAAS;AAAA,IAC7C,sBAAsB,SAAS,0BAA0B,IAAI,sBAAsB;AAAA,IACnF,aAAa,SAAS,iBAAiB,IAAI,aAAa;AAAA,IACxD,iBAAiB,IAAI,qBAAqB;AAAA,IAC1C,kBAAkB,SAAS,qBAAqB,IAAI,iBAAiB;AAAA,EACvE;AACF;;;ACjCO,SAAS,eAAe,aAA6B;AAC1D,SAAO;AACP,QAAM,aAAa,YAAY,QAAQ,CAAC,YAAY,wBAAwB,OAAO,CAAC;AACpF,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,IAAI,MAAM;AAAA,EAAoC,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,EAC7E;AACF;;;ACHA,eAAsB,6BACpB,sBACA,UACiB;AACjB,QAAM,MAAM,MAAM,MAAM,sBAAsB;AAAA,IAC5C,QAAQ;AAAA,IACR,SAAS,EAAE,eAAe,UAAU,QAAQ,GAAG;AAAA,EACjD,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,uCAAuC,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EACvF;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,KAAK,YAAY,MAAM;AACzB,UAAM,IAAI,MAAM,yEAAyE;AAAA,EAC3F;AACA,SAAO,KAAK,KAAK;AACnB;;;AClBA,IAAM,0BAA0B;AAUhC,eAAsB,qBACpB,MAAyB,QAAQ,KAChB;AACjB,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,IAAI;AAAA,EACN;AACA,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,IAAI;AAAA,EACN;AACA,QAAM,YAAY,WAAW,SAAS,GAAG,IAAI,MAAM;AACnD,QAAM,MAAM,GAAG,UAAU,GAAG,SAAS,YAAY,mBAAmB,uBAAuB,CAAC;AAC5F,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,eAAe,UAAU,YAAY,GAAG;AAAA,EACrD,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EACnF;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,CAAC,KAAK,OAAO;AACf,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,SAAO,KAAK;AACd;;;ACVA,eAAsB,YACpB,WACA,UACA,aACsB;AACtB,QAAM,MAAM,GAAG,SAAS,gBAAgB,mBAAmB,WAAW,CAAC;AACvE,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,eAAe,UAAU,QAAQ,GAAG;AAAA,EACjD,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,6BAA6B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC7E;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,KAAK,YAAY,MAAM;AACzB,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,SAAO,KAAK;AACd;;;AC/CA,qBAA0C;AAKnC,SAAS,kBAAkB,OAAqB;AACrD,gCAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAC3C,qCAAe,gCAAgC,6CAA6C,KAAK;AAAA,CAAI;AACvG;;;ACRA,IAAAC,kBAA8C;AAC9C,uBAAqB;AAErB,IAAM,YAAY,oBAAI,IAAI,CAAC,gBAAgB,MAAM,CAAC;AAElD,SAAS,mBAAmB,KAA4B;AACtD,MAAI;AACJ,MAAI;AACF,kBAAU,6BAAY,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,UAAU,IAAI,KAAK,EAAG;AAC1B,UAAM,WAAO,uBAAK,KAAK,KAAK;AAC5B,QAAI;AACJ,QAAI;AACF,iBAAO,0BAAS,IAAI;AAAA,IACtB,QAAQ;AACN;AAAA,IACF;AACA,QAAI,KAAK,YAAY,GAAG;AACtB,YAAM,QAAQ,mBAAmB,IAAI;AACrC,UAAI,MAAO,QAAO;AAAA,IACpB,WAAW,iBAAiB,KAAK,KAAK,GAAG;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,KAAa,aAAoC;AACnE,MAAI;AACJ,MAAI;AACF,kBAAU,6BAAY,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC;AACrD,SAAO,YAAQ,uBAAK,KAAK,KAAK,IAAI;AACpC;AAOO,SAAS,gBAAgB,MAAM,QAAQ,IAAI,GAAkB;AAClE,SACE,mBAAmB,GAAG,KACtB,eAAW,uBAAK,KAAK,cAAc,GAAG,QAAQ,KAC9C,eAAW,uBAAK,KAAK,SAAS,GAAG,gBAAgB,KACjD,eAAW,uBAAK,KAAK,UAAU,kBAAkB,GAAG,QAAQ;AAEhE;AAEA,SAAS,WAAW,WAAmB,YAAoB,aAA6B;AACtF,SAAO,GAAG,SAAS,IAAI,UAAU,iBAAiB,WAAW;AAC/D;AAMA,eAAsB,iBAAiB,QAOrB;AAChB,QAAM,EAAE,YAAY,UAAU,iBAAiB,kBAAkB,aAAa,IAAI,IAAI;AACtF,MAAI,CAAC,YAAY;AACf,YAAQ,IAAI,6EAAwE;AACpF;AAAA,EACF;AACA,QAAM,SAAS,gBAAgB,GAAG;AAClC,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,oFAA+E;AAC5F;AAAA,EACF;AACA,MAAI;AACF,YAAQ,IAAI,aAAa,MAAM,EAAE;AACjC,UAAM,QAAQ,WAAW,iBAAiB,kBAAkB,WAAW;AACvE,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,SAAS;AAC/C,UAAM,OAAO,aAAa,MAAM;AAChC,UAAM,MAAM,GAAG,UAAU,UAAU,mBAAmB,KAAK,CAAC;AAC5D,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,UAAU,QAAQ,IAAI,gBAAgB,WAAW;AAAA,MAC3E;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,gCAAgC,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,IAChF;AACA,gCAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAAA,EAChC,SAAS,KAAK;AACZ,YAAQ,KAAK,8DAA8D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EAC/H;AACF;;;AC7FA,eAAsB,oBAAoB,UAAkB,WAAoC;AAC9F,QAAM,MAAM,MAAM,MAAM,UAAU;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,EAAE,eAAe,UAAU,SAAS,GAAG;AAAA,EAClD,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,8BAA8B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC9E;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,KAAK,YAAY,MAAM;AACzB,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACA,SAAO,KAAK,KAAK;AACnB;;;ACeA,eAAsB,IAAI,MAAM,cAAc,GAAsC;AAClF,MAAI,cAAc;AAClB,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI;AACF,eAAW,MAAM,oBAAoB,IAAI,UAAU,MAAM,qBAAqB,CAAC;AAC/E,aAAS,MAAM,YAAY,IAAI,WAAW,UAAU,IAAI,WAAW;AACnE,yBAAqB,OAAO,YAAY;AACxC,sBAAkB,IAAI,KAAK;AAC3B,eAAW,OAAO,SAAS,OAAO,OAAO;AACzC,eAAW,OAAO,SAAS,MAAM,MAAM;AACvC,mBAAe,OAAO,SAAS,IAAI;AAAA,EACrC,SAAS,KAAK;AACZ,kBAAc;AACd,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,EAChE;AAEA,MAAI,CAAC,UAAU;AACb,YAAQ,MAAM,iFAA4E;AAC1F,WAAO,EAAE,aAAa,KAAK;AAAA,EAC7B;AAEA,QAAM,iBAAiB;AAAA,IACrB,YAAY,QAAQ,UAAU;AAAA,IAC9B;AAAA,IACA,iBAAiB,IAAI;AAAA,IACrB,kBAAkB,IAAI;AAAA,IACtB,aAAa,IAAI;AAAA,EACnB,CAAC;AAED,MAAI,CAAC,eAAe,QAAQ;AAC1B,QAAI;AACF,qBAAe,OAAO,WAAW,WAAW;AAC5C,YAAM,oBAAoB,MAAM;AAAA,QAC9B,IAAI;AAAA,QACJ;AAAA,MACF;AACA,YAAM,SAAS,cAAc,OAAO,QAAQ,IAAI,OAAO,iBAAiB;AACxE,eAAS,OAAO;AAAA,IAClB,SAAS,KAAK;AACZ,oBAAc;AACd,cAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,QAAM,WAAW;AAAA,IACf,aAAa,IAAI;AAAA,IACjB;AAAA,IACA,QAAQ,cAAc,WAAW;AAAA,IACjC;AAAA,EACF,CAAC;AAED,SAAO,EAAE,YAAY;AACvB;;;ACxFA,IAAI,EACD,KAAK,CAAC,EAAE,YAAY,MAAM;AACzB,UAAQ,WAAW,cAAc,IAAI;AACvC,CAAC,EACA,MAAM,CAAC,QAAQ;AAGd,UAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,UAAQ,WAAW;AACrB,CAAC;",
|
|
6
|
+
"names": ["import_node_child_process", "import_node_fs"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quikit/quikpilot-runner",
|
|
3
|
+
"version": "7.0.2",
|
|
4
|
+
"description": "QuikPilot's CI runner. Fetches the run's work packet, runs setup/lint/test, reports results to QuikTest, enforces path guardrails, commits and pushes, and reports back to QuikTrack. Published publicly (Q12): the workflow file that invokes it lives in the customer's own repository, so this package is how a fix ships without a new pull request into every onboarded repo.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"quikpilot-runner": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "node build.mjs",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.17.10",
|
|
22
|
+
"esbuild": "^0.28.1",
|
|
23
|
+
"typescript": "^5.3.3",
|
|
24
|
+
"vitest": "^4.1.4"
|
|
25
|
+
}
|
|
26
|
+
}
|