@wipcomputer/wip-ai-devops-toolbox 1.9.71-alpha.7 → 1.9.71-alpha.8
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/CHANGELOG.md +53 -0
- package/RELEASE-NOTES-v1-9-71-alpha-8.md +50 -0
- package/package.json +1 -1
- package/tools/wip-release/core.mjs +143 -18
- package/tools/wip-release/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.71-alpha.8 (2026-04-05)
|
|
4
|
+
|
|
5
|
+
# v1.9.71-alpha.8
|
|
6
|
+
|
|
7
|
+
## wip-release: automatic PR flow for protected main (Phase 4)
|
|
8
|
+
|
|
9
|
+
When `git push origin main` fails with GitHub's "protected branch" rejection (`GH006: Changes must be made through a pull request`), wip-release now automatically:
|
|
10
|
+
|
|
11
|
+
1. Creates a release branch `cc-mini/release-v<version>` at the current commit
|
|
12
|
+
2. Pushes the branch to origin
|
|
13
|
+
3. Opens a PR via `gh pr create` with title `release: v<version>`
|
|
14
|
+
4. Merges the PR via `gh pr merge --merge --delete-branch`
|
|
15
|
+
5. Pushes the tag separately (tags bypass branch protection on most GitHub setups)
|
|
16
|
+
6. Fast-forwards local main so downstream steps (deploy-public, etc.) have a clean state
|
|
17
|
+
|
|
18
|
+
Previously this was a 4-command manual workflow every release:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
git branch cc-mini/release-alpha-N
|
|
22
|
+
git push -u origin cc-mini/release-alpha-N
|
|
23
|
+
gh pr create --base main --head cc-mini/release-alpha-N --title '...'
|
|
24
|
+
gh pr merge <pr> --merge --delete-branch
|
|
25
|
+
git push origin v<version>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every release. Every time. Eliminated.
|
|
29
|
+
|
|
30
|
+
## Fallback behavior
|
|
31
|
+
|
|
32
|
+
If any step of the auto-PR flow fails (gh CLI missing, PR create failure, merge failure, tag push failure), wip-release logs a concrete recovery command for the exact failure mode and continues (non-fatal, matches prior push-failed behavior). The user can always complete the remaining steps manually.
|
|
33
|
+
|
|
34
|
+
## Direct push still works
|
|
35
|
+
|
|
36
|
+
If the repo allows direct push to main (typical for private staging repos), wip-release tries direct push first and only falls back to the PR flow on the specific GH006 / "protected branch" error. No behavioral change for unprotected repos.
|
|
37
|
+
|
|
38
|
+
## Files changed
|
|
39
|
+
|
|
40
|
+
- `tools/wip-release/core.mjs`: new `pushReleaseWithAutoPr(repoPath, newVersion, level)` and `logPushFailure(result, tag)` helpers. Three push sites in `release()`, `releaseHotfix()`, `releasePrerelease()` migrated to use the helper.
|
|
41
|
+
- `tools/wip-release/package.json`: 1.9.72 -> 1.9.73
|
|
42
|
+
- `CHANGELOG.md`: entry added
|
|
43
|
+
|
|
44
|
+
## Verified
|
|
45
|
+
|
|
46
|
+
- Module imports cleanly via `node -e "import('./tools/wip-release/core.mjs')"`.
|
|
47
|
+
- Error detection regex handles GH006 variants: `/protected branch|GH006|Changes must be made through a pull request/i`.
|
|
48
|
+
- All three release tracks (stable, prerelease, hotfix) use the same helper.
|
|
49
|
+
|
|
50
|
+
## Cross-references
|
|
51
|
+
|
|
52
|
+
- `ai/product/bugs/release-pipeline/2026-04-05--cc-mini--release-pipeline-master-plan.md` Phase 4 (Incident 4)
|
|
53
|
+
- `ai/product/bugs/master-plans/bugs-plan-04-05-2026-002.md` Wave 2 phase 7
|
|
54
|
+
- Prior ship: alpha.7 closed Phases 1, 2, 8 of the same plan.
|
|
55
|
+
|
|
3
56
|
## 1.9.71-alpha.7 (2026-04-05)
|
|
4
57
|
|
|
5
58
|
# v1.9.71-alpha.7
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# v1.9.71-alpha.8
|
|
2
|
+
|
|
3
|
+
## wip-release: automatic PR flow for protected main (Phase 4)
|
|
4
|
+
|
|
5
|
+
When `git push origin main` fails with GitHub's "protected branch" rejection (`GH006: Changes must be made through a pull request`), wip-release now automatically:
|
|
6
|
+
|
|
7
|
+
1. Creates a release branch `cc-mini/release-v<version>` at the current commit
|
|
8
|
+
2. Pushes the branch to origin
|
|
9
|
+
3. Opens a PR via `gh pr create` with title `release: v<version>`
|
|
10
|
+
4. Merges the PR via `gh pr merge --merge --delete-branch`
|
|
11
|
+
5. Pushes the tag separately (tags bypass branch protection on most GitHub setups)
|
|
12
|
+
6. Fast-forwards local main so downstream steps (deploy-public, etc.) have a clean state
|
|
13
|
+
|
|
14
|
+
Previously this was a 4-command manual workflow every release:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
git branch cc-mini/release-alpha-N
|
|
18
|
+
git push -u origin cc-mini/release-alpha-N
|
|
19
|
+
gh pr create --base main --head cc-mini/release-alpha-N --title '...'
|
|
20
|
+
gh pr merge <pr> --merge --delete-branch
|
|
21
|
+
git push origin v<version>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Every release. Every time. Eliminated.
|
|
25
|
+
|
|
26
|
+
## Fallback behavior
|
|
27
|
+
|
|
28
|
+
If any step of the auto-PR flow fails (gh CLI missing, PR create failure, merge failure, tag push failure), wip-release logs a concrete recovery command for the exact failure mode and continues (non-fatal, matches prior push-failed behavior). The user can always complete the remaining steps manually.
|
|
29
|
+
|
|
30
|
+
## Direct push still works
|
|
31
|
+
|
|
32
|
+
If the repo allows direct push to main (typical for private staging repos), wip-release tries direct push first and only falls back to the PR flow on the specific GH006 / "protected branch" error. No behavioral change for unprotected repos.
|
|
33
|
+
|
|
34
|
+
## Files changed
|
|
35
|
+
|
|
36
|
+
- `tools/wip-release/core.mjs`: new `pushReleaseWithAutoPr(repoPath, newVersion, level)` and `logPushFailure(result, tag)` helpers. Three push sites in `release()`, `releaseHotfix()`, `releasePrerelease()` migrated to use the helper.
|
|
37
|
+
- `tools/wip-release/package.json`: 1.9.72 -> 1.9.73
|
|
38
|
+
- `CHANGELOG.md`: entry added
|
|
39
|
+
|
|
40
|
+
## Verified
|
|
41
|
+
|
|
42
|
+
- Module imports cleanly via `node -e "import('./tools/wip-release/core.mjs')"`.
|
|
43
|
+
- Error detection regex handles GH006 variants: `/protected branch|GH006|Changes must be made through a pull request/i`.
|
|
44
|
+
- All three release tracks (stable, prerelease, hotfix) use the same helper.
|
|
45
|
+
|
|
46
|
+
## Cross-references
|
|
47
|
+
|
|
48
|
+
- `ai/product/bugs/release-pipeline/2026-04-05--cc-mini--release-pipeline-master-plan.md` Phase 4 (Incident 4)
|
|
49
|
+
- `ai/product/bugs/master-plans/bugs-plan-04-05-2026-002.md` Wave 2 phase 7
|
|
50
|
+
- Prior ship: alpha.7 closed Phases 1, 2, 8 of the same plan.
|
package/package.json
CHANGED
|
@@ -1507,6 +1507,125 @@ function checkTagCollision(repoPath, newVersion) {
|
|
|
1507
1507
|
return { ok: true };
|
|
1508
1508
|
}
|
|
1509
1509
|
|
|
1510
|
+
/**
|
|
1511
|
+
* Push the release commit + tag, handling protected-main rejection via
|
|
1512
|
+
* automatic PR flow.
|
|
1513
|
+
*
|
|
1514
|
+
* If `git push origin main` succeeds directly, we also push tags and return.
|
|
1515
|
+
* If it fails with a "protected branch" error (GH006), create a temporary
|
|
1516
|
+
* release branch from the current HEAD, push the branch, open a PR targeting
|
|
1517
|
+
* main, auto-merge it via `gh pr merge --merge --delete-branch`, then push
|
|
1518
|
+
* the tag separately (tags bypass branch protection).
|
|
1519
|
+
*
|
|
1520
|
+
* Phase 4 of the release-pipeline master plan. Earlier today this entire
|
|
1521
|
+
* flow was done manually every time, adding 2-3 minutes per release.
|
|
1522
|
+
*
|
|
1523
|
+
* Returns `{ ok: true, via }` on success where `via` is `"direct"` or `"pr"`.
|
|
1524
|
+
* Returns `{ ok: false, reason, detail }` on failure; caller logs and
|
|
1525
|
+
* continues (matches prior non-fatal push behavior).
|
|
1526
|
+
*/
|
|
1527
|
+
function pushReleaseWithAutoPr(repoPath, newVersion, level) {
|
|
1528
|
+
const tag = `v${newVersion}`;
|
|
1529
|
+
// 1. Try direct push first. Most repos allow it.
|
|
1530
|
+
try {
|
|
1531
|
+
execFileSync('git', ['push'], { cwd: repoPath, stdio: 'pipe' });
|
|
1532
|
+
execFileSync('git', ['push', 'origin', tag], { cwd: repoPath, stdio: 'pipe' });
|
|
1533
|
+
return { ok: true, via: 'direct' };
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
const msg = String(err?.stderr ?? err?.message ?? err);
|
|
1536
|
+
const isProtected = /protected branch|GH006|Changes must be made through a pull request/i.test(msg);
|
|
1537
|
+
if (!isProtected) {
|
|
1538
|
+
return { ok: false, reason: 'push-failed', detail: msg };
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// 2. Protected main. Open auto-PR flow.
|
|
1543
|
+
const releaseBranch = `cc-mini/release-${tag}`;
|
|
1544
|
+
console.log(` - Direct push to main refused (protected). Opening release PR...`);
|
|
1545
|
+
try {
|
|
1546
|
+
// Create branch at current HEAD
|
|
1547
|
+
execFileSync('git', ['branch', releaseBranch], { cwd: repoPath, stdio: 'pipe' });
|
|
1548
|
+
execFileSync('git', ['push', '-u', 'origin', releaseBranch], {
|
|
1549
|
+
cwd: repoPath, stdio: 'pipe'
|
|
1550
|
+
});
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
return {
|
|
1553
|
+
ok: false,
|
|
1554
|
+
reason: 'branch-push-failed',
|
|
1555
|
+
detail: String(err?.stderr ?? err?.message ?? err),
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// 3. Create and merge PR via gh CLI
|
|
1560
|
+
try {
|
|
1561
|
+
const prTitle = `release: ${tag}`;
|
|
1562
|
+
const prBody = `Release commit for ${tag} (${level}). Auto-generated by wip-release.`;
|
|
1563
|
+
execFileSync('gh', [
|
|
1564
|
+
'pr', 'create',
|
|
1565
|
+
'--base', 'main',
|
|
1566
|
+
'--head', releaseBranch,
|
|
1567
|
+
'--title', prTitle,
|
|
1568
|
+
'--body', prBody,
|
|
1569
|
+
], { cwd: repoPath, stdio: 'pipe' });
|
|
1570
|
+
execFileSync('gh', [
|
|
1571
|
+
'pr', 'merge', releaseBranch,
|
|
1572
|
+
'--merge', '--delete-branch',
|
|
1573
|
+
], { cwd: repoPath, stdio: 'pipe' });
|
|
1574
|
+
console.log(` ✓ Release PR merged`);
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
return {
|
|
1577
|
+
ok: false,
|
|
1578
|
+
reason: 'gh-pr-failed',
|
|
1579
|
+
detail: String(err?.stderr ?? err?.message ?? err),
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// 4. Push the tag separately. Tags bypass branch protection on most
|
|
1584
|
+
// GitHub setups, but if this fails the user can push the tag manually.
|
|
1585
|
+
try {
|
|
1586
|
+
execFileSync('git', ['push', 'origin', tag], { cwd: repoPath, stdio: 'pipe' });
|
|
1587
|
+
console.log(` ✓ Pushed tag ${tag}`);
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
return {
|
|
1590
|
+
ok: false,
|
|
1591
|
+
reason: 'tag-push-failed',
|
|
1592
|
+
detail: String(err?.stderr ?? err?.message ?? err),
|
|
1593
|
+
partialSuccess: 'pr-merged',
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// 5. Pull latest main so local HEAD reflects the merge commit. This
|
|
1598
|
+
// keeps subsequent git operations (like deploy-public) happy.
|
|
1599
|
+
try {
|
|
1600
|
+
execFileSync('git', ['fetch', 'origin', 'main'], { cwd: repoPath, stdio: 'pipe' });
|
|
1601
|
+
execFileSync('git', ['merge', '--ff-only', 'origin/main'], {
|
|
1602
|
+
cwd: repoPath, stdio: 'pipe'
|
|
1603
|
+
});
|
|
1604
|
+
} catch {
|
|
1605
|
+
// Non-fatal: main may have diverged (unlikely here). Deploy-public
|
|
1606
|
+
// will handle its own state.
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return { ok: true, via: 'pr' };
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function logPushFailure(result, tag) {
|
|
1613
|
+
console.log(` ! Push failed: ${result.reason}`);
|
|
1614
|
+
if (result.detail) {
|
|
1615
|
+
console.log(` ${result.detail.split('\n')[0]}`);
|
|
1616
|
+
}
|
|
1617
|
+
console.log(` Manual recovery:`);
|
|
1618
|
+
if (result.reason === 'push-failed' || result.reason === 'branch-push-failed') {
|
|
1619
|
+
console.log(` git push && git push origin ${tag}`);
|
|
1620
|
+
} else if (result.reason === 'gh-pr-failed') {
|
|
1621
|
+
console.log(` Open a PR for cc-mini/release-${tag} targeting main, merge, then:`);
|
|
1622
|
+
console.log(` git push origin ${tag}`);
|
|
1623
|
+
} else if (result.reason === 'tag-push-failed') {
|
|
1624
|
+
console.log(` PR already merged. Just push the tag:`);
|
|
1625
|
+
console.log(` git push origin ${tag}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1510
1629
|
function logMainBranchGuardFailure(result) {
|
|
1511
1630
|
if (result.reason === 'linked-worktree') {
|
|
1512
1631
|
console.log(` \u2717 wip-release must run from the main working tree, not a worktree.`);
|
|
@@ -1898,12 +2017,14 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1898
2017
|
gitCommitAndTag(repoPath, newVersion, notes);
|
|
1899
2018
|
console.log(` ✓ Committed and tagged v${newVersion}`);
|
|
1900
2019
|
|
|
1901
|
-
// 5. Push commit + tag
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2020
|
+
// 5. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
|
|
2021
|
+
{
|
|
2022
|
+
const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, level);
|
|
2023
|
+
if (pushResult.ok) {
|
|
2024
|
+
console.log(` ✓ Pushed to remote (${pushResult.via})`);
|
|
2025
|
+
} else {
|
|
2026
|
+
logPushFailure(pushResult, `v${newVersion}`);
|
|
2027
|
+
}
|
|
1907
2028
|
}
|
|
1908
2029
|
|
|
1909
2030
|
// Distribution results collector (#104)
|
|
@@ -2260,12 +2381,14 @@ export async function releasePrerelease({ repoPath, track, notes, dryRun, noPubl
|
|
|
2260
2381
|
execFileSync('git', ['tag', `v${newVersion}`], { cwd: repoPath, stdio: 'pipe' });
|
|
2261
2382
|
console.log(` \u2713 Committed and tagged v${newVersion}`);
|
|
2262
2383
|
|
|
2263
|
-
// 4. Push commit + tag
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2384
|
+
// 4. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
|
|
2385
|
+
{
|
|
2386
|
+
const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, track);
|
|
2387
|
+
if (pushResult.ok) {
|
|
2388
|
+
console.log(` \u2713 Pushed to remote (${pushResult.via})`);
|
|
2389
|
+
} else {
|
|
2390
|
+
logPushFailure(pushResult, `v${newVersion}`);
|
|
2391
|
+
}
|
|
2269
2392
|
}
|
|
2270
2393
|
|
|
2271
2394
|
const distResults = [];
|
|
@@ -2470,12 +2593,14 @@ export async function releaseHotfix({ repoPath, notes, notesSource, dryRun, noPu
|
|
|
2470
2593
|
gitCommitAndTag(repoPath, newVersion, notes);
|
|
2471
2594
|
console.log(` \u2713 Committed and tagged v${newVersion}`);
|
|
2472
2595
|
|
|
2473
|
-
// 5. Push commit + tag
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2596
|
+
// 5. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
|
|
2597
|
+
{
|
|
2598
|
+
const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, 'hotfix');
|
|
2599
|
+
if (pushResult.ok) {
|
|
2600
|
+
console.log(` \u2713 Pushed to remote (${pushResult.via})`);
|
|
2601
|
+
} else {
|
|
2602
|
+
logPushFailure(pushResult, `v${newVersion}`);
|
|
2603
|
+
}
|
|
2479
2604
|
}
|
|
2480
2605
|
|
|
2481
2606
|
const distResults = [];
|