@webpieces/pr-gate 0.4.664 → 0.4.666
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/scripts/commands/build-command.d.ts +36 -0
- package/src/scripts/commands/build-command.js +58 -0
- package/src/scripts/commands/build-command.js.map +1 -0
- package/src/scripts/pr-gate-app.d.ts +9 -1
- package/src/scripts/pr-gate-app.js +13 -1
- package/src/scripts/pr-gate-app.js.map +1 -1
- package/src/scripts/workflow/build-gate-log.d.ts +1 -0
- package/src/scripts/workflow/build-gate-log.js +4 -1
- package/src/scripts/workflow/build-gate-log.js.map +1 -1
- package/src/scripts/wp-build.d.ts +2 -0
- package/src/scripts/wp-build.js +20 -0
- package/src/scripts/wp-build.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.666",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"directory": "packages/tooling/pr-gate"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@webpieces/rules-config": "0.4.
|
|
18
|
+
"@webpieces/rules-config": "0.4.666",
|
|
19
19
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
20
20
|
"inversify": "7.10.4",
|
|
21
21
|
"reflect-metadata": "0.2.2"
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"types": "./src/index.d.ts",
|
|
27
27
|
"bin": {
|
|
28
|
+
"wp-build": "./src/scripts/wp-build.js",
|
|
28
29
|
"wp-finish-upsert-pr": "./src/scripts/git-finishUpsertPr.js",
|
|
29
30
|
"wp-start-upsert-pr": "./src/scripts/wp-start-upsert-pr.js",
|
|
30
31
|
"wp-start-update": "./src/scripts/wp-start-update.js",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RepoRootFinder } from '@webpieces/rules-config';
|
|
2
|
+
import { BuildAffected } from '../workflow/build-affected';
|
|
3
|
+
/**
|
|
4
|
+
* `wp-build` — run THE project's build, and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* ─── Why this bin exists ───────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* The correct verification command has lived in `commands.pr-gate.buildCommand` all along, and NOTHING
|
|
8
|
+
* SURFACED IT. So every repo hand-composed its own verify chain, and those chains drifted: one sibling
|
|
9
|
+
* repo's `ci:local` was `prettier --check .` (whole repo) + `wp-ci` (whole monorepo) +
|
|
10
|
+
* `nx affected -t test` with NO `--base` (whole repo again) — three whole-world passes on an inner
|
|
11
|
+
* loop, none of them the command the gate runs. `wp-build` closes that by making the configured value
|
|
12
|
+
* the only thing anyone has to type.
|
|
13
|
+
*
|
|
14
|
+
* ─── It composes NOTHING, deliberately ─────────────────────────────────────────────────────────────
|
|
15
|
+
* This command runs `commands.pr-gate.buildCommand` VERBATIM and adds no leg of its own — no format
|
|
16
|
+
* check, no lint pass, no extra test run. Composition is exactly what drifted. Anything that must run
|
|
17
|
+
* on every build belongs INSIDE `buildCommand`, where the PR gate runs it too; a leg added here would
|
|
18
|
+
* be a leg the gate never sees, which is the same defect one level in.
|
|
19
|
+
*
|
|
20
|
+
* ─── ONE resolver, and ONE gate ────────────────────────────────────────────────────────────────────
|
|
21
|
+
* `BuildAffected.runBuildGate` is the single code path that resolves the command
|
|
22
|
+
* (`resolveBuildCommand`), announces it, runs it, honours `buildGateLogCapture`, and renders the
|
|
23
|
+
* failure. `wp-build` calls THAT — it does not re-read the config key and does not hand-write a second
|
|
24
|
+
* failure string. A second reader is a second thing that can drift, and a second failure message is a
|
|
25
|
+
* second thing that can teach a command the gate does not run; drift is the entire reason this bin
|
|
26
|
+
* exists, so reproducing it one level in would be self-defeating.
|
|
27
|
+
*
|
|
28
|
+
* Only `BuildGateOptions` differs from stage ② and stage ③ — the label, the command to re-run, the
|
|
29
|
+
* headline, and the log-file stage id.
|
|
30
|
+
*/
|
|
31
|
+
export declare class BuildCommand {
|
|
32
|
+
private readonly buildAffected;
|
|
33
|
+
private readonly repoRootFinder;
|
|
34
|
+
constructor(buildAffected: BuildAffected, repoRootFinder: RepoRootFinder);
|
|
35
|
+
run(): Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BuildCommand = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
|
+
const inversify_1 = require("inversify");
|
|
7
|
+
const build_affected_1 = require("../workflow/build-affected");
|
|
8
|
+
const build_gate_log_1 = require("../workflow/build-gate-log");
|
|
9
|
+
/**
|
|
10
|
+
* `wp-build` — run THE project's build, and nothing else.
|
|
11
|
+
*
|
|
12
|
+
* ─── Why this bin exists ───────────────────────────────────────────────────────────────────────────
|
|
13
|
+
* The correct verification command has lived in `commands.pr-gate.buildCommand` all along, and NOTHING
|
|
14
|
+
* SURFACED IT. So every repo hand-composed its own verify chain, and those chains drifted: one sibling
|
|
15
|
+
* repo's `ci:local` was `prettier --check .` (whole repo) + `wp-ci` (whole monorepo) +
|
|
16
|
+
* `nx affected -t test` with NO `--base` (whole repo again) — three whole-world passes on an inner
|
|
17
|
+
* loop, none of them the command the gate runs. `wp-build` closes that by making the configured value
|
|
18
|
+
* the only thing anyone has to type.
|
|
19
|
+
*
|
|
20
|
+
* ─── It composes NOTHING, deliberately ─────────────────────────────────────────────────────────────
|
|
21
|
+
* This command runs `commands.pr-gate.buildCommand` VERBATIM and adds no leg of its own — no format
|
|
22
|
+
* check, no lint pass, no extra test run. Composition is exactly what drifted. Anything that must run
|
|
23
|
+
* on every build belongs INSIDE `buildCommand`, where the PR gate runs it too; a leg added here would
|
|
24
|
+
* be a leg the gate never sees, which is the same defect one level in.
|
|
25
|
+
*
|
|
26
|
+
* ─── ONE resolver, and ONE gate ────────────────────────────────────────────────────────────────────
|
|
27
|
+
* `BuildAffected.runBuildGate` is the single code path that resolves the command
|
|
28
|
+
* (`resolveBuildCommand`), announces it, runs it, honours `buildGateLogCapture`, and renders the
|
|
29
|
+
* failure. `wp-build` calls THAT — it does not re-read the config key and does not hand-write a second
|
|
30
|
+
* failure string. A second reader is a second thing that can drift, and a second failure message is a
|
|
31
|
+
* second thing that can teach a command the gate does not run; drift is the entire reason this bin
|
|
32
|
+
* exists, so reproducing it one level in would be self-defeating.
|
|
33
|
+
*
|
|
34
|
+
* Only `BuildGateOptions` differs from stage ② and stage ③ — the label, the command to re-run, the
|
|
35
|
+
* headline, and the log-file stage id.
|
|
36
|
+
*/
|
|
37
|
+
let BuildCommand = class BuildCommand {
|
|
38
|
+
buildAffected;
|
|
39
|
+
repoRootFinder;
|
|
40
|
+
constructor(buildAffected, repoRootFinder) {
|
|
41
|
+
this.buildAffected = buildAffected;
|
|
42
|
+
this.repoRootFinder = repoRootFinder;
|
|
43
|
+
}
|
|
44
|
+
run() {
|
|
45
|
+
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
46
|
+
// runBuildGate announces the resolved command, runs it, and throws CliExitError on failure so
|
|
47
|
+
// runMain owns the exit — the same three things it does for stage ② and stage ③.
|
|
48
|
+
this.buildAffected.runBuildGate(repoRoot, new build_affected_1.BuildGateOptions('🛠️ wp-build', 'pnpm wp-build', 'Build failed.', build_gate_log_1.BUILD_STAGE));
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.BuildCommand = BuildCommand;
|
|
53
|
+
exports.BuildCommand = BuildCommand = tslib_1.__decorate([
|
|
54
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
55
|
+
tslib_1.__metadata("design:paramtypes", [build_affected_1.BuildAffected,
|
|
56
|
+
rules_config_1.RepoRootFinder])
|
|
57
|
+
], BuildCommand);
|
|
58
|
+
//# sourceMappingURL=build-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/build-command.ts"],"names":[],"mappings":";;;;AAAA,0DAAyD;AACzD,yCAA2D;AAE3D,+DAA6E;AAC7E,+DAAyD;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEA;IACA;IAFrB,YACqB,aAA4B,EAC5B,cAA8B;QAD9B,kBAAa,GAAb,aAAa,CAAe;QAC5B,mBAAc,GAAd,cAAc,CAAgB;IAChD,CAAC;IAEJ,GAAG;QACC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,8FAA8F;QAC9F,iFAAiF;QACjF,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,iCAAgB,CAC1D,eAAe,EACf,eAAe,EACf,eAAe,EACf,4BAAW,CACd,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;CACJ,CAAA;AAlBY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGD,8BAAa;QACZ,6BAAc;GAH1C,YAAY,CAkBxB","sourcesContent":["import { RepoRootFinder } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { BuildAffected, BuildGateOptions } from '../workflow/build-affected';\nimport { BUILD_STAGE } from '../workflow/build-gate-log';\n\n/**\n * `wp-build` — run THE project's build, and nothing else.\n *\n * ─── Why this bin exists ───────────────────────────────────────────────────────────────────────────\n * The correct verification command has lived in `commands.pr-gate.buildCommand` all along, and NOTHING\n * SURFACED IT. So every repo hand-composed its own verify chain, and those chains drifted: one sibling\n * repo's `ci:local` was `prettier --check .` (whole repo) + `wp-ci` (whole monorepo) +\n * `nx affected -t test` with NO `--base` (whole repo again) — three whole-world passes on an inner\n * loop, none of them the command the gate runs. `wp-build` closes that by making the configured value\n * the only thing anyone has to type.\n *\n * ─── It composes NOTHING, deliberately ─────────────────────────────────────────────────────────────\n * This command runs `commands.pr-gate.buildCommand` VERBATIM and adds no leg of its own — no format\n * check, no lint pass, no extra test run. Composition is exactly what drifted. Anything that must run\n * on every build belongs INSIDE `buildCommand`, where the PR gate runs it too; a leg added here would\n * be a leg the gate never sees, which is the same defect one level in.\n *\n * ─── ONE resolver, and ONE gate ────────────────────────────────────────────────────────────────────\n * `BuildAffected.runBuildGate` is the single code path that resolves the command\n * (`resolveBuildCommand`), announces it, runs it, honours `buildGateLogCapture`, and renders the\n * failure. `wp-build` calls THAT — it does not re-read the config key and does not hand-write a second\n * failure string. A second reader is a second thing that can drift, and a second failure message is a\n * second thing that can teach a command the gate does not run; drift is the entire reason this bin\n * exists, so reproducing it one level in would be self-defeating.\n *\n * Only `BuildGateOptions` differs from stage ② and stage ③ — the label, the command to re-run, the\n * headline, and the log-file stage id.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class BuildCommand {\n constructor(\n private readonly buildAffected: BuildAffected,\n private readonly repoRootFinder: RepoRootFinder,\n ) {}\n\n run(): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // runBuildGate announces the resolved command, runs it, and throws CliExitError on failure so\n // runMain owns the exit — the same three things it does for stage ② and stage ③.\n this.buildAffected.runBuildGate(repoRoot, new BuildGateOptions(\n '🛠️ wp-build',\n 'pnpm wp-build',\n 'Build failed.',\n BUILD_STAGE,\n ));\n return Promise.resolve();\n }\n}\n"]}
|
|
@@ -8,6 +8,7 @@ import { LandPrCommand } from './commands/land-pr-command';
|
|
|
8
8
|
import { CheckPrCommand } from './commands/check-pr-command';
|
|
9
9
|
import { ReviewUpsertPrCommand, ReviewUpsertPrOptions } from './commands/review-upsert-pr-command';
|
|
10
10
|
import { ReapWorktreeCommand } from './commands/reap-worktree-command';
|
|
11
|
+
import { BuildCommand } from './commands/build-command';
|
|
11
12
|
import { PushDevCommand, PushDevOptions } from './commands/push-dev-command';
|
|
12
13
|
import { FinishPushDevCommand, FinishPushDevOptions } from './commands/finish-push-dev-command';
|
|
13
14
|
import { PushDevStateStore } from './workflow/push-dev-state';
|
|
@@ -29,11 +30,12 @@ export declare class PrGateApp {
|
|
|
29
30
|
private readonly checkPrCommand;
|
|
30
31
|
private readonly reviewUpsertPrCommand;
|
|
31
32
|
private readonly reapWorktreeCommand;
|
|
33
|
+
private readonly buildCommand;
|
|
32
34
|
private readonly pushDevCommand;
|
|
33
35
|
private readonly finishPushDevCommand;
|
|
34
36
|
private readonly pushDevStateStore;
|
|
35
37
|
private readonly repoRootFinder;
|
|
36
|
-
constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, checkoutCleanMainCommand: CheckoutCleanMainCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand, reviewUpsertPrCommand: ReviewUpsertPrCommand, reapWorktreeCommand: ReapWorktreeCommand, pushDevCommand: PushDevCommand, finishPushDevCommand: FinishPushDevCommand, pushDevStateStore: PushDevStateStore, repoRootFinder: RepoRootFinder);
|
|
38
|
+
constructor(startUpdateCommand: StartUpdateCommand, finishUpdateCommand: FinishUpdateCommand, startUpsertPrCommand: StartUpsertPrCommand, finishUpsertPrCommand: FinishUpsertPrCommand, cleanupCommand: CleanupCommand, checkoutCleanMainCommand: CheckoutCleanMainCommand, landPrCommand: LandPrCommand, checkPrCommand: CheckPrCommand, reviewUpsertPrCommand: ReviewUpsertPrCommand, reapWorktreeCommand: ReapWorktreeCommand, buildCommand: BuildCommand, pushDevCommand: PushDevCommand, finishPushDevCommand: FinishPushDevCommand, pushDevStateStore: PushDevStateStore, repoRootFinder: RepoRootFinder);
|
|
37
39
|
/**
|
|
38
40
|
* Refuse `command` while a `wp-push-dev --resolve` is half-finished.
|
|
39
41
|
*
|
|
@@ -53,6 +55,12 @@ export declare class PrGateApp {
|
|
|
53
55
|
* reaped by a process that is not standing in it.
|
|
54
56
|
*/
|
|
55
57
|
reapWorktree(args: string[]): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* `wp-build`: run this repo's ONE configured build (`commands.pr-gate.buildCommand`) — the same
|
|
60
|
+
* command, resolved by the same resolver, that the PR gate's build stage runs. Not blocked during a
|
|
61
|
+
* `wp-push-dev --resolve`: it mutates nothing and reads no branch state.
|
|
62
|
+
*/
|
|
63
|
+
build(): Promise<void>;
|
|
56
64
|
/** `wp-start-update`: 3-point squash-update from main (no PR). */
|
|
57
65
|
startUpdate(): Promise<void>;
|
|
58
66
|
/** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */
|
|
@@ -14,6 +14,7 @@ const land_pr_command_1 = require("./commands/land-pr-command");
|
|
|
14
14
|
const check_pr_command_1 = require("./commands/check-pr-command");
|
|
15
15
|
const review_upsert_pr_command_1 = require("./commands/review-upsert-pr-command");
|
|
16
16
|
const reap_worktree_command_1 = require("./commands/reap-worktree-command");
|
|
17
|
+
const build_command_1 = require("./commands/build-command");
|
|
17
18
|
const push_dev_command_1 = require("./commands/push-dev-command");
|
|
18
19
|
const finish_push_dev_command_1 = require("./commands/finish-push-dev-command");
|
|
19
20
|
const push_dev_state_1 = require("./workflow/push-dev-state");
|
|
@@ -35,11 +36,12 @@ let PrGateApp = class PrGateApp {
|
|
|
35
36
|
checkPrCommand;
|
|
36
37
|
reviewUpsertPrCommand;
|
|
37
38
|
reapWorktreeCommand;
|
|
39
|
+
buildCommand;
|
|
38
40
|
pushDevCommand;
|
|
39
41
|
finishPushDevCommand;
|
|
40
42
|
pushDevStateStore;
|
|
41
43
|
repoRootFinder;
|
|
42
|
-
constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, checkoutCleanMainCommand, landPrCommand, checkPrCommand, reviewUpsertPrCommand, reapWorktreeCommand, pushDevCommand, finishPushDevCommand, pushDevStateStore, repoRootFinder) {
|
|
44
|
+
constructor(startUpdateCommand, finishUpdateCommand, startUpsertPrCommand, finishUpsertPrCommand, cleanupCommand, checkoutCleanMainCommand, landPrCommand, checkPrCommand, reviewUpsertPrCommand, reapWorktreeCommand, buildCommand, pushDevCommand, finishPushDevCommand, pushDevStateStore, repoRootFinder) {
|
|
43
45
|
this.startUpdateCommand = startUpdateCommand;
|
|
44
46
|
this.finishUpdateCommand = finishUpdateCommand;
|
|
45
47
|
this.startUpsertPrCommand = startUpsertPrCommand;
|
|
@@ -50,6 +52,7 @@ let PrGateApp = class PrGateApp {
|
|
|
50
52
|
this.checkPrCommand = checkPrCommand;
|
|
51
53
|
this.reviewUpsertPrCommand = reviewUpsertPrCommand;
|
|
52
54
|
this.reapWorktreeCommand = reapWorktreeCommand;
|
|
55
|
+
this.buildCommand = buildCommand;
|
|
53
56
|
this.pushDevCommand = pushDevCommand;
|
|
54
57
|
this.finishPushDevCommand = finishPushDevCommand;
|
|
55
58
|
this.pushDevStateStore = pushDevStateStore;
|
|
@@ -84,6 +87,14 @@ let PrGateApp = class PrGateApp {
|
|
|
84
87
|
reapWorktree(args) {
|
|
85
88
|
return this.reapWorktreeCommand.run(args);
|
|
86
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* `wp-build`: run this repo's ONE configured build (`commands.pr-gate.buildCommand`) — the same
|
|
92
|
+
* command, resolved by the same resolver, that the PR gate's build stage runs. Not blocked during a
|
|
93
|
+
* `wp-push-dev --resolve`: it mutates nothing and reads no branch state.
|
|
94
|
+
*/
|
|
95
|
+
build() {
|
|
96
|
+
return this.buildCommand.run();
|
|
97
|
+
}
|
|
87
98
|
/** `wp-start-update`: 3-point squash-update from main (no PR). */
|
|
88
99
|
startUpdate() {
|
|
89
100
|
this.assertNoResolveInProgress('wp-start-update');
|
|
@@ -152,6 +163,7 @@ exports.PrGateApp = PrGateApp = tslib_1.__decorate([
|
|
|
152
163
|
check_pr_command_1.CheckPrCommand,
|
|
153
164
|
review_upsert_pr_command_1.ReviewUpsertPrCommand,
|
|
154
165
|
reap_worktree_command_1.ReapWorktreeCommand,
|
|
166
|
+
build_command_1.BuildCommand,
|
|
155
167
|
push_dev_command_1.PushDevCommand,
|
|
156
168
|
finish_push_dev_command_1.FinishPushDevCommand,
|
|
157
169
|
push_dev_state_1.PushDevStateStore,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-gate-app.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/pr-gate-app.ts"],"names":[],"mappings":"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","sourcesContent":["import { DocumentDesign } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { StartUpdateCommand } from './commands/start-update-command';\nimport { FinishUpdateCommand } from './commands/finish-update-command';\nimport { StartUpsertPrCommand } from './commands/start-upsert-pr-command';\nimport { FinishUpsertPrCommand } from './commands/finish-upsert-pr-command';\nimport { CleanupCommand } from './commands/cleanup-command';\nimport { CheckoutCleanMainCommand } from './commands/checkout-clean-main-command';\nimport { LandPrCommand } from './commands/land-pr-command';\nimport { CheckPrCommand } from './commands/check-pr-command';\nimport { ReviewUpsertPrCommand, ReviewUpsertPrOptions } from './commands/review-upsert-pr-command';\nimport { ReapWorktreeCommand } from './commands/reap-worktree-command';\nimport { PushDevCommand, PushDevOptions } from './commands/push-dev-command';\nimport { FinishPushDevCommand, FinishPushDevOptions } from './commands/finish-push-dev-command';\nimport { PushDevStateStore } from './workflow/push-dev-state';\nimport { RepoRootFinder } from '@webpieces/rules-config';\n\n/**\n * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command\n * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the\n * top-of-DAG the DI-design analyzer roots on, so `role:app` pr-gate draws its design. Each `bin/*`\n * entry resolves THIS and calls the matching command method.\n */\n@DocumentDesign()\n@injectable(bindingScopeValues.Singleton)\nexport class PrGateApp {\n constructor(\n private readonly startUpdateCommand: StartUpdateCommand,\n private readonly finishUpdateCommand: FinishUpdateCommand,\n private readonly startUpsertPrCommand: StartUpsertPrCommand,\n private readonly finishUpsertPrCommand: FinishUpsertPrCommand,\n private readonly cleanupCommand: CleanupCommand,\n private readonly checkoutCleanMainCommand: CheckoutCleanMainCommand,\n private readonly landPrCommand: LandPrCommand,\n private readonly checkPrCommand: CheckPrCommand,\n private readonly reviewUpsertPrCommand: ReviewUpsertPrCommand,\n private readonly reapWorktreeCommand: ReapWorktreeCommand,\n private readonly pushDevCommand: PushDevCommand,\n private readonly finishPushDevCommand: FinishPushDevCommand,\n private readonly pushDevStateStore: PushDevStateStore,\n private readonly repoRootFinder: RepoRootFinder,\n ) {}\n\n /**\n * Refuse `command` while a `wp-push-dev --resolve` is half-finished.\n *\n * Enforced HERE, at the one place every bin funnels through, rather than in each command: a resolve\n * parks the checkout on a throwaway branch, so every command below would act on a branch that is not\n * the one it thinks it is. Putting the check in nine constructors is nine chances to forget it in the\n * tenth. PushDevStateStore owns the blocked list AND renders the hint from it, so the two cannot drift.\n */\n private assertNoResolveInProgress(command: string): void {\n if (!this.pushDevStateStore.isBlockedDuringResolve(command)) return;\n this.pushDevStateStore.assertIdle(this.repoRootFinder.resolveRepoRoot(process.cwd()), command);\n }\n\n /** `wp-push-dev`: publish a DISPOSABLE copy of this branch for the shared dev environment. No PR, no build. */\n pushDev(opts: PushDevOptions = new PushDevOptions()): Promise<void> {\n return this.pushDevCommand.run(opts);\n }\n\n /** `wp-finish-push-dev`: commit a resolved dev composition and publish it (conflict path only). */\n finishPushDev(opts: FinishPushDevOptions = new FinishPushDevOptions()): Promise<void> {\n return this.finishPushDevCommand.run(opts);\n }\n\n /**\n * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.\n * `wp-land-pr` spawns it with cwd = the primary clone so the tree it just landed from can be\n * reaped by a process that is not standing in it.\n */\n reapWorktree(args: string[]): Promise<void> {\n return this.reapWorktreeCommand.run(args);\n }\n\n /** `wp-start-update`: 3-point squash-update from main (no PR). */\n startUpdate(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-update');\n return this.startUpdateCommand.run();\n }\n\n /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */\n finishUpdate(): Promise<void> {\n this.assertNoResolveInProgress('wp-finish-update');\n return this.finishUpdateCommand.run();\n }\n\n /** `wp-start-upsert-pr`: update from main (3-point merge), hand off review.json. No build gate, no push. */\n startUpsertPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-upsert-pr');\n return this.startUpsertPrCommand.run();\n }\n\n /** `wp-finish-upsert-pr`: finalize merge, authoritative build gate, dashboard, create/update PR. */\n finishUpsertPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-finish-upsert-pr');\n return this.finishUpsertPrCommand.run();\n }\n\n /** `wp-cleanup`: reap what is provably merged, and ASK about everything that merely looks dead. */\n cleanup(): Promise<void> {\n this.assertNoResolveInProgress('wp-cleanup');\n return this.cleanupCommand.run();\n }\n\n /**\n * `wp-checkout-clean-main`: go to main, fast-forward it, reap dead worktrees and branches, and sweep\n * the orphan directories a project move leaves behind. Replaces `git checkout main && git pull\n * origin main` outright — see CheckoutCleanMainCommand for why the old pair must stop being accepted\n * rather than surviving beside this.\n */\n checkoutCleanMain(): Promise<void> {\n this.assertNoResolveInProgress('wp-checkout-clean-main');\n return this.checkoutCleanMainCommand.run();\n }\n\n /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */\n landPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-land-pr');\n return this.landPrCommand.run();\n }\n\n /**\n * `wp-review-upsert-pr`: STAGE ② — validate the 3-point merge, run the build gate, extract this\n * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN\n * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.\n */\n reviewUpsertPr(opts: ReviewUpsertPrOptions = new ReviewUpsertPrOptions()): Promise<void> {\n this.assertNoResolveInProgress('wp-review-upsert-pr');\n return this.reviewUpsertPrCommand.run(opts);\n }\n\n /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */\n checkPr(): Promise<void> {\n return this.checkPrCommand.run();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pr-gate-app.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/pr-gate-app.ts"],"names":[],"mappings":"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","sourcesContent":["import { DocumentDesign } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { StartUpdateCommand } from './commands/start-update-command';\nimport { FinishUpdateCommand } from './commands/finish-update-command';\nimport { StartUpsertPrCommand } from './commands/start-upsert-pr-command';\nimport { FinishUpsertPrCommand } from './commands/finish-upsert-pr-command';\nimport { CleanupCommand } from './commands/cleanup-command';\nimport { CheckoutCleanMainCommand } from './commands/checkout-clean-main-command';\nimport { LandPrCommand } from './commands/land-pr-command';\nimport { CheckPrCommand } from './commands/check-pr-command';\nimport { ReviewUpsertPrCommand, ReviewUpsertPrOptions } from './commands/review-upsert-pr-command';\nimport { ReapWorktreeCommand } from './commands/reap-worktree-command';\nimport { BuildCommand } from './commands/build-command';\nimport { PushDevCommand, PushDevOptions } from './commands/push-dev-command';\nimport { FinishPushDevCommand, FinishPushDevOptions } from './commands/finish-push-dev-command';\nimport { PushDevStateStore } from './workflow/push-dev-state';\nimport { RepoRootFinder } from '@webpieces/rules-config';\n\n/**\n * The pr-gate application root. `container.get(PrGateApp)` resolves the entire workflow DAG (the command\n * classes → the injected git/merge/dashboard services). `@DocumentDesign` marks it the\n * top-of-DAG the DI-design analyzer roots on, so `role:app` pr-gate draws its design. Each `bin/*`\n * entry resolves THIS and calls the matching command method.\n */\n@DocumentDesign()\n@injectable(bindingScopeValues.Singleton)\nexport class PrGateApp {\n constructor(\n private readonly startUpdateCommand: StartUpdateCommand,\n private readonly finishUpdateCommand: FinishUpdateCommand,\n private readonly startUpsertPrCommand: StartUpsertPrCommand,\n private readonly finishUpsertPrCommand: FinishUpsertPrCommand,\n private readonly cleanupCommand: CleanupCommand,\n private readonly checkoutCleanMainCommand: CheckoutCleanMainCommand,\n private readonly landPrCommand: LandPrCommand,\n private readonly checkPrCommand: CheckPrCommand,\n private readonly reviewUpsertPrCommand: ReviewUpsertPrCommand,\n private readonly reapWorktreeCommand: ReapWorktreeCommand,\n private readonly buildCommand: BuildCommand,\n private readonly pushDevCommand: PushDevCommand,\n private readonly finishPushDevCommand: FinishPushDevCommand,\n private readonly pushDevStateStore: PushDevStateStore,\n private readonly repoRootFinder: RepoRootFinder,\n ) {}\n\n /**\n * Refuse `command` while a `wp-push-dev --resolve` is half-finished.\n *\n * Enforced HERE, at the one place every bin funnels through, rather than in each command: a resolve\n * parks the checkout on a throwaway branch, so every command below would act on a branch that is not\n * the one it thinks it is. Putting the check in nine constructors is nine chances to forget it in the\n * tenth. PushDevStateStore owns the blocked list AND renders the hint from it, so the two cannot drift.\n */\n private assertNoResolveInProgress(command: string): void {\n if (!this.pushDevStateStore.isBlockedDuringResolve(command)) return;\n this.pushDevStateStore.assertIdle(this.repoRootFinder.resolveRepoRoot(process.cwd()), command);\n }\n\n /** `wp-push-dev`: publish a DISPOSABLE copy of this branch for the shared dev environment. No PR, no build. */\n pushDev(opts: PushDevOptions = new PushDevOptions()): Promise<void> {\n return this.pushDevCommand.run(opts);\n }\n\n /** `wp-finish-push-dev`: commit a resolved dev composition and publish it (conflict path only). */\n finishPushDev(opts: FinishPushDevOptions = new FinishPushDevOptions()): Promise<void> {\n return this.finishPushDevCommand.run(opts);\n }\n\n /**\n * INTERNAL (`wp-reap-worktree.js`, no `bin` entry): remove ONE named worktree and its branch.\n * `wp-land-pr` spawns it with cwd = the primary clone so the tree it just landed from can be\n * reaped by a process that is not standing in it.\n */\n reapWorktree(args: string[]): Promise<void> {\n return this.reapWorktreeCommand.run(args);\n }\n\n /**\n * `wp-build`: run this repo's ONE configured build (`commands.pr-gate.buildCommand`) — the same\n * command, resolved by the same resolver, that the PR gate's build stage runs. Not blocked during a\n * `wp-push-dev --resolve`: it mutates nothing and reads no branch state.\n */\n build(): Promise<void> {\n return this.buildCommand.run();\n }\n\n /** `wp-start-update`: 3-point squash-update from main (no PR). */\n startUpdate(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-update');\n return this.startUpdateCommand.run();\n }\n\n /** `wp-finish-update`: validate + finalize a resolved 3-point merge (no PR). */\n finishUpdate(): Promise<void> {\n this.assertNoResolveInProgress('wp-finish-update');\n return this.finishUpdateCommand.run();\n }\n\n /** `wp-start-upsert-pr`: update from main (3-point merge), hand off review.json. No build gate, no push. */\n startUpsertPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-start-upsert-pr');\n return this.startUpsertPrCommand.run();\n }\n\n /** `wp-finish-upsert-pr`: finalize merge, authoritative build gate, dashboard, create/update PR. */\n finishUpsertPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-finish-upsert-pr');\n return this.finishUpsertPrCommand.run();\n }\n\n /** `wp-cleanup`: reap what is provably merged, and ASK about everything that merely looks dead. */\n cleanup(): Promise<void> {\n this.assertNoResolveInProgress('wp-cleanup');\n return this.cleanupCommand.run();\n }\n\n /**\n * `wp-checkout-clean-main`: go to main, fast-forward it, reap dead worktrees and branches, and sweep\n * the orphan directories a project move leaves behind. Replaces `git checkout main && git pull\n * origin main` outright — see CheckoutCleanMainCommand for why the old pair must stop being accepted\n * rather than surviving beside this.\n */\n checkoutCleanMain(): Promise<void> {\n this.assertNoResolveInProgress('wp-checkout-clean-main');\n return this.checkoutCleanMainCommand.run();\n }\n\n /** `wp-land-pr`: squash-merge this branch's PR into main with the compact commit body. */\n landPr(): Promise<void> {\n this.assertNoResolveInProgress('wp-land-pr');\n return this.landPrCommand.run();\n }\n\n /**\n * `wp-review-upsert-pr`: STAGE ② — validate the 3-point merge, run the build gate, extract this\n * branch's diff, and brief the reviewer subagents. Unlike the report-only command it replaces, this CAN\n * fail — before any reviewer is spawned, so a broken branch costs no reviewer tokens.\n */\n reviewUpsertPr(opts: ReviewUpsertPrOptions = new ReviewUpsertPrOptions()): Promise<void> {\n this.assertNoResolveInProgress('wp-review-upsert-pr');\n return this.reviewUpsertPrCommand.run(opts);\n }\n\n /** `wp-check-pr`: READ-ONLY CI check — verify the PR body carries a valid HMAC gate token for its head sha. */\n checkPr(): Promise<void> {\n return this.checkPrCommand.run();\n }\n}\n"]}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const REVIEW_STAGE = "review";
|
|
6
6
|
export declare const FINISH_STAGE = "finish";
|
|
7
|
+
export declare const BUILD_STAGE = "build";
|
|
7
8
|
/**
|
|
8
9
|
* Captures the build gate's full output to `.webpieces/logs/`, and renders the small pointer the AI is
|
|
9
10
|
* handed instead of a rebuild instruction.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BuildGateLog = exports.FINISH_STAGE = exports.REVIEW_STAGE = void 0;
|
|
3
|
+
exports.BuildGateLog = exports.BUILD_STAGE = exports.FINISH_STAGE = exports.REVIEW_STAGE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const fs = tslib_1.__importStar(require("fs"));
|
|
@@ -39,6 +39,9 @@ const STATUS_SUFFIX = '.status';
|
|
|
39
39
|
*/
|
|
40
40
|
exports.REVIEW_STAGE = 'review';
|
|
41
41
|
exports.FINISH_STAGE = 'finish';
|
|
42
|
+
// `wp-build`, which is not a stage of the PR flow but runs the SAME gate (BuildAffected.runBuildGate).
|
|
43
|
+
// Its own id so a developer's inner-loop build never overwrites the log stage ② or ③ is holding.
|
|
44
|
+
exports.BUILD_STAGE = 'build';
|
|
42
45
|
/**
|
|
43
46
|
* Captures the build gate's full output to `.webpieces/logs/`, and renders the small pointer the AI is
|
|
44
47
|
* handed instead of a rebuild instruction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-gate-log.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/build-gate-log.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { dotWebpieces } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// EXPERIMENTAL (opt-in via `~/.webpieces/config.json` → experimental.buildGateLogCapture). Nothing in this\n// file runs for a user who has not created that OPTIONAL file — see `HomeConfigService`\n// (`@webpieces/rules-config`), which returns all-defaults silently when it is absent.\n//\n// EVERY key in that file is optional, so the smallest document enabling THIS feature is just:\n// { \"experimental\": { \"buildGateLogCapture\": true } }\n// Optional is not a convenience: the file is MACHINE-GLOBAL and the repos on one machine pin different\n// webpieces releases, so a REQUIRED key there has no satisfiable value — omitting it fails the new\n// release, adding it fails every older one. `HomeConfigService` owns that rule.\n//\n// ─── Why ───────────────────────────────────────────────────────────────────────────────────────────────\n// The build gate already builds everything. When it fails, the pre-existing message says \"run THIS exact\n// command to reproduce\", and an AI agent obeys it — so the repo is built a SECOND time purely to see the\n// errors that scrolled past the first time. This captures the first build's output instead.\n//\n// ─── Why a shell `tee` and not spawnSync's pipes ───────────────────────────────────────────────────────\n// spawnSync BUFFERS a piped stream and hands it over only after the child exits, so the human would watch\n// a silent terminal for the length of a full build. The output has to be split at the OS level, which is\n// what `tee` is. The gate keeps `stdio: 'inherit'`, so the terminal is byte-identical to today.\n//\n// ─── Why an exit-status side file and not `pipefail` ───────────────────────────────────────────────────\n// In a pipeline the shell reports the LAST command's status, i.e. tee's, which is 0 whether the build\n// passed or failed. `set -o pipefail` and `${PIPESTATUS[0]}` are bash/zsh, and spawnSync's `shell: true`\n// gives `/bin/sh` — dash on Debian. Writing `$?` to a side file inside the group is plain POSIX and works\n// under every one of them. The redirect keeps it out of the pipe, so it never lands in the log.\nconst STATUS_SUFFIX = '.status';\n\n/**\n * Which stage's gate is being captured. The value is part of the log FILENAME, so review and finish never\n * write the same file even at the same commit on the same branch.\n */\nexport const REVIEW_STAGE = 'review';\nexport const FINISH_STAGE = 'finish';\n\n/**\n * Captures the build gate's full output to `.webpieces/logs/`, and renders the small pointer the AI is\n * handed instead of a rebuild instruction.\n *\n * ─── Why the filename cannot collide ───────────────────────────────────────────────────────────────────\n * The path is `dotWebpieces.logsFile(repoRoot, 'build-gate-<stage>-<branch>-<shortSha>.log')`, and each\n * component rules out one class of concurrent writer:\n * • the DIRECTORY is `dotWebpieces.local()`-scoped — `<primary>/.webpieces/worktrees/<git worktree\n * name>/logs/` in a linked worktree. Every worktree therefore has its own log directory already, which\n * is what makes \"N agents in N worktrees\" safe by construction rather than by naming.\n * • `<stage>` separates review from finish, the two gates that CAN run against one commit.\n * • `<branch>` — a branch can be checked out in at most one worktree (git enforces it), so within one\n * log directory the branch is effectively constant; it is in the name so a human reading the directory\n * can tell whose log is whose, and so switching branches never appends to a stale file.\n * • `<shortSha>` — a re-run at a NEW commit gets a new file, so the log a failure message points at is\n * always the build for the code that failed. A re-run at the SAME commit deliberately TRUNCATES: it is\n * the same build of the same tree, and the fresh one is the one worth reading.\n * The residual case is two agents running the same stage, at the same commit, in the SAME worktree,\n * simultaneously. That is already unsupported — both would be driving one git index and one merge state —\n * and it is the only case this scheme does not separate.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class BuildGateLog {\n /** Absolute path of the log for `stage` at the current HEAD, creating the log directory. */\n pathFor(repoRoot: string, stage: string): string {\n const file = dotWebpieces.logsFile(repoRoot, this.fileNameFor(repoRoot, stage));\n fs.mkdirSync(path.dirname(file), { recursive: true });\n return file;\n }\n\n /** The same path WITHOUT creating anything, and '' when no such log exists. Used by finish's skip path. */\n existingLogFor(repoRoot: string, stage: string): string {\n const file = dotWebpieces.logsFile(repoRoot, this.fileNameFor(repoRoot, stage));\n return fs.existsSync(file) ? file : '';\n }\n\n /** `build-gate-<stage>-<branch>-<shortSha>.log` — see the class docstring for why this cannot collide. */\n fileNameFor(repoRoot: string, stage: string): string {\n const branch = this.slug(this.git(repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD']));\n const sha = this.slug(this.git(repoRoot, ['rev-parse', '--short', 'HEAD']));\n return `build-gate-${this.slug(stage)}-${branch === '' ? 'nobranch' : branch}-${sha === '' ? 'nosha' : sha}.log`;\n }\n\n /**\n * Run `buildCommand` with its combined stdout+stderr streaming to the terminal AND appended in full to\n * `logPath`. Returns the BUILD's exit code (not tee's). Nothing is truncated.\n */\n run(repoRoot: string, buildCommand: string, logPath: string): number {\n const statusFile = `${logPath}${STATUS_SUFFIX}`;\n fs.rmSync(statusFile, { force: true });\n const result = spawnSync(this.wrap(buildCommand, logPath, statusFile),\n { stdio: 'inherit', cwd: repoRoot, shell: true });\n const fromFile = this.readStatus(statusFile);\n fs.rmSync(statusFile, { force: true });\n if (fromFile !== null) return fromFile;\n // The side file never appeared ⇒ the shell died before the echo. The PIPELINE's status is tee's,\n // which is 0, and reporting 0 there would call a dead build green — so fail CLOSED instead.\n return result.status !== null && result.status !== 0 ? result.status : 1;\n }\n\n /**\n * The ENTIRE message the AI gets on a captured failure. Deliberately tiny: the whole point is that the\n * agent reads ONE file rather than carrying a build transcript in its context.\n *\n * The last sentence is not filler. If the log holds no visible failure then something upstream is wrong\n * (a runner that died without printing, a truncated pipe), and the worst possible response is an agent\n * guessing or rebuilding — so it is told to surface the contradiction to the human and stop.\n */\n failureMessage(buildCommand: string, logPath: string): string {\n return `\\n❌ The CI build failed. We ran\\n\\n` +\n ` ${buildCommand} > ${logPath}\\n\\n` +\n `and it failed, so read that file for the failures. Do NOT re-run the build to see them.\\n` +\n `If you do not see failures in that log, report that to the user and stop.\\n`;\n }\n\n /**\n * `{ ( <build> ) ; echo $? > <status> ; } 2>&1 | tee <log>` — written across LINES so a build command\n * ending in a `#` comment cannot swallow what follows it.\n *\n * The inner `( … )` is load-bearing, not decoration. The left side of a pipeline is already a subshell,\n * so a build command containing a plain `exit 7` (or any script that calls `exit`) would terminate that\n * subshell OUTRIGHT — `echo $?` never runs, the side file never appears, and the only status left is\n * tee's 0. The nested subshell absorbs the `exit`, so `$?` is the build's code every time.\n */\n private wrap(buildCommand: string, logPath: string, statusFile: string): string {\n return `{\\n(\\n${buildCommand}\\n)\\necho $? > ${this.quote(statusFile)}\\n} 2>&1 | tee ${this.quote(logPath)}`;\n }\n\n // The build's own exit code, or null when the side file never appeared (the shell died before the echo).\n private readStatus(statusFile: string): number | null {\n if (!fs.existsSync(statusFile)) return null;\n const parsed = Number.parseInt(fs.readFileSync(statusFile, 'utf8').trim(), 10);\n return Number.isNaN(parsed) ? null : parsed;\n }\n\n // POSIX single-quoting: everything is literal inside '…', and a literal ' is spelled '\\''.\n private quote(value: string): string {\n return `'${value.split(\"'\").join(`'\\\\''`)}'`;\n }\n\n // Anything that is not a filename-safe character becomes '-', so `dean/feat` cannot create directories.\n private slug(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');\n }\n\n // One local git read. Fails SOFT to '' — a missing branch/sha degrades the FILENAME, and degrading a\n // filename may never be the reason a build gate does not run.\n private git(repoRoot: string, args: string[]): string {\n const result = spawnSync('git', ['-C', repoRoot, ...args], { encoding: 'utf8' });\n if (result.status !== 0) return '';\n return (result.stdout ?? '').trim();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"build-gate-log.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/build-gate-log.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { dotWebpieces } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\n// EXPERIMENTAL (opt-in via `~/.webpieces/config.json` → experimental.buildGateLogCapture). Nothing in this\n// file runs for a user who has not created that OPTIONAL file — see `HomeConfigService`\n// (`@webpieces/rules-config`), which returns all-defaults silently when it is absent.\n//\n// EVERY key in that file is optional, so the smallest document enabling THIS feature is just:\n// { \"experimental\": { \"buildGateLogCapture\": true } }\n// Optional is not a convenience: the file is MACHINE-GLOBAL and the repos on one machine pin different\n// webpieces releases, so a REQUIRED key there has no satisfiable value — omitting it fails the new\n// release, adding it fails every older one. `HomeConfigService` owns that rule.\n//\n// ─── Why ───────────────────────────────────────────────────────────────────────────────────────────────\n// The build gate already builds everything. When it fails, the pre-existing message says \"run THIS exact\n// command to reproduce\", and an AI agent obeys it — so the repo is built a SECOND time purely to see the\n// errors that scrolled past the first time. This captures the first build's output instead.\n//\n// ─── Why a shell `tee` and not spawnSync's pipes ───────────────────────────────────────────────────────\n// spawnSync BUFFERS a piped stream and hands it over only after the child exits, so the human would watch\n// a silent terminal for the length of a full build. The output has to be split at the OS level, which is\n// what `tee` is. The gate keeps `stdio: 'inherit'`, so the terminal is byte-identical to today.\n//\n// ─── Why an exit-status side file and not `pipefail` ───────────────────────────────────────────────────\n// In a pipeline the shell reports the LAST command's status, i.e. tee's, which is 0 whether the build\n// passed or failed. `set -o pipefail` and `${PIPESTATUS[0]}` are bash/zsh, and spawnSync's `shell: true`\n// gives `/bin/sh` — dash on Debian. Writing `$?` to a side file inside the group is plain POSIX and works\n// under every one of them. The redirect keeps it out of the pipe, so it never lands in the log.\nconst STATUS_SUFFIX = '.status';\n\n/**\n * Which stage's gate is being captured. The value is part of the log FILENAME, so review and finish never\n * write the same file even at the same commit on the same branch.\n */\nexport const REVIEW_STAGE = 'review';\nexport const FINISH_STAGE = 'finish';\n// `wp-build`, which is not a stage of the PR flow but runs the SAME gate (BuildAffected.runBuildGate).\n// Its own id so a developer's inner-loop build never overwrites the log stage ② or ③ is holding.\nexport const BUILD_STAGE = 'build';\n\n/**\n * Captures the build gate's full output to `.webpieces/logs/`, and renders the small pointer the AI is\n * handed instead of a rebuild instruction.\n *\n * ─── Why the filename cannot collide ───────────────────────────────────────────────────────────────────\n * The path is `dotWebpieces.logsFile(repoRoot, 'build-gate-<stage>-<branch>-<shortSha>.log')`, and each\n * component rules out one class of concurrent writer:\n * • the DIRECTORY is `dotWebpieces.local()`-scoped — `<primary>/.webpieces/worktrees/<git worktree\n * name>/logs/` in a linked worktree. Every worktree therefore has its own log directory already, which\n * is what makes \"N agents in N worktrees\" safe by construction rather than by naming.\n * • `<stage>` separates review from finish, the two gates that CAN run against one commit.\n * • `<branch>` — a branch can be checked out in at most one worktree (git enforces it), so within one\n * log directory the branch is effectively constant; it is in the name so a human reading the directory\n * can tell whose log is whose, and so switching branches never appends to a stale file.\n * • `<shortSha>` — a re-run at a NEW commit gets a new file, so the log a failure message points at is\n * always the build for the code that failed. A re-run at the SAME commit deliberately TRUNCATES: it is\n * the same build of the same tree, and the fresh one is the one worth reading.\n * The residual case is two agents running the same stage, at the same commit, in the SAME worktree,\n * simultaneously. That is already unsupported — both would be driving one git index and one merge state —\n * and it is the only case this scheme does not separate.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class BuildGateLog {\n /** Absolute path of the log for `stage` at the current HEAD, creating the log directory. */\n pathFor(repoRoot: string, stage: string): string {\n const file = dotWebpieces.logsFile(repoRoot, this.fileNameFor(repoRoot, stage));\n fs.mkdirSync(path.dirname(file), { recursive: true });\n return file;\n }\n\n /** The same path WITHOUT creating anything, and '' when no such log exists. Used by finish's skip path. */\n existingLogFor(repoRoot: string, stage: string): string {\n const file = dotWebpieces.logsFile(repoRoot, this.fileNameFor(repoRoot, stage));\n return fs.existsSync(file) ? file : '';\n }\n\n /** `build-gate-<stage>-<branch>-<shortSha>.log` — see the class docstring for why this cannot collide. */\n fileNameFor(repoRoot: string, stage: string): string {\n const branch = this.slug(this.git(repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD']));\n const sha = this.slug(this.git(repoRoot, ['rev-parse', '--short', 'HEAD']));\n return `build-gate-${this.slug(stage)}-${branch === '' ? 'nobranch' : branch}-${sha === '' ? 'nosha' : sha}.log`;\n }\n\n /**\n * Run `buildCommand` with its combined stdout+stderr streaming to the terminal AND appended in full to\n * `logPath`. Returns the BUILD's exit code (not tee's). Nothing is truncated.\n */\n run(repoRoot: string, buildCommand: string, logPath: string): number {\n const statusFile = `${logPath}${STATUS_SUFFIX}`;\n fs.rmSync(statusFile, { force: true });\n const result = spawnSync(this.wrap(buildCommand, logPath, statusFile),\n { stdio: 'inherit', cwd: repoRoot, shell: true });\n const fromFile = this.readStatus(statusFile);\n fs.rmSync(statusFile, { force: true });\n if (fromFile !== null) return fromFile;\n // The side file never appeared ⇒ the shell died before the echo. The PIPELINE's status is tee's,\n // which is 0, and reporting 0 there would call a dead build green — so fail CLOSED instead.\n return result.status !== null && result.status !== 0 ? result.status : 1;\n }\n\n /**\n * The ENTIRE message the AI gets on a captured failure. Deliberately tiny: the whole point is that the\n * agent reads ONE file rather than carrying a build transcript in its context.\n *\n * The last sentence is not filler. If the log holds no visible failure then something upstream is wrong\n * (a runner that died without printing, a truncated pipe), and the worst possible response is an agent\n * guessing or rebuilding — so it is told to surface the contradiction to the human and stop.\n */\n failureMessage(buildCommand: string, logPath: string): string {\n return `\\n❌ The CI build failed. We ran\\n\\n` +\n ` ${buildCommand} > ${logPath}\\n\\n` +\n `and it failed, so read that file for the failures. Do NOT re-run the build to see them.\\n` +\n `If you do not see failures in that log, report that to the user and stop.\\n`;\n }\n\n /**\n * `{ ( <build> ) ; echo $? > <status> ; } 2>&1 | tee <log>` — written across LINES so a build command\n * ending in a `#` comment cannot swallow what follows it.\n *\n * The inner `( … )` is load-bearing, not decoration. The left side of a pipeline is already a subshell,\n * so a build command containing a plain `exit 7` (or any script that calls `exit`) would terminate that\n * subshell OUTRIGHT — `echo $?` never runs, the side file never appears, and the only status left is\n * tee's 0. The nested subshell absorbs the `exit`, so `$?` is the build's code every time.\n */\n private wrap(buildCommand: string, logPath: string, statusFile: string): string {\n return `{\\n(\\n${buildCommand}\\n)\\necho $? > ${this.quote(statusFile)}\\n} 2>&1 | tee ${this.quote(logPath)}`;\n }\n\n // The build's own exit code, or null when the side file never appeared (the shell died before the echo).\n private readStatus(statusFile: string): number | null {\n if (!fs.existsSync(statusFile)) return null;\n const parsed = Number.parseInt(fs.readFileSync(statusFile, 'utf8').trim(), 10);\n return Number.isNaN(parsed) ? null : parsed;\n }\n\n // POSIX single-quoting: everything is literal inside '…', and a literal ' is spelled '\\''.\n private quote(value: string): string {\n return `'${value.split(\"'\").join(`'\\\\''`)}'`;\n }\n\n // Anything that is not a filename-safe character becomes '-', so `dean/feat` cannot create directories.\n private slug(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');\n }\n\n // One local git read. Fails SOFT to '' — a missing branch/sha degrades the FILENAME, and degrading a\n // filename may never be the reason a build gate does not run.\n private git(repoRoot: string, args: string[]): string {\n const result = spawnSync('git', ['-C', repoRoot, ...args], { encoding: 'utf8' });\n if (result.status !== 0) return '';\n return (result.stdout ?? '').trim();\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const inversify_1 = require("inversify");
|
|
6
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
7
|
+
const pr_gate_app_1 = require("./pr-gate-app");
|
|
8
|
+
// Composition root for `wp-build`: run this repo's ONE configured build command
|
|
9
|
+
// (`commands.pr-gate.buildCommand`) and nothing else. See BuildCommand for why it composes no other
|
|
10
|
+
// leg — a verify chain assembled per repo is what drifted into building the world three times over.
|
|
11
|
+
//
|
|
12
|
+
// Takes NO arguments: a flag here would be a flag the PR gate's own build never receives, so the two
|
|
13
|
+
// would stop being the same command. Narrower loops have their own commands (`nx run <project>:ci`,
|
|
14
|
+
// `vitest run <path>`) and do not go through this bin.
|
|
15
|
+
(0, rules_config_1.runMain)(async () => {
|
|
16
|
+
const container = new inversify_1.Container({ autobind: true });
|
|
17
|
+
container.get(rules_config_1.CliArgs).assertNoArgs(new rules_config_1.CliUsage('wp-build', 'Run this repo`s configured build (commands.pr-gate.buildCommand) — the same command the PR gate runs.'));
|
|
18
|
+
await container.get(pr_gate_app_1.PrGateApp).build();
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=wp-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wp-build.js","sourceRoot":"","sources":["../../../../../../packages/tooling/pr-gate/src/scripts/wp-build.ts"],"names":[],"mappings":";;;AACA,4BAA0B;AAC1B,yCAAsC;AACtC,0DAAqE;AACrE,+CAA0C;AAE1C,gFAAgF;AAChF,oGAAoG;AACpG,oGAAoG;AACpG,EAAE;AACF,qGAAqG;AACrG,oGAAoG;AACpG,uDAAuD;AACvD,IAAA,sBAAO,EAAC,KAAK,IAAmB,EAAE;IAC9B,MAAM,SAAS,GAAG,IAAI,qBAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,SAAS,CAAC,GAAG,CAAC,sBAAO,CAAC,CAAC,YAAY,CAAC,IAAI,uBAAQ,CAAC,UAAU,EACvD,uGAAuG,CAAC,CAAC,CAAC;IAC9G,MAAM,SAAS,CAAC,GAAG,CAAC,uBAAS,CAAC,CAAC,KAAK,EAAE,CAAC;AAC3C,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport 'reflect-metadata';\nimport { Container } from 'inversify';\nimport { runMain, CliArgs, CliUsage } from '@webpieces/rules-config';\nimport { PrGateApp } from './pr-gate-app';\n\n// Composition root for `wp-build`: run this repo's ONE configured build command\n// (`commands.pr-gate.buildCommand`) and nothing else. See BuildCommand for why it composes no other\n// leg — a verify chain assembled per repo is what drifted into building the world three times over.\n//\n// Takes NO arguments: a flag here would be a flag the PR gate's own build never receives, so the two\n// would stop being the same command. Narrower loops have their own commands (`nx run <project>:ci`,\n// `vitest run <path>`) and do not go through this bin.\nrunMain(async (): Promise<void> => {\n const container = new Container({ autobind: true });\n container.get(CliArgs).assertNoArgs(new CliUsage('wp-build',\n 'Run this repo`s configured build (commands.pr-gate.buildCommand) — the same command the PR gate runs.'));\n await container.get(PrGateApp).build();\n});\n"]}
|