Skip to content

Commit d861221

Browse files
committed
release: prepare deckflow-core 0.3.0
1 parent 0f03f13 commit d861221

54 files changed

Lines changed: 3548 additions & 4111 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,25 @@ on:
66
pull_request:
77
workflow_dispatch:
88

9+
concurrency:
10+
group: ci-${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
916
jobs:
1017
metadata:
18+
name: Verify package metadata
1119
# Properties of the files, not of the interpreter, so this runs once on a
1220
# fixed version rather than in the matrix — `tomllib` only exists on 3.11+
1321
# and the floor of the support range is 3.10.
1422
runs-on: ubuntu-latest
1523
steps:
16-
- uses: actions/checkout@v4
17-
- uses: actions/setup-python@v5
24+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
25+
with:
26+
persist-credentials: false
27+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
1828
with:
1929
python-version: "3.12"
2030

@@ -32,35 +42,48 @@ jobs:
3242
print("zero runtime dependencies")
3343
PY
3444
35-
- name: Verify the provider matrix tracks the package version
45+
- name: Verify the provider pin is exact
46+
run: |
47+
PYTHONPATH=src python - <<'PY'
48+
import sys
49+
from deckflow_core.extract import pin
50+
if pin.VERSION in ("latest", "*", ""):
51+
sys.exit(f"::error::the extract pin is not exact: {pin.VERSION!r}")
52+
# A source fallback naming a different version would silently install
53+
# the wrong wheel whenever the index is unreachable.
54+
if pin.VERSION not in pin.SOURCE:
55+
sys.exit("::error::the SOURCE fallback does not name the pinned version")
56+
print(f"deckflow-extract pinned to {pin.VERSION}")
57+
PY
58+
59+
- name: Verify the launcher pins the same core version
3660
run: |
3761
python - <<'PY'
38-
import json, pathlib, sys, tomllib
39-
version = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]
40-
matrix = json.loads(
41-
pathlib.Path("src/deckflow_core/providers/providers.json").read_text()
42-
)
43-
if matrix["core_version"] != version:
44-
sys.exit(f"::error::providers.json says {matrix['core_version']}, pyproject says {version}")
45-
floating = [n for n, s in matrix["providers"].items() if s["version"] in ("latest", "*", "")]
46-
if floating:
47-
sys.exit(f"::error::providers are not pinned: {floating}")
48-
print(f"matrix {version}:", {n: s["version"] for n, s in matrix["providers"].items()})
62+
import pathlib, re, sys, tomllib
63+
declared = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"]
64+
launcher = pathlib.Path("launcher/deckflow").read_text()
65+
pinned = re.search(r'^CORE_VERSION = "([^"]+)"', launcher, re.M).group(1)
66+
if pinned != declared:
67+
sys.exit(f"::error::launcher pins {pinned}, pyproject declares {declared}")
68+
print(f"launcher pins {pinned}")
4969
PY
5070
5171
test:
72+
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
5273
strategy:
5374
fail-fast: false
5475
matrix:
5576
# The floor and the current release. Core is pure standard library, so
5677
# the interesting risk is a stdlib or platform difference, not a
5778
# dependency conflict — which is why Windows is in the matrix.
58-
python-version: ["3.10", "3.13"]
79+
python-version: ["3.10", "3.14"]
5980
os: [ubuntu-latest, macos-latest, windows-latest]
6081
runs-on: ${{ matrix.os }}
6182
steps:
62-
- uses: actions/checkout@v4
63-
- uses: actions/setup-python@v5
83+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
84+
with:
85+
persist-credentials: false
86+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
6487
with:
6588
python-version: ${{ matrix.python-version }}
6689

@@ -74,10 +97,13 @@ jobs:
7497
run: python -m unittest discover -s . -v
7598

7699
package:
100+
name: Build and smoke-test wheel
77101
runs-on: ubuntu-latest
78102
steps:
79-
- uses: actions/checkout@v4
80-
- uses: actions/setup-python@v5
103+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
104+
with:
105+
persist-credentials: false
106+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
81107
with:
82108
python-version: "3.12"
83109
- name: Build and install into a clean directory
@@ -87,9 +113,8 @@ jobs:
87113
python -m build
88114
python -m pip install --target "${RUNNER_TEMP}/smoke" --no-input dist/*.whl
89115
PYTHONPATH="${RUNNER_TEMP}/smoke" python -m deckflow_core --version
90-
# Listing providers has no side effects, so it is safe on a runner
91-
# with nothing installed.
92-
PYTHONPATH="${RUNNER_TEMP}/smoke" python -m deckflow_core providers --json > /dev/null
116+
# The default environment check is side-effect free.
117+
PYTHONPATH="${RUNNER_TEMP}/smoke" python -m deckflow_core env check > /dev/null
93118
PYTHONPATH="${RUNNER_TEMP}/smoke" python -c "
94119
from deckflow_core import schemas_dir
95120
assert (schemas_dir() / 'envelope.schema.json').is_file(), 'schemas missing from the wheel'

.github/workflows/release.yml

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,40 @@ on:
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+
1522
jobs:
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

Comments
 (0)