@webpieces/pr-gate 0.3.353 → 0.3.355
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 +7 -2
- package/src/dashboard/dashboard.d.ts +12 -3
- package/src/dashboard/dashboard.js +136 -129
- package/src/dashboard/dashboard.js.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +4 -4
- package/src/index.js.map +1 -1
- package/src/scripts/commands/finish-update-command.d.ts +10 -0
- package/src/scripts/commands/finish-update-command.js +51 -0
- package/src/scripts/commands/finish-update-command.js.map +1 -0
- package/src/scripts/commands/finish-upsert-pr-command.d.ts +22 -0
- package/src/scripts/commands/finish-upsert-pr-command.js +131 -0
- package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -0
- package/src/scripts/commands/start-update-command.d.ts +9 -0
- package/src/scripts/commands/start-update-command.js +67 -0
- package/src/scripts/commands/start-update-command.js.map +1 -0
- package/src/scripts/commands/start-upsert-pr-command.d.ts +15 -0
- package/src/scripts/commands/start-upsert-pr-command.js +73 -0
- package/src/scripts/commands/start-upsert-pr-command.js.map +1 -0
- package/src/scripts/git-finishUpsertPr.d.ts +1 -1
- package/src/scripts/git-finishUpsertPr.js +11 -109
- package/src/scripts/git-finishUpsertPr.js.map +1 -1
- package/src/scripts/git-gatherInfo.d.ts +15 -1
- package/src/scripts/git-gatherInfo.js +79 -61
- package/src/scripts/git-gatherInfo.js.map +1 -1
- package/src/scripts/pr-gate-app.d.ts +25 -0
- package/src/scripts/pr-gate-app.js +56 -0
- package/src/scripts/pr-gate-app.js.map +1 -0
- package/src/scripts/workflow/branch-naming.d.ts +11 -8
- package/src/scripts/workflow/branch-naming.js +30 -21
- package/src/scripts/workflow/branch-naming.js.map +1 -1
- package/src/scripts/workflow/build-affected.d.ts +21 -30
- package/src/scripts/workflow/build-affected.js +57 -63
- package/src/scripts/workflow/build-affected.js.map +1 -1
- package/src/scripts/workflow/cleanTmp.d.ts +6 -1
- package/src/scripts/workflow/cleanTmp.js +83 -78
- package/src/scripts/workflow/cleanTmp.js.map +1 -1
- package/src/scripts/workflow/git-exec.d.ts +32 -28
- package/src/scripts/workflow/git-exec.js +92 -85
- package/src/scripts/workflow/git-exec.js.map +1 -1
- package/src/scripts/workflow/git-findForkPoint.d.ts +12 -3
- package/src/scripts/workflow/git-findForkPoint.js +116 -107
- package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
- package/src/scripts/workflow/git-readAiBranchName.d.ts +7 -2
- package/src/scripts/workflow/git-readAiBranchName.js +24 -15
- package/src/scripts/workflow/git-readAiBranchName.js.map +1 -1
- package/src/scripts/workflow/merge-end.d.ts +21 -6
- package/src/scripts/workflow/merge-end.js +147 -133
- package/src/scripts/workflow/merge-end.js.map +1 -1
- package/src/scripts/workflow/merge-start.d.ts +22 -1
- package/src/scripts/workflow/merge-start.js +180 -186
- package/src/scripts/workflow/merge-start.js.map +1 -1
- package/src/scripts/workflow/merge-state.d.ts +26 -23
- package/src/scripts/workflow/merge-state.js +131 -137
- package/src/scripts/workflow/merge-state.js.map +1 -1
- package/src/scripts/workflow/open-pr-check.d.ts +7 -1
- package/src/scripts/workflow/open-pr-check.js +34 -23
- package/src/scripts/workflow/open-pr-check.js.map +1 -1
- package/src/scripts/workflow/run-update.d.ts +13 -1
- package/src/scripts/workflow/run-update.js +59 -28
- package/src/scripts/workflow/run-update.js.map +1 -1
- package/src/scripts/wp-finish-update.d.ts +1 -1
- package/src/scripts/wp-finish-update.js +11 -30
- package/src/scripts/wp-finish-update.js.map +1 -1
- package/src/scripts/wp-start-update.d.ts +1 -2
- package/src/scripts/wp-start-update.js +11 -51
- package/src/scripts/wp-start-update.js.map +1 -1
- package/src/scripts/wp-start-upsert-pr.d.ts +1 -1
- package/src/scripts/wp-start-upsert-pr.js +11 -47
- package/src/scripts/wp-start-upsert-pr.js.map +1 -1
|
@@ -1,98 +1,105 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
exports.assertCleanTree = assertCleanTree;
|
|
6
|
-
exports.assertNoUntracked = assertNoUntracked;
|
|
7
|
-
exports.runGitChecked = runGitChecked;
|
|
8
|
-
exports.ensurePushed = ensurePushed;
|
|
3
|
+
exports.GitExec = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
9
5
|
const child_process_1 = require("child_process");
|
|
10
6
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
7
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
8
|
+
const inversify_1 = require("inversify");
|
|
11
9
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
12
10
|
// The AI-facing doc the failure messages point at (generated under `.webpieces/instruct-ai`, at the
|
|
13
11
|
// REPO ROOT — resolved absolute so the AI opens it regardless of the cwd it reads the message from).
|
|
14
12
|
const GIT_WORKFLOW_DOC_NAME = 'webpieces.git-workflow.md';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (out.status !== 0) {
|
|
21
|
-
throw new rules_config_1.CliExitError(1, `❌ ${failMsg}`);
|
|
13
|
+
/** Shared git precondition checks + push logic for the PR/merge workflow. */
|
|
14
|
+
let GitExec = class GitExec {
|
|
15
|
+
repoRootFinder;
|
|
16
|
+
constructor(repoRootFinder) {
|
|
17
|
+
this.repoRootFinder = repoRootFinder;
|
|
22
18
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function uncommittedFiles(cwd) {
|
|
28
|
-
return gitQuery(['status', '--porcelain'], cwd, 'Failed to run `git status --porcelain` to check the working tree.');
|
|
29
|
-
}
|
|
30
|
-
// Untracked files only (respects .gitignore). Empty string = none. Exported for unit testing.
|
|
31
|
-
function untrackedFiles(cwd) {
|
|
32
|
-
return gitQuery(['ls-files', '--others', '--exclude-standard'], cwd, 'Failed to list untracked files (git ls-files --others).');
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Precondition for every PR/merge entry point: the working tree must be fully committed. The webpieces
|
|
36
|
-
* tooling deliberately does NOT `git add`/`commit` the developer's work for them — a blanket `git add
|
|
37
|
-
* -A` once swept a stale untracked directory into a squash commit, and building the working tree while
|
|
38
|
-
* pushing HEAD let an uncommitted fix pass the build gate yet push a stale commit. Aborts with
|
|
39
|
-
* instructions if anything is uncommitted (tracked or untracked, excluding gitignored).
|
|
40
|
-
*/
|
|
41
|
-
function assertCleanTree(cwd) {
|
|
42
|
-
const dirty = uncommittedFiles(cwd);
|
|
43
|
-
if (dirty === '')
|
|
44
|
-
return;
|
|
45
|
-
throw new rules_config_1.CliExitError(1, '\n' + SEP +
|
|
46
|
-
'❌ ERROR: You have uncommitted or untracked changes\n' +
|
|
47
|
-
SEP + '\n' +
|
|
48
|
-
'The webpieces PR tooling will NOT commit your work for you. Commit your\n' +
|
|
49
|
-
'changes, and either commit or delete any untracked files, then re-run.\n\n' +
|
|
50
|
-
'Working tree (git status --porcelain):\n' +
|
|
51
|
-
dirty + '\n\n' +
|
|
52
|
-
`See ${new rules_config_1.RepoRootFinder().docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\n` +
|
|
53
|
-
SEP);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Narrower guard for the merge-resolve commit point, where tracked resolutions are legitimately in the
|
|
57
|
-
* tree but untracked files must NOT be swept into the squash commit (replaces the old blanket `git add
|
|
58
|
-
* -A`). Lists any untracked files and aborts so the AI commits or deletes them explicitly.
|
|
59
|
-
*/
|
|
60
|
-
function assertNoUntracked(cwd) {
|
|
61
|
-
const untracked = untrackedFiles(cwd);
|
|
62
|
-
if (untracked === '')
|
|
63
|
-
return;
|
|
64
|
-
throw new rules_config_1.CliExitError(1, '\n' + SEP +
|
|
65
|
-
'❌ ERROR: Untracked files present during merge finalize\n' +
|
|
66
|
-
SEP + '\n' +
|
|
67
|
-
'The tooling will not sweep untracked files into the squash commit. Commit\n' +
|
|
68
|
-
'or delete these, then re-run:\n\n' +
|
|
69
|
-
untracked + '\n\n' +
|
|
70
|
-
`See ${new rules_config_1.RepoRootFinder().docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\n` +
|
|
71
|
-
SEP);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Run a git command that is expected to succeed; abort the process with a clear message
|
|
75
|
-
* if it fails. Used for fetch/pull/checkout/commit where silently continuing on failure
|
|
76
|
-
* would operate on stale or wrong state.
|
|
77
|
-
*/
|
|
78
|
-
function runGitChecked(args, errMsg) {
|
|
79
|
-
const result = (0, child_process_1.spawnSync)('git', args, { stdio: 'inherit' });
|
|
80
|
-
if (result.status !== 0) {
|
|
81
|
-
throw new rules_config_1.CliExitError(1, `❌ ${errMsg} (git ${args.join(' ')} exited ${String(result.status)})`);
|
|
19
|
+
// Tracked changes (staged + unstaged) AND untracked files, EXCLUDING gitignored paths — so
|
|
20
|
+
// `.webpieces/` tooling artifacts never count. Empty string = fully committed.
|
|
21
|
+
uncommittedFiles(cwd) {
|
|
22
|
+
return this.gitQuery(['status', '--porcelain'], cwd, 'Failed to run `git status --porcelain` to check the working tree.');
|
|
82
23
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* wp-finish-upsert-pr (they pushed with identical copy-pasted logic). Uses --force-with-lease for an
|
|
87
|
-
* existing remote branch (the 3-point squash rewrites history) and -u for a brand-new branch.
|
|
88
|
-
*/
|
|
89
|
-
function ensurePushed(currentBranch) {
|
|
90
|
-
const remoteExists = (0, child_process_1.spawnSync)('git', ['ls-remote', '--exit-code', '--heads', 'origin', currentBranch]).status === 0;
|
|
91
|
-
if (remoteExists) {
|
|
92
|
-
runGitChecked(['push', '--force-with-lease', 'origin', `HEAD:${currentBranch}`], 'Failed to push branch');
|
|
24
|
+
// Untracked files only (respects .gitignore). Empty string = none.
|
|
25
|
+
untrackedFiles(cwd) {
|
|
26
|
+
return this.gitQuery(['ls-files', '--others', '--exclude-standard'], cwd, 'Failed to list untracked files (git ls-files --others).');
|
|
93
27
|
}
|
|
94
|
-
|
|
95
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Precondition for every PR/merge entry point: the working tree must be fully committed. The
|
|
30
|
+
* webpieces tooling deliberately does NOT `git add`/`commit` the developer's work for them. Aborts
|
|
31
|
+
* with instructions if anything is uncommitted (tracked or untracked, excluding gitignored).
|
|
32
|
+
*/
|
|
33
|
+
assertCleanTree(cwd) {
|
|
34
|
+
const dirty = this.uncommittedFiles(cwd);
|
|
35
|
+
if (dirty === '')
|
|
36
|
+
return;
|
|
37
|
+
throw new rules_config_1.CliExitError(1, '\n' + SEP +
|
|
38
|
+
'❌ ERROR: You have uncommitted or untracked changes\n' +
|
|
39
|
+
SEP + '\n' +
|
|
40
|
+
'The webpieces PR tooling will NOT commit your work for you. Commit your\n' +
|
|
41
|
+
'changes, and either commit or delete any untracked files, then re-run.\n\n' +
|
|
42
|
+
'Working tree (git status --porcelain):\n' +
|
|
43
|
+
dirty + '\n\n' +
|
|
44
|
+
`See ${this.repoRootFinder.docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\n` +
|
|
45
|
+
SEP);
|
|
96
46
|
}
|
|
97
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Narrower guard for the merge-resolve commit point, where tracked resolutions are legitimately in
|
|
49
|
+
* the tree but untracked files must NOT be swept into the squash commit. Lists any untracked files
|
|
50
|
+
* and aborts so the AI commits or deletes them explicitly.
|
|
51
|
+
*/
|
|
52
|
+
assertNoUntracked(cwd) {
|
|
53
|
+
const untracked = this.untrackedFiles(cwd);
|
|
54
|
+
if (untracked === '')
|
|
55
|
+
return;
|
|
56
|
+
throw new rules_config_1.CliExitError(1, '\n' + SEP +
|
|
57
|
+
'❌ ERROR: Untracked files present during merge finalize\n' +
|
|
58
|
+
SEP + '\n' +
|
|
59
|
+
'The tooling will not sweep untracked files into the squash commit. Commit\n' +
|
|
60
|
+
'or delete these, then re-run:\n\n' +
|
|
61
|
+
untracked + '\n\n' +
|
|
62
|
+
`See ${this.repoRootFinder.docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\n` +
|
|
63
|
+
SEP);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run a git command that is expected to succeed; abort the process with a clear message if it
|
|
67
|
+
* fails. Used for fetch/pull/checkout/commit where silently continuing on failure would operate on
|
|
68
|
+
* stale or wrong state.
|
|
69
|
+
*/
|
|
70
|
+
runGitChecked(args, errMsg) {
|
|
71
|
+
const result = (0, child_process_1.spawnSync)('git', args, { stdio: 'inherit' });
|
|
72
|
+
if (result.status !== 0) {
|
|
73
|
+
throw new rules_config_1.CliExitError(1, `❌ ${errMsg} (git ${args.join(' ')} exited ${String(result.status)})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Push HEAD to origin/<currentBranch>. Uses --force-with-lease for an existing remote branch (the
|
|
78
|
+
* 3-point squash rewrites history) and -u for a brand-new branch.
|
|
79
|
+
*/
|
|
80
|
+
ensurePushed(currentBranch) {
|
|
81
|
+
const remoteExists = (0, child_process_1.spawnSync)('git', ['ls-remote', '--exit-code', '--heads', 'origin', currentBranch]).status === 0;
|
|
82
|
+
if (remoteExists) {
|
|
83
|
+
this.runGitChecked(['push', '--force-with-lease', 'origin', `HEAD:${currentBranch}`], 'Failed to push branch');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.runGitChecked(['push', '-u', 'origin', `HEAD:${currentBranch}`], 'Failed to push new branch');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Run a read-only git query from the repo root; abort if git itself errors. Kept `cwd`-explicit
|
|
90
|
+
// because `git ls-files --others` is scoped to the cwd subtree.
|
|
91
|
+
gitQuery(args, cwd, failMsg) {
|
|
92
|
+
const out = (0, child_process_1.spawnSync)('git', args, { encoding: 'utf8', cwd });
|
|
93
|
+
if (out.status !== 0) {
|
|
94
|
+
throw new rules_config_1.CliExitError(1, `❌ ${failMsg}`);
|
|
95
|
+
}
|
|
96
|
+
return out.stdout.trim();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
exports.GitExec = GitExec;
|
|
100
|
+
exports.GitExec = GitExec = tslib_1.__decorate([
|
|
101
|
+
(0, core_context_1.provideSingleton)(),
|
|
102
|
+
(0, inversify_1.injectable)(),
|
|
103
|
+
tslib_1.__metadata("design:paramtypes", [rules_config_1.RepoRootFinder])
|
|
104
|
+
], GitExec);
|
|
98
105
|
//# sourceMappingURL=git-exec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-exec.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-exec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"git-exec.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-exec.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport { CliExitError, RepoRootFinder } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n// The AI-facing doc the failure messages point at (generated under `.webpieces/instruct-ai`, at the\n// REPO ROOT — resolved absolute so the AI opens it regardless of the cwd it reads the message from).\nconst GIT_WORKFLOW_DOC_NAME = 'webpieces.git-workflow.md';\n\n/** Shared git precondition checks + push logic for the PR/merge workflow. */\n@provideSingleton()\n@injectable()\nexport class GitExec {\n constructor(private readonly repoRootFinder: RepoRootFinder) {}\n\n // Tracked changes (staged + unstaged) AND untracked files, EXCLUDING gitignored paths — so\n // `.webpieces/` tooling artifacts never count. Empty string = fully committed.\n uncommittedFiles(cwd: string): string {\n return this.gitQuery(['status', '--porcelain'], cwd, 'Failed to run `git status --porcelain` to check the working tree.');\n }\n\n // Untracked files only (respects .gitignore). Empty string = none.\n untrackedFiles(cwd: string): string {\n return this.gitQuery(['ls-files', '--others', '--exclude-standard'], cwd, 'Failed to list untracked files (git ls-files --others).');\n }\n\n /**\n * Precondition for every PR/merge entry point: the working tree must be fully committed. The\n * webpieces tooling deliberately does NOT `git add`/`commit` the developer's work for them. Aborts\n * with instructions if anything is uncommitted (tracked or untracked, excluding gitignored).\n */\n assertCleanTree(cwd: string): void {\n const dirty = this.uncommittedFiles(cwd);\n if (dirty === '') return;\n throw new CliExitError(1,\n '\\n' + SEP +\n '❌ ERROR: You have uncommitted or untracked changes\\n' +\n SEP + '\\n' +\n 'The webpieces PR tooling will NOT commit your work for you. Commit your\\n' +\n 'changes, and either commit or delete any untracked files, then re-run.\\n\\n' +\n 'Working tree (git status --porcelain):\\n' +\n dirty + '\\n\\n' +\n `See ${this.repoRootFinder.docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\\n` +\n SEP,\n );\n }\n\n /**\n * Narrower guard for the merge-resolve commit point, where tracked resolutions are legitimately in\n * the tree but untracked files must NOT be swept into the squash commit. Lists any untracked files\n * and aborts so the AI commits or deletes them explicitly.\n */\n assertNoUntracked(cwd: string): void {\n const untracked = this.untrackedFiles(cwd);\n if (untracked === '') return;\n throw new CliExitError(1,\n '\\n' + SEP +\n '❌ ERROR: Untracked files present during merge finalize\\n' +\n SEP + '\\n' +\n 'The tooling will not sweep untracked files into the squash commit. Commit\\n' +\n 'or delete these, then re-run:\\n\\n' +\n untracked + '\\n\\n' +\n `See ${this.repoRootFinder.docPathFrom(cwd, GIT_WORKFLOW_DOC_NAME)} for the full merge + PR process.\\n` +\n SEP,\n );\n }\n\n /**\n * Run a git command that is expected to succeed; abort the process with a clear message if it\n * fails. Used for fetch/pull/checkout/commit where silently continuing on failure would operate on\n * stale or wrong state.\n */\n runGitChecked(args: string[], errMsg: string): void {\n const result = spawnSync('git', args, { stdio: 'inherit' });\n if (result.status !== 0) {\n throw new CliExitError(1, `❌ ${errMsg} (git ${args.join(' ')} exited ${String(result.status)})`);\n }\n }\n\n /**\n * Push HEAD to origin/<currentBranch>. Uses --force-with-lease for an existing remote branch (the\n * 3-point squash rewrites history) and -u for a brand-new branch.\n */\n ensurePushed(currentBranch: string): void {\n const remoteExists = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', currentBranch]).status === 0;\n if (remoteExists) {\n this.runGitChecked(['push', '--force-with-lease', 'origin', `HEAD:${currentBranch}`], 'Failed to push branch');\n } else {\n this.runGitChecked(['push', '-u', 'origin', `HEAD:${currentBranch}`], 'Failed to push new branch');\n }\n }\n\n // Run a read-only git query from the repo root; abort if git itself errors. Kept `cwd`-explicit\n // because `git ls-files --others` is scoped to the cwd subtree.\n private gitQuery(args: string[], cwd: string, failMsg: string): string {\n const out = spawnSync('git', args, { encoding: 'utf8', cwd });\n if (out.status !== 0) {\n throw new CliExitError(1, `❌ ${failMsg}`);\n }\n return out.stdout.trim();\n }\n}\n"]}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { AiBranchName } from './git-readAiBranchName';
|
|
2
|
+
import { MergeState } from './merge-state';
|
|
3
|
+
/** Computes the 3-point fork point and guards against improper merges-from-main. */
|
|
4
|
+
export declare class ForkPoint {
|
|
5
|
+
private readonly aiBranchName;
|
|
6
|
+
private readonly mergeState;
|
|
7
|
+
constructor(aiBranchName: AiBranchName, mergeState: MergeState);
|
|
8
|
+
forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string;
|
|
9
|
+
findForkPoint(workflow: string): Promise<void>;
|
|
10
|
+
private checkMergeCommits;
|
|
11
|
+
private printMergeFromMainError;
|
|
12
|
+
}
|
|
@@ -1,123 +1,132 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.findForkPoint = findForkPoint;
|
|
5
|
-
exports.main = main;
|
|
3
|
+
exports.ForkPoint = void 0;
|
|
6
4
|
const tslib_1 = require("tslib");
|
|
7
5
|
const child_process_1 = require("child_process");
|
|
8
6
|
const fs = tslib_1.__importStar(require("fs"));
|
|
9
7
|
const path = tslib_1.__importStar(require("path"));
|
|
10
8
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
9
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
10
|
+
const inversify_1 = require("inversify");
|
|
11
11
|
const git_readAiBranchName_1 = require("./git-readAiBranchName");
|
|
12
12
|
const merge_state_1 = require("./merge-state");
|
|
13
13
|
const SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
process.stderr.write(`Parent from main: ${parent}\n`);
|
|
22
|
-
process.stderr.write('\n');
|
|
23
|
-
process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\n');
|
|
24
|
-
process.stderr.write('\n');
|
|
25
|
-
process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\n');
|
|
26
|
-
process.stderr.write(' git fetch origin main\n');
|
|
27
|
-
process.stderr.write('\n');
|
|
28
|
-
process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\n');
|
|
29
|
-
process.stderr.write(' (it fatals inside a worktree: "main is already checked out at <primary>"):\n');
|
|
30
|
-
process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\n`);
|
|
31
|
-
process.stderr.write('\n');
|
|
32
|
-
process.stderr.write('3. Squash merge your old branch:\n');
|
|
33
|
-
process.stderr.write(` git merge --squash ${currentBranch}\n`);
|
|
34
|
-
process.stderr.write('\n');
|
|
35
|
-
process.stderr.write('4. Commit the squashed changes:\n');
|
|
36
|
-
process.stderr.write(` git add -A && git commit -m "Squashed from ${currentBranch}"\n`);
|
|
37
|
-
process.stderr.write('\n');
|
|
38
|
-
process.stderr.write('5. If you have an existing PR:\n');
|
|
39
|
-
process.stderr.write(` - Create a NEW PR for ${featureName}-v2\n`);
|
|
40
|
-
process.stderr.write(` - Close the old PR for ${currentBranch}\n`);
|
|
41
|
-
process.stderr.write('\n');
|
|
42
|
-
process.stderr.write(SEP);
|
|
43
|
-
process.stderr.write('\n');
|
|
44
|
-
}
|
|
45
|
-
function checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch) {
|
|
46
|
-
process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\n`);
|
|
47
|
-
for (const commit of mergeCommits) {
|
|
48
|
-
const parentsResult = (0, child_process_1.spawnSync)('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });
|
|
49
|
-
const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);
|
|
50
|
-
for (const parent of parents) {
|
|
51
|
-
const ancestorCheck = (0, child_process_1.spawnSync)('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);
|
|
52
|
-
const reverseCheck = (0, child_process_1.spawnSync)('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);
|
|
53
|
-
if (ancestorCheck.status === 0 && reverseCheck.status === 0) {
|
|
54
|
-
const errorJson = JSON.stringify({
|
|
55
|
-
error: 'Merge from main detected',
|
|
56
|
-
mergeCommit: commit,
|
|
57
|
-
parentFromMain: parent,
|
|
58
|
-
timestamp: new Date().toISOString(),
|
|
59
|
-
}, null, 2);
|
|
60
|
-
fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\n');
|
|
61
|
-
printMergeFromMainError(commit, parent, featureName, currentBranch);
|
|
62
|
-
throw new rules_config_1.CliExitError(1, '');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
14
|
+
/** Computes the 3-point fork point and guards against improper merges-from-main. */
|
|
15
|
+
let ForkPoint = class ForkPoint {
|
|
16
|
+
aiBranchName;
|
|
17
|
+
mergeState;
|
|
18
|
+
constructor(aiBranchName, mergeState) {
|
|
19
|
+
this.aiBranchName = aiBranchName;
|
|
20
|
+
this.mergeState = mergeState;
|
|
65
21
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// so the hash file was written to one path and read from another. Routing the writer through these
|
|
73
|
-
// same helpers is what keeps them from diverging again — guarded by git-findForkPoint.spec.ts.
|
|
74
|
-
function forkPointOutputDir(repoRoot, featureName, workflow) {
|
|
75
|
-
return workflow === 'review' ? (0, rules_config_1.prDirFor)(repoRoot, featureName) : (0, merge_state_1.mergeDirFor)(repoRoot, featureName);
|
|
76
|
-
}
|
|
77
|
-
async function findForkPoint(workflow) {
|
|
78
|
-
if (workflow !== 'review' && workflow !== 'merge') {
|
|
79
|
-
throw new rules_config_1.CliExitError(1, 'ERROR: Workflow argument required\n' +
|
|
80
|
-
'Usage: git-findForkPoint <workflow>\n' +
|
|
81
|
-
" workflow: 'review' or 'merge'");
|
|
22
|
+
// Single source of truth for the per-feature dir fork-point output is written to — the SAME nested
|
|
23
|
+
// home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;
|
|
24
|
+
// the review flow reads from prDirFor). Routing the writer through these same helpers is what keeps
|
|
25
|
+
// them from diverging again — guarded by git-findForkPoint.spec.ts.
|
|
26
|
+
forkPointOutputDir(repoRoot, featureName, workflow) {
|
|
27
|
+
return workflow === 'review' ? (0, rules_config_1.prDirFor)(repoRoot, featureName) : this.mergeState.mergeDirFor(repoRoot, featureName);
|
|
82
28
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
29
|
+
async findForkPoint(workflow) {
|
|
30
|
+
if (workflow !== 'review' && workflow !== 'merge') {
|
|
31
|
+
throw new rules_config_1.CliExitError(1, 'ERROR: Workflow argument required\n' +
|
|
32
|
+
'Usage: git-findForkPoint <workflow>\n' +
|
|
33
|
+
" workflow: 'review' or 'merge'");
|
|
34
|
+
}
|
|
35
|
+
const featureName = this.aiBranchName.getFeatureName();
|
|
36
|
+
const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
37
|
+
const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
38
|
+
const outputDir = this.forkPointOutputDir(repoRoot, featureName, workflow);
|
|
39
|
+
const prefix = workflow === 'review' ? 'review-' : 'updatemain-';
|
|
40
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
41
|
+
(0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });
|
|
42
|
+
const featureHead = (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf8' }).trim();
|
|
43
|
+
const originMain = (0, child_process_1.execSync)('git rev-parse origin/main', { encoding: 'utf8' }).trim();
|
|
44
|
+
process.stderr.write('Finding fork point using git merge-base...\n');
|
|
45
|
+
const forkPointResult = (0, child_process_1.spawnSync)('git', ['merge-base', 'origin/main', 'HEAD'], { encoding: 'utf8' });
|
|
46
|
+
const forkPoint = (forkPointResult.stdout ?? '').trim();
|
|
47
|
+
if (!forkPoint || forkPointResult.status !== 0) {
|
|
48
|
+
throw new rules_config_1.CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');
|
|
49
|
+
}
|
|
50
|
+
process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\n`);
|
|
51
|
+
process.stderr.write('Checking for improper merges from main...\n');
|
|
52
|
+
const mergeCommitsResult = (0, child_process_1.spawnSync)('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });
|
|
53
|
+
const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();
|
|
54
|
+
const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\n') : [];
|
|
55
|
+
if (mergeCommits.length > 0) {
|
|
56
|
+
this.checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
process.stderr.write('✅ No merge commits found (clean history)\n');
|
|
60
|
+
}
|
|
61
|
+
const hashesJson = JSON.stringify({
|
|
62
|
+
hashForkPoint: forkPoint,
|
|
63
|
+
hashFeatureHead: featureHead,
|
|
64
|
+
hashMainHead: originMain,
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
}, null, 2);
|
|
67
|
+
fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\n');
|
|
68
|
+
process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\n`);
|
|
97
69
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
70
|
+
checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch) {
|
|
71
|
+
process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\n`);
|
|
72
|
+
for (const commit of mergeCommits) {
|
|
73
|
+
const parentsResult = (0, child_process_1.spawnSync)('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });
|
|
74
|
+
const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);
|
|
75
|
+
for (const parent of parents) {
|
|
76
|
+
const ancestorCheck = (0, child_process_1.spawnSync)('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);
|
|
77
|
+
const reverseCheck = (0, child_process_1.spawnSync)('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);
|
|
78
|
+
if (ancestorCheck.status === 0 && reverseCheck.status === 0) {
|
|
79
|
+
const errorJson = JSON.stringify({
|
|
80
|
+
error: 'Merge from main detected',
|
|
81
|
+
mergeCommit: commit,
|
|
82
|
+
parentFromMain: parent,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
}, null, 2);
|
|
85
|
+
fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\n');
|
|
86
|
+
this.printMergeFromMainError(commit, parent, featureName, currentBranch);
|
|
87
|
+
throw new rules_config_1.CliExitError(1, '');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
process.stderr.write('✅ No improper merges from main detected\n');
|
|
105
92
|
}
|
|
106
|
-
|
|
107
|
-
process.stderr.write('
|
|
93
|
+
printMergeFromMainError(commit, parent, featureName, currentBranch) {
|
|
94
|
+
process.stderr.write('\n');
|
|
95
|
+
process.stderr.write(SEP);
|
|
96
|
+
process.stderr.write('❌ This branch merged main without pnpm wp-start-update\n');
|
|
97
|
+
process.stderr.write(SEP);
|
|
98
|
+
process.stderr.write('\n');
|
|
99
|
+
process.stderr.write(`Merge commit detected: ${commit}\n`);
|
|
100
|
+
process.stderr.write(`Parent from main: ${parent}\n`);
|
|
101
|
+
process.stderr.write('\n');
|
|
102
|
+
process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\n');
|
|
103
|
+
process.stderr.write('\n');
|
|
104
|
+
process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\n');
|
|
105
|
+
process.stderr.write(' git fetch origin main\n');
|
|
106
|
+
process.stderr.write('\n');
|
|
107
|
+
process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\n');
|
|
108
|
+
process.stderr.write(' (it fatals inside a worktree: "main is already checked out at <primary>"):\n');
|
|
109
|
+
process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\n`);
|
|
110
|
+
process.stderr.write('\n');
|
|
111
|
+
process.stderr.write('3. Squash merge your old branch:\n');
|
|
112
|
+
process.stderr.write(` git merge --squash ${currentBranch}\n`);
|
|
113
|
+
process.stderr.write('\n');
|
|
114
|
+
process.stderr.write('4. Commit the squashed changes:\n');
|
|
115
|
+
process.stderr.write(` git add -A && git commit -m "Squashed from ${currentBranch}"\n`);
|
|
116
|
+
process.stderr.write('\n');
|
|
117
|
+
process.stderr.write('5. If you have an existing PR:\n');
|
|
118
|
+
process.stderr.write(` - Create a NEW PR for ${featureName}-v2\n`);
|
|
119
|
+
process.stderr.write(` - Close the old PR for ${currentBranch}\n`);
|
|
120
|
+
process.stderr.write('\n');
|
|
121
|
+
process.stderr.write(SEP);
|
|
122
|
+
process.stderr.write('\n');
|
|
108
123
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
async function main() {
|
|
119
|
-
await findForkPoint(process.argv[2] ?? '');
|
|
120
|
-
}
|
|
121
|
-
if (require.main === module)
|
|
122
|
-
(0, rules_config_1.runMain)(main);
|
|
124
|
+
};
|
|
125
|
+
exports.ForkPoint = ForkPoint;
|
|
126
|
+
exports.ForkPoint = ForkPoint = tslib_1.__decorate([
|
|
127
|
+
(0, core_context_1.provideSingleton)(),
|
|
128
|
+
(0, inversify_1.injectable)(),
|
|
129
|
+
tslib_1.__metadata("design:paramtypes", [git_readAiBranchName_1.AiBranchName,
|
|
130
|
+
merge_state_1.MergeState])
|
|
131
|
+
], ForkPoint);
|
|
123
132
|
//# sourceMappingURL=git-findForkPoint.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-findForkPoint.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-findForkPoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { prDirFor, CliExitError, runMain } from '@webpieces/rules-config';\nimport { getFeatureName } from './git-readAiBranchName';\nimport { mergeDirFor } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\nfunction printMergeFromMainError(commit: string, parent: string, featureName: string, currentBranch: string): void {\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('❌ This branch merged main without pnpm wp-start-update\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write(`Merge commit detected: ${commit}\\n`);\n process.stderr.write(`Parent from main: ${parent}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\\n');\n process.stderr.write('\\n');\n process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\\n');\n process.stderr.write(' git fetch origin main\\n');\n process.stderr.write('\\n');\n process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\\n');\n process.stderr.write(' (it fatals inside a worktree: \"main is already checked out at <primary>\"):\\n');\n process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\\n`);\n process.stderr.write('\\n');\n process.stderr.write('3. Squash merge your old branch:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('4. Commit the squashed changes:\\n');\n process.stderr.write(` git add -A && git commit -m \"Squashed from ${currentBranch}\"\\n`);\n process.stderr.write('\\n');\n process.stderr.write('5. If you have an existing PR:\\n');\n process.stderr.write(` - Create a NEW PR for ${featureName}-v2\\n`);\n process.stderr.write(` - Close the old PR for ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n}\n\nfunction checkMergeCommits(mergeCommits: string[], outputDir: string, prefix: string, featureName: string, currentBranch: string): void {\n process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\\n`);\n\n for (const commit of mergeCommits) {\n const parentsResult = spawnSync('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });\n const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);\n\n for (const parent of parents) {\n const ancestorCheck = spawnSync('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);\n const reverseCheck = spawnSync('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);\n\n if (ancestorCheck.status === 0 && reverseCheck.status === 0) {\n const errorJson = JSON.stringify({\n error: 'Merge from main detected',\n mergeCommit: commit,\n parentFromMain: parent,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\\n');\n printMergeFromMainError(commit, parent, featureName, currentBranch);\n throw new CliExitError(1, '');\n }\n }\n }\n process.stderr.write('✅ No improper merges from main detected\\n');\n}\n\n// Single source of truth for the per-feature dir fork-point output is written to — the SAME nested\n// home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;\n// the review flow reads from prDirFor). Previously findForkPoint wrote to the legacy flat\n// `.webpieces/<workflow>-<feature>/` while readers looked under `.webpieces/merge-info/<feature>/`,\n// so the hash file was written to one path and read from another. Routing the writer through these\n// same helpers is what keeps them from diverging again — guarded by git-findForkPoint.spec.ts.\nexport function forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string {\n return workflow === 'review' ? prDirFor(repoRoot, featureName) : mergeDirFor(repoRoot, featureName);\n}\n\nexport async function findForkPoint(workflow: string): Promise<void> {\n if (workflow !== 'review' && workflow !== 'merge') {\n throw new CliExitError(1,\n 'ERROR: Workflow argument required\\n' +\n 'Usage: git-findForkPoint <workflow>\\n' +\n \" workflow: 'review' or 'merge'\",\n );\n }\n\n const featureName = getFeatureName();\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n\n const outputDir = forkPointOutputDir(repoRoot, featureName, workflow);\n const prefix = workflow === 'review' ? 'review-' : 'updatemain-';\n fs.mkdirSync(outputDir, { recursive: true });\n\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });\n\n const featureHead = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();\n const originMain = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();\n\n process.stderr.write('Finding fork point using git merge-base...\\n');\n\n const forkPointResult = spawnSync('git', ['merge-base', 'origin/main', 'HEAD'], { encoding: 'utf8' });\n const forkPoint = (forkPointResult.stdout ?? '').trim();\n\n if (!forkPoint || forkPointResult.status !== 0) {\n throw new CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');\n }\n\n process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\\n`);\n process.stderr.write('Checking for improper merges from main...\\n');\n\n const mergeCommitsResult = spawnSync('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });\n const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();\n const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\\n') : [];\n\n if (mergeCommits.length > 0) {\n checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);\n } else {\n process.stderr.write('✅ No merge commits found (clean history)\\n');\n }\n\n const hashesJson = JSON.stringify({\n hashForkPoint: forkPoint,\n hashFeatureHead: featureHead,\n hashMainHead: originMain,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\\n');\n process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\\n`);\n}\n\nexport async function main(): Promise<void> {\n await findForkPoint(process.argv[2] ?? '');\n}\n\nif (require.main === module) runMain(main);\n"]}
|
|
1
|
+
{"version":3,"file":"git-findForkPoint.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/git-findForkPoint.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { prDirFor, CliExitError } from '@webpieces/rules-config';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\nimport { MergeState } from './merge-state';\n\nconst SEP = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n';\n\n/** Computes the 3-point fork point and guards against improper merges-from-main. */\n@provideSingleton()\n@injectable()\nexport class ForkPoint {\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly mergeState: MergeState,\n ) {}\n\n // Single source of truth for the per-feature dir fork-point output is written to — the SAME nested\n // home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;\n // the review flow reads from prDirFor). Routing the writer through these same helpers is what keeps\n // them from diverging again — guarded by git-findForkPoint.spec.ts.\n forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string {\n return workflow === 'review' ? prDirFor(repoRoot, featureName) : this.mergeState.mergeDirFor(repoRoot, featureName);\n }\n\n async findForkPoint(workflow: string): Promise<void> {\n if (workflow !== 'review' && workflow !== 'merge') {\n throw new CliExitError(1,\n 'ERROR: Workflow argument required\\n' +\n 'Usage: git-findForkPoint <workflow>\\n' +\n \" workflow: 'review' or 'merge'\",\n );\n }\n\n const featureName = this.aiBranchName.getFeatureName();\n const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();\n const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();\n\n const outputDir = this.forkPointOutputDir(repoRoot, featureName, workflow);\n const prefix = workflow === 'review' ? 'review-' : 'updatemain-';\n fs.mkdirSync(outputDir, { recursive: true });\n\n spawnSync('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });\n\n const featureHead = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();\n const originMain = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();\n\n process.stderr.write('Finding fork point using git merge-base...\\n');\n\n const forkPointResult = spawnSync('git', ['merge-base', 'origin/main', 'HEAD'], { encoding: 'utf8' });\n const forkPoint = (forkPointResult.stdout ?? '').trim();\n\n if (!forkPoint || forkPointResult.status !== 0) {\n throw new CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');\n }\n\n process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\\n`);\n process.stderr.write('Checking for improper merges from main...\\n');\n\n const mergeCommitsResult = spawnSync('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });\n const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();\n const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\\n') : [];\n\n if (mergeCommits.length > 0) {\n this.checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);\n } else {\n process.stderr.write('✅ No merge commits found (clean history)\\n');\n }\n\n const hashesJson = JSON.stringify({\n hashForkPoint: forkPoint,\n hashFeatureHead: featureHead,\n hashMainHead: originMain,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\\n');\n process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\\n`);\n }\n\n private checkMergeCommits(mergeCommits: string[], outputDir: string, prefix: string, featureName: string, currentBranch: string): void {\n process.stderr.write(`Found ${mergeCommits.length} merge commit(s) to check...\\n`);\n\n for (const commit of mergeCommits) {\n const parentsResult = spawnSync('git', ['rev-list', '--parents', '-n', '1', commit], { encoding: 'utf8' });\n const parents = (parentsResult.stdout ?? '').trim().split(' ').slice(1);\n\n for (const parent of parents) {\n const ancestorCheck = spawnSync('git', ['merge-base', '--is-ancestor', parent, 'origin/main']);\n const reverseCheck = spawnSync('git', ['merge-base', '--is-ancestor', 'origin/main', parent]);\n\n if (ancestorCheck.status === 0 && reverseCheck.status === 0) {\n const errorJson = JSON.stringify({\n error: 'Merge from main detected',\n mergeCommit: commit,\n parentFromMain: parent,\n timestamp: new Date().toISOString(),\n }, null, 2);\n\n fs.writeFileSync(path.join(outputDir, `${prefix}forkpoint-error.json`), errorJson + '\\n');\n this.printMergeFromMainError(commit, parent, featureName, currentBranch);\n throw new CliExitError(1, '');\n }\n }\n }\n process.stderr.write('✅ No improper merges from main detected\\n');\n }\n\n private printMergeFromMainError(commit: string, parent: string, featureName: string, currentBranch: string): void {\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('❌ This branch merged main without pnpm wp-start-update\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n process.stderr.write(`Merge commit detected: ${commit}\\n`);\n process.stderr.write(`Parent from main: ${parent}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('This prevents clean squash-merge. To recover, follow these steps:\\n');\n process.stderr.write('\\n');\n process.stderr.write('1. Fetch the latest main (works on the primary repo AND in a worktree):\\n');\n process.stderr.write(' git fetch origin main\\n');\n process.stderr.write('\\n');\n process.stderr.write('2. Create a new branch OFF origin/main — do NOT `git checkout main`\\n');\n process.stderr.write(' (it fatals inside a worktree: \"main is already checked out at <primary>\"):\\n');\n process.stderr.write(` git checkout -b ${featureName}-v2 origin/main\\n`);\n process.stderr.write('\\n');\n process.stderr.write('3. Squash merge your old branch:\\n');\n process.stderr.write(` git merge --squash ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write('4. Commit the squashed changes:\\n');\n process.stderr.write(` git add -A && git commit -m \"Squashed from ${currentBranch}\"\\n`);\n process.stderr.write('\\n');\n process.stderr.write('5. If you have an existing PR:\\n');\n process.stderr.write(` - Create a NEW PR for ${featureName}-v2\\n`);\n process.stderr.write(` - Close the old PR for ${currentBranch}\\n`);\n process.stderr.write('\\n');\n process.stderr.write(SEP);\n process.stderr.write('\\n');\n }\n}\n"]}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { BranchNaming } from './branch-naming';
|
|
2
|
+
/** The stable feature identity used to key the merge-context + PR-body dirs. */
|
|
3
|
+
export declare class AiBranchName {
|
|
4
|
+
private readonly branchNaming;
|
|
5
|
+
constructor(branchNaming: BranchNaming);
|
|
6
|
+
getFeatureName(): string;
|
|
7
|
+
}
|