@webpieces/pr-gate 0.4.566 → 0.4.567
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 +2 -2
- package/src/scripts/commands/push-dev-command.d.ts +3 -1
- package/src/scripts/commands/push-dev-command.js +10 -3
- package/src/scripts/commands/push-dev-command.js.map +1 -1
- package/src/scripts/workflow/dev-deploy-refs.d.ts +8 -0
- package/src/scripts/workflow/dev-deploy-refs.js +10 -0
- package/src/scripts/workflow/dev-deploy-refs.js.map +1 -1
- package/src/scripts/workflow/dev-deploy-watch-hints.d.ts +35 -0
- package/src/scripts/workflow/dev-deploy-watch-hints.js +76 -0
- package/src/scripts/workflow/dev-deploy-watch-hints.js.map +1 -0
- package/src/scripts/workflow/dev-resolve-runner.d.ts +3 -1
- package/src/scripts/workflow/dev-resolve-runner.js +11 -3
- package/src/scripts/workflow/dev-resolve-runner.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.567",
|
|
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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"directory": "packages/tooling/pr-gate"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@webpieces/rules-config": "0.4.
|
|
31
|
+
"@webpieces/rules-config": "0.4.567",
|
|
32
32
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
33
33
|
"inversify": "7.10.4",
|
|
34
34
|
"reflect-metadata": "0.2.2"
|
|
@@ -3,6 +3,7 @@ import { GitExec } from '../workflow/git-exec';
|
|
|
3
3
|
import { DevDeployRefs } from '../workflow/dev-deploy-refs';
|
|
4
4
|
import { PushDevStateStore } from '../workflow/push-dev-state';
|
|
5
5
|
import { DevResolveRunner } from '../workflow/dev-resolve-runner';
|
|
6
|
+
import { DevDeployWatchHints } from '../workflow/dev-deploy-watch-hints';
|
|
6
7
|
/**
|
|
7
8
|
* Which of `wp-push-dev`'s modes was asked for. Data-only (per CLAUDE.md) with plain assigned fields
|
|
8
9
|
* rather than a seven-argument constructor — the bin sets what argv carried and nothing else.
|
|
@@ -45,7 +46,8 @@ export declare class PushDevCommand {
|
|
|
45
46
|
private readonly refs;
|
|
46
47
|
private readonly store;
|
|
47
48
|
private readonly runner;
|
|
48
|
-
|
|
49
|
+
private readonly watchHints;
|
|
50
|
+
constructor(repoRootFinder: RepoRootFinder, gitExec: GitExec, refs: DevDeployRefs, store: PushDevStateStore, runner: DevResolveRunner, watchHints: DevDeployWatchHints);
|
|
49
51
|
run(opts: PushDevOptions): Promise<void>;
|
|
50
52
|
/** `--list`: exactly the live copies, straight from the remote — never a cached local view. */
|
|
51
53
|
private list;
|
|
@@ -8,6 +8,7 @@ const git_exec_1 = require("../workflow/git-exec");
|
|
|
8
8
|
const dev_deploy_refs_1 = require("../workflow/dev-deploy-refs");
|
|
9
9
|
const push_dev_state_1 = require("../workflow/push-dev-state");
|
|
10
10
|
const dev_resolve_runner_1 = require("../workflow/dev-resolve-runner");
|
|
11
|
+
const dev_deploy_watch_hints_1 = require("../workflow/dev-deploy-watch-hints");
|
|
11
12
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
12
13
|
/**
|
|
13
14
|
* Which of `wp-push-dev`'s modes was asked for. Data-only (per CLAUDE.md) with plain assigned fields
|
|
@@ -52,12 +53,14 @@ let PushDevCommand = class PushDevCommand {
|
|
|
52
53
|
refs;
|
|
53
54
|
store;
|
|
54
55
|
runner;
|
|
55
|
-
|
|
56
|
+
watchHints;
|
|
57
|
+
constructor(repoRootFinder, gitExec, refs, store, runner, watchHints) {
|
|
56
58
|
this.repoRootFinder = repoRootFinder;
|
|
57
59
|
this.gitExec = gitExec;
|
|
58
60
|
this.refs = refs;
|
|
59
61
|
this.store = store;
|
|
60
62
|
this.runner = runner;
|
|
63
|
+
this.watchHints = watchHints;
|
|
61
64
|
}
|
|
62
65
|
async run(opts) {
|
|
63
66
|
const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());
|
|
@@ -138,11 +141,14 @@ let PushDevCommand = class PushDevCommand {
|
|
|
138
141
|
}
|
|
139
142
|
this.gitExec.runGitChecked(['-C', repoRoot, 'push', ...this.pushSafety(targetRef, remoteSha, opts.force),
|
|
140
143
|
'origin', `HEAD:refs/heads/${targetRef}`], `Failed to publish the dev copy ${targetRef}`);
|
|
144
|
+
// Read AFTER the push, so the sha in the watch hints is provably the one that is now published.
|
|
145
|
+
const published = this.refs.headSha(repoRoot);
|
|
141
146
|
process.stdout.write('\n' + SEP + `✅ Published ${targetRef}\n` + SEP + '\n'
|
|
142
147
|
+ `\`${branch}\` is now in the pool your CI composes \`${cfg.devBranch}\` from — it will be picked up on\n`
|
|
143
148
|
+ 'the next composition run. No PR was opened and nothing landed on main.\n\n'
|
|
144
149
|
+ ` ${rules_config_1.WP_PUSH_DEV} --list ← what else is in the pool\n`
|
|
145
|
-
+ ` ${rules_config_1.WP_PUSH_DEV} --remove ← take this branch back out\n`
|
|
150
|
+
+ ` ${rules_config_1.WP_PUSH_DEV} --remove ← take this branch back out\n`
|
|
151
|
+
+ this.watchHints.render(cfg, targetRef, published));
|
|
146
152
|
}
|
|
147
153
|
/**
|
|
148
154
|
* `--rebase-resolution`: keep the published resolution, replay it on top of the new commits.
|
|
@@ -244,6 +250,7 @@ exports.PushDevCommand = PushDevCommand = tslib_1.__decorate([
|
|
|
244
250
|
git_exec_1.GitExec,
|
|
245
251
|
dev_deploy_refs_1.DevDeployRefs,
|
|
246
252
|
push_dev_state_1.PushDevStateStore,
|
|
247
|
-
dev_resolve_runner_1.DevResolveRunner
|
|
253
|
+
dev_resolve_runner_1.DevResolveRunner,
|
|
254
|
+
dev_deploy_watch_hints_1.DevDeployWatchHints])
|
|
248
255
|
], PushDevCommand);
|
|
249
256
|
//# sourceMappingURL=push-dev-command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-dev-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/push-dev-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CliExitError,\n DevDeployConfig,\n RepoRootFinder,\n WP_FINISH_PUSH_DEV,\n WP_PUSH_DEV,\n writeTemplate,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from '../workflow/git-exec';\nimport { DevCopy, DevDeployRefs } from '../workflow/dev-deploy-refs';\nimport { PushDevState, PushDevStateStore } from '../workflow/push-dev-state';\nimport { DevResolveRunner } from '../workflow/dev-resolve-runner';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * Which of `wp-push-dev`'s modes was asked for. Data-only (per CLAUDE.md) with plain assigned fields\n * rather than a seven-argument constructor — the bin sets what argv carried and nothing else.\n */\nexport class PushDevOptions {\n /** Delete the remote copy — the rollback. */\n remove = false;\n /** List the live copies and stop. The one mode that needs no branch preconditions. */\n list = false;\n /** Discard a conflict resolution already published on the copy, and overwrite it. */\n force = false;\n /** Replay that resolution on top of the new local commits instead of discarding it. */\n rebaseResolution = false;\n /** Compose the other copies onto this one so the shared environment can build them together. */\n resolve = false;\n /** With `--resolve`: merge ONLY this developer's branch. Empty ⇒ every other copy. */\n resolveTarget = '';\n}\n\n/**\n * `wp-push-dev` — publish a DISPOSABLE copy of this branch so a shared dev environment can build it,\n * without landing anything on `main` and without opening a PR.\n *\n * WHY THIS COMMAND HAS TO EXIST AT ALL: `pr-creation-or-push-guard` blocks every `git push`, no\n * exceptions, by design. Its own fix-hint offers \"ask a human to run the push\" for out-of-band cases —\n * which is the right answer for a genuine exception and the wrong one for a routine daily action. Without\n * a gated command there is simply no legal way for an AI to get a branch onto a dev server, so the\n * feature was undoable rather than merely awkward.\n *\n * WHY THERE IS NO BUILD, deliberately unlike `wp-start-upsert-pr`: the shared environment's CI builds the\n * COMPOSED tree (main + every listed branch). A local build here proves the branch compiles against\n * `main` alone, which is not the question — it would be both slower and reassuring about the wrong thing.\n * The gate that matters runs on the composition, in CI, where it belongs.\n *\n * WHAT IT NEVER DOES: touch the feature branch. See DevDeployRefs for the invariant and why the copy\n * exists to hold conflict resolutions that must not reach the PR.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class PushDevCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly gitExec: GitExec,\n private readonly refs: DevDeployRefs,\n private readonly store: PushDevStateStore,\n private readonly runner: DevResolveRunner,\n ) {}\n\n async run(opts: PushDevOptions): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it is present + current for any failure message to cite —\n // same reason every other wp-* command opens this way.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n const cfg = this.refs.config(repoRoot);\n\n // A resolve already in flight owns the checkout; starting a second one would strand the first.\n this.assertNoResolveInProgress(repoRoot);\n\n if (opts.list) {\n this.list(repoRoot, cfg);\n return Promise.resolve();\n }\n\n const branch = this.refs.currentBranch(repoRoot);\n this.refs.assertPushable(cfg, branch);\n const targetRef = cfg.copyRefFor(branch);\n\n if (opts.remove) {\n this.remove(repoRoot, cfg, branch, targetRef);\n return Promise.resolve();\n }\n // Every remaining mode commits or merges, so the tree must be the AI's committed work and nothing\n // else. The tooling never commits for you.\n this.gitExec.assertCleanTree(repoRoot);\n\n if (opts.resolve) {\n this.startResolve(repoRoot, cfg, branch, targetRef, opts.resolveTarget);\n return Promise.resolve();\n }\n this.publish(repoRoot, cfg, branch, targetRef, opts);\n return Promise.resolve();\n }\n\n /** `--list`: exactly the live copies, straight from the remote — never a cached local view. */\n private list(repoRoot: string, cfg: DevDeployConfig): void {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (copies.length === 0) {\n process.stdout.write(\n `\\nNo dev copies published — nothing matches \\`${cfg.copyRefGlob()}\\` on origin.\\n`\n + `Publish this branch's with: ${WP_PUSH_DEV}\\n`);\n return;\n }\n let out = '\\n' + SEP + `📋 ${String(copies.length)} dev cop${copies.length === 1 ? 'y' : 'ies'} on origin\\n` + SEP + '\\n';\n for (const copy of copies) out += ` ${copy.sha.slice(0, 8)} ${copy.ref}\\n`;\n out += `\\nYour CI composes \\`${cfg.devBranch}\\` from origin/main plus every ref above, on every run.\\n`\n + `Remove yours with: ${WP_PUSH_DEV} --remove\\n`;\n process.stdout.write(out);\n }\n\n /** `--remove`: delete the remote copy. This IS the rollback — nothing else has to be undone. */\n private remove(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n process.stdout.write(\n `\\nNothing to remove — \\`${targetRef}\\` is not published.\\n`\n + `List what IS published with: ${WP_PUSH_DEV} --list\\n`);\n return;\n }\n this.gitExec.runGitChecked(['-C', repoRoot, 'push', 'origin', '--delete', `refs/heads/${targetRef}`],\n `Failed to delete the dev copy ${targetRef}`);\n process.stdout.write(\n '\\n' + SEP + `🗑️ Removed ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` will drop out of \\`${cfg.devBranch}\\` on its next composition run. Your branch and any\\n`\n + 'PR on it are untouched.\\n');\n }\n\n /**\n * The ordinary path: publish HEAD as `<namespace>/<branch>`.\n *\n * THE CLOBBER GUARD is the whole of the interesting logic here. If the remote copy holds commits the\n * local branch does not, somebody (quite possibly this developer, days ago) resolved a real conflict\n * over there. A silent force-push destroys work that cost a human a genuine merge resolution, so the\n * default is refusal with two named ways forward — never a \"use --force\" one-liner, which is how a\n * warning becomes a reflex.\n */\n private publish(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, opts: PushDevOptions): void {\n const remoteSha = this.refs.remoteSha(repoRoot, targetRef);\n if (remoteSha !== '') {\n this.refs.fetchCopies(repoRoot, [targetRef]);\n const ahead = this.refs.commitsOnlyOnRemote(repoRoot, targetRef);\n if (ahead > 0 && opts.rebaseResolution) {\n this.rebaseResolution(repoRoot, branch, targetRef);\n return;\n }\n if (ahead > 0 && !opts.force) this.refuseClobber(cfg, branch, targetRef, ahead);\n }\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'push', ...this.pushSafety(targetRef, remoteSha, opts.force),\n 'origin', `HEAD:refs/heads/${targetRef}`],\n `Failed to publish the dev copy ${targetRef}`);\n process.stdout.write(\n '\\n' + SEP + `✅ Published ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` is now in the pool your CI composes \\`${cfg.devBranch}\\` from — it will be picked up on\\n`\n + 'the next composition run. No PR was opened and nothing landed on main.\\n\\n'\n + ` ${WP_PUSH_DEV} --list ← what else is in the pool\\n`\n + ` ${WP_PUSH_DEV} --remove ← take this branch back out\\n`);\n }\n\n /**\n * `--rebase-resolution`: keep the published resolution, replay it on top of the new commits.\n *\n * Implemented as a resolve whose BASE is the local HEAD and whose queue is the copy itself, so a\n * replay that conflicts again lands in exactly the same halt/`wp-finish-push-dev` state machine as\n * any other conflict, rather than inventing a second way to be stuck.\n */\n private rebaseResolution(repoRoot: string, branch: string, targetRef: string): void {\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, [targetRef]);\n process.stdout.write(\n '\\n' + SEP + `♻️ Replaying the published resolution onto \\`${branch}\\`\\n` + SEP + '\\n'\n + `Merging \\`${targetRef}\\` (which holds the resolution) into your new commits on a throwaway branch.\\n`\n + `Your feature branch \\`${branch}\\` is not touched.\\n\\n`);\n this.runner.start(repoRoot, state, 'HEAD', [targetRef]);\n }\n\n /**\n * `--resolve`: compose the other copies onto this one, so what is published is what CI will build.\n *\n * With no argument the queue is EVERY other copy, in the same sorted order CI composes them — the\n * local resolution is then a rehearsal of the real composition, not a guess at it. With an argument\n * (the branch a CI conflict message names) it is just that one.\n */\n private startResolve(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, only: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` is not published yet\\n` + SEP + '\\n'\n + 'There is nothing to compose against. Publish your copy first, then resolve:\\n'\n + ` ${WP_PUSH_DEV}\\n ${WP_PUSH_DEV} --resolve\\n`);\n }\n const queue = this.resolveQueue(repoRoot, cfg, targetRef, only);\n if (queue.length === 0) {\n process.stdout.write(\n `\\nNothing to compose — \\`${targetRef}\\` is the only copy in \\`${cfg.copyRefGlob()}\\`.\\n`\n + `Your CI will merge it onto origin/main by itself; there is no other branch to conflict with.\\n`);\n return;\n }\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, queue);\n process.stdout.write(this.runner.announce(cfg, state));\n this.runner.start(repoRoot, state, `refs/remotes/origin/${targetRef}`, [targetRef, ...queue]);\n }\n\n // The refs to merge: one named copy, or every copy that is not mine. A named branch is accepted with\n // or without the namespace prefix, because the CI message that names it may print either.\n private resolveQueue(repoRoot: string, cfg: DevDeployConfig, targetRef: string, only: string): string[] {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (only === '') {\n return copies.filter((c: DevCopy): boolean => c.ref !== targetRef).map((c: DevCopy): string => c.ref);\n }\n const wanted = only.startsWith(`${cfg.branchNamespace}/`) ? only : cfg.copyRefFor(only);\n if (wanted === targetRef) {\n throw new CliExitError(2, `\\n⛔ \\`${only}\\` is your own copy — there is nothing to compose it with.\\n`);\n }\n if (!copies.some((c: DevCopy): boolean => c.ref === wanted)) {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ No published copy \\`${wanted}\\`\\n` + SEP + '\\n'\n + `Live copies (${WP_PUSH_DEV} --list):\\n`\n + copies.map((c: DevCopy): string => ` ${c.ref}`).join('\\n') + '\\n');\n }\n return [wanted];\n }\n\n /**\n * `--force-with-lease` pinned to the sha we just inspected, so the clobber guard's verdict cannot go\n * stale between the check and the push. Plain `--force` only when the caller explicitly asked to\n * discard, and no force at all for a brand-new copy.\n */\n private pushSafety(targetRef: string, remoteSha: string, force: boolean): string[] {\n if (remoteSha === '') return [];\n if (force) return ['--force'];\n return [`--force-with-lease=refs/heads/${targetRef}:${remoteSha}`];\n }\n\n private refuseClobber(cfg: DevDeployConfig, branch: string, targetRef: string, ahead: number): never {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` holds ${String(ahead)} commit(s) your branch does not\\n` + SEP + '\\n'\n + 'That is what a CONFLICT RESOLUTION looks like: somebody composed this copy against another\\n'\n + `developer's branch so \\`${cfg.devBranch}\\` could build, and that work exists ONLY on the copy —\\n`\n + 'by design, because it must never reach your feature branch or your PR.\\n\\n'\n + 'Publishing now would force-push over it and destroy it, so this command refuses. Pick one:\\n\\n'\n + ` ${WP_PUSH_DEV} --rebase-resolution\\n`\n + ' Replay that resolution on top of your new commits. Keeps the work; may ask you to\\n'\n + ' re-resolve the parts your new commits actually changed.\\n\\n'\n + ` ${WP_PUSH_DEV} --force\\n`\n + ' Discard it and publish your branch as-is. The composition breaks again until someone\\n'\n + ` re-resolves it with \\`${WP_PUSH_DEV} --resolve\\`.\\n\\n`\n + `See what is on the copy: git log --oneline ${branch}..refs/remotes/origin/${targetRef}\\n`);\n }\n\n // A half-finished resolve owns the checkout — it is parked on a throwaway branch, so \"the current\n // branch\" is not the feature branch this command would otherwise act on.\n private assertNoResolveInProgress(repoRoot: string): void {\n const state = this.store.read(repoRoot);\n if (state === null) return;\n throw new CliExitError(2,\n '\\n' + SEP + '⛔ A dev-deploy resolve is already in progress\\n' + SEP + '\\n'\n + `You are on the throwaway branch \\`${state.tmpBranch}\\`, mid-composition. Finish it or bail out:\\n`\n + ` ${WP_FINISH_PUSH_DEV} ← commit the resolution, resume the queue, publish\\n`\n + ` ${WP_FINISH_PUSH_DEV} --abort ← throw it away and go back to \\`${state.originalBranch}\\`\\n`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"push-dev-command.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/commands/push-dev-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CliExitError,\n DevDeployConfig,\n RepoRootFinder,\n WP_FINISH_PUSH_DEV,\n WP_PUSH_DEV,\n writeTemplate,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from '../workflow/git-exec';\nimport { DevCopy, DevDeployRefs } from '../workflow/dev-deploy-refs';\nimport { PushDevState, PushDevStateStore } from '../workflow/push-dev-state';\nimport { DevResolveRunner } from '../workflow/dev-resolve-runner';\nimport { DevDeployWatchHints } from '../workflow/dev-deploy-watch-hints';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * Which of `wp-push-dev`'s modes was asked for. Data-only (per CLAUDE.md) with plain assigned fields\n * rather than a seven-argument constructor — the bin sets what argv carried and nothing else.\n */\nexport class PushDevOptions {\n /** Delete the remote copy — the rollback. */\n remove = false;\n /** List the live copies and stop. The one mode that needs no branch preconditions. */\n list = false;\n /** Discard a conflict resolution already published on the copy, and overwrite it. */\n force = false;\n /** Replay that resolution on top of the new local commits instead of discarding it. */\n rebaseResolution = false;\n /** Compose the other copies onto this one so the shared environment can build them together. */\n resolve = false;\n /** With `--resolve`: merge ONLY this developer's branch. Empty ⇒ every other copy. */\n resolveTarget = '';\n}\n\n/**\n * `wp-push-dev` — publish a DISPOSABLE copy of this branch so a shared dev environment can build it,\n * without landing anything on `main` and without opening a PR.\n *\n * WHY THIS COMMAND HAS TO EXIST AT ALL: `pr-creation-or-push-guard` blocks every `git push`, no\n * exceptions, by design. Its own fix-hint offers \"ask a human to run the push\" for out-of-band cases —\n * which is the right answer for a genuine exception and the wrong one for a routine daily action. Without\n * a gated command there is simply no legal way for an AI to get a branch onto a dev server, so the\n * feature was undoable rather than merely awkward.\n *\n * WHY THERE IS NO BUILD, deliberately unlike `wp-start-upsert-pr`: the shared environment's CI builds the\n * COMPOSED tree (main + every listed branch). A local build here proves the branch compiles against\n * `main` alone, which is not the question — it would be both slower and reassuring about the wrong thing.\n * The gate that matters runs on the composition, in CI, where it belongs.\n *\n * WHAT IT NEVER DOES: touch the feature branch. See DevDeployRefs for the invariant and why the copy\n * exists to hold conflict resolutions that must not reach the PR.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class PushDevCommand {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly gitExec: GitExec,\n private readonly refs: DevDeployRefs,\n private readonly store: PushDevStateStore,\n private readonly runner: DevResolveRunner,\n private readonly watchHints: DevDeployWatchHints,\n ) {}\n\n async run(opts: PushDevOptions): Promise<void> {\n const repoRoot = this.repoRootFinder.resolveRepoRoot(process.cwd());\n // Refresh the AI-facing workflow doc so it is present + current for any failure message to cite —\n // same reason every other wp-* command opens this way.\n writeTemplate(repoRoot, 'webpieces.git-workflow.md');\n const cfg = this.refs.config(repoRoot);\n\n // A resolve already in flight owns the checkout; starting a second one would strand the first.\n this.assertNoResolveInProgress(repoRoot);\n\n if (opts.list) {\n this.list(repoRoot, cfg);\n return Promise.resolve();\n }\n\n const branch = this.refs.currentBranch(repoRoot);\n this.refs.assertPushable(cfg, branch);\n const targetRef = cfg.copyRefFor(branch);\n\n if (opts.remove) {\n this.remove(repoRoot, cfg, branch, targetRef);\n return Promise.resolve();\n }\n // Every remaining mode commits or merges, so the tree must be the AI's committed work and nothing\n // else. The tooling never commits for you.\n this.gitExec.assertCleanTree(repoRoot);\n\n if (opts.resolve) {\n this.startResolve(repoRoot, cfg, branch, targetRef, opts.resolveTarget);\n return Promise.resolve();\n }\n this.publish(repoRoot, cfg, branch, targetRef, opts);\n return Promise.resolve();\n }\n\n /** `--list`: exactly the live copies, straight from the remote — never a cached local view. */\n private list(repoRoot: string, cfg: DevDeployConfig): void {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (copies.length === 0) {\n process.stdout.write(\n `\\nNo dev copies published — nothing matches \\`${cfg.copyRefGlob()}\\` on origin.\\n`\n + `Publish this branch's with: ${WP_PUSH_DEV}\\n`);\n return;\n }\n let out = '\\n' + SEP + `📋 ${String(copies.length)} dev cop${copies.length === 1 ? 'y' : 'ies'} on origin\\n` + SEP + '\\n';\n for (const copy of copies) out += ` ${copy.sha.slice(0, 8)} ${copy.ref}\\n`;\n out += `\\nYour CI composes \\`${cfg.devBranch}\\` from origin/main plus every ref above, on every run.\\n`\n + `Remove yours with: ${WP_PUSH_DEV} --remove\\n`;\n process.stdout.write(out);\n }\n\n /** `--remove`: delete the remote copy. This IS the rollback — nothing else has to be undone. */\n private remove(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n process.stdout.write(\n `\\nNothing to remove — \\`${targetRef}\\` is not published.\\n`\n + `List what IS published with: ${WP_PUSH_DEV} --list\\n`);\n return;\n }\n this.gitExec.runGitChecked(['-C', repoRoot, 'push', 'origin', '--delete', `refs/heads/${targetRef}`],\n `Failed to delete the dev copy ${targetRef}`);\n process.stdout.write(\n '\\n' + SEP + `🗑️ Removed ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` will drop out of \\`${cfg.devBranch}\\` on its next composition run. Your branch and any\\n`\n + 'PR on it are untouched.\\n');\n }\n\n /**\n * The ordinary path: publish HEAD as `<namespace>/<branch>`.\n *\n * THE CLOBBER GUARD is the whole of the interesting logic here. If the remote copy holds commits the\n * local branch does not, somebody (quite possibly this developer, days ago) resolved a real conflict\n * over there. A silent force-push destroys work that cost a human a genuine merge resolution, so the\n * default is refusal with two named ways forward — never a \"use --force\" one-liner, which is how a\n * warning becomes a reflex.\n */\n private publish(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, opts: PushDevOptions): void {\n const remoteSha = this.refs.remoteSha(repoRoot, targetRef);\n if (remoteSha !== '') {\n this.refs.fetchCopies(repoRoot, [targetRef]);\n const ahead = this.refs.commitsOnlyOnRemote(repoRoot, targetRef);\n if (ahead > 0 && opts.rebaseResolution) {\n this.rebaseResolution(repoRoot, branch, targetRef);\n return;\n }\n if (ahead > 0 && !opts.force) this.refuseClobber(cfg, branch, targetRef, ahead);\n }\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'push', ...this.pushSafety(targetRef, remoteSha, opts.force),\n 'origin', `HEAD:refs/heads/${targetRef}`],\n `Failed to publish the dev copy ${targetRef}`);\n // Read AFTER the push, so the sha in the watch hints is provably the one that is now published.\n const published = this.refs.headSha(repoRoot);\n process.stdout.write(\n '\\n' + SEP + `✅ Published ${targetRef}\\n` + SEP + '\\n'\n + `\\`${branch}\\` is now in the pool your CI composes \\`${cfg.devBranch}\\` from — it will be picked up on\\n`\n + 'the next composition run. No PR was opened and nothing landed on main.\\n\\n'\n + ` ${WP_PUSH_DEV} --list ← what else is in the pool\\n`\n + ` ${WP_PUSH_DEV} --remove ← take this branch back out\\n`\n + this.watchHints.render(cfg, targetRef, published));\n }\n\n /**\n * `--rebase-resolution`: keep the published resolution, replay it on top of the new commits.\n *\n * Implemented as a resolve whose BASE is the local HEAD and whose queue is the copy itself, so a\n * replay that conflicts again lands in exactly the same halt/`wp-finish-push-dev` state machine as\n * any other conflict, rather than inventing a second way to be stuck.\n */\n private rebaseResolution(repoRoot: string, branch: string, targetRef: string): void {\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, [targetRef]);\n process.stdout.write(\n '\\n' + SEP + `♻️ Replaying the published resolution onto \\`${branch}\\`\\n` + SEP + '\\n'\n + `Merging \\`${targetRef}\\` (which holds the resolution) into your new commits on a throwaway branch.\\n`\n + `Your feature branch \\`${branch}\\` is not touched.\\n\\n`);\n this.runner.start(repoRoot, state, 'HEAD', [targetRef]);\n }\n\n /**\n * `--resolve`: compose the other copies onto this one, so what is published is what CI will build.\n *\n * With no argument the queue is EVERY other copy, in the same sorted order CI composes them — the\n * local resolution is then a rehearsal of the real composition, not a guess at it. With an argument\n * (the branch a CI conflict message names) it is just that one.\n */\n private startResolve(repoRoot: string, cfg: DevDeployConfig, branch: string, targetRef: string, only: string): void {\n if (this.refs.remoteSha(repoRoot, targetRef) === '') {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` is not published yet\\n` + SEP + '\\n'\n + 'There is nothing to compose against. Publish your copy first, then resolve:\\n'\n + ` ${WP_PUSH_DEV}\\n ${WP_PUSH_DEV} --resolve\\n`);\n }\n const queue = this.resolveQueue(repoRoot, cfg, targetRef, only);\n if (queue.length === 0) {\n process.stdout.write(\n `\\nNothing to compose — \\`${targetRef}\\` is the only copy in \\`${cfg.copyRefGlob()}\\`.\\n`\n + `Your CI will merge it onto origin/main by itself; there is no other branch to conflict with.\\n`);\n return;\n }\n const state = new PushDevState(branch, this.runner.tmpBranchFor(branch), targetRef, queue);\n process.stdout.write(this.runner.announce(cfg, state));\n this.runner.start(repoRoot, state, `refs/remotes/origin/${targetRef}`, [targetRef, ...queue]);\n }\n\n // The refs to merge: one named copy, or every copy that is not mine. A named branch is accepted with\n // or without the namespace prefix, because the CI message that names it may print either.\n private resolveQueue(repoRoot: string, cfg: DevDeployConfig, targetRef: string, only: string): string[] {\n const copies = this.refs.liveCopies(repoRoot, cfg);\n if (only === '') {\n return copies.filter((c: DevCopy): boolean => c.ref !== targetRef).map((c: DevCopy): string => c.ref);\n }\n const wanted = only.startsWith(`${cfg.branchNamespace}/`) ? only : cfg.copyRefFor(only);\n if (wanted === targetRef) {\n throw new CliExitError(2, `\\n⛔ \\`${only}\\` is your own copy — there is nothing to compose it with.\\n`);\n }\n if (!copies.some((c: DevCopy): boolean => c.ref === wanted)) {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ No published copy \\`${wanted}\\`\\n` + SEP + '\\n'\n + `Live copies (${WP_PUSH_DEV} --list):\\n`\n + copies.map((c: DevCopy): string => ` ${c.ref}`).join('\\n') + '\\n');\n }\n return [wanted];\n }\n\n /**\n * `--force-with-lease` pinned to the sha we just inspected, so the clobber guard's verdict cannot go\n * stale between the check and the push. Plain `--force` only when the caller explicitly asked to\n * discard, and no force at all for a brand-new copy.\n */\n private pushSafety(targetRef: string, remoteSha: string, force: boolean): string[] {\n if (remoteSha === '') return [];\n if (force) return ['--force'];\n return [`--force-with-lease=refs/heads/${targetRef}:${remoteSha}`];\n }\n\n private refuseClobber(cfg: DevDeployConfig, branch: string, targetRef: string, ahead: number): never {\n throw new CliExitError(2,\n '\\n' + SEP + `⛔ \\`${targetRef}\\` holds ${String(ahead)} commit(s) your branch does not\\n` + SEP + '\\n'\n + 'That is what a CONFLICT RESOLUTION looks like: somebody composed this copy against another\\n'\n + `developer's branch so \\`${cfg.devBranch}\\` could build, and that work exists ONLY on the copy —\\n`\n + 'by design, because it must never reach your feature branch or your PR.\\n\\n'\n + 'Publishing now would force-push over it and destroy it, so this command refuses. Pick one:\\n\\n'\n + ` ${WP_PUSH_DEV} --rebase-resolution\\n`\n + ' Replay that resolution on top of your new commits. Keeps the work; may ask you to\\n'\n + ' re-resolve the parts your new commits actually changed.\\n\\n'\n + ` ${WP_PUSH_DEV} --force\\n`\n + ' Discard it and publish your branch as-is. The composition breaks again until someone\\n'\n + ` re-resolves it with \\`${WP_PUSH_DEV} --resolve\\`.\\n\\n`\n + `See what is on the copy: git log --oneline ${branch}..refs/remotes/origin/${targetRef}\\n`);\n }\n\n // A half-finished resolve owns the checkout — it is parked on a throwaway branch, so \"the current\n // branch\" is not the feature branch this command would otherwise act on.\n private assertNoResolveInProgress(repoRoot: string): void {\n const state = this.store.read(repoRoot);\n if (state === null) return;\n throw new CliExitError(2,\n '\\n' + SEP + '⛔ A dev-deploy resolve is already in progress\\n' + SEP + '\\n'\n + `You are on the throwaway branch \\`${state.tmpBranch}\\`, mid-composition. Finish it or bail out:\\n`\n + ` ${WP_FINISH_PUSH_DEV} ← commit the resolution, resume the queue, publish\\n`\n + ` ${WP_FINISH_PUSH_DEV} --abort ← throw it away and go back to \\`${state.originalBranch}\\`\\n`);\n }\n}\n"]}
|
|
@@ -52,6 +52,14 @@ export declare class DevDeployRefs {
|
|
|
52
52
|
* result, which would make the local resolution a guess about CI rather than a rehearsal of it.
|
|
53
53
|
*/
|
|
54
54
|
liveCopies(repoRoot: string, cfg: DevDeployConfig): DevCopy[];
|
|
55
|
+
/**
|
|
56
|
+
* The sha of a local ref (default HEAD).
|
|
57
|
+
*
|
|
58
|
+
* Read AFTER a push so the watch hints quote the sha that is provably published. Quoting the copy
|
|
59
|
+
* REF instead would be a moving target — it advances the next time anybody publishes — and the whole
|
|
60
|
+
* value of the "did my code land in dev" check is that it is a claim about one specific commit.
|
|
61
|
+
*/
|
|
62
|
+
headSha(repoRoot: string, ref?: string): string;
|
|
55
63
|
/** The remote sha of one copy, or '' when that copy does not exist yet. */
|
|
56
64
|
remoteSha(repoRoot: string, ref: string): string;
|
|
57
65
|
/**
|
|
@@ -106,6 +106,16 @@ let DevDeployRefs = class DevDeployRefs {
|
|
|
106
106
|
}
|
|
107
107
|
return copies.sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The sha of a local ref (default HEAD).
|
|
111
|
+
*
|
|
112
|
+
* Read AFTER a push so the watch hints quote the sha that is provably published. Quoting the copy
|
|
113
|
+
* REF instead would be a moving target — it advances the next time anybody publishes — and the whole
|
|
114
|
+
* value of the "did my code land in dev" check is that it is a claim about one specific commit.
|
|
115
|
+
*/
|
|
116
|
+
headSha(repoRoot, ref = 'HEAD') {
|
|
117
|
+
return this.gitExec.gitQuery(['-C', repoRoot, 'rev-parse', ref], repoRoot, `Failed to read the sha of ${ref} (git rev-parse).`);
|
|
118
|
+
}
|
|
109
119
|
/** The remote sha of one copy, or '' when that copy does not exist yet. */
|
|
110
120
|
remoteSha(repoRoot, ref) {
|
|
111
121
|
const out = this.gitExec.gitQuery(['ls-remote', '--heads', 'origin', ref], repoRoot, `Failed to look up origin/${ref} (git ls-remote).`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-deploy-refs.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/dev-deploy-refs.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliExitError, DevDeployConfig, loadAndValidate, WP_PUSH_DEV } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from './git-exec';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\nconst MAIN = 'main';\nconst DETACHED = 'HEAD';\n\n/**\n * One live copy in the dev namespace: the remote ref and the sha it points at. Data-only per CLAUDE.md —\n * `--list` prints it and the clobber guard compares against `sha`, and neither should be re-parsing\n * `ls-remote` output.\n */\nexport class DevCopy {\n /** The full ref name WITHOUT `refs/heads/`, e.g. `dev-include/dean/ONE-2275`. */\n ref: string;\n sha: string;\n /** The feature branch this is a copy OF — `ref` with the namespace prefix stripped. */\n sourceBranch: string;\n\n constructor(ref: string, sha: string, sourceBranch: string) {\n this.ref = ref;\n this.sha = sha;\n this.sourceBranch = sourceBranch;\n }\n}\n\n/**\n * Ref derivation and preconditions for the dev-deploy flow — the half of `wp-push-dev` that decides WHICH\n * refs are in play, kept out of the commands so both `wp-push-dev` and `wp-finish-push-dev` answer those\n * questions identically.\n *\n * THE INVARIANT THIS FILE EXISTS TO PROTECT: the feature branch is the PR head, so it must never acquire\n * another developer's commits — landing that PR would ship their unreviewed work to production. Every\n * write this flow performs therefore targets either the `<namespace>/<feature>` copy or a local throwaway\n * branch, and NOTHING here ever moves `<feature>` itself. That is why a conflict resolution needs the copy\n * to live in: it is the only place the resolution can be recorded without contaminating the PR.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class DevDeployRefs {\n constructor(private readonly gitExec: GitExec) {}\n\n /** The `pr-gate.devDeploy` block — `dev-include` / `dev` unless the consumer overrode it. */\n config(repoRoot: string): DevDeployConfig {\n return loadAndValidate(repoRoot).prGate.devDeploy;\n }\n\n /** The checked-out branch name, or `'HEAD'` on a detached HEAD (which {@link assertPushable} refuses). */\n currentBranch(repoRoot: string): string {\n return this.gitExec.gitQuery(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot,\n 'Failed to read the current branch (git rev-parse --abbrev-ref HEAD).');\n }\n\n /**\n * Refuse every branch that must never be the SOURCE of a dev copy, before anything is fetched or\n * pushed.\n *\n * `main` and the composed dev branch are refused for the same reason from opposite ends: `main` is the\n * trunk (whatever is on it already deploys, so copying it is meaningless), and the dev branch is a\n * BUILD ARTIFACT that CI recomputes from `origin/main` on every run — copying it would publish a\n * composition of everyone's work under one developer's name, and it would be wiped on the next CI run\n * anyway. A branch already inside the namespace is refused because publishing a copy of a copy nests\n * the prefix (`dev-include/dev-include/…`) and nothing would ever clean it up.\n */\n assertPushable(cfg: DevDeployConfig, branch: string): void {\n if (branch === DETACHED || branch === '') {\n throw new CliExitError(2, this.refusal(\n 'Detached HEAD',\n 'There is no branch name to derive the dev copy from. Check out your feature branch first:\\n'\n + ' git checkout <your-feature-branch>\\n'));\n }\n if (branch === MAIN) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of \\`${MAIN}\\``,\n `\\`${MAIN}\\` is the trunk — whatever is on it already deploys, so a dev copy of it means nothing.\\n`\n + 'Check out the feature branch you actually want on the dev server and re-run.\\n'));\n }\n if (branch === cfg.devBranch) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of \\`${cfg.devBranch}\\``,\n `\\`${cfg.devBranch}\\` is a BUILD ARTIFACT, not a source branch: your CI resets it to origin/${MAIN},\\n`\n + `merges every \\`${cfg.copyRefGlob()}\\` ref into it, and force-pushes — every run. Copying it would\\n`\n + \"publish a composition of everybody's work under your branch's name, and the next CI run would\\n\"\n + 'wipe it. Check out your own feature branch and re-run.\\n'));\n }\n if (branch === cfg.branchNamespace || branch.startsWith(`${cfg.branchNamespace}/`)) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of a dev copy (\\`${branch}\\`)`,\n `\\`${cfg.branchNamespace}/\\` holds the disposable copies this command WRITES; you are standing on one.\\n`\n + `Publishing from here would nest the prefix (\\`${cfg.branchNamespace}/${branch}\\`) and nothing ever\\n`\n + 'cleans that up. Check out the real feature branch and re-run.\\n'));\n }\n }\n\n /**\n * Every live copy in the namespace, sorted by ref name.\n *\n * The sort is not cosmetic: `--resolve` with no argument queues these in exactly this order, and the\n * consumer's CI composes `dev` in the same order, so what a developer resolves locally is the\n * composition CI will actually build. A different order can produce a different (still valid) merge\n * result, which would make the local resolution a guess about CI rather than a rehearsal of it.\n */\n liveCopies(repoRoot: string, cfg: DevDeployConfig): DevCopy[] {\n const out = this.gitExec.gitQuery(['ls-remote', '--heads', 'origin', cfg.copyRefGlob()], repoRoot,\n `Failed to list the dev copies (git ls-remote --heads origin '${cfg.copyRefGlob()}').`);\n const copies: DevCopy[] = [];\n for (const line of out.split('\\n')) {\n if (line.trim() === '') continue;\n const fields = line.split(/\\s+/);\n const sha = fields[0];\n const ref = fields[1];\n if (sha === undefined || ref === undefined || !ref.startsWith('refs/heads/')) continue;\n const shortRef = ref.slice('refs/heads/'.length);\n copies.push(new DevCopy(shortRef, sha, shortRef.slice(cfg.branchNamespace.length + 1)));\n }\n return copies.sort((a: DevCopy, b: DevCopy): number => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));\n }\n\n /** The remote sha of one copy, or '' when that copy does not exist yet. */\n remoteSha(repoRoot: string, ref: string): string {\n const out = this.gitExec.gitQuery(['ls-remote', '--heads', 'origin', ref], repoRoot,\n `Failed to look up origin/${ref} (git ls-remote).`);\n const line = out.split('\\n').find((l: string): boolean => l.trim() !== '');\n if (line === undefined) return '';\n return line.split(/\\s+/)[0] ?? '';\n }\n\n /**\n * Fetch the named copy refs into `refs/remotes/origin/<ref>` so the local merges below run against\n * fresh state. A no-op for an empty list, and a hard failure otherwise: a resolve queued against\n * stale refs rehearses a composition CI will not build.\n */\n fetchCopies(repoRoot: string, refs: string[]): void {\n if (refs.length === 0) return;\n const specs = refs.map((r: string): string => `+refs/heads/${r}:refs/remotes/origin/${r}`);\n this.gitExec.runGitChecked(['-C', repoRoot, 'fetch', 'origin', ...specs],\n 'Failed to fetch the dev copy refs from origin');\n }\n\n /** How many commits `origin/<ref>` has that `HEAD` does not — the clobber signal. 0 = safe to publish. */\n commitsOnlyOnRemote(repoRoot: string, ref: string): number {\n const out = this.gitExec.gitQuery(['rev-list', '--count', `HEAD..refs/remotes/origin/${ref}`], repoRoot,\n `Failed to compare HEAD against origin/${ref} (git rev-list --count).`);\n const count = Number(out);\n return Number.isInteger(count) ? count : 0;\n }\n\n private refusal(headline: string, body: string): string {\n return '\\n' + SEP + `⛔ ${headline}\\n` + SEP + '\\n' + body + `\\nRun \\`${WP_PUSH_DEV} --help\\` for the full command.\\n`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dev-deploy-refs.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/dev-deploy-refs.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliExitError, DevDeployConfig, loadAndValidate, WP_PUSH_DEV } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from './git-exec';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\nconst MAIN = 'main';\nconst DETACHED = 'HEAD';\n\n/**\n * One live copy in the dev namespace: the remote ref and the sha it points at. Data-only per CLAUDE.md —\n * `--list` prints it and the clobber guard compares against `sha`, and neither should be re-parsing\n * `ls-remote` output.\n */\nexport class DevCopy {\n /** The full ref name WITHOUT `refs/heads/`, e.g. `dev-include/dean/ONE-2275`. */\n ref: string;\n sha: string;\n /** The feature branch this is a copy OF — `ref` with the namespace prefix stripped. */\n sourceBranch: string;\n\n constructor(ref: string, sha: string, sourceBranch: string) {\n this.ref = ref;\n this.sha = sha;\n this.sourceBranch = sourceBranch;\n }\n}\n\n/**\n * Ref derivation and preconditions for the dev-deploy flow — the half of `wp-push-dev` that decides WHICH\n * refs are in play, kept out of the commands so both `wp-push-dev` and `wp-finish-push-dev` answer those\n * questions identically.\n *\n * THE INVARIANT THIS FILE EXISTS TO PROTECT: the feature branch is the PR head, so it must never acquire\n * another developer's commits — landing that PR would ship their unreviewed work to production. Every\n * write this flow performs therefore targets either the `<namespace>/<feature>` copy or a local throwaway\n * branch, and NOTHING here ever moves `<feature>` itself. That is why a conflict resolution needs the copy\n * to live in: it is the only place the resolution can be recorded without contaminating the PR.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class DevDeployRefs {\n constructor(private readonly gitExec: GitExec) {}\n\n /** The `pr-gate.devDeploy` block — `dev-include` / `dev` unless the consumer overrode it. */\n config(repoRoot: string): DevDeployConfig {\n return loadAndValidate(repoRoot).prGate.devDeploy;\n }\n\n /** The checked-out branch name, or `'HEAD'` on a detached HEAD (which {@link assertPushable} refuses). */\n currentBranch(repoRoot: string): string {\n return this.gitExec.gitQuery(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot,\n 'Failed to read the current branch (git rev-parse --abbrev-ref HEAD).');\n }\n\n /**\n * Refuse every branch that must never be the SOURCE of a dev copy, before anything is fetched or\n * pushed.\n *\n * `main` and the composed dev branch are refused for the same reason from opposite ends: `main` is the\n * trunk (whatever is on it already deploys, so copying it is meaningless), and the dev branch is a\n * BUILD ARTIFACT that CI recomputes from `origin/main` on every run — copying it would publish a\n * composition of everyone's work under one developer's name, and it would be wiped on the next CI run\n * anyway. A branch already inside the namespace is refused because publishing a copy of a copy nests\n * the prefix (`dev-include/dev-include/…`) and nothing would ever clean it up.\n */\n assertPushable(cfg: DevDeployConfig, branch: string): void {\n if (branch === DETACHED || branch === '') {\n throw new CliExitError(2, this.refusal(\n 'Detached HEAD',\n 'There is no branch name to derive the dev copy from. Check out your feature branch first:\\n'\n + ' git checkout <your-feature-branch>\\n'));\n }\n if (branch === MAIN) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of \\`${MAIN}\\``,\n `\\`${MAIN}\\` is the trunk — whatever is on it already deploys, so a dev copy of it means nothing.\\n`\n + 'Check out the feature branch you actually want on the dev server and re-run.\\n'));\n }\n if (branch === cfg.devBranch) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of \\`${cfg.devBranch}\\``,\n `\\`${cfg.devBranch}\\` is a BUILD ARTIFACT, not a source branch: your CI resets it to origin/${MAIN},\\n`\n + `merges every \\`${cfg.copyRefGlob()}\\` ref into it, and force-pushes — every run. Copying it would\\n`\n + \"publish a composition of everybody's work under your branch's name, and the next CI run would\\n\"\n + 'wipe it. Check out your own feature branch and re-run.\\n'));\n }\n if (branch === cfg.branchNamespace || branch.startsWith(`${cfg.branchNamespace}/`)) {\n throw new CliExitError(2, this.refusal(\n `Refusing to publish a dev copy of a dev copy (\\`${branch}\\`)`,\n `\\`${cfg.branchNamespace}/\\` holds the disposable copies this command WRITES; you are standing on one.\\n`\n + `Publishing from here would nest the prefix (\\`${cfg.branchNamespace}/${branch}\\`) and nothing ever\\n`\n + 'cleans that up. Check out the real feature branch and re-run.\\n'));\n }\n }\n\n /**\n * Every live copy in the namespace, sorted by ref name.\n *\n * The sort is not cosmetic: `--resolve` with no argument queues these in exactly this order, and the\n * consumer's CI composes `dev` in the same order, so what a developer resolves locally is the\n * composition CI will actually build. A different order can produce a different (still valid) merge\n * result, which would make the local resolution a guess about CI rather than a rehearsal of it.\n */\n liveCopies(repoRoot: string, cfg: DevDeployConfig): DevCopy[] {\n const out = this.gitExec.gitQuery(['ls-remote', '--heads', 'origin', cfg.copyRefGlob()], repoRoot,\n `Failed to list the dev copies (git ls-remote --heads origin '${cfg.copyRefGlob()}').`);\n const copies: DevCopy[] = [];\n for (const line of out.split('\\n')) {\n if (line.trim() === '') continue;\n const fields = line.split(/\\s+/);\n const sha = fields[0];\n const ref = fields[1];\n if (sha === undefined || ref === undefined || !ref.startsWith('refs/heads/')) continue;\n const shortRef = ref.slice('refs/heads/'.length);\n copies.push(new DevCopy(shortRef, sha, shortRef.slice(cfg.branchNamespace.length + 1)));\n }\n return copies.sort((a: DevCopy, b: DevCopy): number => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));\n }\n\n /**\n * The sha of a local ref (default HEAD).\n *\n * Read AFTER a push so the watch hints quote the sha that is provably published. Quoting the copy\n * REF instead would be a moving target — it advances the next time anybody publishes — and the whole\n * value of the \"did my code land in dev\" check is that it is a claim about one specific commit.\n */\n headSha(repoRoot: string, ref = 'HEAD'): string {\n return this.gitExec.gitQuery(['-C', repoRoot, 'rev-parse', ref], repoRoot,\n `Failed to read the sha of ${ref} (git rev-parse).`);\n }\n\n /** The remote sha of one copy, or '' when that copy does not exist yet. */\n remoteSha(repoRoot: string, ref: string): string {\n const out = this.gitExec.gitQuery(['ls-remote', '--heads', 'origin', ref], repoRoot,\n `Failed to look up origin/${ref} (git ls-remote).`);\n const line = out.split('\\n').find((l: string): boolean => l.trim() !== '');\n if (line === undefined) return '';\n return line.split(/\\s+/)[0] ?? '';\n }\n\n /**\n * Fetch the named copy refs into `refs/remotes/origin/<ref>` so the local merges below run against\n * fresh state. A no-op for an empty list, and a hard failure otherwise: a resolve queued against\n * stale refs rehearses a composition CI will not build.\n */\n fetchCopies(repoRoot: string, refs: string[]): void {\n if (refs.length === 0) return;\n const specs = refs.map((r: string): string => `+refs/heads/${r}:refs/remotes/origin/${r}`);\n this.gitExec.runGitChecked(['-C', repoRoot, 'fetch', 'origin', ...specs],\n 'Failed to fetch the dev copy refs from origin');\n }\n\n /** How many commits `origin/<ref>` has that `HEAD` does not — the clobber signal. 0 = safe to publish. */\n commitsOnlyOnRemote(repoRoot: string, ref: string): number {\n const out = this.gitExec.gitQuery(['rev-list', '--count', `HEAD..refs/remotes/origin/${ref}`], repoRoot,\n `Failed to compare HEAD against origin/${ref} (git rev-list --count).`);\n const count = Number(out);\n return Number.isInteger(count) ? count : 0;\n }\n\n private refusal(headline: string, body: string): string {\n return '\\n' + SEP + `⛔ ${headline}\\n` + SEP + '\\n' + body + `\\nRun \\`${WP_PUSH_DEV} --help\\` for the full command.\\n`;\n }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DevDeployConfig } from '@webpieces/rules-config';
|
|
2
|
+
/**
|
|
3
|
+
* The "now go watch it deploy" block printed after a copy is published.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS IS TEXT AND NOT A FEATURE: webpieces cannot know the consumer's CD. The composed branch might
|
|
6
|
+
* be built by GitHub Actions, by Jenkins, by a cron job on a box — so polling for "the run" would mean
|
|
7
|
+
* either a config key naming their workflow, or a heuristic that is wrong for somebody. Handing the AI
|
|
8
|
+
* the exact commands costs nothing, degrades gracefully (an empty `gh run list` is itself an answer), and
|
|
9
|
+
* keeps webpieces out of the business of modelling someone else's pipeline.
|
|
10
|
+
*
|
|
11
|
+
* The commands are pre-substituted with the real sha and refs — an AI given a template with blanks to
|
|
12
|
+
* fill in gets one of them wrong eventually, and a wrong `git merge-base` argument answers the question
|
|
13
|
+
* confidently and incorrectly.
|
|
14
|
+
*
|
|
15
|
+
* All of them are READ-ONLY and none is blocked: `git fetch`/`merge-base` are queries, and
|
|
16
|
+
* `gh run list|view|watch` is on the guards' allowed list.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DevDeployWatchHints {
|
|
19
|
+
/**
|
|
20
|
+
* @param sha the commit that was just published — the ONLY thing that makes step 1 a proof rather
|
|
21
|
+
* than a guess, since `<copyRef>` itself moves the next time anyone publishes.
|
|
22
|
+
*/
|
|
23
|
+
render(cfg: DevDeployConfig, copyRef: string, sha: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Step 1 — pure git, works with ANY CI, and is the only claim provably about YOUR code.
|
|
26
|
+
*
|
|
27
|
+
* `<devBranch>` is recomposed from `origin/main` plus every copy on each CI run, so your published
|
|
28
|
+
* sha is an ancestor of it if and only if the composition included you.
|
|
29
|
+
*/
|
|
30
|
+
private landedSection;
|
|
31
|
+
/** Step 2 — GitHub Actions, if that is where this repo's dev deploy lives. Empty output is an answer. */
|
|
32
|
+
private runSection;
|
|
33
|
+
/** The trap worth one sentence: a green run on a SHARED branch is not a statement about your code. */
|
|
34
|
+
private orderingWarning;
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DevDeployWatchHints = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const inversify_1 = require("inversify");
|
|
6
|
+
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
7
|
+
/**
|
|
8
|
+
* The "now go watch it deploy" block printed after a copy is published.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS TEXT AND NOT A FEATURE: webpieces cannot know the consumer's CD. The composed branch might
|
|
11
|
+
* be built by GitHub Actions, by Jenkins, by a cron job on a box — so polling for "the run" would mean
|
|
12
|
+
* either a config key naming their workflow, or a heuristic that is wrong for somebody. Handing the AI
|
|
13
|
+
* the exact commands costs nothing, degrades gracefully (an empty `gh run list` is itself an answer), and
|
|
14
|
+
* keeps webpieces out of the business of modelling someone else's pipeline.
|
|
15
|
+
*
|
|
16
|
+
* The commands are pre-substituted with the real sha and refs — an AI given a template with blanks to
|
|
17
|
+
* fill in gets one of them wrong eventually, and a wrong `git merge-base` argument answers the question
|
|
18
|
+
* confidently and incorrectly.
|
|
19
|
+
*
|
|
20
|
+
* All of them are READ-ONLY and none is blocked: `git fetch`/`merge-base` are queries, and
|
|
21
|
+
* `gh run list|view|watch` is on the guards' allowed list.
|
|
22
|
+
*/
|
|
23
|
+
let DevDeployWatchHints = class DevDeployWatchHints {
|
|
24
|
+
/**
|
|
25
|
+
* @param sha the commit that was just published — the ONLY thing that makes step 1 a proof rather
|
|
26
|
+
* than a guess, since `<copyRef>` itself moves the next time anyone publishes.
|
|
27
|
+
*/
|
|
28
|
+
render(cfg, copyRef, sha) {
|
|
29
|
+
return '\n' + SEP + '👀 Is it on the dev server yet? — two questions, in this order\n' + SEP + '\n'
|
|
30
|
+
+ this.landedSection(cfg, sha)
|
|
31
|
+
+ '\n'
|
|
32
|
+
+ this.runSection(cfg, copyRef)
|
|
33
|
+
+ '\n'
|
|
34
|
+
+ this.orderingWarning(cfg);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Step 1 — pure git, works with ANY CI, and is the only claim provably about YOUR code.
|
|
38
|
+
*
|
|
39
|
+
* `<devBranch>` is recomposed from `origin/main` plus every copy on each CI run, so your published
|
|
40
|
+
* sha is an ancestor of it if and only if the composition included you.
|
|
41
|
+
*/
|
|
42
|
+
landedSection(cfg, sha) {
|
|
43
|
+
return `1. Did YOUR code land in \`${cfg.devBranch}\`? (pure git — works whatever your CI is)\n`
|
|
44
|
+
+ ` git fetch origin ${cfg.devBranch}\n`
|
|
45
|
+
+ ` git merge-base --is-ancestor ${sha} origin/${cfg.devBranch} && echo IN || echo "not yet"\n`
|
|
46
|
+
+ ` \`${cfg.devBranch}\` is rebuilt from origin/main + every \`${cfg.copyRefGlob()}\` ref on each run, so\n`
|
|
47
|
+
+ ' ancestry IS inclusion. Re-run after the next composition if it says "not yet".\n';
|
|
48
|
+
}
|
|
49
|
+
/** Step 2 — GitHub Actions, if that is where this repo's dev deploy lives. Empty output is an answer. */
|
|
50
|
+
runSection(cfg, copyRef) {
|
|
51
|
+
const rows = [
|
|
52
|
+
[`gh run list --branch ${copyRef} --limit 5`, 'the run your push triggered, if any'],
|
|
53
|
+
[`gh run list --branch ${cfg.devBranch} --limit 5`, 'the composition / deploy run'],
|
|
54
|
+
['gh run watch <run-id>', 'follow one to completion'],
|
|
55
|
+
['gh run view <run-id> --log-failed', 'read why it failed'],
|
|
56
|
+
];
|
|
57
|
+
// Aligned off the longest command, because the ref names are consumer-configurable and any
|
|
58
|
+
// hand-counted padding would go crooked the moment somebody renames the namespace.
|
|
59
|
+
const width = Math.max(...rows.map((row) => row[0].length));
|
|
60
|
+
return '2. Is the CI/CD run finished? (GitHub Actions — all read-only, none of it is blocked)\n'
|
|
61
|
+
+ rows.map((row) => ` ${row[0].padEnd(width)} ← ${row[1]}\n`).join('')
|
|
62
|
+
+ ' Empty output is not a bug: it means this repo deploys from somewhere other than Actions.\n'
|
|
63
|
+
+ ' Ask the human where the dev deploy runs, and do not guess.\n';
|
|
64
|
+
}
|
|
65
|
+
/** The trap worth one sentence: a green run on a SHARED branch is not a statement about your code. */
|
|
66
|
+
orderingWarning(cfg) {
|
|
67
|
+
return `A green run on \`${cfg.devBranch}\` is NOT proof your change deployed — that branch carries everyone's\n`
|
|
68
|
+
+ 'copies, and the composition may have excluded yours. Question 1 is the one that answers for YOUR\n'
|
|
69
|
+
+ 'code; question 2 only tells you whether the machinery finished.\n';
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.DevDeployWatchHints = DevDeployWatchHints;
|
|
73
|
+
exports.DevDeployWatchHints = DevDeployWatchHints = tslib_1.__decorate([
|
|
74
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
|
|
75
|
+
], DevDeployWatchHints);
|
|
76
|
+
//# sourceMappingURL=dev-deploy-watch-hints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-deploy-watch-hints.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/dev-deploy-watch-hints.ts"],"names":[],"mappings":";;;;AACA,yCAA2D;AAE3D,MAAM,GAAG,GAAG,0DAA0D,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC5B;;;OAGG;IACH,MAAM,CAAC,GAAoB,EAAE,OAAe,EAAE,GAAW;QACrD,OAAO,IAAI,GAAG,GAAG,GAAG,kEAAkE,GAAG,GAAG,GAAG,IAAI;cAC7F,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;cAC5B,IAAI;cACJ,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;cAC7B,IAAI;cACJ,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACK,aAAa,CAAC,GAAoB,EAAE,GAAW;QACnD,OAAO,8BAA8B,GAAG,CAAC,SAAS,+CAA+C;cAC3F,yBAAyB,GAAG,CAAC,SAAS,IAAI;cAC1C,qCAAqC,GAAG,WAAW,GAAG,CAAC,SAAS,iCAAiC;cACjG,QAAQ,GAAG,CAAC,SAAS,4CAA4C,GAAG,CAAC,WAAW,EAAE,0BAA0B;cAC5G,qFAAqF,CAAC;IAChG,CAAC;IAED,yGAAyG;IACjG,UAAU,CAAC,GAAoB,EAAE,OAAe;QACpD,MAAM,IAAI,GAAG;YACT,CAAC,wBAAwB,OAAO,YAAY,EAAE,qCAAqC,CAAC;YACpF,CAAC,wBAAwB,GAAG,CAAC,SAAS,YAAY,EAAE,8BAA8B,CAAC;YACnF,CAAC,uBAAuB,EAAE,0BAA0B,CAAC;YACrD,CAAC,mCAAmC,EAAE,oBAAoB,CAAC;SAC9D,CAAC;QACF,2FAA2F;QAC3F,mFAAmF;QACnF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAa,EAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,OAAO,0FAA0F;cAC3F,IAAI,CAAC,GAAG,CAAC,CAAC,GAAa,EAAU,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;cAC5F,+FAA+F;cAC/F,iEAAiE,CAAC;IAC5E,CAAC;IAED,sGAAsG;IAC9F,eAAe,CAAC,GAAoB;QACxC,OAAO,oBAAoB,GAAG,CAAC,SAAS,yEAAyE;cAC3G,oGAAoG;cACpG,mEAAmE,CAAC;IAC9E,CAAC;CACJ,CAAA;AAnDY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,mBAAmB,CAmD/B","sourcesContent":["import { DevDeployConfig } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * The \"now go watch it deploy\" block printed after a copy is published.\n *\n * WHY THIS IS TEXT AND NOT A FEATURE: webpieces cannot know the consumer's CD. The composed branch might\n * be built by GitHub Actions, by Jenkins, by a cron job on a box — so polling for \"the run\" would mean\n * either a config key naming their workflow, or a heuristic that is wrong for somebody. Handing the AI\n * the exact commands costs nothing, degrades gracefully (an empty `gh run list` is itself an answer), and\n * keeps webpieces out of the business of modelling someone else's pipeline.\n *\n * The commands are pre-substituted with the real sha and refs — an AI given a template with blanks to\n * fill in gets one of them wrong eventually, and a wrong `git merge-base` argument answers the question\n * confidently and incorrectly.\n *\n * All of them are READ-ONLY and none is blocked: `git fetch`/`merge-base` are queries, and\n * `gh run list|view|watch` is on the guards' allowed list.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class DevDeployWatchHints {\n /**\n * @param sha the commit that was just published — the ONLY thing that makes step 1 a proof rather\n * than a guess, since `<copyRef>` itself moves the next time anyone publishes.\n */\n render(cfg: DevDeployConfig, copyRef: string, sha: string): string {\n return '\\n' + SEP + '👀 Is it on the dev server yet? — two questions, in this order\\n' + SEP + '\\n'\n + this.landedSection(cfg, sha)\n + '\\n'\n + this.runSection(cfg, copyRef)\n + '\\n'\n + this.orderingWarning(cfg);\n }\n\n /**\n * Step 1 — pure git, works with ANY CI, and is the only claim provably about YOUR code.\n *\n * `<devBranch>` is recomposed from `origin/main` plus every copy on each CI run, so your published\n * sha is an ancestor of it if and only if the composition included you.\n */\n private landedSection(cfg: DevDeployConfig, sha: string): string {\n return `1. Did YOUR code land in \\`${cfg.devBranch}\\`? (pure git — works whatever your CI is)\\n`\n + ` git fetch origin ${cfg.devBranch}\\n`\n + ` git merge-base --is-ancestor ${sha} origin/${cfg.devBranch} && echo IN || echo \"not yet\"\\n`\n + ` \\`${cfg.devBranch}\\` is rebuilt from origin/main + every \\`${cfg.copyRefGlob()}\\` ref on each run, so\\n`\n + ' ancestry IS inclusion. Re-run after the next composition if it says \"not yet\".\\n';\n }\n\n /** Step 2 — GitHub Actions, if that is where this repo's dev deploy lives. Empty output is an answer. */\n private runSection(cfg: DevDeployConfig, copyRef: string): string {\n const rows = [\n [`gh run list --branch ${copyRef} --limit 5`, 'the run your push triggered, if any'],\n [`gh run list --branch ${cfg.devBranch} --limit 5`, 'the composition / deploy run'],\n ['gh run watch <run-id>', 'follow one to completion'],\n ['gh run view <run-id> --log-failed', 'read why it failed'],\n ];\n // Aligned off the longest command, because the ref names are consumer-configurable and any\n // hand-counted padding would go crooked the moment somebody renames the namespace.\n const width = Math.max(...rows.map((row: string[]): number => row[0].length));\n return '2. Is the CI/CD run finished? (GitHub Actions — all read-only, none of it is blocked)\\n'\n + rows.map((row: string[]): string => ` ${row[0].padEnd(width)} ← ${row[1]}\\n`).join('')\n + ' Empty output is not a bug: it means this repo deploys from somewhere other than Actions.\\n'\n + ' Ask the human where the dev deploy runs, and do not guess.\\n';\n }\n\n /** The trap worth one sentence: a green run on a SHARED branch is not a statement about your code. */\n private orderingWarning(cfg: DevDeployConfig): string {\n return `A green run on \\`${cfg.devBranch}\\` is NOT proof your change deployed — that branch carries everyone's\\n`\n + 'copies, and the composition may have excluded yours. Question 1 is the one that answers for YOUR\\n'\n + 'code; question 2 only tells you whether the machinery finished.\\n';\n }\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { DevDeployConfig } from '@webpieces/rules-config';
|
|
|
2
2
|
import { GitExec } from './git-exec';
|
|
3
3
|
import { DevDeployRefs } from './dev-deploy-refs';
|
|
4
4
|
import { PushDevState, PushDevStateStore } from './push-dev-state';
|
|
5
|
+
import { DevDeployWatchHints } from './dev-deploy-watch-hints';
|
|
5
6
|
/**
|
|
6
7
|
* The queue-draining engine behind `wp-push-dev --resolve` and `wp-finish-push-dev`.
|
|
7
8
|
*
|
|
@@ -16,7 +17,8 @@ export declare class DevResolveRunner {
|
|
|
16
17
|
private readonly gitExec;
|
|
17
18
|
private readonly refs;
|
|
18
19
|
private readonly store;
|
|
19
|
-
|
|
20
|
+
private readonly watchHints;
|
|
21
|
+
constructor(gitExec: GitExec, refs: DevDeployRefs, store: PushDevStateStore, watchHints: DevDeployWatchHints);
|
|
20
22
|
/**
|
|
21
23
|
* Start a resolve: park on a throwaway branch at the current copy, then drain the queue.
|
|
22
24
|
*
|
|
@@ -7,6 +7,7 @@ const inversify_1 = require("inversify");
|
|
|
7
7
|
const git_exec_1 = require("./git-exec");
|
|
8
8
|
const dev_deploy_refs_1 = require("./dev-deploy-refs");
|
|
9
9
|
const push_dev_state_1 = require("./push-dev-state");
|
|
10
|
+
const dev_deploy_watch_hints_1 = require("./dev-deploy-watch-hints");
|
|
10
11
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
11
12
|
/**
|
|
12
13
|
* The queue-draining engine behind `wp-push-dev --resolve` and `wp-finish-push-dev`.
|
|
@@ -22,10 +23,12 @@ let DevResolveRunner = class DevResolveRunner {
|
|
|
22
23
|
gitExec;
|
|
23
24
|
refs;
|
|
24
25
|
store;
|
|
25
|
-
|
|
26
|
+
watchHints;
|
|
27
|
+
constructor(gitExec, refs, store, watchHints) {
|
|
26
28
|
this.gitExec = gitExec;
|
|
27
29
|
this.refs = refs;
|
|
28
30
|
this.store = store;
|
|
31
|
+
this.watchHints = watchHints;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Start a resolve: park on a throwaway branch at the current copy, then drain the queue.
|
|
@@ -125,13 +128,17 @@ let DevResolveRunner = class DevResolveRunner {
|
|
|
125
128
|
/** Queue empty: force-push the composed result over the copy, then restore the original checkout. */
|
|
126
129
|
publish(repoRoot, state) {
|
|
127
130
|
this.gitExec.runGitChecked(['-C', repoRoot, 'push', '--force', 'origin', `${state.tmpBranch}:refs/heads/${state.targetRef}`], `Failed to publish the dev copy ${state.targetRef}`);
|
|
131
|
+
// Captured BEFORE the tmp branch is deleted — it is the composed sha the watch hints are about,
|
|
132
|
+
// and after the checkout below HEAD is the feature branch, which is a different commit entirely.
|
|
133
|
+
const published = this.refs.headSha(repoRoot, state.tmpBranch);
|
|
128
134
|
this.gitExec.runGitChecked(['-C', repoRoot, 'checkout', state.originalBranch], `Failed to return to ${state.originalBranch}`);
|
|
129
135
|
this.gitExec.tryGit(['-C', repoRoot, 'branch', '-D', state.tmpBranch], repoRoot);
|
|
130
136
|
this.store.clear(repoRoot);
|
|
131
137
|
process.stdout.write('\n' + SEP + `✅ Published ${state.targetRef} with the resolutions\n` + SEP + '\n'
|
|
132
138
|
+ `You are back on \`${state.originalBranch}\`, which was never modified — verify with:\n`
|
|
133
139
|
+ ` git log --oneline ${state.originalBranch}\n`
|
|
134
|
-
+ 'Your CI will pick the copy up on its next composition run.\n'
|
|
140
|
+
+ 'Your CI will pick the copy up on its next composition run.\n'
|
|
141
|
+
+ this.watchHints.render(this.refs.config(repoRoot), state.targetRef, published));
|
|
135
142
|
}
|
|
136
143
|
/** Conflicted (unmerged-in-index) paths. Empty once every one has been resolved and `git add`ed. */
|
|
137
144
|
unmergedPaths(repoRoot) {
|
|
@@ -175,6 +182,7 @@ exports.DevResolveRunner = DevResolveRunner = tslib_1.__decorate([
|
|
|
175
182
|
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
176
183
|
tslib_1.__metadata("design:paramtypes", [git_exec_1.GitExec,
|
|
177
184
|
dev_deploy_refs_1.DevDeployRefs,
|
|
178
|
-
push_dev_state_1.PushDevStateStore
|
|
185
|
+
push_dev_state_1.PushDevStateStore,
|
|
186
|
+
dev_deploy_watch_hints_1.DevDeployWatchHints])
|
|
179
187
|
], DevResolveRunner);
|
|
180
188
|
//# sourceMappingURL=dev-resolve-runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-resolve-runner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/dev-resolve-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliExitError, DevDeployConfig, WP_FINISH_PUSH_DEV } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from './git-exec';\nimport { DevDeployRefs } from './dev-deploy-refs';\nimport { PushDevState, PushDevStateStore } from './push-dev-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * The queue-draining engine behind `wp-push-dev --resolve` and `wp-finish-push-dev`.\n *\n * ONE class owns every git WRITE in the resolve flow, and that is a hard requirement rather than tidiness:\n * `redirect-how-to-merge-main` blocks `git merge` in every form INCLUDING `--continue`, and\n * `pr-creation-or-push-guard` blocks every `git push`. So an AI cannot be told \"now run `git commit`\" or\n * \"`git merge --continue`\" — those instructions are unrunnable by the only party reading them. The command\n * owns the merges, the commit and the push (as child processes the hooks never see); the AI's entire job is\n * editing the conflicted files and `git add`-ing them, neither of which is blocked.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class DevResolveRunner {\n constructor(\n private readonly gitExec: GitExec,\n private readonly refs: DevDeployRefs,\n private readonly store: PushDevStateStore,\n ) {}\n\n /**\n * Start a resolve: park on a throwaway branch at the current copy, then drain the queue.\n *\n * The checkout is `-B <tmp> <base>` and NEVER the feature branch, which is the cardinal constraint made\n * mechanical — every merge below lands on a branch that is deleted at the end, so no other developer's\n * commits can reach the PR head even if the resolve is abandoned halfway.\n *\n * `base` differs between the two callers and the difference IS the semantics. `--resolve` starts at the\n * published copy and merges the other copies in (compose what CI will build). `--rebase-resolution`\n * starts at the LOCAL HEAD and merges the published copy in (replay an existing resolution on top of\n * the new commits, instead of force-pushing over it).\n *\n * `fetchRefs` is passed separately from the queue because `--rebase-resolution` merges a ref that is\n * also its push destination, so \"everything to fetch\" and \"everything to merge\" are not the same list.\n */\n start(repoRoot: string, state: PushDevState, base: string, fetchRefs: string[]): void {\n this.refs.fetchCopies(repoRoot, fetchRefs);\n this.store.write(repoRoot, state);\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'checkout', '-B', state.tmpBranch, base],\n `Failed to create the throwaway resolve branch ${state.tmpBranch}`);\n this.drain(repoRoot, state);\n }\n\n /**\n * Resume after the human/AI has resolved the halted merge: commit it, then keep draining.\n *\n * REFETCHES the remaining queue first. A resolution session can run long, and queuing against refs\n * fetched an hour ago rehearses a composition CI will not build — the one freshness gap the sibling\n * update flow is still known to have (only its stage ① ever fetches).\n */\n resume(repoRoot: string, state: PushDevState): void {\n this.assertNoUnmergedPaths(repoRoot, state);\n this.gitExec.assertNoUntracked(repoRoot);\n this.gitExec.runGitChecked(['-C', repoRoot, 'add', '-u'], 'Failed to stage the resolved files (git add -u)');\n this.gitExec.runGitChecked(['-C', repoRoot, 'commit', '--no-edit'],\n `Failed to commit the resolution of ${state.current}`);\n process.stdout.write(`✅ Committed the resolution of ${state.current}.\\n`);\n state.current = '';\n this.refs.fetchCopies(repoRoot, state.queue);\n this.drain(repoRoot, state);\n }\n\n /**\n * Throw the whole resolve away: undo any halted merge, go back to the original branch, delete the\n * throwaway branch and the state file. Nothing published, nothing on the feature branch.\n */\n abort(repoRoot: string, state: PushDevState): void {\n // Allowed to fail: there is no merge to abort when the resolve halted between merges, and that is\n // not an error condition — the rest of the teardown must still run.\n this.gitExec.tryGit(['-C', repoRoot, 'merge', '--abort'], repoRoot);\n this.gitExec.runGitChecked(['-C', repoRoot, 'checkout', state.originalBranch],\n `Failed to return to ${state.originalBranch}`);\n this.gitExec.tryGit(['-C', repoRoot, 'branch', '-D', state.tmpBranch], repoRoot);\n this.store.clear(repoRoot);\n process.stdout.write(\n '\\n' + SEP + '🛑 Dev-deploy resolve aborted\\n' + SEP + '\\n'\n + `You are back on \\`${state.originalBranch}\\`; \\`${state.tmpBranch}\\` and the state file are gone.\\n`\n + `The remote copy \\`${state.targetRef}\\` is untouched, and so is your feature branch.\\n`);\n }\n\n /**\n * Merge queued refs until one conflicts (HALT, hand the tree over) or the queue empties (PUBLISH).\n *\n * A LATER ref can conflict too, which is why this is a loop resumed from a state file rather than a\n * single pass: `wp-finish-push-dev` can legitimately stop again on the next ref in the queue.\n */\n private drain(repoRoot: string, state: PushDevState): void {\n while (state.queue.length > 0) {\n const ref = state.queue[0];\n state.queue = state.queue.slice(1);\n const outcome = this.gitExec.tryGit(\n ['-C', repoRoot, 'merge', '--no-ff', '--no-edit', '-m',\n `Dev-deploy: merge ${ref} into ${state.targetRef}`, `refs/remotes/origin/${ref}`],\n repoRoot);\n if (outcome.ok) {\n process.stdout.write(` ✓ merged ${ref}\\n`);\n continue;\n }\n state.current = ref;\n this.store.write(repoRoot, state);\n this.halt(repoRoot, state);\n return;\n }\n this.publish(repoRoot, state);\n }\n\n /** Stop with the conflicted files listed and the ONE command that continues. Exit 2 = \"your turn\". */\n private halt(repoRoot: string, state: PushDevState): never {\n const conflicted = this.unmergedPaths(repoRoot);\n const remaining = state.queue.length > 0\n ? `\\nStill queued after this one: ${state.queue.join(', ')}\\n`\n + `\\`${WP_FINISH_PUSH_DEV}\\` resumes the queue, so it may stop again on a later ref.\\n`\n : '';\n throw new CliExitError(2,\n '\\n' + SEP + `⚠️ CONFLICT merging ${state.current}\\n` + SEP + '\\n'\n + `You are on the throwaway branch \\`${state.tmpBranch}\\` — NOT \\`${state.originalBranch}\\`. Nothing here\\n`\n + 'can reach your feature branch or your PR, which is exactly why the resolution happens over here.\\n\\n'\n + 'Resolve these files (edit until no conflict markers remain, then `git add` each one):\\n'\n + conflicted.map((f: string): string => ` ${f}`).join('\\n') + '\\n'\n + remaining\n + '\\nThen run ONE of:\\n'\n + ` ${WP_FINISH_PUSH_DEV} ← commit, resume the queue, publish the copy\\n`\n + ` ${WP_FINISH_PUSH_DEV} --abort ← throw it away and go back to \\`${state.originalBranch}\\`\\n\\n`\n + 'Do NOT run `git merge --continue`, `git commit` or `git push` yourself — they are blocked, and\\n'\n + 'the finish command above does all three for you.\\n');\n }\n\n /** Queue empty: force-push the composed result over the copy, then restore the original checkout. */\n private publish(repoRoot: string, state: PushDevState): void {\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'push', '--force', 'origin', `${state.tmpBranch}:refs/heads/${state.targetRef}`],\n `Failed to publish the dev copy ${state.targetRef}`);\n this.gitExec.runGitChecked(['-C', repoRoot, 'checkout', state.originalBranch],\n `Failed to return to ${state.originalBranch}`);\n this.gitExec.tryGit(['-C', repoRoot, 'branch', '-D', state.tmpBranch], repoRoot);\n this.store.clear(repoRoot);\n process.stdout.write(\n '\\n' + SEP + `✅ Published ${state.targetRef} with the resolutions\\n` + SEP + '\\n'\n + `You are back on \\`${state.originalBranch}\\`, which was never modified — verify with:\\n`\n + ` git log --oneline ${state.originalBranch}\\n`\n + 'Your CI will pick the copy up on its next composition run.\\n');\n }\n\n /** Conflicted (unmerged-in-index) paths. Empty once every one has been resolved and `git add`ed. */\n private unmergedPaths(repoRoot: string): string[] {\n const out = this.gitExec.gitQuery(['-C', repoRoot, 'diff', '--name-only', '--diff-filter=U'], repoRoot,\n 'Failed to list the conflicted files (git diff --name-only --diff-filter=U).');\n return out.split('\\n').filter((l: string): boolean => l.trim() !== '');\n }\n\n // The gate on `wp-finish-push-dev`: refuse (and re-print the list) while anything is still unmerged.\n private assertNoUnmergedPaths(repoRoot: string, state: PushDevState): void {\n const conflicted = this.unmergedPaths(repoRoot);\n if (conflicted.length === 0) return;\n throw new CliExitError(2,\n '\\n' + SEP + '⛔ Conflicts are NOT resolved yet\\n' + SEP + '\\n'\n + `${WP_FINISH_PUSH_DEV} commits the resolution — it cannot write one for you. These files are still\\n`\n + 'unmerged (edit until no conflict markers remain, then `git add` each one):\\n'\n + conflicted.map((f: string): string => ` ${f}`).join('\\n') + '\\n\\n'\n + `Merging: ${state.current === '' ? '(none)' : state.current}\\n`\n + `Give up instead with: ${WP_FINISH_PUSH_DEV} --abort\\n`);\n }\n\n /**\n * The name of the throwaway branch for a resolve of `branch`.\n *\n * Suffixed rather than prefixed, matching the existing `<feature>Squash` / `<feature>PreMerge<n>`\n * convention — and deliberately NOT inside the dev namespace, which holds REMOTE copies only.\n */\n tmpBranchFor(branch: string): string {\n return `${branch}DevResolve`;\n }\n\n /** The banner `--resolve` prints before the first merge, so the queue is visible before it halts. */\n announce(cfg: DevDeployConfig, state: PushDevState): string {\n return '\\n' + SEP + `🔀 Resolving the dev composition for ${state.targetRef}\\n` + SEP + '\\n'\n + `Merging ${String(state.queue.length)} other cop${state.queue.length === 1 ? 'y' : 'ies'} from `\n + `\\`${cfg.copyRefGlob()}\\`, in the order your CI composes\\n\\`${cfg.devBranch}\\`, onto the throwaway branch `\n + `\\`${state.tmpBranch}\\`:\\n`\n + state.queue.map((r: string): string => ` ${r}`).join('\\n') + '\\n\\n'\n + `Your feature branch \\`${state.originalBranch}\\` is not touched by any of this.\\n`\n + `A clean run publishes the copy itself — ${WP_FINISH_PUSH_DEV} is only needed if a merge stops.\\n\\n`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dev-resolve-runner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/dev-resolve-runner.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliExitError, DevDeployConfig, WP_FINISH_PUSH_DEV } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { GitExec } from './git-exec';\nimport { DevDeployRefs } from './dev-deploy-refs';\nimport { PushDevState, PushDevStateStore } from './push-dev-state';\nimport { DevDeployWatchHints } from './dev-deploy-watch-hints';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/**\n * The queue-draining engine behind `wp-push-dev --resolve` and `wp-finish-push-dev`.\n *\n * ONE class owns every git WRITE in the resolve flow, and that is a hard requirement rather than tidiness:\n * `redirect-how-to-merge-main` blocks `git merge` in every form INCLUDING `--continue`, and\n * `pr-creation-or-push-guard` blocks every `git push`. So an AI cannot be told \"now run `git commit`\" or\n * \"`git merge --continue`\" — those instructions are unrunnable by the only party reading them. The command\n * owns the merges, the commit and the push (as child processes the hooks never see); the AI's entire job is\n * editing the conflicted files and `git add`-ing them, neither of which is blocked.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class DevResolveRunner {\n constructor(\n private readonly gitExec: GitExec,\n private readonly refs: DevDeployRefs,\n private readonly store: PushDevStateStore,\n private readonly watchHints: DevDeployWatchHints,\n ) {}\n\n /**\n * Start a resolve: park on a throwaway branch at the current copy, then drain the queue.\n *\n * The checkout is `-B <tmp> <base>` and NEVER the feature branch, which is the cardinal constraint made\n * mechanical — every merge below lands on a branch that is deleted at the end, so no other developer's\n * commits can reach the PR head even if the resolve is abandoned halfway.\n *\n * `base` differs between the two callers and the difference IS the semantics. `--resolve` starts at the\n * published copy and merges the other copies in (compose what CI will build). `--rebase-resolution`\n * starts at the LOCAL HEAD and merges the published copy in (replay an existing resolution on top of\n * the new commits, instead of force-pushing over it).\n *\n * `fetchRefs` is passed separately from the queue because `--rebase-resolution` merges a ref that is\n * also its push destination, so \"everything to fetch\" and \"everything to merge\" are not the same list.\n */\n start(repoRoot: string, state: PushDevState, base: string, fetchRefs: string[]): void {\n this.refs.fetchCopies(repoRoot, fetchRefs);\n this.store.write(repoRoot, state);\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'checkout', '-B', state.tmpBranch, base],\n `Failed to create the throwaway resolve branch ${state.tmpBranch}`);\n this.drain(repoRoot, state);\n }\n\n /**\n * Resume after the human/AI has resolved the halted merge: commit it, then keep draining.\n *\n * REFETCHES the remaining queue first. A resolution session can run long, and queuing against refs\n * fetched an hour ago rehearses a composition CI will not build — the one freshness gap the sibling\n * update flow is still known to have (only its stage ① ever fetches).\n */\n resume(repoRoot: string, state: PushDevState): void {\n this.assertNoUnmergedPaths(repoRoot, state);\n this.gitExec.assertNoUntracked(repoRoot);\n this.gitExec.runGitChecked(['-C', repoRoot, 'add', '-u'], 'Failed to stage the resolved files (git add -u)');\n this.gitExec.runGitChecked(['-C', repoRoot, 'commit', '--no-edit'],\n `Failed to commit the resolution of ${state.current}`);\n process.stdout.write(`✅ Committed the resolution of ${state.current}.\\n`);\n state.current = '';\n this.refs.fetchCopies(repoRoot, state.queue);\n this.drain(repoRoot, state);\n }\n\n /**\n * Throw the whole resolve away: undo any halted merge, go back to the original branch, delete the\n * throwaway branch and the state file. Nothing published, nothing on the feature branch.\n */\n abort(repoRoot: string, state: PushDevState): void {\n // Allowed to fail: there is no merge to abort when the resolve halted between merges, and that is\n // not an error condition — the rest of the teardown must still run.\n this.gitExec.tryGit(['-C', repoRoot, 'merge', '--abort'], repoRoot);\n this.gitExec.runGitChecked(['-C', repoRoot, 'checkout', state.originalBranch],\n `Failed to return to ${state.originalBranch}`);\n this.gitExec.tryGit(['-C', repoRoot, 'branch', '-D', state.tmpBranch], repoRoot);\n this.store.clear(repoRoot);\n process.stdout.write(\n '\\n' + SEP + '🛑 Dev-deploy resolve aborted\\n' + SEP + '\\n'\n + `You are back on \\`${state.originalBranch}\\`; \\`${state.tmpBranch}\\` and the state file are gone.\\n`\n + `The remote copy \\`${state.targetRef}\\` is untouched, and so is your feature branch.\\n`);\n }\n\n /**\n * Merge queued refs until one conflicts (HALT, hand the tree over) or the queue empties (PUBLISH).\n *\n * A LATER ref can conflict too, which is why this is a loop resumed from a state file rather than a\n * single pass: `wp-finish-push-dev` can legitimately stop again on the next ref in the queue.\n */\n private drain(repoRoot: string, state: PushDevState): void {\n while (state.queue.length > 0) {\n const ref = state.queue[0];\n state.queue = state.queue.slice(1);\n const outcome = this.gitExec.tryGit(\n ['-C', repoRoot, 'merge', '--no-ff', '--no-edit', '-m',\n `Dev-deploy: merge ${ref} into ${state.targetRef}`, `refs/remotes/origin/${ref}`],\n repoRoot);\n if (outcome.ok) {\n process.stdout.write(` ✓ merged ${ref}\\n`);\n continue;\n }\n state.current = ref;\n this.store.write(repoRoot, state);\n this.halt(repoRoot, state);\n return;\n }\n this.publish(repoRoot, state);\n }\n\n /** Stop with the conflicted files listed and the ONE command that continues. Exit 2 = \"your turn\". */\n private halt(repoRoot: string, state: PushDevState): never {\n const conflicted = this.unmergedPaths(repoRoot);\n const remaining = state.queue.length > 0\n ? `\\nStill queued after this one: ${state.queue.join(', ')}\\n`\n + `\\`${WP_FINISH_PUSH_DEV}\\` resumes the queue, so it may stop again on a later ref.\\n`\n : '';\n throw new CliExitError(2,\n '\\n' + SEP + `⚠️ CONFLICT merging ${state.current}\\n` + SEP + '\\n'\n + `You are on the throwaway branch \\`${state.tmpBranch}\\` — NOT \\`${state.originalBranch}\\`. Nothing here\\n`\n + 'can reach your feature branch or your PR, which is exactly why the resolution happens over here.\\n\\n'\n + 'Resolve these files (edit until no conflict markers remain, then `git add` each one):\\n'\n + conflicted.map((f: string): string => ` ${f}`).join('\\n') + '\\n'\n + remaining\n + '\\nThen run ONE of:\\n'\n + ` ${WP_FINISH_PUSH_DEV} ← commit, resume the queue, publish the copy\\n`\n + ` ${WP_FINISH_PUSH_DEV} --abort ← throw it away and go back to \\`${state.originalBranch}\\`\\n\\n`\n + 'Do NOT run `git merge --continue`, `git commit` or `git push` yourself — they are blocked, and\\n'\n + 'the finish command above does all three for you.\\n');\n }\n\n /** Queue empty: force-push the composed result over the copy, then restore the original checkout. */\n private publish(repoRoot: string, state: PushDevState): void {\n this.gitExec.runGitChecked(\n ['-C', repoRoot, 'push', '--force', 'origin', `${state.tmpBranch}:refs/heads/${state.targetRef}`],\n `Failed to publish the dev copy ${state.targetRef}`);\n // Captured BEFORE the tmp branch is deleted — it is the composed sha the watch hints are about,\n // and after the checkout below HEAD is the feature branch, which is a different commit entirely.\n const published = this.refs.headSha(repoRoot, state.tmpBranch);\n this.gitExec.runGitChecked(['-C', repoRoot, 'checkout', state.originalBranch],\n `Failed to return to ${state.originalBranch}`);\n this.gitExec.tryGit(['-C', repoRoot, 'branch', '-D', state.tmpBranch], repoRoot);\n this.store.clear(repoRoot);\n process.stdout.write(\n '\\n' + SEP + `✅ Published ${state.targetRef} with the resolutions\\n` + SEP + '\\n'\n + `You are back on \\`${state.originalBranch}\\`, which was never modified — verify with:\\n`\n + ` git log --oneline ${state.originalBranch}\\n`\n + 'Your CI will pick the copy up on its next composition run.\\n'\n + this.watchHints.render(this.refs.config(repoRoot), state.targetRef, published));\n }\n\n /** Conflicted (unmerged-in-index) paths. Empty once every one has been resolved and `git add`ed. */\n private unmergedPaths(repoRoot: string): string[] {\n const out = this.gitExec.gitQuery(['-C', repoRoot, 'diff', '--name-only', '--diff-filter=U'], repoRoot,\n 'Failed to list the conflicted files (git diff --name-only --diff-filter=U).');\n return out.split('\\n').filter((l: string): boolean => l.trim() !== '');\n }\n\n // The gate on `wp-finish-push-dev`: refuse (and re-print the list) while anything is still unmerged.\n private assertNoUnmergedPaths(repoRoot: string, state: PushDevState): void {\n const conflicted = this.unmergedPaths(repoRoot);\n if (conflicted.length === 0) return;\n throw new CliExitError(2,\n '\\n' + SEP + '⛔ Conflicts are NOT resolved yet\\n' + SEP + '\\n'\n + `${WP_FINISH_PUSH_DEV} commits the resolution — it cannot write one for you. These files are still\\n`\n + 'unmerged (edit until no conflict markers remain, then `git add` each one):\\n'\n + conflicted.map((f: string): string => ` ${f}`).join('\\n') + '\\n\\n'\n + `Merging: ${state.current === '' ? '(none)' : state.current}\\n`\n + `Give up instead with: ${WP_FINISH_PUSH_DEV} --abort\\n`);\n }\n\n /**\n * The name of the throwaway branch for a resolve of `branch`.\n *\n * Suffixed rather than prefixed, matching the existing `<feature>Squash` / `<feature>PreMerge<n>`\n * convention — and deliberately NOT inside the dev namespace, which holds REMOTE copies only.\n */\n tmpBranchFor(branch: string): string {\n return `${branch}DevResolve`;\n }\n\n /** The banner `--resolve` prints before the first merge, so the queue is visible before it halts. */\n announce(cfg: DevDeployConfig, state: PushDevState): string {\n return '\\n' + SEP + `🔀 Resolving the dev composition for ${state.targetRef}\\n` + SEP + '\\n'\n + `Merging ${String(state.queue.length)} other cop${state.queue.length === 1 ? 'y' : 'ies'} from `\n + `\\`${cfg.copyRefGlob()}\\`, in the order your CI composes\\n\\`${cfg.devBranch}\\`, onto the throwaway branch `\n + `\\`${state.tmpBranch}\\`:\\n`\n + state.queue.map((r: string): string => ` ${r}`).join('\\n') + '\\n\\n'\n + `Your feature branch \\`${state.originalBranch}\\` is not touched by any of this.\\n`\n + `A clean run publishes the copy itself — ${WP_FINISH_PUSH_DEV} is only needed if a merge stops.\\n\\n`;\n }\n}\n"]}
|