@patronage/factory-ci 1.0.0-alpha.34 → 1.0.0-alpha.36
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 +14 -2
- package/dist/.build-fingerprint.json +1 -1
- package/dist/alchemy/index.d.ts +122 -13
- package/dist/alchemy/index.js +192 -29
- package/dist/{execute-alchemy-entry-DB8fFZTu.d.ts → execute-alchemy-entry-B397GSk5.d.ts} +19 -1
- package/dist/{execute-alchemy-entry-BZpk0eOQ.js → execute-alchemy-entry-CZ7Knz4q.js} +6 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.js +63 -2
- package/package.json +2 -2
- package/src/alchemy/child-diagnostics.ts +148 -0
- package/src/alchemy/credential-preflight.ts +27 -9
- package/src/alchemy/index.ts +2 -0
- package/src/alchemy/lifecycle-failure.ts +20 -1
- package/src/alchemy/lifecycle.ts +185 -27
- package/src/execute-alchemy-entry.ts +23 -1
- package/src/index.ts +5 -0
- package/src/preview-receipt-workflow.ts +83 -0
package/dist/alchemy/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { i as isLocalPreviewStage, n as executeAlchemyEntry, r as resolveAlchemyChildCwd, s as ALCHEMY_BASELINE } from "../execute-alchemy-entry-
|
|
1
|
+
import { i as isLocalPreviewStage, n as executeAlchemyEntry, r as resolveAlchemyChildCwd, s as ALCHEMY_BASELINE } from "../execute-alchemy-entry-CZ7Knz4q.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { appendFileSync, closeSync, openSync, readFileSync } from "node:fs";
|
|
6
6
|
import * as Alchemy from "alchemy";
|
|
7
7
|
import * as Output from "alchemy/Output";
|
|
8
8
|
import { tryFindProviderByType } from "alchemy/Provider";
|
|
@@ -176,10 +176,11 @@ const assertRetainedIdentities = ({ after, before, markers }) => {
|
|
|
176
176
|
* repeat is the account, the permission list, the resource scope, and where
|
|
177
177
|
* the credential comes from, so every one of those is an input.
|
|
178
178
|
*
|
|
179
|
-
* **No credential value is ever printed.** Errors name environment variables
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
179
|
+
* **No credential value is ever printed.** Errors name environment variables and
|
|
180
|
+
* missing groups from the caller-owned permission requirements. Untrusted API
|
|
181
|
+
* status, receipt group names, and resource identifiers are never reflected.
|
|
182
|
+
* The result carries the token id, which Cloudflare's own verification endpoint
|
|
183
|
+
* returns and which is not a secret.
|
|
183
184
|
*/
|
|
184
185
|
const CLOUDFLARE_API = "https://api.cloudflare.com/client/v4";
|
|
185
186
|
/**
|
|
@@ -246,6 +247,7 @@ const permissionGroupsFromPolicies = (policies, resourcesFor) => {
|
|
|
246
247
|
const found = [];
|
|
247
248
|
for (const policy of policies) {
|
|
248
249
|
if (!isPolicyShaped(policy)) throw new Error("Cloudflare token policy has an invalid shape.");
|
|
250
|
+
if (policy.permission_groups.length === 0) throw new Error("Cloudflare token policy has no permission groups.");
|
|
249
251
|
if (policy.effect !== "allow") throw new Error("Cloudflare token must carry allow policies only.");
|
|
250
252
|
if (Object.values(policy.resources).some((selection) => selection !== "*")) throw new Error("Cloudflare token policy has an invalid resource value.");
|
|
251
253
|
const scope = new Set(Object.keys(policy.resources));
|
|
@@ -253,8 +255,8 @@ const permissionGroupsFromPolicies = (policies, resourcesFor) => {
|
|
|
253
255
|
if (!permission?.name) throw new Error("Cloudflare token has an unnamed permission group.");
|
|
254
256
|
found.push(permission.name);
|
|
255
257
|
const expected = new Set(resourcesFor(permission.name));
|
|
256
|
-
if (expected.size === 0) throw new Error(
|
|
257
|
-
if (scope.size !== expected.size || [...scope].some((resource) => !expected.has(resource))) throw new Error(
|
|
258
|
+
if (expected.size === 0) throw new Error("Cloudflare token policy has no expected resource scope.");
|
|
259
|
+
if (scope.size !== expected.size || [...scope].some((resource) => !expected.has(resource))) throw new Error("Cloudflare token policy has an unexpected resource scope.");
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
262
|
return found;
|
|
@@ -265,7 +267,7 @@ const assertExactPermissionGroups = (found, required) => {
|
|
|
265
267
|
const missing = [...expected].filter((name) => !received.has(name));
|
|
266
268
|
const extra = [...received].filter((name) => !expected.has(name));
|
|
267
269
|
const duplicates = found.filter((name, index) => found.indexOf(name) !== index);
|
|
268
|
-
if (missing.length > 0 || extra.length > 0 || duplicates.length > 0) throw new Error(`Cloudflare permission mismatch; missing: ${missing.join(", ") || "none"}; extra: ${extra.
|
|
270
|
+
if (missing.length > 0 || extra.length > 0 || duplicates.length > 0) throw new Error(`Cloudflare permission mismatch; missing: ${missing.join(", ") || "none"}; extra count: ${extra.length}; duplicate count: ${duplicates.length}`);
|
|
269
271
|
};
|
|
270
272
|
const assertRequiredPermissionGroups = (required) => {
|
|
271
273
|
if (required.some((name) => !name.trim())) throw new Error("Credential preflight requires named permission groups; a blank name matches nothing.");
|
|
@@ -307,7 +309,8 @@ const resolveResolvedCredential = async (resolveCredential, environment) => {
|
|
|
307
309
|
* Cloudflare could not be asked; every other error means the credential is
|
|
308
310
|
* wrong. Nothing it throws, and nothing it returns, carries a secret value.
|
|
309
311
|
*/
|
|
310
|
-
const credentialPreflight = async ({ accountId, environment, fetchImplementation = fetch, requiredPermissionGroups, resolveCredential, resourcesFor, source }) => {
|
|
312
|
+
const credentialPreflight = async ({ accountId, environment, fetchImplementation = fetch, requiredPermissionGroups, resolveCredential, resourcesFor, source, tokenKind = "account" }) => {
|
|
313
|
+
if (tokenKind !== "account" && tokenKind !== "user") throw new Error("Credential preflight has an unknown token kind.");
|
|
311
314
|
if (!accountId.trim()) throw new Error("Credential preflight requires a Cloudflare account id.");
|
|
312
315
|
if (requiredPermissionGroups.length === 0) throw new Error("Credential preflight requires at least one permission group; an empty list would admit any token.");
|
|
313
316
|
assertRequiredPermissionGroups(requiredPermissionGroups);
|
|
@@ -317,8 +320,8 @@ const credentialPreflight = async ({ accountId, environment, fetchImplementation
|
|
|
317
320
|
const credential = await resolveResolvedCredential(resolveCredential, environment);
|
|
318
321
|
if (!(credential.token.trim() && credential.policyJson.trim())) throw new Error("Credential preflight resolved an empty credential.");
|
|
319
322
|
if (source === "hosted" && credential.token.trim() !== environment.CLOUDFLARE_API_TOKEN?.trim()) throw new Error("Hosted credential preflight resolved a token that is not the environment token.");
|
|
320
|
-
const verification = await cloudflareJson(`${CLOUDFLARE_API}/accounts/${accountId}/tokens/verify`, credential.token, fetchImplementation);
|
|
321
|
-
if (verification.status !== "active") throw new Error(
|
|
323
|
+
const verification = await cloudflareJson(tokenKind === "user" ? `${CLOUDFLARE_API}/user/tokens/verify` : `${CLOUDFLARE_API}/accounts/${accountId}/tokens/verify`, credential.token, fetchImplementation);
|
|
324
|
+
if (verification.status !== "active") throw new Error("Cloudflare token is not active.");
|
|
322
325
|
const reviewed = parseReviewedTokenPolicy(credential.policyJson);
|
|
323
326
|
if (reviewed.tokenId !== verification.id) throw new Error("Cloudflare token policy metadata does not describe the live token.");
|
|
324
327
|
const found = permissionGroupsFromPolicies(reviewed.policies, scopeFor);
|
|
@@ -1211,6 +1214,7 @@ const secretNamePolicy = ({ names, values }) => {
|
|
|
1211
1214
|
const LIFECYCLE_FAILURE_MESSAGES = {
|
|
1212
1215
|
AMBIGUOUS_RESOURCE: "The plan prints one resource id for more than one resource.",
|
|
1213
1216
|
AMBIGUOUS_STAGE: "The stage table declares the stage both protected and disposable.",
|
|
1217
|
+
CHILD_DIAGNOSTICS_UNWRITABLE: "The requested child diagnostics file could not be written.",
|
|
1214
1218
|
CHILD_FAILED: "The Alchemy child did not exit successfully.",
|
|
1215
1219
|
DEPLOY_INCOMPLETE: "The deploy completed without the persisted state its plan described.",
|
|
1216
1220
|
DESTROY_RESIDUE: "The destroy left persisted state registered for the stage.",
|
|
@@ -1236,6 +1240,7 @@ const LIFECYCLE_FAILURE_MESSAGES = {
|
|
|
1236
1240
|
};
|
|
1237
1241
|
/** A sanitized lifecycle failure. See the module comment. */
|
|
1238
1242
|
var AlchemyLifecycleError = class extends Error {
|
|
1243
|
+
childDiagnosticsPath;
|
|
1239
1244
|
code;
|
|
1240
1245
|
mutation;
|
|
1241
1246
|
operation;
|
|
@@ -1248,11 +1253,13 @@ var AlchemyLifecycleError = class extends Error {
|
|
|
1248
1253
|
this.mutation = fields.mutation;
|
|
1249
1254
|
this.operation = fields.operation;
|
|
1250
1255
|
this.phase = fields.phase;
|
|
1256
|
+
if (fields.childDiagnosticsPath !== void 0) this.childDiagnosticsPath = fields.childDiagnosticsPath;
|
|
1251
1257
|
if (fields.planHash !== void 0) this.planHash = fields.planHash;
|
|
1252
1258
|
}
|
|
1253
1259
|
/** The fields an evidence sink prints. Fixed shape, fixed words. */
|
|
1254
1260
|
toJSON() {
|
|
1255
1261
|
return {
|
|
1262
|
+
...this.childDiagnosticsPath === void 0 ? {} : { childDiagnosticsPath: this.childDiagnosticsPath },
|
|
1256
1263
|
code: this.code,
|
|
1257
1264
|
message: this.message,
|
|
1258
1265
|
mutation: this.mutation,
|
|
@@ -1368,6 +1375,81 @@ const deleteStageRows = async (state, target) => {
|
|
|
1368
1375
|
}));
|
|
1369
1376
|
};
|
|
1370
1377
|
//#endregion
|
|
1378
|
+
//#region src/alchemy/child-diagnostics.ts
|
|
1379
|
+
/**
|
|
1380
|
+
* The optional record of what an Alchemy child printed (#1260).
|
|
1381
|
+
*
|
|
1382
|
+
* `runAlchemyLifecycle` reports a failed child as `CHILD_FAILED` and nothing
|
|
1383
|
+
* else: the child's output is parsed and dropped, so no secret in it can
|
|
1384
|
+
* reach a job log or an evidence file through the operation. That is the
|
|
1385
|
+
* right default and it stays the default. It also leaves an operator with a
|
|
1386
|
+
* failure code and no record of what failed, which twice cost a paitronage
|
|
1387
|
+
* investigation its evidence.
|
|
1388
|
+
*
|
|
1389
|
+
* This is the opt-in other half: a consumer that names a file gets every
|
|
1390
|
+
* child's streams appended to it, each stream passed whole through the
|
|
1391
|
+
* consumer's own redactor, under a header naming the child. The consumer chooses the path
|
|
1392
|
+
* and owns the redactor; this module never picks either, and nothing here
|
|
1393
|
+
* changes what the lifecycle decides.
|
|
1394
|
+
*
|
|
1395
|
+
* The file is opened, and the header written, **before** the child starts,
|
|
1396
|
+
* so an unwritable path is known before anything runs and a parent that dies
|
|
1397
|
+
* mid-child still leaves the marker for the child that was in flight. The
|
|
1398
|
+
* streams themselves arrive after the child exits, because the child is
|
|
1399
|
+
* captured (`spawnSync`), not streamed.
|
|
1400
|
+
*
|
|
1401
|
+
* Nothing here throws into the lifecycle's control flow once the handle is
|
|
1402
|
+
* open. A write failure is retained and reported by the caller under its own
|
|
1403
|
+
* failure code, because a record that could not be written is a different
|
|
1404
|
+
* fact from a child that failed, and reporting it as the second one would
|
|
1405
|
+
* put a false cause in front of the operator this file exists for. When the
|
|
1406
|
+
* caller reports it — up front, or only after the operation has run to the
|
|
1407
|
+
* end — is the caller's rule, not this module's; see `runAlchemyLifecycle`.
|
|
1408
|
+
*/
|
|
1409
|
+
const streamSection = (label, text, redact) => {
|
|
1410
|
+
if (text === null || text === "") return `--- ${label}: empty ---\n`;
|
|
1411
|
+
const body = redact(text);
|
|
1412
|
+
return `--- ${label} ---\n${body.endsWith("\n") ? body : `${body}\n`}`;
|
|
1413
|
+
};
|
|
1414
|
+
/**
|
|
1415
|
+
* Open the record for one child and write its header.
|
|
1416
|
+
*
|
|
1417
|
+
* Throws when the file cannot be opened or the header cannot be written.
|
|
1418
|
+
* That throw happens before the child is launched, so a caller that has not
|
|
1419
|
+
* yet mutated anything can refuse the run with the stage still untouched.
|
|
1420
|
+
*/
|
|
1421
|
+
const openChildDiagnostics = ({ child, diagnostics, operation, stack, stage }) => {
|
|
1422
|
+
const handle = openSync(diagnostics.path, "a");
|
|
1423
|
+
let retained;
|
|
1424
|
+
const guard = (write) => {
|
|
1425
|
+
try {
|
|
1426
|
+
write();
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
retained ??= error instanceof Error ? error : new Error(String(error));
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
try {
|
|
1432
|
+
appendFileSync(handle, `\n===== ${operation} ${child} child — ${stack}/${stage} — ${(/* @__PURE__ */ new Date()).toISOString()} =====\n`);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
closeSync(handle);
|
|
1435
|
+
throw error;
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
append: ({ stderr, stdout }) => {
|
|
1439
|
+
guard(() => {
|
|
1440
|
+
appendFileSync(handle, streamSection("stdout", stdout, diagnostics.redact));
|
|
1441
|
+
appendFileSync(handle, streamSection("stderr", stderr, diagnostics.redact));
|
|
1442
|
+
});
|
|
1443
|
+
},
|
|
1444
|
+
close: () => {
|
|
1445
|
+
guard(() => {
|
|
1446
|
+
closeSync(handle);
|
|
1447
|
+
});
|
|
1448
|
+
},
|
|
1449
|
+
failure: () => retained
|
|
1450
|
+
};
|
|
1451
|
+
};
|
|
1452
|
+
//#endregion
|
|
1371
1453
|
//#region src/alchemy/lifecycle.ts
|
|
1372
1454
|
/**
|
|
1373
1455
|
* The shared Alchemy lifecycle operation (#1077).
|
|
@@ -1480,6 +1562,12 @@ var LifecycleRun = class {
|
|
|
1480
1562
|
policy;
|
|
1481
1563
|
state;
|
|
1482
1564
|
entry;
|
|
1565
|
+
childDiagnostics;
|
|
1566
|
+
/**
|
|
1567
|
+
* A diagnostics write that failed after a mutating child had started, held
|
|
1568
|
+
* until the operation has run to its end. See `noteDiagnosticsFailure`.
|
|
1569
|
+
*/
|
|
1570
|
+
diagnosticsUnwritable = false;
|
|
1483
1571
|
bundledEntry;
|
|
1484
1572
|
/** What the run has done to the stage so far. Only children advance it. */
|
|
1485
1573
|
certainty = "not-started";
|
|
@@ -1488,9 +1576,13 @@ var LifecycleRun = class {
|
|
|
1488
1576
|
this.policy = options.policy;
|
|
1489
1577
|
this.state = options.state;
|
|
1490
1578
|
this.entry = options.entry;
|
|
1579
|
+
this.childDiagnostics = options.childDiagnostics;
|
|
1491
1580
|
}
|
|
1492
|
-
fail(code, phase,
|
|
1581
|
+
fail(code, phase, extra = {}) {
|
|
1582
|
+
const { namesDiagnostics, planHash } = extra;
|
|
1583
|
+
const diagnosticsPath = namesDiagnostics === true ? this.childDiagnostics?.path : void 0;
|
|
1493
1584
|
return new AlchemyLifecycleError({
|
|
1585
|
+
...diagnosticsPath === void 0 ? {} : { childDiagnosticsPath: diagnosticsPath },
|
|
1494
1586
|
code,
|
|
1495
1587
|
mutation: this.certainty,
|
|
1496
1588
|
operation: this.request.operation,
|
|
@@ -1661,22 +1753,75 @@ var LifecycleRun = class {
|
|
|
1661
1753
|
return ["--stage", this.request.stage];
|
|
1662
1754
|
}
|
|
1663
1755
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1756
|
+
* Where a diagnostics write failure is reported.
|
|
1757
|
+
*
|
|
1758
|
+
* Before any mutating child has started, it is reported here and now: the
|
|
1759
|
+
* operator asked for a record, the record cannot be kept, and nothing has
|
|
1760
|
+
* been changed yet, so refusing costs nothing.
|
|
1761
|
+
*
|
|
1762
|
+
* Once a deploy or destroy child has started, it is **retained** and
|
|
1763
|
+
* reported only after the operation has run to its end, the way a failed
|
|
1764
|
+
* lease release is. The apply succeeded; failing here would abandon the
|
|
1765
|
+
* convergence check on a good deploy and invite an operator to destroy and
|
|
1766
|
+
* retry a stage that is fine. A missing record is not a reason to treat a
|
|
1767
|
+
* successful mutation as a failed one, and the run still ends in
|
|
1768
|
+
* `CHILD_DIAGNOSTICS_UNWRITABLE` — after the postconditions have run, with
|
|
1769
|
+
* the mutation certainty the operation actually reached.
|
|
1770
|
+
*/
|
|
1771
|
+
noteDiagnosticsFailure(failed) {
|
|
1772
|
+
if (!failed) return;
|
|
1773
|
+
this.diagnosticsUnwritable = true;
|
|
1774
|
+
if (this.certainty !== "mutation-may-have-occurred") throw this.fail("CHILD_DIAGNOSTICS_UNWRITABLE", "execution", { namesDiagnostics: true });
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* The retained record failure, once the operation itself is complete. The
|
|
1778
|
+
* operation's own failures are reported ahead of it, as they are for a
|
|
1779
|
+
* failed lease release.
|
|
1780
|
+
*/
|
|
1781
|
+
assertDiagnosticsWritten() {
|
|
1782
|
+
if (this.diagnosticsUnwritable) throw this.fail("CHILD_DIAGNOSTICS_UNWRITABLE", "execution", { namesDiagnostics: true });
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* The diagnostics section for one child, opened and headed before the
|
|
1786
|
+
* child starts. A path that cannot be opened refuses the run while nothing
|
|
1787
|
+
* has been mutated; after that it is retained and the child runs without a
|
|
1788
|
+
* record rather than a good apply being abandoned over one.
|
|
1789
|
+
*/
|
|
1790
|
+
openDiagnostics(child) {
|
|
1791
|
+
if (this.childDiagnostics === void 0) return;
|
|
1792
|
+
try {
|
|
1793
|
+
return openChildDiagnostics({
|
|
1794
|
+
child,
|
|
1795
|
+
diagnostics: this.childDiagnostics,
|
|
1796
|
+
operation: this.request.operation,
|
|
1797
|
+
stack: this.request.stack,
|
|
1798
|
+
stage: this.request.stage
|
|
1799
|
+
});
|
|
1800
|
+
} catch {
|
|
1801
|
+
this.noteDiagnosticsFailure(true);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Run one child under `lease` and return its stdout for parsing. Its output
|
|
1807
|
+
* goes nowhere else unless the consumer asked for `childDiagnostics`, which
|
|
1808
|
+
* writes a redacted copy and never touches what is returned here. The
|
|
1809
|
+
* child's environment carries the lease, so the adapter in the child adopts
|
|
1810
|
+
* it rather than taking its own: there is no child without a lease, and a
|
|
1811
|
+
* child that writes outside this lease is refused by the store, not by this
|
|
1812
|
+
* module. A `plan` child leaves the certainty where it is unless it is the
|
|
1813
|
+
* first child; a mutating child advances it before it starts.
|
|
1671
1814
|
*/
|
|
1672
1815
|
async runChild(child, args, lease) {
|
|
1673
1816
|
if (this.bundledEntry === void 0) throw this.fail("PREPARATION_FAILED", "admission");
|
|
1817
|
+
const record = this.openDiagnostics(child);
|
|
1674
1818
|
if (child !== "plan") this.certainty = "mutation-may-have-occurred";
|
|
1675
1819
|
else if (this.certainty === "not-started") this.certainty = "read-only-started";
|
|
1676
1820
|
const { env, from } = this.entry;
|
|
1821
|
+
let stdout;
|
|
1677
1822
|
try {
|
|
1678
|
-
|
|
1679
|
-
args,
|
|
1823
|
+
stdout = (await executeAlchemyEntry({
|
|
1824
|
+
args: [...args, ...this.entry.envFile === void 0 ? [] : ["--env-file", this.entry.envFile]],
|
|
1680
1825
|
bundledEntry: this.bundledEntry,
|
|
1681
1826
|
cwd: resolveAlchemyChildCwd(this.entry),
|
|
1682
1827
|
env: {
|
|
@@ -1685,11 +1830,16 @@ var LifecycleRun = class {
|
|
|
1685
1830
|
},
|
|
1686
1831
|
from,
|
|
1687
1832
|
maxBuffer: 67108864,
|
|
1833
|
+
...record === void 0 ? {} : { onCaptured: record.append },
|
|
1688
1834
|
stdio: "pipe"
|
|
1689
1835
|
})).stdout ?? "";
|
|
1690
1836
|
} catch {
|
|
1691
|
-
|
|
1837
|
+
stdout = void 0;
|
|
1692
1838
|
}
|
|
1839
|
+
record?.close();
|
|
1840
|
+
if (stdout === void 0) throw this.fail("CHILD_FAILED", "execution", { namesDiagnostics: true });
|
|
1841
|
+
this.noteDiagnosticsFailure(record?.failure() !== void 0);
|
|
1842
|
+
return stdout;
|
|
1693
1843
|
}
|
|
1694
1844
|
effectsOf(output, phase) {
|
|
1695
1845
|
let effects;
|
|
@@ -1782,7 +1932,7 @@ var LifecycleRun = class {
|
|
|
1782
1932
|
stage: this.request.stage
|
|
1783
1933
|
});
|
|
1784
1934
|
} catch {
|
|
1785
|
-
throw this.fail("FIRST_DEPLOY_UNAUTHORIZED", "admission", planHash);
|
|
1935
|
+
throw this.fail("FIRST_DEPLOY_UNAUTHORIZED", "admission", { planHash });
|
|
1786
1936
|
}
|
|
1787
1937
|
}
|
|
1788
1938
|
retainedMarkers() {
|
|
@@ -1942,6 +2092,15 @@ var LifecycleRun = class {
|
|
|
1942
2092
|
*
|
|
1943
2093
|
* Every failure is an `AlchemyLifecycleError`. After a deploy or destroy
|
|
1944
2094
|
* child was started, no failure reports `not-started`.
|
|
2095
|
+
*
|
|
2096
|
+
* `childDiagnostics` is the one opt-in that keeps what the children printed:
|
|
2097
|
+
* both streams of every child, each passed whole through the consumer's own
|
|
2098
|
+
* redactor, are appended to the consumer's own file under a per-child
|
|
2099
|
+
* header, and a `CHILD_FAILED` then names that path so an operator knows
|
|
2100
|
+
* where to look. Nothing about the operation changes otherwise: a record
|
|
2101
|
+
* that cannot be written refuses the run only while nothing has been
|
|
2102
|
+
* mutated, and after that is reported as `CHILD_DIAGNOSTICS_UNWRITABLE` once
|
|
2103
|
+
* the operation has finished, never in place of its postconditions.
|
|
1945
2104
|
*/
|
|
1946
2105
|
const runAlchemyLifecycle = async (options) => {
|
|
1947
2106
|
const run = new LifecycleRun(options);
|
|
@@ -1949,11 +2108,15 @@ const runAlchemyLifecycle = async (options) => {
|
|
|
1949
2108
|
const { isProtected } = run.admitStage();
|
|
1950
2109
|
const alchemyVersion = run.installedAlchemyVersion();
|
|
1951
2110
|
await run.prepareEntry();
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2111
|
+
const result = await (async () => {
|
|
2112
|
+
switch (options.request.operation) {
|
|
2113
|
+
case "plan": return await run.plan(alchemyVersion, isProtected);
|
|
2114
|
+
case "deploy": return await run.deploy(alchemyVersion, isProtected);
|
|
2115
|
+
default: return await run.destroy(alchemyVersion);
|
|
2116
|
+
}
|
|
2117
|
+
})();
|
|
2118
|
+
run.assertDiagnosticsWritten();
|
|
2119
|
+
return result;
|
|
1957
2120
|
};
|
|
1958
2121
|
//#endregion
|
|
1959
2122
|
//#region src/alchemy/index.ts
|
|
@@ -70,6 +70,23 @@ interface ExecuteAlchemyEntryCommon {
|
|
|
70
70
|
* pass `EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER` or another explicit value.
|
|
71
71
|
*/
|
|
72
72
|
readonly maxBuffer?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Called once with the child's captured streams, after the child exits and
|
|
75
|
+
* **before its exit status is judged**, so a caller keeping a diagnostic
|
|
76
|
+
* record still receives the output of a child that failed. Unused when
|
|
77
|
+
* stdio is `"inherit"`.
|
|
78
|
+
*
|
|
79
|
+
* The text handed over is the raw capture: `redact` is not applied to it,
|
|
80
|
+
* because `redact` shapes what this function returns and a record is not a
|
|
81
|
+
* return value. A caller that writes this anywhere owns its own redaction.
|
|
82
|
+
*
|
|
83
|
+
* It must not throw. Its work is a side record, and a throw from it would
|
|
84
|
+
* be indistinguishable here from the child's own failure.
|
|
85
|
+
*/
|
|
86
|
+
readonly onCaptured?: (captured: {
|
|
87
|
+
readonly stdout: string | null;
|
|
88
|
+
readonly stderr: string | null;
|
|
89
|
+
}) => void;
|
|
73
90
|
/**
|
|
74
91
|
* Applied to captured stdout and stderr before they are returned. Unused
|
|
75
92
|
* when stdio is `"inherit"`. This package does not supply a secret-name
|
|
@@ -106,7 +123,8 @@ interface ExecuteAlchemyEntryResult {
|
|
|
106
123
|
* Options carry exactly one entry source: `bundle` to bundle now, or
|
|
107
124
|
* `bundledEntry` to reuse an already-bundled path. Captured stdout and stderr (`stdio: "pipe"`) can be redacted through
|
|
108
125
|
* `redact` before they are returned; `stdio: "inherit"` still streams the
|
|
109
|
-
* child unredacted.
|
|
126
|
+
* child unredacted. `onCaptured` receives the raw streams of every piped
|
|
127
|
+
* child, including one that then fails.
|
|
110
128
|
*
|
|
111
129
|
* Deploy/destroy/plan selection, stage admission, credentials, and every
|
|
112
130
|
* convergence or destructive-change policy remain the caller's responsibility.
|
|
@@ -123,7 +123,8 @@ const captured = (text, redact) => {
|
|
|
123
123
|
* Options carry exactly one entry source: `bundle` to bundle now, or
|
|
124
124
|
* `bundledEntry` to reuse an already-bundled path. Captured stdout and stderr (`stdio: "pipe"`) can be redacted through
|
|
125
125
|
* `redact` before they are returned; `stdio: "inherit"` still streams the
|
|
126
|
-
* child unredacted.
|
|
126
|
+
* child unredacted. `onCaptured` receives the raw streams of every piped
|
|
127
|
+
* child, including one that then fails.
|
|
127
128
|
*
|
|
128
129
|
* Deploy/destroy/plan selection, stage admission, credentials, and every
|
|
129
130
|
* convergence or destructive-change policy remain the caller's responsibility.
|
|
@@ -142,6 +143,10 @@ const executeAlchemyEntry = async (options) => {
|
|
|
142
143
|
...options.maxBuffer === void 0 ? {} : { maxBuffer: options.maxBuffer },
|
|
143
144
|
stdio: options.stdio ?? "inherit"
|
|
144
145
|
});
|
|
146
|
+
options.onCaptured?.({
|
|
147
|
+
stderr: result.stderr,
|
|
148
|
+
stdout: result.stdout
|
|
149
|
+
});
|
|
145
150
|
if (result.error) throw new Error("Failed to start the Alchemy CLI.");
|
|
146
151
|
if (result.signal) throw new Error(`Alchemy CLI terminated by signal ${result.signal}.`);
|
|
147
152
|
if (result.status !== 0) throw new Error(result.status === null ? "Alchemy CLI exited without reporting a status." : `Alchemy CLI exited with status ${result.status}.`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as BundleAlchemyEntryOptions, i as executeAlchemyEntry, n as ExecuteAlchemyEntryOptions, o as bundleAlchemyEntry, r as ExecuteAlchemyEntryResult, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-
|
|
1
|
+
import { a as BundleAlchemyEntryOptions, i as executeAlchemyEntry, n as ExecuteAlchemyEntryOptions, o as bundleAlchemyEntry, r as ExecuteAlchemyEntryResult, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-B397GSk5.js";
|
|
2
2
|
import { StdioOptions } from "node:child_process";
|
|
3
3
|
|
|
4
4
|
//#region src/alchemy-baseline.d.ts
|
|
@@ -1203,6 +1203,30 @@ declare const previewCleanupDestroyJobId: (stack: string) => string;
|
|
|
1203
1203
|
*/
|
|
1204
1204
|
declare const factoryPreviewCleanupTopology: (options: FactoryPreviewCleanupTopologyOptions) => FactoryPreviewCleanupTopology;
|
|
1205
1205
|
//#endregion
|
|
1206
|
+
//#region src/preview-receipt-workflow.d.ts
|
|
1207
|
+
interface FactoryPreviewReceiptWorkflowOptions {
|
|
1208
|
+
readonly target: string;
|
|
1209
|
+
readonly cli?: string;
|
|
1210
|
+
/** Caller-owned PR number expression, passed through the environment. */
|
|
1211
|
+
readonly pr?: string;
|
|
1212
|
+
}
|
|
1213
|
+
interface FactoryPreviewReceiptWorkflow {
|
|
1214
|
+
/** Place before the selected target's first mutation, under its selection guard. */
|
|
1215
|
+
readonly beginStep: WorkflowStep;
|
|
1216
|
+
/** Append after consumer deploy and required smoke steps. Never rename the bind step. */
|
|
1217
|
+
readonly completionSteps: readonly WorkflowStep[];
|
|
1218
|
+
/** Place before cleanup, including cleanup of a failed/unregistered deployment. */
|
|
1219
|
+
readonly invalidateStep: WorkflowStep;
|
|
1220
|
+
readonly permissions: Readonly<{
|
|
1221
|
+
actions: "read";
|
|
1222
|
+
contents: "read";
|
|
1223
|
+
deployments: "write";
|
|
1224
|
+
"pull-requests": "read";
|
|
1225
|
+
}>;
|
|
1226
|
+
}
|
|
1227
|
+
/** Receipt transport only. The consumer owns selection, lifecycle, smoke, and freshness. */
|
|
1228
|
+
declare const factoryPreviewReceiptWorkflow: (options: FactoryPreviewReceiptWorkflowOptions) => FactoryPreviewReceiptWorkflow;
|
|
1229
|
+
//#endregion
|
|
1206
1230
|
//#region src/production-impact-workflow.d.ts
|
|
1207
1231
|
declare const FACTORY_PRODUCTION_IMPACT_STEP_ID = "production_impact";
|
|
1208
1232
|
declare const FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT = "decision";
|
|
@@ -1645,4 +1669,4 @@ declare const assertWorkflowShellParses: (yaml: string, options: {
|
|
|
1645
1669
|
readonly source: string;
|
|
1646
1670
|
}) => void;
|
|
1647
1671
|
//#endregion
|
|
1648
|
-
export { ALCHEMY_BASELINE, type AlchemyBaselinePackageJson, type AssertAlchemyBaselineInput, type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryMergeFreezeJob, type FactoryMergeFreezeJobOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryRunnerDecision, type FactoryRunnerHome, type FactoryRunnerKind, type FactoryRunnerOverride, type FactoryVerifyRunner, type FactoryVerifyRunnerMismatch, type FactoryVerifyRunnerPolicyInput, type FactoryVerifyRunnerPolicyReport, type FactoryVerifyRunnerTable, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, GitHubAppTokenTransportError, type GithubAppCredentials, type GithubAppInstallationPermissions, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, PreviewProofTransportError, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, SETUP_STEP_ROLES, type SetupNodeStepOptions, type SetupStepRole, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
|
1672
|
+
export { ALCHEMY_BASELINE, type AlchemyBaselinePackageJson, type AssertAlchemyBaselineInput, type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryMergeFreezeJob, type FactoryMergeFreezeJobOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryPreviewReceiptWorkflow, type FactoryPreviewReceiptWorkflowOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryRunnerDecision, type FactoryRunnerHome, type FactoryRunnerKind, type FactoryRunnerOverride, type FactoryVerifyRunner, type FactoryVerifyRunnerMismatch, type FactoryVerifyRunnerPolicyInput, type FactoryVerifyRunnerPolicyReport, type FactoryVerifyRunnerTable, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, GitHubAppTokenTransportError, type GithubAppCredentials, type GithubAppInstallationPermissions, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, PreviewProofTransportError, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, SETUP_STEP_ROLES, type SetupNodeStepOptions, type SetupStepRole, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryPreviewReceiptWorkflow, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as bundleAlchemyEntry } from "./bundle-alchemy-entry-CT5hCphA.js";
|
|
2
|
-
import { a as localPreviewStage, c as assertAlchemyBaseline, i as isLocalPreviewStage, n as executeAlchemyEntry, o as parseLocalPreviewStage, s as ALCHEMY_BASELINE, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-
|
|
2
|
+
import { a as localPreviewStage, c as assertAlchemyBaseline, i as isLocalPreviewStage, n as executeAlchemyEntry, o as parseLocalPreviewStage, s as ALCHEMY_BASELINE, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-CZ7Knz4q.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
@@ -2337,6 +2337,67 @@ const factoryPreviewCleanupTopology = (options) => {
|
|
|
2337
2337
|
});
|
|
2338
2338
|
};
|
|
2339
2339
|
//#endregion
|
|
2340
|
+
//#region src/preview-receipt-workflow.ts
|
|
2341
|
+
/** Receipt transport only. The consumer owns selection, lifecycle, smoke, and freshness. */
|
|
2342
|
+
const factoryPreviewReceiptWorkflow = (options) => {
|
|
2343
|
+
if (!/^preview:[a-z][a-z0-9-]*$/u.test(options.target)) throw new Error("Preview receipts require a declared preview target name.");
|
|
2344
|
+
const cli = options.cli ?? "pnpm exec psf";
|
|
2345
|
+
assertRunnableCli("cli", cli);
|
|
2346
|
+
const id = `preview_intent_${options.target.slice(8).replaceAll("-", "_")}`;
|
|
2347
|
+
const deploymentId = `\${{ steps.${id}.outputs.deployment_id }}`;
|
|
2348
|
+
const hasIntent = `steps.${id}.outputs.deployment_id != ''`;
|
|
2349
|
+
const env = {
|
|
2350
|
+
FACTORY_PREVIEW_PR: options.pr ?? `\${{ github.event.pull_request.number }}`,
|
|
2351
|
+
GH_TOKEN: `\${{ github.token }}`
|
|
2352
|
+
};
|
|
2353
|
+
const command = `${cli} preview:receipt --pr "$FACTORY_PREVIEW_PR" --target '${options.target}'`;
|
|
2354
|
+
return Object.freeze({
|
|
2355
|
+
beginStep: Object.freeze({
|
|
2356
|
+
env,
|
|
2357
|
+
id,
|
|
2358
|
+
name: `Record preview intent for ${options.target}`,
|
|
2359
|
+
run: `${command} --action begin --github-output "$GITHUB_OUTPUT"`
|
|
2360
|
+
}),
|
|
2361
|
+
completionSteps: Object.freeze([
|
|
2362
|
+
Object.freeze({
|
|
2363
|
+
env: {
|
|
2364
|
+
...env,
|
|
2365
|
+
FACTORY_DEPLOYMENT_ID: deploymentId
|
|
2366
|
+
},
|
|
2367
|
+
if: `success() && ${hasIntent}`,
|
|
2368
|
+
name: `Complete preview receipt for ${options.target}`,
|
|
2369
|
+
run: `${command} --action complete --deployment-id "$FACTORY_DEPLOYMENT_ID" --outcome success`
|
|
2370
|
+
}),
|
|
2371
|
+
Object.freeze({
|
|
2372
|
+
env: { FACTORY_DEPLOYMENT_ID: deploymentId },
|
|
2373
|
+
if: `success() && ${hasIntent}`,
|
|
2374
|
+
name: `Bind deployment receipt ${deploymentId}`,
|
|
2375
|
+
run: "test -n \"$FACTORY_DEPLOYMENT_ID\""
|
|
2376
|
+
}),
|
|
2377
|
+
Object.freeze({
|
|
2378
|
+
env: {
|
|
2379
|
+
...env,
|
|
2380
|
+
FACTORY_DEPLOYMENT_ID: deploymentId
|
|
2381
|
+
},
|
|
2382
|
+
if: `failure() && ${hasIntent}`,
|
|
2383
|
+
name: `Fail preview receipt for ${options.target}`,
|
|
2384
|
+
run: `${command} --action complete --deployment-id "$FACTORY_DEPLOYMENT_ID" --outcome failure`
|
|
2385
|
+
})
|
|
2386
|
+
]),
|
|
2387
|
+
invalidateStep: Object.freeze({
|
|
2388
|
+
env,
|
|
2389
|
+
name: `Invalidate preview receipts for ${options.target}`,
|
|
2390
|
+
run: `${command} --action invalidate`
|
|
2391
|
+
}),
|
|
2392
|
+
permissions: Object.freeze({
|
|
2393
|
+
actions: "read",
|
|
2394
|
+
contents: "read",
|
|
2395
|
+
deployments: "write",
|
|
2396
|
+
"pull-requests": "read"
|
|
2397
|
+
})
|
|
2398
|
+
});
|
|
2399
|
+
};
|
|
2400
|
+
//#endregion
|
|
2340
2401
|
//#region src/push-identity-workflow.ts
|
|
2341
2402
|
/** `owner/repo` slice of the catalog's `UPLOAD_ARTIFACT` pin, derived rather
|
|
2342
2403
|
* than repeated as a literal, so a repository rename here cannot drift from
|
|
@@ -3530,4 +3591,4 @@ const assertWorkflowShellParses = (yaml, options) => {
|
|
|
3530
3591
|
throw new Error(`${options.source} emits shell bash cannot parse; the runner would treat it as a no-op:\n${detail}`);
|
|
3531
3592
|
};
|
|
3532
3593
|
//#endregion
|
|
3533
|
-
export { ALCHEMY_BASELINE, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, GitHubApiError, GitHubAppTokenTransportError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, PreviewProofTransportError, SETUP_STEP_ROLES, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
|
3594
|
+
export { ALCHEMY_BASELINE, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, GitHubApiError, GitHubAppTokenTransportError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, PreviewProofTransportError, SETUP_STEP_ROLES, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryPreviewReceiptWorkflow, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patronage/factory-ci",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.36",
|
|
4
4
|
"description": "Deep CI and deploy building blocks for Patronage factory projects: workflow source artifacts, hosted diff classification, Alchemy entry execution, and disposable-stage semantics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"typescript": "5.9.3",
|
|
50
50
|
"ultracite": "7.10.5",
|
|
51
51
|
"vitest": "4.1.10",
|
|
52
|
-
"@patronage/alchemy-d1-state": "0.
|
|
52
|
+
"@patronage/alchemy-d1-state": "0.4.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"alchemy": ">=2.0.0-beta.63 <3",
|