@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.
Files changed (70) hide show
  1. package/package.json +7 -2
  2. package/src/dashboard/dashboard.d.ts +12 -3
  3. package/src/dashboard/dashboard.js +136 -129
  4. package/src/dashboard/dashboard.js.map +1 -1
  5. package/src/index.d.ts +2 -1
  6. package/src/index.js +4 -4
  7. package/src/index.js.map +1 -1
  8. package/src/scripts/commands/finish-update-command.d.ts +10 -0
  9. package/src/scripts/commands/finish-update-command.js +51 -0
  10. package/src/scripts/commands/finish-update-command.js.map +1 -0
  11. package/src/scripts/commands/finish-upsert-pr-command.d.ts +22 -0
  12. package/src/scripts/commands/finish-upsert-pr-command.js +131 -0
  13. package/src/scripts/commands/finish-upsert-pr-command.js.map +1 -0
  14. package/src/scripts/commands/start-update-command.d.ts +9 -0
  15. package/src/scripts/commands/start-update-command.js +67 -0
  16. package/src/scripts/commands/start-update-command.js.map +1 -0
  17. package/src/scripts/commands/start-upsert-pr-command.d.ts +15 -0
  18. package/src/scripts/commands/start-upsert-pr-command.js +73 -0
  19. package/src/scripts/commands/start-upsert-pr-command.js.map +1 -0
  20. package/src/scripts/git-finishUpsertPr.d.ts +1 -1
  21. package/src/scripts/git-finishUpsertPr.js +11 -109
  22. package/src/scripts/git-finishUpsertPr.js.map +1 -1
  23. package/src/scripts/git-gatherInfo.d.ts +15 -1
  24. package/src/scripts/git-gatherInfo.js +79 -61
  25. package/src/scripts/git-gatherInfo.js.map +1 -1
  26. package/src/scripts/pr-gate-app.d.ts +25 -0
  27. package/src/scripts/pr-gate-app.js +56 -0
  28. package/src/scripts/pr-gate-app.js.map +1 -0
  29. package/src/scripts/workflow/branch-naming.d.ts +11 -8
  30. package/src/scripts/workflow/branch-naming.js +30 -21
  31. package/src/scripts/workflow/branch-naming.js.map +1 -1
  32. package/src/scripts/workflow/build-affected.d.ts +21 -30
  33. package/src/scripts/workflow/build-affected.js +57 -63
  34. package/src/scripts/workflow/build-affected.js.map +1 -1
  35. package/src/scripts/workflow/cleanTmp.d.ts +6 -1
  36. package/src/scripts/workflow/cleanTmp.js +83 -78
  37. package/src/scripts/workflow/cleanTmp.js.map +1 -1
  38. package/src/scripts/workflow/git-exec.d.ts +32 -28
  39. package/src/scripts/workflow/git-exec.js +92 -85
  40. package/src/scripts/workflow/git-exec.js.map +1 -1
  41. package/src/scripts/workflow/git-findForkPoint.d.ts +12 -3
  42. package/src/scripts/workflow/git-findForkPoint.js +116 -107
  43. package/src/scripts/workflow/git-findForkPoint.js.map +1 -1
  44. package/src/scripts/workflow/git-readAiBranchName.d.ts +7 -2
  45. package/src/scripts/workflow/git-readAiBranchName.js +24 -15
  46. package/src/scripts/workflow/git-readAiBranchName.js.map +1 -1
  47. package/src/scripts/workflow/merge-end.d.ts +21 -6
  48. package/src/scripts/workflow/merge-end.js +147 -133
  49. package/src/scripts/workflow/merge-end.js.map +1 -1
  50. package/src/scripts/workflow/merge-start.d.ts +22 -1
  51. package/src/scripts/workflow/merge-start.js +180 -186
  52. package/src/scripts/workflow/merge-start.js.map +1 -1
  53. package/src/scripts/workflow/merge-state.d.ts +26 -23
  54. package/src/scripts/workflow/merge-state.js +131 -137
  55. package/src/scripts/workflow/merge-state.js.map +1 -1
  56. package/src/scripts/workflow/open-pr-check.d.ts +7 -1
  57. package/src/scripts/workflow/open-pr-check.js +34 -23
  58. package/src/scripts/workflow/open-pr-check.js.map +1 -1
  59. package/src/scripts/workflow/run-update.d.ts +13 -1
  60. package/src/scripts/workflow/run-update.js +59 -28
  61. package/src/scripts/workflow/run-update.js.map +1 -1
  62. package/src/scripts/wp-finish-update.d.ts +1 -1
  63. package/src/scripts/wp-finish-update.js +11 -30
  64. package/src/scripts/wp-finish-update.js.map +1 -1
  65. package/src/scripts/wp-start-update.d.ts +1 -2
  66. package/src/scripts/wp-start-update.js +11 -51
  67. package/src/scripts/wp-start-update.js.map +1 -1
  68. package/src/scripts/wp-start-upsert-pr.d.ts +1 -1
  69. package/src/scripts/wp-start-upsert-pr.js +11 -47
  70. 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.uncommittedFiles = uncommittedFiles;
4
- exports.untrackedFiles = untrackedFiles;
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
- // Run a read-only git query from the repo root; abort if git itself errors. Kept `cwd`-explicit (not
16
- // process.cwd()) because `git ls-files --others` is scoped to the cwd subtree — running from a subdir
17
- // would miss untracked files elsewhere in the repo.
18
- function gitQuery(args, cwd, failMsg) {
19
- const out = (0, child_process_1.spawnSync)('git', args, { encoding: 'utf8', cwd });
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
- return out.stdout.trim();
24
- }
25
- // Tracked changes (staged + unstaged) AND untracked files, EXCLUDING gitignored paths — so `.webpieces/`
26
- // tooling artifacts never count. Empty string = fully committed. Exported for unit testing.
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
- * Push HEAD to origin/<currentBranch>. Single source of truth shared by wp-start-upsert-pr and
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
- else {
95
- runGitChecked(['push', '-u', 'origin', `HEAD:${currentBranch}`], 'Failed to push new branch');
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":";;AAqBA,4CAEC;AAGD,wCAEC;AASD,0CAcC;AAOD,8CAaC;AAOD,sCAKC;AAOD,oCAOC;AAjGD,iDAA0C;AAC1C,0DAAuE;AAEvE,MAAM,GAAG,GAAG,0DAA0D,CAAC;AACvE,oGAAoG;AACpG,qGAAqG;AACrG,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AAE1D,qGAAqG;AACrG,sGAAsG;AACtG,oDAAoD;AACpD,SAAS,QAAQ,CAAC,IAAc,EAAE,GAAW,EAAE,OAAe;IAC1D,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9D,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,yGAAyG;AACzG,4FAA4F;AAC5F,SAAgB,gBAAgB,CAAC,GAAW;IACxC,OAAO,QAAQ,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,GAAG,EAAE,mEAAmE,CAAC,CAAC;AACzH,CAAC;AAED,8FAA8F;AAC9F,SAAgB,cAAc,CAAC,GAAW;IACtC,OAAO,QAAQ,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE,GAAG,EAAE,yDAAyD,CAAC,CAAC;AACpI,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO;IACzB,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG;QACV,sDAAsD;QACtD,GAAG,GAAG,IAAI;QACV,2EAA2E;QAC3E,4EAA4E;QAC5E,0CAA0C;QAC1C,KAAK,GAAG,MAAM;QACd,OAAO,IAAI,6BAAc,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,qBAAqB,CAAC,qCAAqC;QACxG,GAAG,CACN,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,GAAW;IACzC,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO;IAC7B,MAAM,IAAI,2BAAY,CAAC,CAAC,EACpB,IAAI,GAAG,GAAG;QACV,0DAA0D;QAC1D,GAAG,GAAG,IAAI;QACV,6EAA6E;QAC7E,mCAAmC;QACnC,SAAS,GAAG,MAAM;QAClB,OAAO,IAAI,6BAAc,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,qBAAqB,CAAC,qCAAqC;QACxG,GAAG,CACN,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAAC,IAAc,EAAE,MAAc;IACxD,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,2BAAY,CAAC,CAAC,EAAE,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACrG,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,aAAqB;IAC9C,MAAM,YAAY,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACrH,IAAI,YAAY,EAAE,CAAC;QACf,aAAa,CAAC,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,QAAQ,aAAa,EAAE,CAAC,EAAE,uBAAuB,CAAC,CAAC;IAC9G,CAAC;SAAM,CAAC;QACJ,aAAa,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,aAAa,EAAE,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAClG,CAAC;AACL,CAAC","sourcesContent":["import { spawnSync } from 'child_process';\nimport { CliExitError, RepoRootFinder } from '@webpieces/rules-config';\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// Run a read-only git query from the repo root; abort if git itself errors. Kept `cwd`-explicit (not\n// process.cwd()) because `git ls-files --others` is scoped to the cwd subtree — running from a subdir\n// would miss untracked files elsewhere in the repo.\nfunction 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// Tracked changes (staged + unstaged) AND untracked files, EXCLUDING gitignored paths — so `.webpieces/`\n// tooling artifacts never count. Empty string = fully committed. Exported for unit testing.\nexport function uncommittedFiles(cwd: string): string {\n return 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. Exported for unit testing.\nexport function untrackedFiles(cwd: string): string {\n return 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 webpieces\n * tooling deliberately does NOT `git add`/`commit` the developer's work for them — a blanket `git add\n * -A` once swept a stale untracked directory into a squash commit, and building the working tree while\n * pushing HEAD let an uncommitted fix pass the build gate yet push a stale commit. Aborts with\n * instructions if anything is uncommitted (tracked or untracked, excluding gitignored).\n */\nexport function assertCleanTree(cwd: string): void {\n const dirty = 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 ${new 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 the\n * tree but untracked files must NOT be swept into the squash commit (replaces the old blanket `git add\n * -A`). Lists any untracked files and aborts so the AI commits or deletes them explicitly.\n */\nexport function assertNoUntracked(cwd: string): void {\n const untracked = 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 ${new 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\n * if it fails. Used for fetch/pull/checkout/commit where silently continuing on failure\n * would operate on stale or wrong state.\n */\nexport function 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>. Single source of truth shared by wp-start-upsert-pr and\n * wp-finish-upsert-pr (they pushed with identical copy-pasted logic). Uses --force-with-lease for an\n * existing remote branch (the 3-point squash rewrites history) and -u for a brand-new branch.\n */\nexport function ensurePushed(currentBranch: string): void {\n const remoteExists = spawnSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', currentBranch]).status === 0;\n if (remoteExists) {\n runGitChecked(['push', '--force-with-lease', 'origin', `HEAD:${currentBranch}`], 'Failed to push branch');\n } else {\n runGitChecked(['push', '-u', 'origin', `HEAD:${currentBranch}`], 'Failed to push new branch');\n }\n}\n"]}
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
- export declare function forkPointOutputDir(repoRoot: string, featureName: string, workflow: string): string;
2
- export declare function findForkPoint(workflow: string): Promise<void>;
3
- export declare function main(): Promise<void>;
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.forkPointOutputDir = forkPointOutputDir;
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
- function printMergeFromMainError(commit, parent, featureName, currentBranch) {
15
- process.stderr.write('\n');
16
- process.stderr.write(SEP);
17
- process.stderr.write('❌ This branch merged main without pnpm wp-start-update\n');
18
- process.stderr.write(SEP);
19
- process.stderr.write('\n');
20
- process.stderr.write(`Merge commit detected: ${commit}\n`);
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
- process.stderr.write('✅ No improper merges from main detected\n');
67
- }
68
- // Single source of truth for the per-feature dir fork-point output is written to the SAME nested
69
- // home the readers use (git-gatherInfo / merge-start read updatemain-hashes.json from mergeDirFor;
70
- // the review flow reads from prDirFor). Previously findForkPoint wrote to the legacy flat
71
- // `.webpieces/<workflow>-<feature>/` while readers looked under `.webpieces/merge-info/<feature>/`,
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
- const featureName = (0, git_readAiBranchName_1.getFeatureName)();
84
- const currentBranch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
85
- const repoRoot = (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
86
- const outputDir = forkPointOutputDir(repoRoot, featureName, workflow);
87
- const prefix = workflow === 'review' ? 'review-' : 'updatemain-';
88
- fs.mkdirSync(outputDir, { recursive: true });
89
- (0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { stdio: 'ignore' });
90
- const featureHead = (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf8' }).trim();
91
- const originMain = (0, child_process_1.execSync)('git rev-parse origin/main', { encoding: 'utf8' }).trim();
92
- process.stderr.write('Finding fork point using git merge-base...\n');
93
- const forkPointResult = (0, child_process_1.spawnSync)('git', ['merge-base', 'origin/main', 'HEAD'], { encoding: 'utf8' });
94
- const forkPoint = (forkPointResult.stdout ?? '').trim();
95
- if (!forkPoint || forkPointResult.status !== 0) {
96
- throw new rules_config_1.CliExitError(1, 'ERROR: Could not find common ancestor with origin/main');
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
- process.stderr.write(`✅ Fork point found: ${forkPoint.slice(0, 7)}\n`);
99
- process.stderr.write('Checking for improper merges from main...\n');
100
- const mergeCommitsResult = (0, child_process_1.spawnSync)('git', ['log', `${forkPoint}..HEAD`, '--merges', '--format=%H'], { encoding: 'utf8' });
101
- const mergeCommitsRaw = (mergeCommitsResult.stdout ?? '').trim();
102
- const mergeCommits = mergeCommitsRaw ? mergeCommitsRaw.split('\n') : [];
103
- if (mergeCommits.length > 0) {
104
- checkMergeCommits(mergeCommits, outputDir, prefix, featureName, currentBranch);
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
- else {
107
- process.stderr.write('✅ No merge commits found (clean history)\n');
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
- const hashesJson = JSON.stringify({
110
- hashForkPoint: forkPoint,
111
- hashFeatureHead: featureHead,
112
- hashMainHead: originMain,
113
- timestamp: new Date().toISOString(),
114
- }, null, 2);
115
- fs.writeFileSync(path.join(outputDir, `${prefix}hashes.json`), hashesJson + '\n');
116
- process.stderr.write(`✅ Hash points written to: ${outputDir}/${prefix}hashes.json\n`);
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
- export declare function getFeatureName(): string;
2
- export declare function main(): Promise<void>;
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
+ }