fastapi-router-lazy 0.1.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.
Files changed (47) hide show
  1. fastapi_router_lazy-0.1.0/.github/actions/ensure-pages-published/action.yml +78 -0
  2. fastapi_router_lazy-0.1.0/.github/workflows/ci.yml +280 -0
  3. fastapi_router_lazy-0.1.0/.github/workflows/zensical-develop.yml +43 -0
  4. fastapi_router_lazy-0.1.0/.github/workflows/zensical-preview.yml +79 -0
  5. fastapi_router_lazy-0.1.0/.github/workflows/zensical.yml +46 -0
  6. fastapi_router_lazy-0.1.0/.gitignore +218 -0
  7. fastapi_router_lazy-0.1.0/.pre-commit-config.yaml +33 -0
  8. fastapi_router_lazy-0.1.0/CHANGELOG.md +11 -0
  9. fastapi_router_lazy-0.1.0/LICENSE +21 -0
  10. fastapi_router_lazy-0.1.0/PKG-INFO +165 -0
  11. fastapi_router_lazy-0.1.0/README.md +123 -0
  12. fastapi_router_lazy-0.1.0/codecov.yml +29 -0
  13. fastapi_router_lazy-0.1.0/docs/api.md +340 -0
  14. fastapi_router_lazy-0.1.0/docs/extractors.md +187 -0
  15. fastapi_router_lazy-0.1.0/docs/index.md +160 -0
  16. fastapi_router_lazy-0.1.0/docs/usage.md +185 -0
  17. fastapi_router_lazy-0.1.0/hatch_build.py +66 -0
  18. fastapi_router_lazy-0.1.0/pyproject.toml +159 -0
  19. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/__init__.py +75 -0
  20. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractor.py +56 -0
  21. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractors/__init__.py +41 -0
  22. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractors/abc.py +76 -0
  23. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractors/cached_extractor.py +250 -0
  24. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractors/plain.py +119 -0
  25. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/extractors/sandbox.py +133 -0
  26. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/middleware.py +142 -0
  27. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/py.typed +0 -0
  28. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/route_info.py +71 -0
  29. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/router_loader.py +201 -0
  30. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/variants/__init__.py +14 -0
  31. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/variants/extractor.py +189 -0
  32. fastapi_router_lazy-0.1.0/src/fastapi_router_lazy/variants/loader.py +66 -0
  33. fastapi_router_lazy-0.1.0/tests/conftest.py +55 -0
  34. fastapi_router_lazy-0.1.0/tests/test_cached_extractor.py +337 -0
  35. fastapi_router_lazy-0.1.0/tests/test_extractor_factory.py +45 -0
  36. fastapi_router_lazy-0.1.0/tests/test_middleware.py +278 -0
  37. fastapi_router_lazy-0.1.0/tests/test_performance.py +172 -0
  38. fastapi_router_lazy-0.1.0/tests/test_plain_extractor.py +118 -0
  39. fastapi_router_lazy-0.1.0/tests/test_readme.py +69 -0
  40. fastapi_router_lazy-0.1.0/tests/test_route_info.py +45 -0
  41. fastapi_router_lazy-0.1.0/tests/test_router_loader.py +290 -0
  42. fastapi_router_lazy-0.1.0/tests/test_sandbox_extractor.py +125 -0
  43. fastapi_router_lazy-0.1.0/tests/variants/routers.py +36 -0
  44. fastapi_router_lazy-0.1.0/tests/variants/test_extractor.py +60 -0
  45. fastapi_router_lazy-0.1.0/tests/variants/test_loader.py +74 -0
  46. fastapi_router_lazy-0.1.0/uv.lock +1872 -0
  47. fastapi_router_lazy-0.1.0/zensical.toml +79 -0
@@ -0,0 +1,78 @@
1
+ name: Ensure Pages published
2
+ description: >-
3
+ Wait for GitHub's auto "pages build and deployment" of the current gh-pages
4
+ HEAD and rerun it (with backoff) if it fails with a transient error.
5
+
6
+ runs:
7
+ using: composite
8
+ steps:
9
+ - shell: bash
10
+ env:
11
+ GH_TOKEN: ${{ github.token }}
12
+ REPO: ${{ github.repository }}
13
+ ATTEMPTS: "5"
14
+ run: |
15
+ set -euo pipefail
16
+
17
+ target_sha="$(gh api "repos/$REPO/branches/gh-pages" --jq '.commit.sha')"
18
+ echo "Ensuring Pages is published for gh-pages@$target_sha"
19
+
20
+ find_run() {
21
+ gh run list --repo "$REPO" --workflow "pages-build-deployment" \
22
+ --commit "$target_sha" --limit 1 \
23
+ --json databaseId,status,conclusion \
24
+ --jq '.[0] // empty | "\(.databaseId) \(.status) \(.conclusion // "")"' 2>/dev/null || true
25
+ }
26
+
27
+ # Block until a run for this SHA reaches "completed"; echo "id conclusion".
28
+ await_completed() {
29
+ for _ in $(seq 1 60); do
30
+ local info id status conclusion
31
+ info="$(find_run)"
32
+ id="$(printf '%s' "$info" | awk '{print $1}')"
33
+ status="$(printf '%s' "$info" | awk '{print $2}')"
34
+ conclusion="$(printf '%s' "$info" | awk '{print $3}')"
35
+ if [ -n "$id" ] && [ "$status" = "completed" ]; then
36
+ printf '%s %s\n' "$id" "$conclusion"
37
+ return 0
38
+ fi
39
+ sleep 10
40
+ done
41
+ return 1
42
+ }
43
+
44
+ for attempt in $(seq 1 "$ATTEMPTS"); do
45
+ echo "::group::Pages publish check (attempt $attempt/$ATTEMPTS)"
46
+ if ! result="$(await_completed)"; then
47
+ echo "Timed out waiting for the pages-build-deployment run." >&2
48
+ echo "::endgroup::"
49
+ exit 1
50
+ fi
51
+ run_id="${result%% *}"
52
+ conclusion="${result##* }"
53
+ echo "pages-build-deployment run $run_id -> $conclusion"
54
+ echo "::endgroup::"
55
+
56
+ if [ "$conclusion" = "success" ]; then
57
+ echo "Pages published for gh-pages@$target_sha."
58
+ exit 0
59
+ fi
60
+
61
+ if [ "$attempt" -lt "$ATTEMPTS" ]; then
62
+ # Progressive backoff: transient failures are usually account-level
63
+ # Pages throttling under concurrent deploys — give it room to clear.
64
+ backoff=$((attempt * 30))
65
+ echo "Transient failure; waiting ${backoff}s before rerunning run $run_id…"
66
+ sleep "$backoff"
67
+ gh run rerun "$run_id" --repo "$REPO" || true
68
+ # Let the rerun leave the "completed" state before we poll again.
69
+ for _ in $(seq 1 12); do
70
+ status="$(find_run | awk '{print $2}')"
71
+ [ "$status" != "completed" ] && break
72
+ sleep 5
73
+ done
74
+ fi
75
+ done
76
+
77
+ echo "Pages deployment still failing after $ATTEMPTS attempts for gh-pages@$target_sha." >&2
78
+ exit 1
@@ -0,0 +1,280 @@
1
+ name: ci
2
+ on:
3
+ push: ~
4
+ pull_request: ~
5
+ jobs:
6
+ ci:
7
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ name: Python ${{ matrix.python-version }}
14
+
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version: [ "3.12", "3.13", "3.14" ]
19
+
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v7
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v8.2.0
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ enable-cache: true
29
+
30
+ - name: Install Dependencies (core only, no variants extra)
31
+ run: uv sync --locked --no-dev --group test
32
+
33
+ - name: Test
34
+ # The wall-clock startup benchmark runs once in the dedicated
35
+ # `benchmark` job, without coverage, so instrumentation and the matrix
36
+ # do not distort its timings.
37
+ run: uv run --no-sync pytest -m "not benchmark" --cov=fastapi_router_lazy --cov-report=xml
38
+
39
+ - name: Codecov
40
+ # Core-only coverage; a single matrix leg uploads (the report is
41
+ # identical across versions). Combined with the variants job's upload.
42
+ if: matrix.python-version == '3.14'
43
+ uses: codecov/codecov-action@v7
44
+ with:
45
+ use_oidc: true
46
+ flags: core
47
+
48
+ variants:
49
+ # The variant/version-aware extractors live behind the optional `variants`
50
+ # extra. The dev group pulls fastapi-router-variants (git source, see
51
+ # pyproject [tool.uv.sources]) so the variant integration tests actually run.
52
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
53
+
54
+ runs-on: ubuntu-latest
55
+
56
+ name: Variants integration
57
+
58
+ permissions:
59
+ contents: read
60
+ id-token: write # OIDC token for Codecov upload
61
+
62
+ steps:
63
+ - uses: actions/checkout@v7
64
+
65
+ - name: Install uv
66
+ uses: astral-sh/setup-uv@v8.2.0
67
+ with:
68
+ python-version: "3.12"
69
+
70
+ - name: Install (with fastapi-router-variants from git)
71
+ run: uv sync
72
+
73
+ - name: Test
74
+ run: uv run --no-sync pytest -m "not benchmark" --cov=fastapi_router_lazy --cov-report=xml
75
+
76
+ - name: Codecov
77
+ # Covers the variant-aware subpackage, which is skipped in the core job.
78
+ uses: codecov/codecov-action@v7
79
+ with:
80
+ use_oidc: true
81
+ flags: variants
82
+
83
+ fastapi-compat:
84
+ # Core lazy loading only needs plain FastAPI routing/middleware. Validate
85
+ # against the supported floor (0.115) up to latest.
86
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
87
+
88
+ runs-on: ubuntu-latest
89
+
90
+ name: FastAPI ${{ matrix.fastapi-version }}
91
+
92
+ strategy:
93
+ fail-fast: false
94
+ matrix:
95
+ fastapi-version:
96
+ - "0.115.0" # floor
97
+ - "0.120.2"
98
+ - "0.129.0"
99
+ - "0.139.0" # latest
100
+
101
+ steps:
102
+ - uses: actions/checkout@v7
103
+
104
+ - name: Install uv
105
+ uses: astral-sh/setup-uv@v8.2.0
106
+ with:
107
+ python-version: "3.12"
108
+
109
+ - name: Install (core + test deps)
110
+ run: uv sync --no-dev --group test
111
+
112
+ - name: Force FastAPI ${{ matrix.fastapi-version }}
113
+ run: uv pip install "fastapi==${{ matrix.fastapi-version }}"
114
+
115
+ - name: Test
116
+ run: uv run --no-sync pytest -m "not benchmark"
117
+
118
+ benchmark:
119
+ # Startup benchmark: fails (and fails the pipeline) if a warm cache does not
120
+ # defer imports and speed app startup up by at least the required factor
121
+ # over the default extractor. Run without coverage so instrumentation does
122
+ # not distort the timings.
123
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
124
+
125
+ runs-on: ubuntu-latest
126
+
127
+ steps:
128
+ - uses: actions/checkout@v7
129
+
130
+ - name: Install uv
131
+ uses: astral-sh/setup-uv@v8.2.0
132
+ with:
133
+ python-version: "3.12"
134
+ enable-cache: true
135
+
136
+ - name: Install (core + test deps)
137
+ run: uv sync --no-dev --group test
138
+
139
+ - name: Benchmark (fails if cached startup speedup < 3x)
140
+ run: uv run --no-sync pytest -m benchmark
141
+
142
+ pre-commit:
143
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
144
+
145
+ runs-on: ubuntu-latest
146
+
147
+ steps:
148
+ - uses: actions/checkout@v7
149
+
150
+ - name: Install uv
151
+ uses: astral-sh/setup-uv@v8.2.0
152
+ with:
153
+ python-version: "3.14"
154
+
155
+ - name: Install dependencies (incl. fastapi-router-variants from git)
156
+ run: uv sync
157
+
158
+ - name: Run pre-commit
159
+ run: uv run pre-commit run --all-files --show-diff-on-failure
160
+
161
+ commitizen:
162
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
163
+
164
+ runs-on: ubuntu-latest
165
+
166
+ steps:
167
+ - uses: actions/checkout@v7
168
+ with:
169
+ fetch-depth: 0
170
+
171
+ - name: Install uv
172
+ uses: astral-sh/setup-uv@v8.2.0
173
+ with:
174
+ python-version: "3.14"
175
+
176
+ - name: Check commit messages
177
+ run: |
178
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
179
+ RANGE="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
180
+ elif [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ] \
181
+ || ! git cat-file -e "${{ github.event.before }}^{commit}" 2>/dev/null; then
182
+ RANGE="HEAD~1..HEAD"
183
+ else
184
+ RANGE="${{ github.event.before }}..${{ github.event.after }}"
185
+ fi
186
+ echo "Checking commit range: ${RANGE}"
187
+ uv run cz check --rev-range "${RANGE}"
188
+
189
+ build:
190
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
191
+ needs: [ ci ]
192
+
193
+ runs-on: ubuntu-latest
194
+
195
+ steps:
196
+ - name: Checkout
197
+ uses: actions/checkout@v7
198
+ with:
199
+ fetch-depth: 0
200
+
201
+ - name: Install uv
202
+ uses: astral-sh/setup-uv@v8.2.0
203
+ with:
204
+ python-version: "3.14"
205
+ enable-cache: true
206
+
207
+ - name: Git User config
208
+ run: |
209
+ git config --global user.email "action@github.com"
210
+ git config --global user.name "github-actions"
211
+
212
+ - name: Bump version
213
+ run: uvx --from python-semantic-release semantic-release version --no-commit --no-tag --no-push
214
+ env:
215
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
216
+
217
+ - name: Build
218
+ run: uv build
219
+
220
+ - uses: actions/upload-artifact@v7
221
+ with:
222
+ name: fastapi-router-lazy-python
223
+ path: ./dist
224
+
225
+ release:
226
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
227
+ needs: [ pre-commit, commitizen, build ]
228
+
229
+ runs-on: ubuntu-latest
230
+
231
+ environment: pypi
232
+
233
+ permissions:
234
+ contents: write # semantic-release pushes the version bump commit + tag + GitHub release
235
+ id-token: write # OIDC token for PyPI Trusted Publishing (no API token needed)
236
+
237
+ steps:
238
+ - name: Checkout
239
+ uses: actions/checkout@v7
240
+ with:
241
+ fetch-depth: 0
242
+
243
+ - name: Install uv
244
+ uses: astral-sh/setup-uv@v8.2.0
245
+ with:
246
+ python-version: "3.14"
247
+ enable-cache: true
248
+
249
+ - name: Git User config
250
+ run: |
251
+ git config --global user.email "action@github.com"
252
+ git config --global user.name "github-actions"
253
+
254
+ - name: Bump version
255
+ run: uvx --from python-semantic-release semantic-release version
256
+ env:
257
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
258
+
259
+ - name: Build
260
+ run: uv build
261
+
262
+ - name: Upload to pypi
263
+ # Trusted Publishing via the job's OIDC token (see permissions above);
264
+ # requires a trusted publisher configured on PyPI for this repo + ci.yml.
265
+ run: uv publish --trusted-publishing always dist/*.tar.gz dist/*.whl
266
+
267
+ - name: Publish release
268
+ run: uvx --from python-semantic-release semantic-release publish
269
+ env:
270
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
271
+
272
+ - name: Merge main to develop
273
+ uses: robotology/gh-action-nightly-merge@v1.5.2
274
+ with:
275
+ stable_branch: 'main'
276
+ development_branch: 'develop'
277
+ allow_ff: true
278
+ user_name: github-actions
279
+ env:
280
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,43 @@
1
+ name: zensical-develop
2
+ on:
3
+ push:
4
+ branches:
5
+ - develop
6
+ # Per-target group: coalesce rapid develop pushes to the latest build, without
7
+ # cancelling the main/preview deploys. Concurrent pushes to disjoint gh-pages
8
+ # folders are handled by the deploy action's fetch+rebase+retry.
9
+ concurrency:
10
+ group: gh-pages-develop
11
+ cancel-in-progress: true
12
+ permissions:
13
+ contents: write
14
+ actions: write
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v8.2.0
22
+ with:
23
+ python-version: "3.13"
24
+ enable-cache: true
25
+
26
+ - run: uv run --only-group docs zensical build --strict
27
+
28
+ # GitHub Pages would otherwise run Jekyll on the static site; disable it.
29
+
30
+ - run: touch site/.nojekyll
31
+
32
+ - name: Deploy develop preview 🚀
33
+ uses: JamesIves/github-pages-deploy-action@v4
34
+ with:
35
+ token: ${{ secrets.GITHUB_TOKEN }}
36
+ branch: gh-pages
37
+ folder: site
38
+ # Publish the develop build under /dev/ without touching the stable site at the root.
39
+ target-folder: dev
40
+ clean: true
41
+
42
+ - name: Ensure Pages published (retry ×3)
43
+ uses: ./.github/actions/ensure-pages-published
@@ -0,0 +1,79 @@
1
+ name: zensical-preview
2
+ on:
3
+ pull_request:
4
+ types: [opened, synchronize, reopened, closed]
5
+ # Per-PR group: a new push (or the close/cleanup) supersedes an in-flight build
6
+ # for the same PR, without touching other PRs' or the main/develop deploys.
7
+ # Concurrent pushes to disjoint gh-pages folders are handled by the deploy
8
+ # action's fetch+rebase+retry.
9
+ concurrency:
10
+ group: gh-pages-preview-${{ github.head_ref }}
11
+ cancel-in-progress: true
12
+ permissions:
13
+ contents: write
14
+ actions: write
15
+ jobs:
16
+ deploy:
17
+ # Same-repo docs/* PRs only: fork PRs get a read-only token and cannot push to gh-pages.
18
+ if: >-
19
+ github.event.action != 'closed'
20
+ && github.event.pull_request.head.repo.full_name == github.repository
21
+ && startsWith(github.head_ref, 'docs/')
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+ - name: Compute preview slug
26
+ id: slug
27
+ run: |
28
+ name="${GITHUB_HEAD_REF#docs/}"
29
+ name="$(printf '%s' "$name" | tr '/' '-' | tr -cd '[:alnum:]._-')"
30
+ echo "name=$name" >> "$GITHUB_OUTPUT"
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v8.2.0
33
+ with:
34
+ python-version: "3.13"
35
+ enable-cache: true
36
+
37
+ - run: uv run --only-group docs zensical build --strict
38
+
39
+ # GitHub Pages would otherwise run Jekyll on the static site; disable it.
40
+
41
+ - run: touch site/.nojekyll
42
+
43
+ - name: Deploy MR preview 🚀
44
+ uses: JamesIves/github-pages-deploy-action@v4
45
+ with:
46
+ token: ${{ secrets.GITHUB_TOKEN }}
47
+ branch: gh-pages
48
+ folder: site
49
+ # Publish under preview/<branch-without-docs>/ without touching the rest of gh-pages.
50
+ target-folder: preview/${{ steps.slug.outputs.name }}
51
+ clean: true
52
+
53
+ - name: Ensure Pages published (retry ×3)
54
+ uses: ./.github/actions/ensure-pages-published
55
+
56
+ cleanup:
57
+ # When a docs/* PR is merged or closed, remove its preview folder.
58
+ if: >-
59
+ github.event.action == 'closed'
60
+ && github.event.pull_request.head.repo.full_name == github.repository
61
+ && startsWith(github.head_ref, 'docs/')
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v7
65
+ with:
66
+ ref: gh-pages
67
+ - name: Remove preview folder
68
+ run: |
69
+ name="${GITHUB_HEAD_REF#docs/}"
70
+ name="$(printf '%s' "$name" | tr '/' '-' | tr -cd '[:alnum:]._-')"
71
+ if [ -d "preview/$name" ]; then
72
+ git config user.name "github-actions[bot]"
73
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
74
+ git rm -r "preview/$name"
75
+ git commit -m "chore(docs): remove MR preview for ${name}"
76
+ git push
77
+ else
78
+ echo "No preview folder preview/${name} to remove."
79
+ fi
@@ -0,0 +1,46 @@
1
+ name: zensical
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ # Per-target group: a release deploy is never cancelled by the develop/preview
7
+ # workflows. Concurrent pushes to disjoint gh-pages folders are handled by the
8
+ # deploy action's fetch+rebase+retry.
9
+ concurrency:
10
+ group: gh-pages-main
11
+ cancel-in-progress: false
12
+ permissions:
13
+ contents: write
14
+ actions: write
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v8.2.0
22
+ with:
23
+ python-version: "3.13"
24
+ enable-cache: true
25
+
26
+ - run: uv run --only-group docs zensical build --strict
27
+
28
+ # GitHub Pages would otherwise run Jekyll on the static site; disable it.
29
+
30
+ - run: touch site/.nojekyll
31
+
32
+ - name: Deploy 🚀
33
+ uses: JamesIves/github-pages-deploy-action@v4
34
+ with:
35
+ token: ${{ secrets.GITHUB_TOKEN }}
36
+ branch: gh-pages
37
+ folder: site
38
+ clean: true
39
+ # Preserve the develop preview (dev/) and the docs/* MR previews (preview/)
40
+ # published by the zensical-develop and zensical-preview workflows.
41
+ clean-exclude: |
42
+ dev
43
+ preview
44
+
45
+ - name: Ensure Pages published (retry ×3)
46
+ uses: ./.github/actions/ensure-pages-published