@natjswenson/shipflow 0.2.6 → 0.3.1
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 +320 -0
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/SKILL.md +26 -17
- package/bin/shipflow.js +16 -6
- package/lib/apply.mjs +19 -9
- package/lib/detect.mjs +78 -4
- package/lib/pattern-registry.mjs +79 -0
- package/lib/patterns/dev-main-promotion/index.mjs +49 -0
- package/lib/patterns/gitflow/index.mjs +55 -0
- package/lib/patterns/github-flow/index.mjs +43 -0
- package/lib/plan.mjs +54 -48
- package/lib/render.mjs +42 -2
- package/package.json +4 -2
- package/skill-invariants.json +20 -1
- package/templates/gitflow/hotfix-automerge.yml.tmpl +65 -0
- package/templates/gitflow/hotfix-merge-back.yml.tmpl +54 -0
- package/templates/gitflow/release-automerge.yml.tmpl +65 -0
- package/templates/gitflow/release-merge-back.yml.tmpl +54 -0
- package/templates/github-flow/main-automerge.yml.tmpl +66 -0
- /package/templates/{dev-to-main-automerge.yml.tmpl → dev-main-promotion/dev-to-main-automerge.yml.tmpl} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: auto-merge hotfix/* to {{MAIN_BRANCH}}
|
|
2
|
+
|
|
3
|
+
# Rendered by shipflow's apply.mjs from this template. Do not hand-edit
|
|
4
|
+
# without also updating .github/shipflow.json's renderedTemplateHashes entry
|
|
5
|
+
# for this file, or shipflow's next apply will refuse to overwrite it
|
|
6
|
+
# (handEditDetected) until an explicit --force is passed. Commit both files
|
|
7
|
+
# together in the same commit.
|
|
8
|
+
#
|
|
9
|
+
# GH_TOKEN uses config.release.releaseCredential, NOT a hardcoded
|
|
10
|
+
# secrets.GITHUB_TOKEN, because of GitHub's loop-prevention rule: a PR
|
|
11
|
+
# auto-merged via `gh pr merge --auto` run under the default GITHUB_TOKEN
|
|
12
|
+
# completes (later, asynchronously, once checks pass) attributed to the
|
|
13
|
+
# github-actions[bot] identity — and a `pull_request: closed` event
|
|
14
|
+
# resulting from that bot-attributed merge does NOT trigger this or any
|
|
15
|
+
# other workflow's `on: pull_request` handlers. Confirmed empirically:
|
|
16
|
+
# an identical PR merged by a real, PAT-authenticated actor fired the
|
|
17
|
+
# closed-event trigger immediately; one completed by GITHUB_TOKEN-enabled
|
|
18
|
+
# auto-merge fired no run at all. releaseCredential must therefore name a
|
|
19
|
+
# real PAT/App-installation-token secret (not GITHUB_TOKEN) for
|
|
20
|
+
# label-release-pending to ever actually run.
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
pull_request:
|
|
24
|
+
types: [opened, reopened, synchronize, closed]
|
|
25
|
+
branches: [{{MAIN_BRANCH}}]
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
# Enables native GitHub auto-merge on open/reopen/synchronize — this job
|
|
33
|
+
# does NOT wait for checks itself; it turns on auto-merge and exits. The
|
|
34
|
+
# actual merge happens asynchronously, later, whenever GitHub's own
|
|
35
|
+
# required-checks gate is satisfied (see the design's discussion of why a
|
|
36
|
+
# bespoke polling/blocking job was rejected).
|
|
37
|
+
auto-merge:
|
|
38
|
+
if: >-
|
|
39
|
+
github.event.action != 'closed' &&
|
|
40
|
+
startsWith(github.event.pull_request.head.ref, '{{HOTFIX_BRANCH_PREFIX}}')
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- name: Enable auto-merge
|
|
44
|
+
run: gh pr merge --auto {{MERGE_FLAG}} "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
|
|
45
|
+
env:
|
|
46
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
47
|
+
|
|
48
|
+
# Fires once, when the hotfix PR actually merges (a separate event from
|
|
49
|
+
# the job above, which only *enables* auto-merge). Applies a durable
|
|
50
|
+
# release-pending label so a later, disconnected shipflow invocation can
|
|
51
|
+
# find this promotion and ask about a release — the merge completion has
|
|
52
|
+
# no live Claude session attached to react to it directly.
|
|
53
|
+
label-release-pending:
|
|
54
|
+
if: >-
|
|
55
|
+
github.event.action == 'closed' &&
|
|
56
|
+
github.event.pull_request.merged == true &&
|
|
57
|
+
startsWith(github.event.pull_request.head.ref, '{{HOTFIX_BRANCH_PREFIX}}')
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
permissions:
|
|
60
|
+
pull-requests: write
|
|
61
|
+
steps:
|
|
62
|
+
- name: Apply release-pending label
|
|
63
|
+
run: gh pr edit "${{ github.event.pull_request.number }}" --add-label release-pending --repo "${{ github.repository }}"
|
|
64
|
+
env:
|
|
65
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: merge {{MAIN_BRANCH}} back to {{DEV_BRANCH}} after a hotfix
|
|
2
|
+
|
|
3
|
+
# Rendered by shipflow's apply.mjs. See dev-to-main-automerge.yml.tmpl's header
|
|
4
|
+
# comment for the general hand-edit/renderedTemplateHashes discipline — same rules
|
|
5
|
+
# apply here.
|
|
6
|
+
#
|
|
7
|
+
# GitFlow's hotfix branches merge into BOTH main and develop — this is the
|
|
8
|
+
# defining semantic of a hotfix, not an optional convention (confirmed via
|
|
9
|
+
# research: git-flow CLI's own `hotfix finish` command does both merges
|
|
10
|
+
# atomically). This job runs the develop-side merge automatically once the
|
|
11
|
+
# main-side merge (hotfix-automerge.yml) lands.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
pull_request:
|
|
15
|
+
types: [closed]
|
|
16
|
+
branches: [{{MAIN_BRANCH}}]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
merge-back-to-dev:
|
|
23
|
+
if: >-
|
|
24
|
+
github.event.pull_request.merged == true &&
|
|
25
|
+
startsWith(github.event.pull_request.head.ref, '{{HOTFIX_BRANCH_PREFIX}}')
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
token: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
32
|
+
- name: Attempt a clean merge of {{MAIN_BRANCH}} into {{DEV_BRANCH}}
|
|
33
|
+
id: merge
|
|
34
|
+
continue-on-error: true
|
|
35
|
+
run: |
|
|
36
|
+
git config user.name "shipflow"
|
|
37
|
+
git config user.email "shipflow@users.noreply.github.com"
|
|
38
|
+
git checkout {{DEV_BRANCH}}
|
|
39
|
+
git merge origin/{{MAIN_BRANCH}} --no-edit
|
|
40
|
+
git push origin {{DEV_BRANCH}}
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
43
|
+
- name: On any failure (conflict, rejected push, or otherwise), open a PR for manual resolution
|
|
44
|
+
if: steps.merge.outcome == 'failure'
|
|
45
|
+
run: |
|
|
46
|
+
git merge --abort || true
|
|
47
|
+
BRANCH="shipflow/merge-back-{{MAIN_BRANCH}}-to-{{DEV_BRANCH}}-${{ github.run_id }}"
|
|
48
|
+
git checkout -b "$BRANCH" origin/{{MAIN_BRANCH}}
|
|
49
|
+
git push origin "$BRANCH"
|
|
50
|
+
gh pr create --base {{DEV_BRANCH}} --head "$BRANCH" \
|
|
51
|
+
--title "Manual merge-back needed: {{MAIN_BRANCH}} -> {{DEV_BRANCH}}" \
|
|
52
|
+
--body "Automatic merge-back failed (conflict or rejected push) after a hotfix/release merged to {{MAIN_BRANCH}}. Resolve and merge this PR manually."
|
|
53
|
+
env:
|
|
54
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: auto-merge release/* to {{MAIN_BRANCH}}
|
|
2
|
+
|
|
3
|
+
# Rendered by shipflow's apply.mjs from this template. Do not hand-edit
|
|
4
|
+
# without also updating .github/shipflow.json's renderedTemplateHashes entry
|
|
5
|
+
# for this file, or shipflow's next apply will refuse to overwrite it
|
|
6
|
+
# (handEditDetected) until an explicit --force is passed. Commit both files
|
|
7
|
+
# together in the same commit.
|
|
8
|
+
#
|
|
9
|
+
# GH_TOKEN uses config.release.releaseCredential, NOT a hardcoded
|
|
10
|
+
# secrets.GITHUB_TOKEN, because of GitHub's loop-prevention rule: a PR
|
|
11
|
+
# auto-merged via `gh pr merge --auto` run under the default GITHUB_TOKEN
|
|
12
|
+
# completes (later, asynchronously, once checks pass) attributed to the
|
|
13
|
+
# github-actions[bot] identity — and a `pull_request: closed` event
|
|
14
|
+
# resulting from that bot-attributed merge does NOT trigger this or any
|
|
15
|
+
# other workflow's `on: pull_request` handlers. Confirmed empirically:
|
|
16
|
+
# an identical PR merged by a real, PAT-authenticated actor fired the
|
|
17
|
+
# closed-event trigger immediately; one completed by GITHUB_TOKEN-enabled
|
|
18
|
+
# auto-merge fired no run at all. releaseCredential must therefore name a
|
|
19
|
+
# real PAT/App-installation-token secret (not GITHUB_TOKEN) for
|
|
20
|
+
# label-release-pending to ever actually run.
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
pull_request:
|
|
24
|
+
types: [opened, reopened, synchronize, closed]
|
|
25
|
+
branches: [{{MAIN_BRANCH}}]
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write
|
|
29
|
+
pull-requests: write
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
# Enables native GitHub auto-merge on open/reopen/synchronize — this job
|
|
33
|
+
# does NOT wait for checks itself; it turns on auto-merge and exits. The
|
|
34
|
+
# actual merge happens asynchronously, later, whenever GitHub's own
|
|
35
|
+
# required-checks gate is satisfied (see the design's discussion of why a
|
|
36
|
+
# bespoke polling/blocking job was rejected).
|
|
37
|
+
auto-merge:
|
|
38
|
+
if: >-
|
|
39
|
+
github.event.action != 'closed' &&
|
|
40
|
+
startsWith(github.event.pull_request.head.ref, '{{RELEASE_BRANCH_PREFIX}}')
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- name: Enable auto-merge
|
|
44
|
+
run: gh pr merge --auto {{MERGE_FLAG}} "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
|
|
45
|
+
env:
|
|
46
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
47
|
+
|
|
48
|
+
# Fires once, when the release PR actually merges (a separate event from
|
|
49
|
+
# the job above, which only *enables* auto-merge). Applies a durable
|
|
50
|
+
# release-pending label so a later, disconnected shipflow invocation can
|
|
51
|
+
# find this promotion and ask about a release — the merge completion has
|
|
52
|
+
# no live Claude session attached to react to it directly.
|
|
53
|
+
label-release-pending:
|
|
54
|
+
if: >-
|
|
55
|
+
github.event.action == 'closed' &&
|
|
56
|
+
github.event.pull_request.merged == true &&
|
|
57
|
+
startsWith(github.event.pull_request.head.ref, '{{RELEASE_BRANCH_PREFIX}}')
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
permissions:
|
|
60
|
+
pull-requests: write
|
|
61
|
+
steps:
|
|
62
|
+
- name: Apply release-pending label
|
|
63
|
+
run: gh pr edit "${{ github.event.pull_request.number }}" --add-label release-pending --repo "${{ github.repository }}"
|
|
64
|
+
env:
|
|
65
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: merge {{MAIN_BRANCH}} back to {{DEV_BRANCH}} after a release
|
|
2
|
+
|
|
3
|
+
# Rendered by shipflow's apply.mjs. See dev-to-main-automerge.yml.tmpl's header
|
|
4
|
+
# comment for the general hand-edit/renderedTemplateHashes discipline — same rules
|
|
5
|
+
# apply here.
|
|
6
|
+
#
|
|
7
|
+
# GitFlow's release branches merge into BOTH main and develop — this is the
|
|
8
|
+
# defining semantic of a release, not an optional convention (confirmed via
|
|
9
|
+
# research: git-flow CLI's own `release finish` command does both merges
|
|
10
|
+
# atomically, mirroring hotfix finish). This job runs the develop-side merge
|
|
11
|
+
# automatically once the main-side merge (release-automerge.yml) lands.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
pull_request:
|
|
15
|
+
types: [closed]
|
|
16
|
+
branches: [{{MAIN_BRANCH}}]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
merge-back-to-dev:
|
|
23
|
+
if: >-
|
|
24
|
+
github.event.pull_request.merged == true &&
|
|
25
|
+
startsWith(github.event.pull_request.head.ref, '{{RELEASE_BRANCH_PREFIX}}')
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
token: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
32
|
+
- name: Attempt a clean merge of {{MAIN_BRANCH}} into {{DEV_BRANCH}}
|
|
33
|
+
id: merge
|
|
34
|
+
continue-on-error: true
|
|
35
|
+
run: |
|
|
36
|
+
git config user.name "shipflow"
|
|
37
|
+
git config user.email "shipflow@users.noreply.github.com"
|
|
38
|
+
git checkout {{DEV_BRANCH}}
|
|
39
|
+
git merge origin/{{MAIN_BRANCH}} --no-edit
|
|
40
|
+
git push origin {{DEV_BRANCH}}
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
43
|
+
- name: On any failure (conflict, rejected push, or otherwise), open a PR for manual resolution
|
|
44
|
+
if: steps.merge.outcome == 'failure'
|
|
45
|
+
run: |
|
|
46
|
+
git merge --abort || true
|
|
47
|
+
BRANCH="shipflow/merge-back-{{MAIN_BRANCH}}-to-{{DEV_BRANCH}}-${{ github.run_id }}"
|
|
48
|
+
git checkout -b "$BRANCH" origin/{{MAIN_BRANCH}}
|
|
49
|
+
git push origin "$BRANCH"
|
|
50
|
+
gh pr create --base {{DEV_BRANCH}} --head "$BRANCH" \
|
|
51
|
+
--title "Manual merge-back needed: {{MAIN_BRANCH}} -> {{DEV_BRANCH}}" \
|
|
52
|
+
--body "Automatic merge-back failed (conflict or rejected push) after a hotfix/release merged to {{MAIN_BRANCH}}. Resolve and merge this PR manually."
|
|
53
|
+
env:
|
|
54
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: auto-merge to {{MAIN_BRANCH}}
|
|
2
|
+
|
|
3
|
+
# Rendered by shipflow's apply.mjs from this template. Do not hand-edit
|
|
4
|
+
# without also updating .github/shipflow.json's renderedTemplateHashes entry
|
|
5
|
+
# for this file, or shipflow's next apply will refuse to overwrite it
|
|
6
|
+
# (handEditDetected) until an explicit --force is passed. Commit both files
|
|
7
|
+
# together in the same commit.
|
|
8
|
+
#
|
|
9
|
+
# GitHub Flow has no separate "promotion" branch — every PR into {{MAIN_BRANCH}}
|
|
10
|
+
# is eligible for auto-merge and every merge is release-worthy, so unlike
|
|
11
|
+
# dev-main-promotion's template, neither job here restricts on head.ref.
|
|
12
|
+
#
|
|
13
|
+
# GH_TOKEN uses config.release.releaseCredential, NOT a hardcoded
|
|
14
|
+
# secrets.GITHUB_TOKEN, because of GitHub's loop-prevention rule: a PR
|
|
15
|
+
# auto-merged via `gh pr merge --auto` run under the default GITHUB_TOKEN
|
|
16
|
+
# completes (later, asynchronously, once checks pass) attributed to the
|
|
17
|
+
# github-actions[bot] identity — and a `pull_request: closed` event
|
|
18
|
+
# resulting from that bot-attributed merge does NOT trigger this or any
|
|
19
|
+
# other workflow's `on: pull_request` handlers. Confirmed empirically:
|
|
20
|
+
# an identical PR merged by a real, PAT-authenticated actor fired the
|
|
21
|
+
# closed-event trigger immediately; one completed by GITHUB_TOKEN-enabled
|
|
22
|
+
# auto-merge fired no run at all. releaseCredential must therefore name a
|
|
23
|
+
# real PAT/App-installation-token secret (not GITHUB_TOKEN) for
|
|
24
|
+
# label-release-pending to ever actually run.
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
pull_request:
|
|
28
|
+
types: [opened, reopened, synchronize, closed]
|
|
29
|
+
branches: [{{MAIN_BRANCH}}]
|
|
30
|
+
|
|
31
|
+
permissions:
|
|
32
|
+
contents: write
|
|
33
|
+
pull-requests: write
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
36
|
+
# Enables native GitHub auto-merge on open/reopen/synchronize — this job
|
|
37
|
+
# does NOT wait for checks itself; it turns on auto-merge and exits. The
|
|
38
|
+
# actual merge happens asynchronously, later, whenever GitHub's own
|
|
39
|
+
# required-checks gate is satisfied (see the design's discussion of why a
|
|
40
|
+
# bespoke polling/blocking job was rejected).
|
|
41
|
+
auto-merge:
|
|
42
|
+
if: github.event.action != 'closed'
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- name: Enable auto-merge
|
|
46
|
+
run: gh pr merge --auto {{MERGE_FLAG}} "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
|
|
47
|
+
env:
|
|
48
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
49
|
+
|
|
50
|
+
# Fires once, when a PR actually merges (a separate event from the job
|
|
51
|
+
# above, which only *enables* auto-merge). Applies a durable
|
|
52
|
+
# release-pending label so a later, disconnected shipflow invocation can
|
|
53
|
+
# find this merge and ask about a release — the merge completion has no
|
|
54
|
+
# live Claude session attached to react to it directly.
|
|
55
|
+
label-release-pending:
|
|
56
|
+
if: >-
|
|
57
|
+
github.event.action == 'closed' &&
|
|
58
|
+
github.event.pull_request.merged == true
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
permissions:
|
|
61
|
+
pull-requests: write
|
|
62
|
+
steps:
|
|
63
|
+
- name: Apply release-pending label
|
|
64
|
+
run: gh pr edit "${{ github.event.pull_request.number }}" --add-label release-pending --repo "${{ github.repository }}"
|
|
65
|
+
env:
|
|
66
|
+
GH_TOKEN: ${{ secrets.{{RELEASE_CREDENTIAL_SECRET}} }}
|
|
File without changes
|