@patronage/factory-ci 0.2.1 → 1.0.0-alpha.13
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 +183 -3
- package/dist/index.d.ts +522 -2
- package/dist/index.js +1530 -35
- package/package.json +6 -6
- package/src/bundle-alchemy-entry.ts +94 -1
- package/src/candidate-lifecycle.ts +29 -0
- package/src/factory-workflow.ts +27 -28
- package/src/github-app-token.ts +162 -0
- package/src/index.ts +80 -0
- package/src/pinned-action.ts +30 -0
- package/src/production-impact-workflow.ts +109 -0
- package/src/proof-reuse-gate.ts +141 -10
- package/src/proof-reuse-presentation.ts +125 -0
- package/src/push-identity-workflow.ts +448 -0
- package/src/vitest-profile-reader.test.ts +208 -0
- package/src/vitest-profile-reader.ts +220 -0
- package/src/vitest-profile.ts +631 -0
- package/src/workflow-shell-lint.ts +462 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { StdioOptions } from "node:child_process";
|
|
2
|
+
|
|
1
3
|
//#region src/actions.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* The GitHub Actions this family's workflow generators use, pinned to full
|
|
@@ -52,6 +54,21 @@ declare const NODE_PNPM_ACTION_FAMILY_NODE24: {
|
|
|
52
54
|
};
|
|
53
55
|
};
|
|
54
56
|
//#endregion
|
|
57
|
+
//#region src/candidate-lifecycle.d.ts
|
|
58
|
+
/**
|
|
59
|
+
* Pull-request events that can create or refresh a Factory candidate run.
|
|
60
|
+
*
|
|
61
|
+
* GitHub's draft boolean remains the lifecycle authority: these triggers make
|
|
62
|
+
* a run visible, while `factoryCandidateOrPushCondition` keeps substantive
|
|
63
|
+
* jobs idle until the pull request is a candidate.
|
|
64
|
+
*/
|
|
65
|
+
declare const FACTORY_CANDIDATE_PULL_REQUEST_TYPES: readonly ["opened", "synchronize", "reopened", "ready_for_review"];
|
|
66
|
+
/**
|
|
67
|
+
* Build a GitHub Actions job condition for candidate PRs and merge-target
|
|
68
|
+
* pushes. The caller owns triggers, jobs, runners, permissions, and topology.
|
|
69
|
+
*/
|
|
70
|
+
declare const factoryCandidateOrPushCondition: (candidateCondition?: string) => string;
|
|
71
|
+
//#endregion
|
|
55
72
|
//#region src/bundle-alchemy-entry.d.ts
|
|
56
73
|
interface BundleAlchemyEntryOptions {
|
|
57
74
|
/** The Alchemy entry to bundle, e.g. `alchemy.run.ts`. */
|
|
@@ -60,6 +77,18 @@ interface BundleAlchemyEntryOptions {
|
|
|
60
77
|
readonly outfile: string;
|
|
61
78
|
/** esbuild's working directory; also the base for relative paths. */
|
|
62
79
|
readonly absWorkingDir?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Import specifiers to rewrite before resolution, passed straight to
|
|
82
|
+
* esbuild's `alias`. Substitution happens before the `packages` and
|
|
83
|
+
* `external` decisions, so an aliased bare specifier is inlined even under
|
|
84
|
+
* `packages: "external"`. Values are resolved the way esbuild resolves any
|
|
85
|
+
* import, so give absolute paths or package names — which aliases a repo
|
|
86
|
+
* needs is the consumer's policy and this package bakes in none. A key on
|
|
87
|
+
* `alchemy`, `effect`, or a subpath of either is rejected outright, and the
|
|
88
|
+
* finished bundle is checked for files from those packages however they were
|
|
89
|
+
* reached.
|
|
90
|
+
*/
|
|
91
|
+
readonly alias?: Readonly<Record<string, string>>;
|
|
63
92
|
/**
|
|
64
93
|
* `"external"` (default) leaves every bare import outside the entry's own
|
|
65
94
|
* source graph to Node's resolver at run time — the entry's TypeScript is
|
|
@@ -134,12 +163,17 @@ declare const localPreviewStage: (options: LocalPreviewStageOptions) => LocalPre
|
|
|
134
163
|
//#endregion
|
|
135
164
|
//#region src/factory-workflow.d.ts
|
|
136
165
|
interface WorkflowStep {
|
|
166
|
+
readonly continueOnError?: boolean;
|
|
167
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
168
|
+
readonly id?: string;
|
|
169
|
+
readonly if?: string;
|
|
137
170
|
readonly name: string;
|
|
138
171
|
readonly uses?: string;
|
|
139
172
|
readonly with?: Readonly<Record<string, string>>;
|
|
140
173
|
readonly run?: string;
|
|
141
174
|
}
|
|
142
175
|
interface CheckoutStepOptions {
|
|
176
|
+
readonly fetchDepth?: number;
|
|
143
177
|
readonly name?: string;
|
|
144
178
|
readonly ref?: string;
|
|
145
179
|
}
|
|
@@ -194,6 +228,59 @@ interface FactoryWorkflowArtifact<Additional extends Readonly<Record<string, Pin
|
|
|
194
228
|
*/
|
|
195
229
|
declare const factoryWorkflow: <const Additional extends Readonly<Record<string, PinnedAction>> = Record<never, never>>(options: FactoryWorkflowOptions<Additional>) => FactoryWorkflowArtifact<Additional>;
|
|
196
230
|
//#endregion
|
|
231
|
+
//#region src/github-app-token.d.ts
|
|
232
|
+
/**
|
|
233
|
+
* What a consumer must know to mint: the app id, where the private key is, and
|
|
234
|
+
* — when it has been recorded — which installation to mint against.
|
|
235
|
+
*
|
|
236
|
+
* `installationId` is optional because the installation is discoverable from
|
|
237
|
+
* the repository. `privateKeyPath` is a path rather than key material so no
|
|
238
|
+
* consumer has to hold a secret in memory to call this, and so the key-path
|
|
239
|
+
* convention stays the consumer's.
|
|
240
|
+
*/
|
|
241
|
+
interface GithubAppCredentials {
|
|
242
|
+
appId: number | string;
|
|
243
|
+
installationId?: number;
|
|
244
|
+
privateKeyPath: string;
|
|
245
|
+
}
|
|
246
|
+
/** Carries the HTTP status so a caller can tell a retryable failure apart. */
|
|
247
|
+
declare class GitHubApiError extends Error {
|
|
248
|
+
readonly status: number;
|
|
249
|
+
constructor(status: number, statusText: string);
|
|
250
|
+
}
|
|
251
|
+
interface GithubAppTokenOptions {
|
|
252
|
+
/** Injectable `fetch` (tests, or a caller with its own instrumented one). */
|
|
253
|
+
fetch?: typeof fetch;
|
|
254
|
+
/** Wall clock in milliseconds; only the JWT's validity window uses it. */
|
|
255
|
+
now?: () => number;
|
|
256
|
+
/** Injectable key read, so a caller can hold the PEM itself if it must. */
|
|
257
|
+
readPrivateKey?: (privateKeyPath: string) => Buffer | string;
|
|
258
|
+
/** Per-request timeout; defaults to five seconds. */
|
|
259
|
+
timeoutMs?: number;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The signed app JWT GitHub accepts as `Authorization: Bearer` for the App
|
|
263
|
+
* endpoints. `iss` is stringified because GitHub accepts either spelling and a
|
|
264
|
+
* numeric app id must not depend on JSON's number formatting.
|
|
265
|
+
*
|
|
266
|
+
* The signature is produced from the key on disk and returned; the key
|
|
267
|
+
* material itself never leaves this call.
|
|
268
|
+
*/
|
|
269
|
+
declare const githubAppJwt: (credentials: GithubAppCredentials, options?: Pick<GithubAppTokenOptions, "now" | "readPrivateKey">) => string;
|
|
270
|
+
/**
|
|
271
|
+
* Mint an installation access token for one repository.
|
|
272
|
+
*
|
|
273
|
+
* When the credentials omit `installationId`, the installation is discovered
|
|
274
|
+
* from the repository first — the same call every consumer had written for
|
|
275
|
+
* itself. Nothing is cached: the token is returned to the caller and this
|
|
276
|
+
* module keeps no copy.
|
|
277
|
+
*/
|
|
278
|
+
declare const mintInstallationToken: (input: {
|
|
279
|
+
credentials: GithubAppCredentials;
|
|
280
|
+
owner: string;
|
|
281
|
+
repo: string;
|
|
282
|
+
}, options?: GithubAppTokenOptions) => Promise<string>;
|
|
283
|
+
//#endregion
|
|
197
284
|
//#region src/execute-alchemy-entry.d.ts
|
|
198
285
|
interface ExecuteAlchemyEntryOptions {
|
|
199
286
|
/**
|
|
@@ -257,6 +344,13 @@ declare const FACTORY_PROOF_GATE_CHECK_NAME = "patronage-factory/pr-verify";
|
|
|
257
344
|
declare const FACTORY_PROOF_GATE_APP_ID = "4314840";
|
|
258
345
|
/** Step id the guard condition refers to. */
|
|
259
346
|
declare const FACTORY_PROOF_GATE_STEP_ID = "factory-proof";
|
|
347
|
+
/**
|
|
348
|
+
* Human-visible name of the gate step as the Actions jobs API serves it. A
|
|
349
|
+
* read-only run analyzer (`psf ci:analyze`, #647) matches on this name to
|
|
350
|
+
* classify a run as proof-reuse versus full fallback, so it is exported from
|
|
351
|
+
* exactly the module that writes it — matching on a re-typed copy would drift.
|
|
352
|
+
*/
|
|
353
|
+
declare const FACTORY_PROOF_GATE_STEP_NAME = "Check for factory proof of this head";
|
|
260
354
|
/**
|
|
261
355
|
* The shell the gate runs under, and it is a correctness requirement rather
|
|
262
356
|
* than a preference.
|
|
@@ -294,6 +388,8 @@ declare const FACTORY_PROOF_GATE_REASON_OUTPUT = "reason";
|
|
|
294
388
|
* proof that executed every command the surface requires is reusable.
|
|
295
389
|
*/
|
|
296
390
|
declare const FACTORY_PROOF_GATE_MODE_OUTPUT = "mode";
|
|
391
|
+
/** Exact Checks API URL of the proof generation selected by the gate. */
|
|
392
|
+
declare const FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT = "source-check-url";
|
|
297
393
|
/**
|
|
298
394
|
* Guard for every step the gate protects. Deliberately `!= 'true'` and not
|
|
299
395
|
* `== 'false'`: an unset, empty, or garbled output must run the suite.
|
|
@@ -314,7 +410,8 @@ declare const FACTORY_PROOF_GATE_IF = "github.event_name == 'pull_request'";
|
|
|
314
410
|
* - `pending` the newest generation had not completed when this job read it
|
|
315
411
|
* - `failed` the newest generation records no pass
|
|
316
412
|
* - `unreadable` it passed but carries no binding for this repository and head
|
|
317
|
-
* - `incomplete`
|
|
413
|
+
* - `incomplete` its executed plus stamp-authorized released commands do not
|
|
414
|
+
* cover every required command
|
|
318
415
|
* - `ambiguous` two newest generations share the greatest start time
|
|
319
416
|
* - `error` the gate could not reach a decision (fail open)
|
|
320
417
|
*
|
|
@@ -350,6 +447,34 @@ interface ProofReuseCommand {
|
|
|
350
447
|
* must refuse instead.
|
|
351
448
|
*/
|
|
352
449
|
declare const proofReuseRequiredCommands: (commands: readonly ProofReuseCommand[]) => readonly string[] | undefined;
|
|
450
|
+
/**
|
|
451
|
+
* Resolve command *identities* to their `ProofReuseCommand` objects (ADR
|
|
452
|
+
* 0021).
|
|
453
|
+
*
|
|
454
|
+
* Selection by name is deliberately a consumer decision (proof-surfaces.ts in
|
|
455
|
+
* software-factory-hq, the `paitronage:verify` filter in paitronage's
|
|
456
|
+
* `verify.ts`): only the consumer knows which named commands a guarded
|
|
457
|
+
* surface requires. What both of those implementations independently
|
|
458
|
+
* hand-rolled is the same lookup — find each name in the profile's command
|
|
459
|
+
* catalog, and refuse to silently shrink the required set when a name has no
|
|
460
|
+
* entry. That lookup is what this function is: the mechanic, not the
|
|
461
|
+
* selection.
|
|
462
|
+
*
|
|
463
|
+
* Throwing at generation time (rather than returning `undefined` or an empty
|
|
464
|
+
* array) is deliberate: a name with no catalog entry is a mistake in the
|
|
465
|
+
* generator source, not a runtime condition a consumer should have to check
|
|
466
|
+
* for, and a required set that quietly loses an entry is exactly what makes a
|
|
467
|
+
* passing proof trivially "covering".
|
|
468
|
+
*
|
|
469
|
+
* `selectionLabel` names the failure, nothing else: it is not part of the
|
|
470
|
+
* selection this function resolves, only prose a consumer supplies for its
|
|
471
|
+
* own thrown error (e.g. HQ's surface name, "core" or "docs"). The message
|
|
472
|
+
* deliberately says "the profile's command catalog" rather than naming
|
|
473
|
+
* `software-factory.profile.json`: a fleet-generic library must not assume
|
|
474
|
+
* every consumer's catalog is that exact file, so this wording differs
|
|
475
|
+
* on purpose from the HQ-local message it replaced.
|
|
476
|
+
*/
|
|
477
|
+
declare const resolveProofReuseCommands: (catalog: readonly ProofReuseCommand[], names: readonly string[], selectionLabel?: string) => readonly ProofReuseCommand[];
|
|
353
478
|
interface FactoryProofGateOptions {
|
|
354
479
|
/**
|
|
355
480
|
* The profile command objects this guarded surface selected. A consumer with
|
|
@@ -445,4 +570,399 @@ declare const proofReuseCoverage: ({
|
|
|
445
570
|
/** `proofReuseCoverage`, as a build failure. */
|
|
446
571
|
declare const assertProofReuseCoverage: (input: ProofReuseCoverageInput) => ProofReuseCoverageReport;
|
|
447
572
|
//#endregion
|
|
448
|
-
|
|
573
|
+
//#region src/proof-reuse-presentation.d.ts
|
|
574
|
+
/** Generic timing and presentation steps around the proof-reuse gate (#652). */
|
|
575
|
+
declare const FACTORY_PROOF_TIMING_STEP_ID = "ci-timing";
|
|
576
|
+
declare const FACTORY_PROOF_TIMING_START_STEP_NAME = "Start CI timing";
|
|
577
|
+
declare const FACTORY_PROOF_TIMING_SUMMARY_STEP_NAME = "Record proof-reuse timing";
|
|
578
|
+
interface FactoryProofReusePresentationOptions {
|
|
579
|
+
/** Human-readable name of the guarded suite. */
|
|
580
|
+
readonly surface: string;
|
|
581
|
+
}
|
|
582
|
+
interface FactoryProofTimingStartStep {
|
|
583
|
+
readonly continueOnError: true;
|
|
584
|
+
readonly id: typeof FACTORY_PROOF_TIMING_STEP_ID;
|
|
585
|
+
readonly name: typeof FACTORY_PROOF_TIMING_START_STEP_NAME;
|
|
586
|
+
readonly run: string;
|
|
587
|
+
}
|
|
588
|
+
declare const factoryProofTimingStartStep: () => FactoryProofTimingStartStep;
|
|
589
|
+
declare const factoryProofReuseSummaryScript: ({
|
|
590
|
+
surface
|
|
591
|
+
}: FactoryProofReusePresentationOptions) => string;
|
|
592
|
+
interface FactoryProofReuseSummaryStep {
|
|
593
|
+
readonly continueOnError: true;
|
|
594
|
+
readonly env: Readonly<Record<string, string>>;
|
|
595
|
+
readonly if: "always()";
|
|
596
|
+
readonly name: typeof FACTORY_PROOF_TIMING_SUMMARY_STEP_NAME;
|
|
597
|
+
readonly run: string;
|
|
598
|
+
}
|
|
599
|
+
declare const factoryProofReuseSummaryStep: (options: FactoryProofReusePresentationOptions) => FactoryProofReuseSummaryStep;
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/production-impact-workflow.d.ts
|
|
602
|
+
declare const FACTORY_PRODUCTION_IMPACT_STEP_ID = "production_impact";
|
|
603
|
+
declare const FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT = "decision";
|
|
604
|
+
declare const FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT = "basis";
|
|
605
|
+
declare const FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT = "unsubscribed_paths";
|
|
606
|
+
/** Stable GitHub-output key for one declared target. */
|
|
607
|
+
declare const productionImpactTargetOutput: (targetName: string) => string;
|
|
608
|
+
interface FactoryProductionImpactWorkflowOptions {
|
|
609
|
+
readonly after?: string;
|
|
610
|
+
readonly before?: string;
|
|
611
|
+
readonly cli?: string;
|
|
612
|
+
readonly profilePath?: string;
|
|
613
|
+
readonly targets: readonly string[];
|
|
614
|
+
}
|
|
615
|
+
interface FactoryProductionImpactWorkflow {
|
|
616
|
+
/** Outputs for a caller-owned decision job that subsequent jobs may consume. */
|
|
617
|
+
readonly decisionJobOutputs: Readonly<Record<string, string>>;
|
|
618
|
+
readonly decisionStep: WorkflowStep;
|
|
619
|
+
/** A fail-open condition: only an explicit usable withdrawal skips work. */
|
|
620
|
+
readonly demandedIf: (targetName: string, decisionJob?: string) => string;
|
|
621
|
+
readonly targetOutputs: Readonly<Record<string, string>>;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Generate the small factory-owned decision seam for a consumer production
|
|
625
|
+
* workflow. Consumers retain jobs, deploy commands, credentials, topology,
|
|
626
|
+
* and convergence checks; this artifact supplies only the decision step and
|
|
627
|
+
* per-target withdrawal conditions.
|
|
628
|
+
*/
|
|
629
|
+
declare const factoryProductionImpactWorkflow: (options: FactoryProductionImpactWorkflowOptions) => FactoryProductionImpactWorkflow;
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/push-identity-workflow.d.ts
|
|
632
|
+
declare const FACTORY_PUSH_IDENTITY_SCHEMA_VERSION = 1;
|
|
633
|
+
declare const FACTORY_PUSH_IDENTITY_ARTIFACT_PREFIX = "factory-push-identity";
|
|
634
|
+
declare const FACTORY_PUSH_IDENTITY_RECORD_STEP_ID = "factory_push_identity_record";
|
|
635
|
+
declare const FACTORY_PUSH_IDENTITY_LOOKUP_STEP_ID = "factory_push_identity_lookup";
|
|
636
|
+
declare const FACTORY_PUSH_IDENTITY_DOWNLOAD_STEP_ID = "factory_push_identity_download";
|
|
637
|
+
declare const FACTORY_PUSH_IDENTITY_CHECKOUT_STEP_ID = "factory_push_identity_checkout";
|
|
638
|
+
declare const FACTORY_PUSH_IDENTITY_VALIDATE_STEP_ID = "factory_push_identity";
|
|
639
|
+
/** Versioned document uploaded by a push-triggered verification run. */
|
|
640
|
+
interface FactoryPushIdentityEnvelope {
|
|
641
|
+
readonly after: string;
|
|
642
|
+
readonly before: string;
|
|
643
|
+
readonly repository: string;
|
|
644
|
+
readonly runId: string;
|
|
645
|
+
readonly schemaVersion: typeof FACTORY_PUSH_IDENTITY_SCHEMA_VERSION;
|
|
646
|
+
}
|
|
647
|
+
type FactoryPushIdentityDisposition = "refused" | "usable";
|
|
648
|
+
interface FactoryPushIdentityProducerOptions {
|
|
649
|
+
/** Explicit caller-owned upload-artifact pin from its workflow artifact. */
|
|
650
|
+
readonly uploadArtifact: PinnedAction;
|
|
651
|
+
/** Optional consumer trigger policy combined with the required push event. */
|
|
652
|
+
readonly if?: string;
|
|
653
|
+
}
|
|
654
|
+
interface FactoryPushIdentityProducer {
|
|
655
|
+
readonly artifactName: string;
|
|
656
|
+
readonly steps: readonly WorkflowStep[];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Record and upload the exact push-event identity without changing the Verify
|
|
660
|
+
* result when transport is unavailable. Place these steps after verification.
|
|
661
|
+
*/
|
|
662
|
+
declare const factoryPushIdentityProducer: (options: FactoryPushIdentityProducerOptions) => FactoryPushIdentityProducer;
|
|
663
|
+
interface FactoryPushIdentityConsumer {
|
|
664
|
+
/** Job outputs suitable for a caller-owned decision job and telemetry. */
|
|
665
|
+
readonly outputs: Readonly<Record<"after" | "before" | "disposition" | "provenance" | "reason", string>>;
|
|
666
|
+
readonly requiredPermissions: Readonly<{
|
|
667
|
+
actions: "read";
|
|
668
|
+
contents: "read";
|
|
669
|
+
}>;
|
|
670
|
+
readonly steps: readonly WorkflowStep[];
|
|
671
|
+
/** Run classification only when exact identity transport is usable. */
|
|
672
|
+
readonly usableIf: string;
|
|
673
|
+
}
|
|
674
|
+
interface FactoryPushIdentityConsumerOptions {
|
|
675
|
+
/** Explicit caller-owned checkout pin from its action family. */
|
|
676
|
+
readonly checkout: PinnedAction;
|
|
677
|
+
/** Explicit caller-owned download-artifact pin from its workflow artifact. */
|
|
678
|
+
readonly downloadArtifact: PinnedAction;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Download and validate identity from exactly the triggering workflow run in
|
|
682
|
+
* the current repository. Every failure becomes a typed refusal; consumers
|
|
683
|
+
* retain deploy policy, credentials, commands, topology, and convergence.
|
|
684
|
+
*/
|
|
685
|
+
declare const factoryPushIdentityConsumer: (options: FactoryPushIdentityConsumerOptions) => FactoryPushIdentityConsumer;
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/vitest-profile.d.ts
|
|
688
|
+
/** Schema version of the emitted profile document. */
|
|
689
|
+
declare const VITEST_PROFILE_SCHEMA_VERSION = 1;
|
|
690
|
+
/** `tool` discriminator every emitted profile carries. */
|
|
691
|
+
declare const VITEST_PROFILE_TOOL = "factory-ci-vitest-profile";
|
|
692
|
+
/** Per-assertion status as Vitest's JSON reporter spells it. */
|
|
693
|
+
type VitestTestStatus = "disabled" | "failed" | "passed" | "pending" | "skipped" | "todo";
|
|
694
|
+
/** The subset of Vitest's `--reporter=json` document this module reads. */
|
|
695
|
+
interface VitestJsonReport {
|
|
696
|
+
numFailedTests: number;
|
|
697
|
+
numPassedTests: number;
|
|
698
|
+
numPendingTests: number;
|
|
699
|
+
numTodoTests: number;
|
|
700
|
+
numTotalTests: number;
|
|
701
|
+
numTotalTestSuites: number;
|
|
702
|
+
success: boolean;
|
|
703
|
+
testResults: {
|
|
704
|
+
assertionResults: {
|
|
705
|
+
duration?: number | null;
|
|
706
|
+
fullName: string;
|
|
707
|
+
status: VitestTestStatus;
|
|
708
|
+
}[];
|
|
709
|
+
endTime: number;
|
|
710
|
+
name: string;
|
|
711
|
+
startTime: number;
|
|
712
|
+
status: "failed" | "passed";
|
|
713
|
+
}[];
|
|
714
|
+
}
|
|
715
|
+
/** Duration statistics over one population of samples, in milliseconds. */
|
|
716
|
+
interface VitestProfileDurationSummary {
|
|
717
|
+
maximum: number;
|
|
718
|
+
mean: number;
|
|
719
|
+
median: number;
|
|
720
|
+
minimum: number;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* The machine a sample ran on, plus the commit it measured. `cpuModel` is the
|
|
724
|
+
* field that makes two runs comparable at all: hosted runner pools mix silicon
|
|
725
|
+
* behind one label.
|
|
726
|
+
*/
|
|
727
|
+
interface VitestProfileEnvironment {
|
|
728
|
+
arch: string;
|
|
729
|
+
availableParallelism: number;
|
|
730
|
+
cpuCount: number;
|
|
731
|
+
cpuModel: string | null;
|
|
732
|
+
gitDirty: boolean | null;
|
|
733
|
+
gitHead: string | null;
|
|
734
|
+
node: string;
|
|
735
|
+
osRelease: string;
|
|
736
|
+
platform: string;
|
|
737
|
+
totalMemoryBytes: number;
|
|
738
|
+
vitest: string;
|
|
739
|
+
}
|
|
740
|
+
/** One complete Vitest run inside a profile. */
|
|
741
|
+
interface VitestProfileSample {
|
|
742
|
+
counts: {
|
|
743
|
+
failed: number;
|
|
744
|
+
passed: number;
|
|
745
|
+
pending: number;
|
|
746
|
+
suites: number;
|
|
747
|
+
tests: number;
|
|
748
|
+
todo: number;
|
|
749
|
+
} | null;
|
|
750
|
+
durationMs: number;
|
|
751
|
+
endedAt: string;
|
|
752
|
+
exitCode: number;
|
|
753
|
+
failure: string | null;
|
|
754
|
+
files: {
|
|
755
|
+
durationMs: number;
|
|
756
|
+
path: string;
|
|
757
|
+
status: "failed" | "passed";
|
|
758
|
+
}[];
|
|
759
|
+
reportAvailable: boolean;
|
|
760
|
+
sample: number;
|
|
761
|
+
startedAt: string;
|
|
762
|
+
tests: {
|
|
763
|
+
durationMs: number;
|
|
764
|
+
file: string;
|
|
765
|
+
name: string;
|
|
766
|
+
status: VitestTestStatus;
|
|
767
|
+
}[];
|
|
768
|
+
}
|
|
769
|
+
/** The emitted profile document. */
|
|
770
|
+
interface VitestProfile {
|
|
771
|
+
command: string[];
|
|
772
|
+
endedAt: string;
|
|
773
|
+
environment: VitestProfileEnvironment;
|
|
774
|
+
options: {
|
|
775
|
+
maxWorkers: number;
|
|
776
|
+
samples: number;
|
|
777
|
+
slowLimit: number;
|
|
778
|
+
};
|
|
779
|
+
rawReportDirectory: string;
|
|
780
|
+
runs: VitestProfileSample[];
|
|
781
|
+
schemaVersion: typeof VITEST_PROFILE_SCHEMA_VERSION;
|
|
782
|
+
startedAt: string;
|
|
783
|
+
summary: {
|
|
784
|
+
durationMs: VitestProfileDurationSummary;
|
|
785
|
+
slowFiles: {
|
|
786
|
+
durationMs: VitestProfileDurationSummary;
|
|
787
|
+
path: string;
|
|
788
|
+
samples: number;
|
|
789
|
+
}[];
|
|
790
|
+
slowTests: {
|
|
791
|
+
durationMs: VitestProfileDurationSummary;
|
|
792
|
+
file: string;
|
|
793
|
+
name: string;
|
|
794
|
+
samples: number;
|
|
795
|
+
}[];
|
|
796
|
+
};
|
|
797
|
+
tool: typeof VITEST_PROFILE_TOOL;
|
|
798
|
+
}
|
|
799
|
+
/** What a caller must decide before a profile can run. */
|
|
800
|
+
interface VitestProfileOptions {
|
|
801
|
+
/** Directory Vitest runs in; file paths are recorded relative to it. */
|
|
802
|
+
cwd: string;
|
|
803
|
+
/** Directory whose Git state is recorded. Defaults to `cwd`. */
|
|
804
|
+
gitDirectory?: string;
|
|
805
|
+
/** Vitest `--maxWorkers` for every sample. */
|
|
806
|
+
maxWorkers: number;
|
|
807
|
+
/** Absolute path of the profile document to write. */
|
|
808
|
+
outputPath: string;
|
|
809
|
+
/** Called before each sample starts. */
|
|
810
|
+
onSampleStart?: (input: {
|
|
811
|
+
maxWorkers: number;
|
|
812
|
+
sample: number;
|
|
813
|
+
samples: number;
|
|
814
|
+
}) => void;
|
|
815
|
+
/** Called after each sample is normalized and persisted. */
|
|
816
|
+
onSampleComplete?: (input: {
|
|
817
|
+
result: VitestProfileSample;
|
|
818
|
+
sample: number;
|
|
819
|
+
samples: number;
|
|
820
|
+
}) => void;
|
|
821
|
+
/** How many serial runs to take. */
|
|
822
|
+
samples: number;
|
|
823
|
+
/** How many slow files and slow tests to keep in the summary. */
|
|
824
|
+
slowLimit: number;
|
|
825
|
+
/**
|
|
826
|
+
* `stdio` for the Vitest child. Defaults to `"inherit"`, which is what a
|
|
827
|
+
* caller printing progress to a terminal wants; a caller whose own stdout is
|
|
828
|
+
* structured passes `"ignore"` to silence the run.
|
|
829
|
+
*/
|
|
830
|
+
stdio?: StdioOptions;
|
|
831
|
+
}
|
|
832
|
+
/** Outcome of one Vitest invocation, as `runSample` reports it. */
|
|
833
|
+
interface VitestProfileSampleExecution {
|
|
834
|
+
durationMs: number;
|
|
835
|
+
exitCode: number;
|
|
836
|
+
failure?: string | null;
|
|
837
|
+
report: VitestJsonReport | null;
|
|
838
|
+
}
|
|
839
|
+
/** Injectable seams; production passes none of them. */
|
|
840
|
+
interface VitestProfileDependencies {
|
|
841
|
+
now?: () => Date;
|
|
842
|
+
runSample?: (input: {
|
|
843
|
+
cwd: string;
|
|
844
|
+
maxWorkers: number;
|
|
845
|
+
reportPath: string;
|
|
846
|
+
sample: number;
|
|
847
|
+
stdio: StdioOptions;
|
|
848
|
+
}) => Promise<VitestProfileSampleExecution>;
|
|
849
|
+
writeResult?: (outputPath: string, profile: VitestProfile) => Promise<void>;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* A sample failed. The partial profile is already on disk; `exitCode` is the
|
|
853
|
+
* status a caller should exit with.
|
|
854
|
+
*/
|
|
855
|
+
declare class VitestProfileError extends Error {
|
|
856
|
+
readonly exitCode: number;
|
|
857
|
+
constructor(message: string, exitCode: number);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Fold one Vitest JSON report into a profile sample: file and test timings,
|
|
861
|
+
* both sorted slowest first, plus the run's counts. A missing report (crash,
|
|
862
|
+
* timeout, unwritable output) yields a sample with `reportAvailable: false`
|
|
863
|
+
* rather than nothing at all.
|
|
864
|
+
*/
|
|
865
|
+
declare const normalizeVitestProfileSample: (report: VitestJsonReport | null, input: {
|
|
866
|
+
cwd: string;
|
|
867
|
+
durationMs: number;
|
|
868
|
+
endedAt: Date;
|
|
869
|
+
exitCode: number;
|
|
870
|
+
failure?: string | null;
|
|
871
|
+
sample: number;
|
|
872
|
+
startedAt: Date;
|
|
873
|
+
}) => VitestProfileSample;
|
|
874
|
+
/**
|
|
875
|
+
* Record the machine and commit a profile was taken on. Vitest's version is
|
|
876
|
+
* resolved from `cwd`, so it is the consumer's Vitest and not this package's.
|
|
877
|
+
* Git failures degrade to `null` — an artifact from a tarball checkout is still
|
|
878
|
+
* a usable measurement.
|
|
879
|
+
*/
|
|
880
|
+
declare const captureVitestProfileEnvironment: (options: {
|
|
881
|
+
cwd: string;
|
|
882
|
+
gitDirectory?: string;
|
|
883
|
+
}) => Promise<VitestProfileEnvironment>;
|
|
884
|
+
/**
|
|
885
|
+
* Write a profile document atomically: a partial file must never be readable
|
|
886
|
+
* as a complete measurement, and the profile is rewritten after every sample.
|
|
887
|
+
*/
|
|
888
|
+
declare const writeVitestProfile: (outputPath: string, profile: VitestProfile) => Promise<void>;
|
|
889
|
+
/**
|
|
890
|
+
* Take `samples` serial Vitest runs at one worker count and persist the profile
|
|
891
|
+
* after each one. Samples never overlap: concurrent runs would measure CPU and
|
|
892
|
+
* I/O contention instead of the worker count under test. A failing sample
|
|
893
|
+
* throws `VitestProfileError` with the partial profile already written.
|
|
894
|
+
*/
|
|
895
|
+
declare const runVitestProfile: (options: VitestProfileOptions, dependencies?: VitestProfileDependencies) => Promise<VitestProfile>;
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/vitest-profile-reader.d.ts
|
|
898
|
+
/**
|
|
899
|
+
* Outcome of reading one candidate document. `unrecognized` carries the first
|
|
900
|
+
* reason the document failed — an analyzer reports it verbatim rather than
|
|
901
|
+
* treating an unreadable profile as an empty one.
|
|
902
|
+
*/
|
|
903
|
+
type VitestProfileReadResult = {
|
|
904
|
+
kind: "profile";
|
|
905
|
+
profile: VitestProfile;
|
|
906
|
+
} | {
|
|
907
|
+
kind: "unrecognized";
|
|
908
|
+
reason: string;
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* Check one parsed JSON document against the profile contract the writer
|
|
912
|
+
* emits. Returns the typed profile on success and the first mismatch reason
|
|
913
|
+
* otherwise — never a partially-usable value.
|
|
914
|
+
*/
|
|
915
|
+
declare const readVitestProfileDocument: (value: unknown) => VitestProfileReadResult;
|
|
916
|
+
//#endregion
|
|
917
|
+
//#region src/workflow-shell-lint.d.ts
|
|
918
|
+
/**
|
|
919
|
+
* Parse-check the shell embedded in generated workflow YAML (#376).
|
|
920
|
+
*
|
|
921
|
+
* The generated-workflow lint validates YAML shape. It never parses the shell
|
|
922
|
+
* inside a `run:` block, so a script that cannot execute at all — a stray
|
|
923
|
+
* `fi`, an unclosed quote, a `then` with no `if` — passes every local check
|
|
924
|
+
* and only fails when the runner reaches it. That is not hypothetical:
|
|
925
|
+
* paitronage#1090 shipped a stray `fi` into a generated workflow and made
|
|
926
|
+
* every automated preview destroy a parse-time no-op for five days.
|
|
927
|
+
*
|
|
928
|
+
* `bash -n` is the whole control: it parses without executing. It lives here,
|
|
929
|
+
* once, rather than as a contract test in each consumer, because a guard
|
|
930
|
+
* copied per repository is a guard that exists in some of them.
|
|
931
|
+
*/
|
|
932
|
+
/** One `run:` block that bash refuses to parse. */
|
|
933
|
+
interface WorkflowShellParseFailure {
|
|
934
|
+
/** The step's `name:` when the YAML carried one. */
|
|
935
|
+
readonly step?: string;
|
|
936
|
+
/** The script as bash saw it, expressions already neutralized. */
|
|
937
|
+
readonly script: string;
|
|
938
|
+
/** What bash said. */
|
|
939
|
+
readonly stderr: string;
|
|
940
|
+
}
|
|
941
|
+
/** A `run:` block lifted out of generated YAML, with its step's `shell:`. */
|
|
942
|
+
interface RunBlock {
|
|
943
|
+
readonly script: string;
|
|
944
|
+
readonly shell?: string;
|
|
945
|
+
readonly step?: string;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Every `run:` block in a generated workflow, paired with the `shell:` its
|
|
949
|
+
* step declares.
|
|
950
|
+
*
|
|
951
|
+
* Deliberately a scanner over the emitted text and not a YAML parse: this
|
|
952
|
+
* package takes no dependency it does not need, and the emitted shape is one
|
|
953
|
+
* generator's output, not arbitrary YAML. It reads both block scalars
|
|
954
|
+
* (`run: |-`) and inline scripts.
|
|
955
|
+
*/
|
|
956
|
+
declare const workflowRunBlocks: (yaml: string) => RunBlock[];
|
|
957
|
+
/** Every `run:` block its interpreter refuses to parse. Empty means sound. */
|
|
958
|
+
declare const workflowShellParseFailures: (yaml: string) => WorkflowShellParseFailure[];
|
|
959
|
+
/**
|
|
960
|
+
* Fail the generated-workflow lint when any embedded `run:` block is not
|
|
961
|
+
* parseable bash. Call it on the YAML a generator is about to write, so the
|
|
962
|
+
* defect is caught at generation rather than by the runner.
|
|
963
|
+
*/
|
|
964
|
+
declare const assertWorkflowShellParses: (yaml: string, options: {
|
|
965
|
+
readonly source: string;
|
|
966
|
+
}) => void;
|
|
967
|
+
//#endregion
|
|
968
|
+
export { type BundleAlchemyEntryOptions, type CheckoutStepOptions, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, 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_PROOF_TIMING_START_STEP_NAME, FACTORY_PROOF_TIMING_STEP_ID, FACTORY_PROOF_TIMING_SUMMARY_STEP_NAME, FACTORY_PUSH_IDENTITY_ARTIFACT_PREFIX, FACTORY_PUSH_IDENTITY_CHECKOUT_STEP_ID, FACTORY_PUSH_IDENTITY_DOWNLOAD_STEP_ID, FACTORY_PUSH_IDENTITY_LOOKUP_STEP_ID, FACTORY_PUSH_IDENTITY_RECORD_STEP_ID, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, FACTORY_PUSH_IDENTITY_VALIDATE_STEP_ID, 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 FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, type GithubAppCredentials, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, type SetupNodeStepOptions, 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, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateOrPushCondition, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryScript, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|