Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ runs:
version="${VERSION}"
work_dir="$(mktemp -d)"
trap 'rm -rf "${work_dir}"' EXIT
headers=(-H "Accept: application/vnd.github+json")
if [ -n "${GH_TOKEN:-}" ]; then
headers+=(-H "Authorization: Bearer ${GH_TOKEN}")
fi

if [ "${version}" = "latest" ]; then
headers=(-H "Accept: application/vnd.github+json")
if [ -n "${GH_TOKEN:-}" ]; then
headers+=(-H "Authorization: Bearer ${GH_TOKEN}")
fi

latest_release="${work_dir}/latest-release.json"
if ! curl -fsSL "${headers[@]}" "https://api.github.com/repos/${repository}/releases/latest" -o "${latest_release}"; then
echo "Could not resolve the latest YiiPress release for ${repository}. Pin the version input to an existing release." >&2
Expand All @@ -80,6 +79,55 @@ runs:
version="$(
python3 -c 'import json, sys; print(json.load(sys.stdin)["tag_name"])' < "${latest_release}"
)"
elif [ "${version}" = "nightly" ]; then
version=""
page=1
max_pages=10

while [ "${page}" -le "${max_pages}" ]; do
nightly_releases="${work_dir}/nightly-releases-${page}.json"
if ! curl -fsSL "${headers[@]}" "https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}" -o "${nightly_releases}"; then
echo "Could not resolve the latest nightly YiiPress release for ${repository}. Pin the version input to an existing release." >&2
exit 1
fi

if version="$(
python3 - "${nightly_releases}" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as file:
releases = json.load(file)

for release in releases:
tag = release.get("tag_name", "")
assets = {asset.get("name") for asset in release.get("assets", [])}
if (
not release.get("draft")
and release.get("prerelease")
and tag.startswith("nightly-")
and {"yiipress-linux-amd64.tar.gz", "SHA256SUMS"}.issubset(assets)
):
print(tag)
sys.exit(0)

sys.exit(1)
PY
)"; then
break
fi

if python3 -c 'import json, sys; sys.exit(0 if len(json.load(open(sys.argv[1], encoding="utf-8"))) < 100 else 1)' "${nightly_releases}"; then
break
fi

page=$((page + 1))
done

if [ -z "${version}" ]; then
echo "Could not find a nightly YiiPress release for ${repository}. Pin the version input to an existing release." >&2
exit 1
fi
fi

if [ -z "${version}" ]; then
Expand Down
41 changes: 11 additions & 30 deletions .github/workflows/package-static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,42 +99,23 @@ jobs:
mkdir -p assets
tar -C dist/linux-amd64 -czf assets/yiipress-linux-amd64.tar.gz yiipress
sha256sum assets/yiipress-linux-amd64.tar.gz > assets/SHA256SUMS
short_sha="${GITHUB_SHA::12}"
nightly_tag="nightly-${GITHUB_RUN_NUMBER}-${GITHUB_RUN_ATTEMPT}-${short_sha}"

{
printf '# Nightly\n\n'
printf '# Nightly %s\n\n' "${short_sha}"
printf 'Built from `%s` on `%s`.\n\n' "${GITHUB_SHA}" "${GITHUB_REF_NAME}"
printf '%s\n\n' 'This prerelease is updated after successful package builds on `master`.'
printf 'Nightly tag: `%s`.\n\n' "${nightly_tag}"
printf '%s\n\n' 'This immutable prerelease is created after a successful package build on `master`.'
printf '%s\n' 'It is intended for testing unreleased YiiPress changes in site build workflows.'
} > nightly-release-notes.md

if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/nightly" >/dev/null 2>&1; then
gh api \
--method PATCH \
"repos/${GITHUB_REPOSITORY}/git/refs/tags/nightly" \
-f sha="${GITHUB_SHA}" \
-F force=true >/dev/null
else
gh api \
--method POST \
"repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/tags/nightly" \
-f sha="${GITHUB_SHA}" >/dev/null
fi

if gh release view nightly >/dev/null 2>&1; then
gh release upload nightly assets/* --clobber
gh release edit nightly \
--title "Nightly" \
--notes-file nightly-release-notes.md \
--prerelease
else
gh release create nightly assets/* \
--verify-tag \
--title "Nightly" \
--notes-file nightly-release-notes.md \
--prerelease \
--latest=false
fi
gh release create "${nightly_tag}" assets/* \
--target "${GITHUB_SHA}" \
--title "Nightly ${short_sha}" \
--notes-file nightly-release-notes.md \
--prerelease \
--latest=false

windows:
name: Windows static binary
Expand Down
2 changes: 1 addition & 1 deletion docs/binaries-phar-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ The static executable includes `ext-highlighter`, so syntax highlighting does no
Relative `content-dir`, `output-dir`, `new`, `clean`, `serve`, and `import` paths are resolved from the directory where you run `yiipress`, not from the packaged executable location.
PHAR and static binary runs keep build cache and incremental manifests under the OS temp directory, keyed by the current project directory, instead of writing to `runtime/` in the site checkout. `yiipress clean` removes that packaged cache as well as the configured output directory.

GitHub Actions builds the same outputs in the `Package Static Builds` workflow. Commits to `master` publish separate nightly PHAR, Linux, macOS, and Windows workflow artifacts and push the distroless image as `ghcr.io/<owner>/<repo>-static:nightly` plus a commit-specific `nightly-<sha>` tag.
GitHub Actions builds the same outputs in the `Package Static Builds` workflow. Commits to `master` publish separate nightly PHAR, Linux, macOS, and Windows workflow artifacts, create immutable GitHub prereleases tagged as `nightly-<run>-<attempt>-<sha>` with the Linux binary and checksums, and push the distroless image as `ghcr.io/<owner>/<repo>-static:nightly` plus a commit-specific `nightly-<sha>` tag. The reusable build action resolves `version: nightly` to the newest matching nightly prerelease.

Version tags run the `Release` workflow. It builds the PHAR, Linux, macOS, and Windows binaries, pushes only the binary-based distroless image as `ghcr.io/<owner>/<repo>-static:<tag>` plus semver aliases, then creates a draft GitHub release, attaches all binaries and `SHA256SUMS`, writes release notes from commits with their authors, and publishes the release. The Linux binary and PHAR come from the same static-package build, and the release image is assembled from the Linux binary artifact, so the expensive packaging build is not repeated for those outputs.
2 changes: 1 addition & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Enable GitHub Pages in your repository settings: go to **Settings → Pages** an

For project sites such as `https://user.github.io/project/`, set `base_url` to the full deployed URL including the project path. YiiPress uses that path when rendering root-relative redirect targets, so `redirect_to: /blog/` points to `/project/blog/` in browser-facing redirect HTML. Internal links generated by built-in templates and processors are emitted relative to each page, while feeds, sitemaps, canonical URLs, and redirect pages use absolute or browser-facing URLs with the configured project path. Custom templates should use the `$url()` helper for internal links and `Asset::url()` for copied assets.

> **Real-world example:** YiiPress documentation is built from the nightly binary image after the package workflow succeeds — see [`.github/workflows/build-docs.yml`](https://github.com/yiipress/engine/blob/master/.github/workflows/build-docs.yml) in this repository. Site repositories should use the release action above with a fixed version.
> **Real-world example:** YiiPress documentation is built from the nightly binary image after the package workflow succeeds — see [`.github/workflows/build-docs.yml`](https://github.com/yiipress/engine/blob/master/.github/workflows/build-docs.yml) in this repository. Site repositories should use the release action above with a fixed version for production or `version: nightly` to test the newest immutable nightly prerelease.

## Cloudflare Pages

Expand Down
9 changes: 5 additions & 4 deletions docs/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,21 @@ jobs:
```

For testing unreleased changes from the current `master` branch, use `version: nightly`.
The nightly binary is mutable and intended for preview builds only; use a fixed release tag
for production sites.
YiiPress publishes immutable nightly prereleases tagged as `nightly-*`; the action resolves
`nightly` to the newest available nightly prerelease with Linux binary and checksum assets.
Nightlies are intended for preview builds only; use a fixed release tag for production sites.

The action accepts these inputs:

| Input | Default | Description |
|---|---|---|
| `version` | `latest` | YiiPress release tag to download. Use a fixed tag such as `1.2.3` for stable builds, or `nightly` to test the current master build. |
| `version` | `latest` | YiiPress release tag to download. Use a fixed tag such as `1.2.3` for stable builds, `latest` for the latest stable release, or `nightly` for the newest immutable nightly prerelease. |
| `content-dir` | `content` | Content directory passed to `yiipress build`. |
| `output-dir` | `_site` | Output directory passed to `yiipress build`. Change it when the host expects a custom output directory. |
| `working-directory` | `.` | Repository subdirectory where the build runs. |
| `args` | `--no-cache` | Extra arguments appended to `yiipress build`, one argument per line. |
| `binary-path` | runner temp directory | Path where the downloaded binary is installed. Leave it unset unless later steps need the binary at a fixed path. |
| `github-token` | workflow token | Token used when resolving `version: latest` through the GitHub API. |
| `github-token` | workflow token | Token used when resolving `version: latest` or `version: nightly` through the GitHub API. |

The action exposes `version` and `binary-path` outputs if later workflow steps need to report or reuse the downloaded binary.

Expand Down
26 changes: 26 additions & 0 deletions tests/Unit/Packaging/ConfigurationPackagingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ public function packageWorkflowPublishesNightlyBuilds(): void
self::assertStringContainsString("github.ref == 'refs/heads/master'", $workflow);
self::assertStringContainsString('type=raw,value=nightly', $workflow);
self::assertStringContainsString('type=sha,prefix=nightly-', $workflow);
self::assertStringContainsString('nightly_tag="nightly-${GITHUB_RUN_NUMBER}-${GITHUB_RUN_ATTEMPT}-${short_sha}"', $workflow);
self::assertStringContainsString('gh release create "${nightly_tag}" assets/*', $workflow);
self::assertStringContainsString('--target "${GITHUB_SHA}"', $workflow);
self::assertStringContainsString('--latest=false', $workflow);
self::assertStringNotContainsString('git/ref/tags/nightly', $workflow);
self::assertStringNotContainsString('refs/tags/nightly', $workflow);
self::assertStringNotContainsString('gh release create nightly', $workflow);
self::assertStringNotContainsString('gh release view nightly', $workflow);
self::assertStringNotContainsString('type=semver,pattern={{version}}', $workflow);
self::assertStringNotContainsString("startsWith(github.ref, 'refs/tags/')", $workflow);
self::assertStringContainsString('yiipress-macos-arm64.tar.gz', $workflow);
Expand All @@ -297,6 +305,24 @@ public function buildActionInstallsBinaryOutsideCheckoutByDefault(): void
self::assertStringContainsString('printf \'binary-path=%s\n\' "${binary_path}"', $action);
}

#[Test]
public function buildActionResolvesNightlyToLatestImmutablePrerelease(): void
{
$action = file_get_contents(dirname(__DIR__, 3) . '/.github/actions/build/action.yml');
self::assertIsString($action);

self::assertStringContainsString('elif [ "${version}" = "nightly" ]; then', $action);
self::assertStringContainsString('max_pages=10', $action);
self::assertStringContainsString('while [ "${page}" -le "${max_pages}" ]; do', $action);
self::assertStringContainsString('https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}', $action);
self::assertStringContainsString('not release.get("draft")', $action);
self::assertStringContainsString('release.get("prerelease")', $action);
self::assertStringContainsString('tag.startswith("nightly-")', $action);
self::assertStringContainsString('{"yiipress-linux-amd64.tar.gz", "SHA256SUMS"}.issubset(assets)', $action);
self::assertStringContainsString('len(json.load(open(sys.argv[1], encoding="utf-8"))) < 100', $action);
self::assertStringContainsString('Could not find a nightly YiiPress release', $action);
}

#[Test]
public function documentationWorkflowUsesNightlyBinaryAfterPackageWorkflow(): void
{
Expand Down
Loading