99 workflow_dispatch :
1010 inputs :
1111 tag :
12- description : " Existing tag to (re) build and publish"
12+ description : " Existing tag to build and publish"
1313 required : true
1414
15+ concurrency :
16+ group : release-${{ github.event.inputs.tag || github.ref }}
17+ cancel-in-progress : false
18+
19+ permissions :
20+ contents : read
21+
1522jobs :
1623 build :
24+ name : Build and verify distributions
1725 runs-on : ubuntu-latest
1826 outputs :
1927 version : ${{ steps.version.outputs.version }}
2028 tag : ${{ steps.version.outputs.tag }}
2129 steps :
22- - uses : actions/checkout@v4
30+ - uses : actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
2331 with :
2432 ref : ${{ github.event.inputs.tag || github.ref }}
33+ persist-credentials : false
2534
26- - uses : actions/setup-python@v5
35+ - uses : actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
2736 with :
2837 python-version : " 3.12"
2938
3039 - name : Resolve and verify the version
3140 id : version
41+ env :
42+ RELEASE_TAG : ${{ github.event.inputs.tag || github.ref_name }}
3243 run : |
3344 set -euo pipefail
34- tag="${{ github.event.inputs.tag || github.ref_name }} "
45+ tag="$RELEASE_TAG "
3546 version="${tag#v}"
3647 declared=$(python -c "
3748 import tomllib, pathlib
@@ -44,20 +55,23 @@ jobs:
4455 echo "version=$version" >> "$GITHUB_OUTPUT"
4556 echo "tag=$tag" >> "$GITHUB_OUTPUT"
4657
47- - name : Verify the provider matrix tracks this version
58+ - name : Verify the pins track this version
59+ env :
60+ RELEASE_VERSION : ${{ steps.version.outputs.version }}
4861 run : |
4962 set -euo pipefail
50- python -c "
51- import json, pathlib, sys
52- matrix = json.loads(pathlib.Path('src/deckflow_core/providers/providers.json').read_text())
53- declared = '${{ steps.version.outputs.version }}'
54- if matrix['core_version'] != declared:
55- sys.exit(f\"providers.json core_version {matrix['core_version']} != {declared}\")
56- floating = [n for n, s in matrix['providers'].items() if s['version'] in ('latest', '*', '')]
57- if floating:
58- sys.exit(f'providers are not pinned: {floating}')
59- print('provider matrix is pinned and current')
60- "
63+ PYTHONPATH=src python - <<'PY'
64+ import os, pathlib, re, sys
65+ from deckflow_core.extract import pin
66+ declared = os.environ["RELEASE_VERSION"]
67+ launcher = pathlib.Path("launcher/deckflow").read_text()
68+ pinned = re.search(r'^CORE_VERSION = "([^"]+)"', launcher, re.M).group(1)
69+ if pinned != declared:
70+ sys.exit(f"launcher pins {pinned}, releasing {declared}")
71+ if pin.VERSION in ("latest", "*", "") or pin.VERSION not in pin.SOURCE:
72+ sys.exit(f"the extract pin is not exact and self-consistent: {pin.VERSION!r}")
73+ print(f"core {declared}, extract pinned to {pin.VERSION}")
74+ PY
6175
6276 - name : Run the test suite
6377 run : PYTHONPATH=src:tests python -m unittest discover -s tests
@@ -68,55 +82,61 @@ jobs:
6882 python -m build
6983
7084 - name : Install the built wheel into a clean directory and smoke-test it
85+ env :
86+ RELEASE_VERSION : ${{ steps.version.outputs.version }}
7187 run : |
7288 set -euo pipefail
7389 python -m pip install --target /tmp/smoke --no-input dist/*.whl
7490 actual=$(PYTHONPATH=/tmp/smoke python -m deckflow_core --version)
7591 echo "$actual"
7692 case "$actual" in
77- *"${{ steps.version.outputs.version }} "*) ;;
93+ *"$RELEASE_VERSION "*) ;;
7894 *) echo "::error::installed wheel reports '$actual'"; exit 1 ;;
7995 esac
80- PYTHONPATH=/tmp/smoke python -m deckflow_core providers --json > /dev/null
96+ PYTHONPATH=/tmp/smoke python -m deckflow_core env check > /dev/null
8197
82- - uses : actions/upload-artifact@v4
98+ - uses : actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
8399 with :
84100 name : dist
85101 path : dist/
86102
87103 publish :
104+ name : Publish distributions to PyPI
88105 needs : build
89106 runs-on : ubuntu-latest
90- environment : pypi
107+ environment :
108+ name : pypi
109+ url : https://pypi.org/p/deckflow-core
91110 permissions :
92- # PyPI trusted publishing: the workflow proves its identity via OIDC, so
93- # there is no long-lived API token to store, leak, or rotate.
94- id-token : write
111+ id-token : write # Mint a short-lived PyPI credential through GitHub OIDC.
95112 steps :
96- - uses : actions/download-artifact@v4
113+ - uses : actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
97114 with :
98115 name : dist
99116 path : dist/
100- - uses : pypa/gh-action-pypi-publish@release/v1
117+ - uses : pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
101118
102119 release :
120+ name : Publish the GitHub release
103121 needs : [build, publish]
104122 runs-on : ubuntu-latest
105123 permissions :
106- contents : write
124+ contents : write # Create the GitHub release and upload its distributions.
107125 steps :
108- - uses : actions/checkout@v4
109- - uses : actions/download-artifact@v4
126+ - uses : actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
110127 with :
111128 name : dist
112129 path : dist/
113130 - name : Publish the GitHub release
114131 env :
115132 GH_TOKEN : ${{ github.token }}
133+ RELEASE_REPOSITORY : ${{ github.repository }}
134+ RELEASE_TAG : ${{ needs.build.outputs.tag }}
135+ RELEASE_VERSION : ${{ needs.build.outputs.version }}
116136 run : |
117137 set -euo pipefail
118- tag="${{ needs.build.outputs.tag }} "
119- version="${{ needs.build.outputs.version }} "
138+ tag="$RELEASE_TAG "
139+ version="$RELEASE_VERSION "
120140 notes=$(cat <<EOF
121141 \`\`\`bash
122142 pip install deckflow-core==${version}
@@ -127,12 +147,15 @@ jobs:
127147
128148 \`\`\`bash
129149 python3 -m pip install --target ~/.deckflow/core/${version} deckflow-core==${version}
130- PYTHONPATH=~/.deckflow/core/${version} python3 -m deckflow_core providers
150+ PYTHONPATH=~/.deckflow/core/${version} python3 -m deckflow_core env check
131151 \`\`\`
132152 EOF
133153 )
134- if gh release view "$tag" >/dev/null 2>&1; then
135- gh release upload "$tag" dist/* --clobber
154+ if gh release view "$tag" --repo "$RELEASE_REPOSITORY" >/dev/null 2>&1; then
155+ gh release upload "$tag" dist/* --repo "$RELEASE_REPOSITORY" -- clobber
136156 else
137- gh release create "$tag" dist/* --title "$tag" --notes "$notes"
157+ gh release create "$tag" dist/* \
158+ --repo "$RELEASE_REPOSITORY" \
159+ --title "$tag" \
160+ --notes "$notes"
138161 fi
0 commit comments