shipmill 0.31.0__tar.gz
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.
- shipmill-0.31.0/.claude/settings.json +14 -0
- shipmill-0.31.0/.claude-plugin/marketplace.json +17 -0
- shipmill-0.31.0/.claude-plugin/plugin.json +19 -0
- shipmill-0.31.0/.github/shipmill.toml +53 -0
- shipmill-0.31.0/.github/workflows/ci.yml +49 -0
- shipmill-0.31.0/.github/workflows/deploy.yml +107 -0
- shipmill-0.31.0/.github/workflows/land.yml +107 -0
- shipmill-0.31.0/.github/workflows/move-major-tag.yml +35 -0
- shipmill-0.31.0/.github/workflows/operate-self.yml +44 -0
- shipmill-0.31.0/.github/workflows/operate.yml +61 -0
- shipmill-0.31.0/.github/workflows/prepare.yml +183 -0
- shipmill-0.31.0/.github/workflows/publish.yml +80 -0
- shipmill-0.31.0/.github/workflows/release.yml +68 -0
- shipmill-0.31.0/.gitignore +8 -0
- shipmill-0.31.0/CHANGELOG.md +1000 -0
- shipmill-0.31.0/CLAUDE.md +46 -0
- shipmill-0.31.0/LICENSE +21 -0
- shipmill-0.31.0/PKG-INFO +473 -0
- shipmill-0.31.0/README.md +456 -0
- shipmill-0.31.0/docs/decisions.md +177 -0
- shipmill-0.31.0/docs/design/agent-modes.md +318 -0
- shipmill-0.31.0/docs/design/ephemeral-mode.md +321 -0
- shipmill-0.31.0/docs/flow.md +115 -0
- shipmill-0.31.0/docs/install.md +436 -0
- shipmill-0.31.0/docs/postmortems/2026-10-04-pages-rollback-drill.md +41 -0
- shipmill-0.31.0/docs/postmortems/TEMPLATE.md +55 -0
- shipmill-0.31.0/docs/specs/001-fleet.md +68 -0
- shipmill-0.31.0/docs/specs/002-worktree-prune.md +212 -0
- shipmill-0.31.0/docs/specs/003-waiting-session.md +217 -0
- shipmill-0.31.0/docs/specs/004-agent-app-identity.md +205 -0
- shipmill-0.31.0/docs/specs/005-headless-mode.md +404 -0
- shipmill-0.31.0/docs/specs/006-app-create.md +233 -0
- shipmill-0.31.0/docs/specs/007-app-install.md +123 -0
- shipmill-0.31.0/docs/specs/008-status.md +143 -0
- shipmill-0.31.0/docs/specs/009-status-picture.md +271 -0
- shipmill-0.31.0/docs/specs/010-pypi-publish.md +104 -0
- shipmill-0.31.0/docs/specs/TEMPLATE.md +39 -0
- shipmill-0.31.0/pyproject.toml +46 -0
- shipmill-0.31.0/site/index.html +11 -0
- shipmill-0.31.0/skills/github-issue-resolve/SKILL.md +131 -0
- shipmill-0.31.0/skills/github-issue-triage/SKILL.md +135 -0
- shipmill-0.31.0/skills/github-issue-triage/agents/openai.yaml +4 -0
- shipmill-0.31.0/skills/github-issue-triage/references/comments.md +112 -0
- shipmill-0.31.0/skills/github-issue-triage/references/design-gate.md +59 -0
- shipmill-0.31.0/skills/github-issue-triage/references/implementer-brief.md +34 -0
- shipmill-0.31.0/skills/github-issue-triage/references/needs-decision.md +64 -0
- shipmill-0.31.0/skills/github-issue-triage/references/spec-gate.md +100 -0
- shipmill-0.31.0/skills/github-issue-triage/references/triage-rubric.md +65 -0
- shipmill-0.31.0/skills/github-issue-triage/scripts/decisions.py +230 -0
- shipmill-0.31.0/skills/github-issue-triage/scripts/specs.py +810 -0
- shipmill-0.31.0/skills/github-issue-triage/scripts/triage_state.py +834 -0
- shipmill-0.31.0/skills/github-pr-triage/SKILL.md +120 -0
- shipmill-0.31.0/skills/github-pr-triage/agents/openai.yaml +4 -0
- shipmill-0.31.0/skills/github-pr-triage/references/ci-failures.md +52 -0
- shipmill-0.31.0/skills/github-pr-triage/references/landing.md +147 -0
- shipmill-0.31.0/skills/github-pr-triage/references/reviewer-brief.md +50 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/changelog_guard.py +347 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/failed_tests.py +97 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/landed.py +76 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/pr_gate.py +104 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/release_ready.py +169 -0
- shipmill-0.31.0/skills/github-pr-triage/scripts/shipped.py +107 -0
- shipmill-0.31.0/skills/github-ship-watch/SKILL.md +171 -0
- shipmill-0.31.0/skills/github-ship-watch/agents/openai.yaml +4 -0
- shipmill-0.31.0/skills/github-ship-watch/scripts/fleet.py +417 -0
- shipmill-0.31.0/skills/github-ship-watch/scripts/metrics.py +833 -0
- shipmill-0.31.0/skills/github-ship-watch/scripts/retro.py +231 -0
- shipmill-0.31.0/skills/github-ship-watch/scripts/watch_state.py +1478 -0
- shipmill-0.31.0/skills/product-intake/SKILL.md +170 -0
- shipmill-0.31.0/skills/product-intake/agents/openai.yaml +4 -0
- shipmill-0.31.0/skills/product-intake/references/milestone-proposal.md +40 -0
- shipmill-0.31.0/skills/product-intake/references/opportunity.md +66 -0
- shipmill-0.31.0/skills/product-intake/scripts/intake_state.py +581 -0
- shipmill-0.31.0/skills/product-intake/scripts/roadmap_state.py +345 -0
- shipmill-0.31.0/skills/shipmill-setup/SKILL.md +492 -0
- shipmill-0.31.0/skills/shipmill-setup/scripts/setup_state.py +343 -0
- shipmill-0.31.0/src/shipmill/__init__.py +4 -0
- shipmill-0.31.0/src/shipmill/__main__.py +6 -0
- shipmill-0.31.0/src/shipmill/agents.py +66 -0
- shipmill-0.31.0/src/shipmill/app.py +571 -0
- shipmill-0.31.0/src/shipmill/app_create.py +451 -0
- shipmill-0.31.0/src/shipmill/app_install.py +180 -0
- shipmill-0.31.0/src/shipmill/autonomy.py +176 -0
- shipmill-0.31.0/src/shipmill/changelog.py +259 -0
- shipmill-0.31.0/src/shipmill/cli.py +784 -0
- shipmill-0.31.0/src/shipmill/config.py +106 -0
- shipmill-0.31.0/src/shipmill/doctor.py +390 -0
- shipmill-0.31.0/src/shipmill/environments.py +106 -0
- shipmill-0.31.0/src/shipmill/errors.py +3 -0
- shipmill-0.31.0/src/shipmill/gate.py +998 -0
- shipmill-0.31.0/src/shipmill/github.py +367 -0
- shipmill-0.31.0/src/shipmill/gitrepo.py +125 -0
- shipmill-0.31.0/src/shipmill/init.py +299 -0
- shipmill-0.31.0/src/shipmill/land.py +254 -0
- shipmill-0.31.0/src/shipmill/lanes.py +15 -0
- shipmill-0.31.0/src/shipmill/launchd.py +140 -0
- shipmill-0.31.0/src/shipmill/notify.py +74 -0
- shipmill-0.31.0/src/shipmill/operate.py +857 -0
- shipmill-0.31.0/src/shipmill/planner.py +340 -0
- shipmill-0.31.0/src/shipmill/policy.py +261 -0
- shipmill-0.31.0/src/shipmill/propose.py +158 -0
- shipmill-0.31.0/src/shipmill/py.typed +0 -0
- shipmill-0.31.0/src/shipmill/roadmap.py +30 -0
- shipmill-0.31.0/src/shipmill/schedule.py +85 -0
- shipmill-0.31.0/src/shipmill/stamp.py +167 -0
- shipmill-0.31.0/src/shipmill/status.py +664 -0
- shipmill-0.31.0/src/shipmill/version.py +125 -0
- shipmill-0.31.0/src/shipmill/worktrees.py +420 -0
- shipmill-0.31.0/tests/__init__.py +0 -0
- shipmill-0.31.0/tests/conftest.py +302 -0
- shipmill-0.31.0/tests/test_app.py +400 -0
- shipmill-0.31.0/tests/test_app_create.py +479 -0
- shipmill-0.31.0/tests/test_app_docs.py +91 -0
- shipmill-0.31.0/tests/test_app_install.py +248 -0
- shipmill-0.31.0/tests/test_app_token.py +443 -0
- shipmill-0.31.0/tests/test_autonomy.py +508 -0
- shipmill-0.31.0/tests/test_changelog_guard.py +273 -0
- shipmill-0.31.0/tests/test_cli_docs.py +201 -0
- shipmill-0.31.0/tests/test_config.py +314 -0
- shipmill-0.31.0/tests/test_decisions.py +79 -0
- shipmill-0.31.0/tests/test_fleet.py +599 -0
- shipmill-0.31.0/tests/test_gate.py +584 -0
- shipmill-0.31.0/tests/test_gate_app.py +450 -0
- shipmill-0.31.0/tests/test_gate_decisions.py +327 -0
- shipmill-0.31.0/tests/test_gate_headless.py +713 -0
- shipmill-0.31.0/tests/test_gate_prune.py +181 -0
- shipmill-0.31.0/tests/test_gate_waiting.py +518 -0
- shipmill-0.31.0/tests/test_headless_docs.py +85 -0
- shipmill-0.31.0/tests/test_intake_state.py +350 -0
- shipmill-0.31.0/tests/test_lanes.py +475 -0
- shipmill-0.31.0/tests/test_launchd.py +89 -0
- shipmill-0.31.0/tests/test_metrics.py +458 -0
- shipmill-0.31.0/tests/test_needs_decision.py +393 -0
- shipmill-0.31.0/tests/test_operate.py +847 -0
- shipmill-0.31.0/tests/test_publish.py +137 -0
- shipmill-0.31.0/tests/test_resolve_skill.py +40 -0
- shipmill-0.31.0/tests/test_retro.py +90 -0
- shipmill-0.31.0/tests/test_roadmap_state.py +259 -0
- shipmill-0.31.0/tests/test_setup.py +436 -0
- shipmill-0.31.0/tests/test_setup_state.py +351 -0
- shipmill-0.31.0/tests/test_ship_watch.py +978 -0
- shipmill-0.31.0/tests/test_specs.py +447 -0
- shipmill-0.31.0/tests/test_status.py +179 -0
- shipmill-0.31.0/tests/test_status_picture.py +526 -0
- shipmill-0.31.0/tests/test_triage_state.py +637 -0
- shipmill-0.31.0/tests/test_units.py +361 -0
- shipmill-0.31.0/tests/test_workflows.py +42 -0
- shipmill-0.31.0/tests/test_worktrees.py +697 -0
- shipmill-0.31.0/uv.lock +317 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shipmill",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Roman Medvedev",
|
|
5
|
+
"email": "pypi@romavm.dev"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "The shipmill plugin: GitHub issue triage, PR landing, and release lanes"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "shipmill",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"description": "Issue to release on GitHub: triage issues, review and land pull requests, and cut releases on lanes from a hand-written CHANGELOG"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shipmill",
|
|
3
|
+
"version": "0.31.0",
|
|
4
|
+
"description": "Issue to release on GitHub: triage issues, review and land pull requests, and cut releases on lanes from a hand-written CHANGELOG",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Roman Medvedev",
|
|
7
|
+
"email": "pypi@romavm.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/shipmill/shipmill",
|
|
10
|
+
"repository": "https://github.com/shipmill/shipmill",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"github",
|
|
14
|
+
"triage",
|
|
15
|
+
"pull-requests",
|
|
16
|
+
"release",
|
|
17
|
+
"changelog"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# shipmill releases itself with its own workflows, at the commit being released
|
|
2
|
+
name = "shipmill"
|
|
3
|
+
mode = "release"
|
|
4
|
+
branch = "main"
|
|
5
|
+
version_files = "pyproject"
|
|
6
|
+
after_stamp = ["uv lock --check"]
|
|
7
|
+
|
|
8
|
+
[changelog]
|
|
9
|
+
style = "keep-a-changelog"
|
|
10
|
+
|
|
11
|
+
[bump]
|
|
12
|
+
from = "headings"
|
|
13
|
+
major = ["Breaking"]
|
|
14
|
+
minor = ["Added", "Changed", "Deprecated", "Removed"]
|
|
15
|
+
patch = ["Fixed", "Security"]
|
|
16
|
+
|
|
17
|
+
[gates]
|
|
18
|
+
blocker_label = "release-blocker"
|
|
19
|
+
blocker_lanes = ["stable"]
|
|
20
|
+
|
|
21
|
+
# A stable release after every batch of merges, and hotfixes by hand
|
|
22
|
+
[lanes.stable]
|
|
23
|
+
quiet_minutes = 30
|
|
24
|
+
github_release = true
|
|
25
|
+
dispatch = ["move-major-tag.yml", "publish.yml"] # publish.yml: PyPI (spec 010)
|
|
26
|
+
|
|
27
|
+
[lanes.hotfix]
|
|
28
|
+
github_release = true
|
|
29
|
+
dispatch = ["publish.yml"]
|
|
30
|
+
|
|
31
|
+
# The plugin's version follows the package's, so a plugin update ships with each release
|
|
32
|
+
[[version_lines]]
|
|
33
|
+
file = ".claude-plugin/plugin.json"
|
|
34
|
+
pattern = '"version": "[^"]+"'
|
|
35
|
+
replace = '"version": "{version}"'
|
|
36
|
+
|
|
37
|
+
# shipmill's own GitHub Pages site: each stable release deploys there, and operate checks that
|
|
38
|
+
# its health.json names the deployed release (#53)
|
|
39
|
+
[environments.github-pages]
|
|
40
|
+
lane = "stable"
|
|
41
|
+
workflow = "deploy.yml"
|
|
42
|
+
health = "https://shipmill.github.io/shipmill/health.json"
|
|
43
|
+
|
|
44
|
+
[operate]
|
|
45
|
+
rollback_after = 3
|
|
46
|
+
|
|
47
|
+
# shipmill's own gate: a background session triages new issues and lands green PRs
|
|
48
|
+
[agents]
|
|
49
|
+
prompt = "/github-issue-triage {repo} triage the new issues; merge when green"
|
|
50
|
+
prs = true
|
|
51
|
+
retry_hours = 24
|
|
52
|
+
app_id = 5209412 # gated sessions write as shipmill-romamo[bot] (spec 004)
|
|
53
|
+
mode = "headless" # what the gate runs; written out, not left to the default (D-20)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
workflow_call: # shipmill's Release workflow runs it on its release commit
|
|
9
|
+
inputs:
|
|
10
|
+
ref:
|
|
11
|
+
type: string
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
check:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
with:
|
|
23
|
+
ref: ${{ inputs.ref }} # empty outside workflow_call: the event's commit
|
|
24
|
+
|
|
25
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.14"
|
|
28
|
+
|
|
29
|
+
- run: uv sync --locked
|
|
30
|
+
- run: uv run ruff check src tests skills
|
|
31
|
+
- run: uv run ruff format --check src tests skills
|
|
32
|
+
- run: uv run mypy
|
|
33
|
+
- run: uv run pytest -q
|
|
34
|
+
- name: Check the specs, and that a test proves each criterion of a built one
|
|
35
|
+
run: |
|
|
36
|
+
uv run --no-project python skills/github-issue-triage/scripts/specs.py check
|
|
37
|
+
uv run --no-project python skills/github-issue-triage/scripts/specs.py coverage
|
|
38
|
+
- name: Lint the workflows, including the template init writes
|
|
39
|
+
run: |
|
|
40
|
+
uvx --from actionlint-py actionlint .github/workflows/*.yml
|
|
41
|
+
uv run python -c "
|
|
42
|
+
from shipmill.init import Detected, caller_text
|
|
43
|
+
from shipmill.policy import Style, VersionFiles
|
|
44
|
+
print(caller_text(Detected('demo', 'main', Style.KEEP_A_CHANGELOG, VersionFiles.NONE), 'ci.yml'))
|
|
45
|
+
" > "$RUNNER_TEMP/release.yml"
|
|
46
|
+
uvx --from actionlint-py actionlint -ignore 'ci.yml' "$RUNNER_TEMP/release.yml"
|
|
47
|
+
uv run python -c "from shipmill.init import operate_caller_text; print(operate_caller_text())" \
|
|
48
|
+
> "$RUNNER_TEMP/operate.yml"
|
|
49
|
+
uvx --from actionlint-py actionlint "$RUNNER_TEMP/operate.yml"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
|
|
3
|
+
# shipmill's own deploy target: GitHub Pages at https://shipmill.github.io/shipmill/. land
|
|
4
|
+
# starts it on each stable tag, and shipmill operate on a rollback, with -f tag=<tag>
|
|
5
|
+
# -f environment=github-pages; by hand, run it on the tag too (--ref <tag>), or it fails. The site is site/ at the tag plus a health.json naming the
|
|
6
|
+
# release, {"version": "X.Y.Z"}, which shipmill operate checks against the deployed tag.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
tag:
|
|
12
|
+
description: "The release tag to deploy, such as v1.2.0"
|
|
13
|
+
type: string
|
|
14
|
+
required: true
|
|
15
|
+
environment:
|
|
16
|
+
description: "The GitHub environment; Pages deploys only to github-pages"
|
|
17
|
+
type: string
|
|
18
|
+
required: true
|
|
19
|
+
simulate-failure:
|
|
20
|
+
# The rollback drill's fault (#53): health.json names a version that isn't the tag, so
|
|
21
|
+
# operate's health check fails and it rolls back, without shipping a broken package.
|
|
22
|
+
# Nothing but the drill sets it
|
|
23
|
+
description: "Rollback drill only: health.json reports a version that isn't the tag"
|
|
24
|
+
type: boolean
|
|
25
|
+
default: false
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
# One Pages deploy at a time, none cancelled: a rollback must not be dropped mid-way
|
|
31
|
+
concurrency:
|
|
32
|
+
group: pages
|
|
33
|
+
cancel-in-progress: false
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
36
|
+
build:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- name: Check the inputs
|
|
40
|
+
env:
|
|
41
|
+
TAG: ${{ inputs.tag }}
|
|
42
|
+
ENVIRONMENT: ${{ inputs.environment }}
|
|
43
|
+
run: |
|
|
44
|
+
if [ "$ENVIRONMENT" != "github-pages" ]; then
|
|
45
|
+
echo "::error::environment must be github-pages, got '$ENVIRONMENT'"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
if ! [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.+-]*$ ]]; then
|
|
49
|
+
echo "::error::tag must be a release tag such as v1.2.0, got '$TAG'"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
# GitHub records the deployment under the run's ref, which operate reads as the tag
|
|
53
|
+
# that runs there (#80): a run started without --ref <tag> would record a branch
|
|
54
|
+
if [ "$GITHUB_REF" != "refs/tags/$TAG" ]; then
|
|
55
|
+
echo "::error::run deploy.yml on the tag it deploys, not $GITHUB_REF:" \
|
|
56
|
+
"gh workflow run deploy.yml --ref $TAG -f tag=$TAG -f environment=$ENVIRONMENT"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
- uses: actions/checkout@v7
|
|
61
|
+
with:
|
|
62
|
+
ref: refs/tags/${{ inputs.tag }}
|
|
63
|
+
|
|
64
|
+
- name: Write health.json
|
|
65
|
+
env:
|
|
66
|
+
TAG: ${{ inputs.tag }}
|
|
67
|
+
SIMULATE_FAILURE: ${{ inputs.simulate-failure }}
|
|
68
|
+
run: |
|
|
69
|
+
version="${TAG#v}"
|
|
70
|
+
if [ "$SIMULATE_FAILURE" = "true" ]; then
|
|
71
|
+
version="0.0.0-simulated-failure"
|
|
72
|
+
fi
|
|
73
|
+
printf '{"version": "%s"}\n' "$version" > site/health.json
|
|
74
|
+
cat site/health.json
|
|
75
|
+
|
|
76
|
+
- uses: actions/upload-pages-artifact@v5
|
|
77
|
+
with:
|
|
78
|
+
path: site/
|
|
79
|
+
|
|
80
|
+
deploy:
|
|
81
|
+
needs: build
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
permissions:
|
|
84
|
+
pages: write # deploys the artifact
|
|
85
|
+
id-token: write # proves the deploy comes from this workflow
|
|
86
|
+
environment:
|
|
87
|
+
name: github-pages
|
|
88
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
89
|
+
steps:
|
|
90
|
+
- id: deployment
|
|
91
|
+
uses: actions/deploy-pages@v5
|
|
92
|
+
|
|
93
|
+
# The first health check right after the deploy, not at the next scheduled operate run
|
|
94
|
+
# (#84); the schedule stays the backstop. A job of its own: a failure here leaves the
|
|
95
|
+
# deployment's success alone. operate deploys a tag to an environment at most once, so a
|
|
96
|
+
# run it starts never deploys this tag again, nor races land's dispatch
|
|
97
|
+
operate:
|
|
98
|
+
needs: deploy
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
permissions:
|
|
101
|
+
actions: write # starts operate-self.yml
|
|
102
|
+
steps:
|
|
103
|
+
- name: Start shipmill operate
|
|
104
|
+
env:
|
|
105
|
+
GH_TOKEN: ${{ github.token }}
|
|
106
|
+
GH_REPO: ${{ github.repository }}
|
|
107
|
+
run: gh workflow run operate-self.yml -f dry-run=false
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
name: shipmill land
|
|
2
|
+
|
|
3
|
+
# Called after the caller's CI ran on the release commit. When CI passed it lands the
|
|
4
|
+
# release: main (a stable release on an unmoved main), release/X.Y (a hotfix), or the tag
|
|
5
|
+
# alone (a pre-release, or a stable release promoted while main moved on, which then gets a
|
|
6
|
+
# sync commit on main). It tags, publishes, and starts the lane's dispatch workflows.
|
|
7
|
+
# Whatever happened, the work branch is deleted: a failed release is planned again. Under
|
|
8
|
+
# release = "propose" it then closes the lane's proposal issue.
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
workflow_call:
|
|
12
|
+
inputs:
|
|
13
|
+
ci-result:
|
|
14
|
+
type: string
|
|
15
|
+
required: true
|
|
16
|
+
lane:
|
|
17
|
+
type: string
|
|
18
|
+
required: true
|
|
19
|
+
version:
|
|
20
|
+
type: string
|
|
21
|
+
required: true
|
|
22
|
+
sha:
|
|
23
|
+
type: string
|
|
24
|
+
required: true
|
|
25
|
+
base:
|
|
26
|
+
type: string
|
|
27
|
+
required: true
|
|
28
|
+
tool:
|
|
29
|
+
type: string
|
|
30
|
+
default: "git+https://github.com/shipmill/shipmill@v0"
|
|
31
|
+
|
|
32
|
+
# No workflow-level permissions: each job sets its own, except close-proposal, which takes
|
|
33
|
+
# the caller's grant, so a caller that grants no `issues: write` still starts this workflow
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
36
|
+
land:
|
|
37
|
+
if: inputs.ci-result == 'success'
|
|
38
|
+
concurrency:
|
|
39
|
+
group: shipmill
|
|
40
|
+
cancel-in-progress: false
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
permissions:
|
|
43
|
+
contents: write # main, release branches, tags, and releases
|
|
44
|
+
actions: write # starts dispatch workflows; a push with this token starts none
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
with:
|
|
48
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
|
|
51
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
52
|
+
|
|
53
|
+
- name: Land, tag, and publish
|
|
54
|
+
shell: bash # -eo pipefail: a failed land fails the step, though its output goes through tee
|
|
55
|
+
env:
|
|
56
|
+
GH_TOKEN: ${{ github.token }}
|
|
57
|
+
TOOL: ${{ inputs.tool }}
|
|
58
|
+
LANE: ${{ inputs.lane }}
|
|
59
|
+
VERSION: ${{ inputs.version }}
|
|
60
|
+
SHA: ${{ inputs.sha }}
|
|
61
|
+
BASE: ${{ inputs.base }}
|
|
62
|
+
run: |
|
|
63
|
+
uvx --from "$TOOL" shipmill land --lane "$LANE" --version "$VERSION" --sha "$SHA" --base "$BASE" \
|
|
64
|
+
| tee -a "$GITHUB_STEP_SUMMARY"
|
|
65
|
+
|
|
66
|
+
# Closes the lane's "Ready to release" issue, with the tag and this run. It sets no
|
|
67
|
+
# permissions, so it runs with the caller's grant: under release = "propose" closing needs
|
|
68
|
+
# `issues: write` on the caller's land job; without it this job fails, after the release
|
|
69
|
+
# landed. Under any other release autonomy it reads nothing from GitHub
|
|
70
|
+
close-proposal:
|
|
71
|
+
needs: land
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v7
|
|
75
|
+
with:
|
|
76
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
77
|
+
|
|
78
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
79
|
+
|
|
80
|
+
- name: Close the lane's proposal issue
|
|
81
|
+
shell: bash # -eo pipefail: a failed close fails the step, though its output goes through tee
|
|
82
|
+
env:
|
|
83
|
+
GH_TOKEN: ${{ github.token }}
|
|
84
|
+
TOOL: ${{ inputs.tool }}
|
|
85
|
+
LANE: ${{ inputs.lane }}
|
|
86
|
+
VERSION: ${{ inputs.version }}
|
|
87
|
+
run: |
|
|
88
|
+
uvx --from "$TOOL" shipmill close-proposal --lane "$LANE" --version "$VERSION" | tee -a "$GITHUB_STEP_SUMMARY"
|
|
89
|
+
|
|
90
|
+
cleanup:
|
|
91
|
+
needs: land
|
|
92
|
+
if: always()
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
permissions:
|
|
95
|
+
contents: write
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v7
|
|
98
|
+
with:
|
|
99
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
100
|
+
|
|
101
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
102
|
+
|
|
103
|
+
- name: Delete the work branch
|
|
104
|
+
env:
|
|
105
|
+
TOOL: ${{ inputs.tool }}
|
|
106
|
+
VERSION: ${{ inputs.version }}
|
|
107
|
+
run: uvx --from "$TOOL" shipmill cleanup --version "$VERSION"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Move the major tag
|
|
2
|
+
|
|
3
|
+
# Callers pin `@v0`: after a stable release, point the major tag at it. shipmill's land
|
|
4
|
+
# workflow starts this one with the release's tag.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
tag:
|
|
10
|
+
description: "The release tag, such as v0.2.0"
|
|
11
|
+
type: string
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
move:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Point the major tag at the release
|
|
26
|
+
env:
|
|
27
|
+
TAG: ${{ inputs.tag }}
|
|
28
|
+
run: |
|
|
29
|
+
if ! [[ "$TAG" =~ ^v([0-9]+)\.[0-9]+\.[0-9]+$ ]]; then
|
|
30
|
+
echo "::error::$TAG is not a stable vX.Y.Z tag"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
major="v${BASH_REMATCH[1]}"
|
|
34
|
+
git tag -f "$major" "$TAG^{commit}"
|
|
35
|
+
git push -f origin "refs/tags/$major"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Operate
|
|
2
|
+
|
|
3
|
+
# shipmill dogfoods itself: the caller `shipmill init --operate` writes, but calling the
|
|
4
|
+
# reusable operate.yml in this repository (whose name it takes) and running the tool from the
|
|
5
|
+
# checkout. Every 10 minutes shipmill operate checks the health of each environment in
|
|
6
|
+
# .github/shipmill.toml, records it as GitHub deployment statuses, and rolls back an
|
|
7
|
+
# environment that fails its checks, opening an incident issue. Approve a deploy that waits
|
|
8
|
+
# on a proposal issue with: gh workflow run operate-self.yml -f approve=<environment> -f
|
|
9
|
+
# dry-run=false; a proposed rollback with -f approve-rollback=<environment> instead
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
schedule:
|
|
13
|
+
- cron: "*/10 * * * *"
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
inputs:
|
|
16
|
+
approve:
|
|
17
|
+
description: "Deploy the tag proposed for this environment, once"
|
|
18
|
+
type: string
|
|
19
|
+
default: ""
|
|
20
|
+
approve-rollback:
|
|
21
|
+
description: "Start the rollback an incident proposes for this environment, once"
|
|
22
|
+
type: string
|
|
23
|
+
default: ""
|
|
24
|
+
dry-run:
|
|
25
|
+
description: "Show what it would do, change nothing"
|
|
26
|
+
type: boolean
|
|
27
|
+
default: true
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
operate:
|
|
34
|
+
uses: ./.github/workflows/operate.yml
|
|
35
|
+
with:
|
|
36
|
+
approve: ${{ inputs.approve || '' }}
|
|
37
|
+
approve-rollback: ${{ inputs.approve-rollback || '' }}
|
|
38
|
+
dry-run: ${{ inputs.dry-run || false }}
|
|
39
|
+
tool: "."
|
|
40
|
+
permissions:
|
|
41
|
+
contents: read # the config and the release tags
|
|
42
|
+
deployments: write # reads deployments and records health as their statuses
|
|
43
|
+
actions: write # starts the deploy workflows
|
|
44
|
+
issues: write # the hold, the issue a deploy under propose or the hold opens, and incidents
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: shipmill operate
|
|
2
|
+
|
|
3
|
+
# Called on a schedule by a repository's operate workflow (see `shipmill init --operate`).
|
|
4
|
+
# It checks the health of each environment in [environments], records it as GitHub
|
|
5
|
+
# deployment statuses, and promotes a release to an environment with `from` once its source
|
|
6
|
+
# baked it (or opens the issue a deploy under propose or a hold waits on). An environment
|
|
7
|
+
# failing [operate] rollback_after checks in a row is rolled back and gets an incident issue.
|
|
8
|
+
# With `approve`, it deploys the tag proposed for that environment, once, instead; with
|
|
9
|
+
# `approve-rollback`, it starts the rollback an incident proposes for it, once.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
workflow_call:
|
|
13
|
+
inputs:
|
|
14
|
+
approve:
|
|
15
|
+
description: "An environment whose proposed deploy to run; empty runs the checks"
|
|
16
|
+
type: string
|
|
17
|
+
default: ""
|
|
18
|
+
approve-rollback:
|
|
19
|
+
description: "An environment whose proposed rollback to start; empty runs the checks"
|
|
20
|
+
type: string
|
|
21
|
+
default: ""
|
|
22
|
+
dry-run:
|
|
23
|
+
type: boolean
|
|
24
|
+
default: false
|
|
25
|
+
tool:
|
|
26
|
+
description: "Where uvx installs shipmill from"
|
|
27
|
+
type: string
|
|
28
|
+
default: "git+https://github.com/shipmill/shipmill@v0"
|
|
29
|
+
|
|
30
|
+
# No permissions here or on the job: it runs with the caller's grant, as a reusable job that
|
|
31
|
+
# asks for more than its caller grants fails to start. It needs contents: read, deployments:
|
|
32
|
+
# write, actions: write, and issues: write to open a proposal or an incident issue
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
operate:
|
|
36
|
+
# One operate at a time: a run that overlapped the previous one could deploy twice
|
|
37
|
+
concurrency:
|
|
38
|
+
group: shipmill-operate
|
|
39
|
+
cancel-in-progress: false
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v7
|
|
43
|
+
with:
|
|
44
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
45
|
+
fetch-depth: 0 # the release tags, to tell a missed deploy
|
|
46
|
+
|
|
47
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
48
|
+
|
|
49
|
+
- name: Operate
|
|
50
|
+
env:
|
|
51
|
+
GH_TOKEN: ${{ github.token }}
|
|
52
|
+
TOOL: ${{ inputs.tool }}
|
|
53
|
+
APPROVE: ${{ inputs.approve }}
|
|
54
|
+
APPROVE_ROLLBACK: ${{ inputs.approve-rollback }}
|
|
55
|
+
DRY_RUN: ${{ inputs.dry-run && '--dry-run' || '' }}
|
|
56
|
+
run: |
|
|
57
|
+
args=(operate)
|
|
58
|
+
[ -n "$APPROVE" ] && args+=(--approve "$APPROVE")
|
|
59
|
+
[ -n "$APPROVE_ROLLBACK" ] && args+=(--approve-rollback "$APPROVE_ROLLBACK")
|
|
60
|
+
[ -n "$DRY_RUN" ] && args+=(--dry-run)
|
|
61
|
+
uvx --from "$TOOL" shipmill "${args[@]}"
|