Replace the GitHub workflows with a gitbay pipeline !5
13 files changed, +328 −346
Layout: unified · split
.gitbay/ci.yml added +27
| @@ -0,0 +1,27 @@ | |||
| 1 | # gitbay CI. Each job becomes one build per push; the runner clones this | ||
| 2 | # repository at the pushed commit and runs every step with `sh -c`, stopping at | ||
| 3 | # the first failure. Repository secrets arrive as environment variables. | ||
| 4 | # | ||
| 5 | # The runner is Linux, and `runner next` claims the oldest pending build with no | ||
| 6 | # platform targeting, so a second runner could not be aimed at macOS jobs. The | ||
| 7 | # darwin tarballs are therefore built on a Mac by .githooks/pre-push at tag time | ||
| 8 | # and uploaded to the same release; see RELEASING.org. | ||
| 9 | jobs: | ||
| 10 | test: | ||
| 11 | steps: | ||
| 12 | - cargo test --locked | ||
| 13 | - cargo clippy --all-targets --locked -- -D warnings | ||
| 14 | - cargo run --locked -- build docs -o "/tmp/orgo-docs-$GITBAY_SHA" --strict | ||
| 15 | - rm -rf "/tmp/orgo-docs-$GITBAY_SHA" | ||
| 16 | |||
| 17 | # Publishes the documentation site by force-pushing the built output to the | ||
| 18 | # `pages` branch, which gitbay serves at https://orgo.krz.sh. A no-op off main. | ||
| 19 | pages: | ||
| 20 | steps: | ||
| 21 | - sh .gitbay/pages.sh | ||
| 22 | |||
| 23 | # The tag push is the release: binaries, the gitbay release, and crates.io. | ||
| 24 | release: | ||
| 25 | tags: "v*" | ||
| 26 | steps: | ||
| 27 | - sh .gitbay/release.sh | ||
.gitbay/pages.sh added +36
| @@ -0,0 +1,36 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # Publish the documentation site to the `pages` branch, which gitbay serves at | ||
| 3 | # https://orgo.krz.sh. Queued on every branch push; does nothing off main. | ||
| 4 | # | ||
| 5 | # The site is fifteen org files built by the tool that documents them, so this is | ||
| 6 | # also the most honest smoke test in the repository: if orgo cannot build its own | ||
| 7 | # documentation, the job fails and says so. --strict makes a broken internal link | ||
| 8 | # a failure, because docs rot by having their links quietly stop resolving. | ||
| 9 | set -eu | ||
| 10 | |||
| 11 | if [ "${GITBAY_REF:-}" != "main" ]; then | ||
| 12 | echo "ref is ${GITBAY_REF:-unset}, not main — nothing to publish" | ||
| 13 | exit 0 | ||
| 14 | fi | ||
| 15 | |||
| 16 | tmp=$(mktemp -d) | ||
| 17 | trap 'rm -rf "$tmp"' EXIT | ||
| 18 | site="$tmp/site" | ||
| 19 | work="$tmp/work" | ||
| 20 | |||
| 21 | cargo run --release --locked -- build docs -o "$site" --strict | ||
| 22 | rm -f "$site/.orgo-cache.json" | ||
| 23 | |||
| 24 | # An orphan history: the pages branch carries the built site and nothing else, so | ||
| 25 | # each publish replaces it wholesale rather than accumulating every past build. | ||
| 26 | git init -q "$work" | ||
| 27 | cp -R "$site/." "$work/" | ||
| 28 | printf '.orgo-cache.json\n' >"$work/.gitignore" | ||
| 29 | git -C "$work" add -A | ||
| 30 | git -C "$work" -c user.name=gitbay-ci -c user.email=ci@orgo.krz.sh commit -q \ | ||
| 31 | -m "Publish the documentation site | ||
| 32 | |||
| 33 | Built from ${GITBAY_SHA} by \`orgo build docs -o _site --strict\`." | ||
| 34 | git -C "$work" push -q --force "ssh://git@gitbay.org/${GITBAY_REPO}.git" HEAD:refs/heads/pages | ||
| 35 | |||
| 36 | echo "published the site from ${GITBAY_SHA} to the pages branch" | ||
.gitbay/release.sh added +61
| @@ -0,0 +1,61 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # The tag push is the release. Builds the Linux binaries, creates the gitbay | ||
| 3 | # release with the tag's own annotation as its notes, attaches the tarballs, and | ||
| 4 | # publishes to crates.io. | ||
| 5 | # | ||
| 6 | # The two darwin tarballs are not built here: this runner is Linux, and gitbay's | ||
| 7 | # `runner next` claims the oldest pending build with no platform targeting, so a | ||
| 8 | # Mac runner could not be aimed at them. .githooks/pre-push builds them on a Mac | ||
| 9 | # before the tag is pushed and uploads them to this release once it exists. | ||
| 10 | # | ||
| 11 | # crates.io is the one step that cannot be undone — a version can be yanked but | ||
| 12 | # never replaced — so it runs last, after the release exists and the binaries are | ||
| 13 | # attached. CARGO_REGISTRY_TOKEN is a repository secret (`gitbay repo secret set`). | ||
| 14 | set -eu | ||
| 15 | |||
| 16 | tag="${GITBAY_REF:?no tag in GITBAY_REF}" | ||
| 17 | repo="${GITBAY_REPO:?no repository in GITBAY_REPO}" | ||
| 18 | : "${CARGO_REGISTRY_TOKEN:?CARGO_REGISTRY_TOKEN secret is not set}" | ||
| 19 | |||
| 20 | # The tag is the source of truth for the version, checked rather than trusted: a | ||
| 21 | # release tagged v0.18.0 whose binary reports 0.17.0 is the kind of thing nobody | ||
| 22 | # notices for months. | ||
| 23 | version=$(cargo pkgid | sed 's/.*[#@]//') | ||
| 24 | if [ "$tag" != "v$version" ]; then | ||
| 25 | echo "tag $tag does not match Cargo.toml version $version" >&2 | ||
| 26 | exit 1 | ||
| 27 | fi | ||
| 28 | |||
| 29 | dist=dist | ||
| 30 | mkdir -p "$dist" | ||
| 31 | |||
| 32 | # glibc for ordinary distributions, musl for containers and anything older than | ||
| 33 | # the runner's glibc — a dynamically linked binary is the usual reason a download | ||
| 34 | # does not run. | ||
| 35 | for target in x86_64-unknown-linux-gnu x86_64-unknown-linux-musl; do | ||
| 36 | cargo build --release --locked --target "$target" | ||
| 37 | # A tarball rather than a bare binary: it keeps the executable bit through the | ||
| 38 | # download path, and carries the licence with the thing it licenses. | ||
| 39 | staging="orgo-$tag-$target" | ||
| 40 | rm -rf "$staging" | ||
| 41 | mkdir "$staging" | ||
| 42 | cp "target/$target/release/orgo" README.md LICENSE "$staging/" | ||
| 43 | tar czf "$dist/$staging.tar.gz" "$staging" | ||
| 44 | rm -rf "$staging" | ||
| 45 | (cd "$dist" && sha256sum "$staging.tar.gz" >"$staging.tar.gz.sha256") | ||
| 46 | done | ||
| 47 | |||
| 48 | # Notes come from the annotated tag, so the person cutting the release writes | ||
| 49 | # them at the moment they decide to cut it (`git tag -a "$tag" -F notes.md`). | ||
| 50 | git tag -l --format='%(contents)' "$tag" | | ||
| 51 | ssh git@gitbay.org release create "$repo" "$tag" --title "${tag#v}" --file - | ||
| 52 | |||
| 53 | for f in "$dist"/*; do | ||
| 54 | ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f" | ||
| 55 | echo "attached $(basename "$f")" | ||
| 56 | done | ||
| 57 | |||
| 58 | # --locked publishes exactly the dependency versions the tests ran against, | ||
| 59 | # rather than whatever resolves at publish time. | ||
| 60 | cargo publish --locked | ||
| 61 | echo "published $tag to crates.io" | ||
.githooks/pre-push added +53
| @@ -0,0 +1,53 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # Build the macOS binaries when a version tag is pushed. | ||
| 3 | # | ||
| 4 | # gitbay's runner is Linux and has no platform targeting, so the darwin tarballs | ||
| 5 | # cannot be built in CI. They are built here instead, on the Mac doing the push, | ||
| 6 | # and this is the better place for the gate: a build failure aborts the push, so | ||
| 7 | # a tag whose Mac binaries do not compile is never published. GitHub Actions | ||
| 8 | # found that out only after the tag was already public. | ||
| 9 | # | ||
| 10 | # Uploading has to wait: `release asset add` needs the release, and the release is | ||
| 11 | # created by the CI job this push triggers. So the upload is handed to a detached | ||
| 12 | # helper that waits for the release to appear. Its log, and the tarballs, stay in | ||
| 13 | # dist/macos/<tag>/ — rerun .githooks/upload-macos <tag> if it fails. | ||
| 14 | # | ||
| 15 | # Wired up with: git config core.hooksPath .githooks | ||
| 16 | set -eu | ||
| 17 | |||
| 18 | root=$(git rev-parse --show-toplevel) | ||
| 19 | targets="aarch64-apple-darwin x86_64-apple-darwin" | ||
| 20 | |||
| 21 | # stdin: <local ref> <local sha> <remote ref> <remote sha>, one line per ref. | ||
| 22 | while read -r _local_ref _local_sha remote_ref _remote_sha; do | ||
| 23 | case "$remote_ref" in | ||
| 24 | refs/tags/v*) ;; | ||
| 25 | *) continue ;; | ||
| 26 | esac | ||
| 27 | tag=${remote_ref#refs/tags/} | ||
| 28 | |||
| 29 | version=$(cargo pkgid --manifest-path "$root/Cargo.toml" | sed 's/.*[#@]//') | ||
| 30 | if [ "$tag" != "v$version" ]; then | ||
| 31 | echo "pre-push: tag $tag does not match Cargo.toml version $version" >&2 | ||
| 32 | exit 1 | ||
| 33 | fi | ||
| 34 | |||
| 35 | dist="$root/dist/macos/$tag" | ||
| 36 | rm -rf "$dist" | ||
| 37 | mkdir -p "$dist" | ||
| 38 | |||
| 39 | for target in $targets; do | ||
| 40 | echo "pre-push: building $target" | ||
| 41 | (cd "$root" && cargo build --release --locked --target "$target") | ||
| 42 | staging="orgo-$tag-$target" | ||
| 43 | rm -rf "$root/$staging" | ||
| 44 | mkdir "$root/$staging" | ||
| 45 | cp "$root/target/$target/release/orgo" "$root/README.md" "$root/LICENSE" "$root/$staging/" | ||
| 46 | (cd "$root" && tar czf "$dist/$staging.tar.gz" "$staging") | ||
| 47 | rm -rf "$root/$staging" | ||
| 48 | (cd "$dist" && shasum -a 256 "$staging.tar.gz" >"$staging.tar.gz.sha256") | ||
| 49 | done | ||
| 50 | |||
| 51 | echo "pre-push: built $tag for $targets; upload waits for the release" | ||
| 52 | nohup "$root/.githooks/upload-macos" "$tag" >>"$dist/upload.log" 2>&1 & | ||
| 53 | done | ||
.githooks/upload-macos added +34
| @@ -0,0 +1,34 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # Attach the macOS tarballs built by pre-push to the gitbay release. | ||
| 3 | # | ||
| 4 | # The release is created by the CI job the tag push triggers, so this waits for it | ||
| 5 | # rather than assuming it is there. Run by hand as `.githooks/upload-macos <tag>` | ||
| 6 | # if the detached run from the hook failed; the tarballs are already on disk and | ||
| 7 | # uploading one twice replaces it rather than duplicating it. | ||
| 8 | set -eu | ||
| 9 | |||
| 10 | tag=${1:?usage: upload-macos <tag>} | ||
| 11 | root=$(git rev-parse --show-toplevel) | ||
| 12 | dist="$root/dist/macos/$tag" | ||
| 13 | repo=$(git config --get remote.origin.url | sed 's#.*gitbay.org[:/]##; s#\.git$##') | ||
| 14 | |||
| 15 | [ -d "$dist" ] || { echo "no build for $tag in $dist" >&2; exit 1; } | ||
| 16 | |||
| 17 | # The release job builds two Linux targets before creating the release, so the | ||
| 18 | # wait is minutes rather than seconds. Twenty minutes, then give up loudly. | ||
| 19 | i=0 | ||
| 20 | until ssh git@gitbay.org release show "$repo" "$tag" >/dev/null 2>&1; do | ||
| 21 | i=$((i + 1)) | ||
| 22 | if [ "$i" -gt 80 ]; then | ||
| 23 | echo "$(date -u +%FT%TZ) release $tag never appeared; rerun this script" >&2 | ||
| 24 | exit 1 | ||
| 25 | fi | ||
| 26 | sleep 15 | ||
| 27 | done | ||
| 28 | |||
| 29 | for f in "$dist"/*.tar.gz "$dist"/*.sha256; do | ||
| 30 | [ -e "$f" ] || continue | ||
| 31 | ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f" | ||
| 32 | echo "$(date -u +%FT%TZ) attached $(basename "$f")" | ||
| 33 | done | ||
| 34 | echo "$(date -u +%FT%TZ) macOS assets attached to $tag" | ||
.github/workflows/docs.yml deleted −75
| @@ -1,75 +0,0 @@ | |||
| 1 | name: Docs | ||
| 2 | |||
| 3 | # Publish the documentation site to GitHub Pages. | ||
| 4 | # | ||
| 5 | # The site is fifteen org files under docs/, built by the tool they document — so this | ||
| 6 | # job is also the most honest smoke test in the repository: if orgo cannot build its own | ||
| 7 | # documentation, the deploy fails and says so. | ||
| 8 | # | ||
| 9 | # `--strict` makes a broken internal link a build failure. Docs rot by having their links | ||
| 10 | # quietly stop resolving, and that is exactly the failure a static site generator is | ||
| 11 | # supposed to be able to catch. | ||
| 12 | |||
| 13 | on: | ||
| 14 | push: | ||
| 15 | branches: [main] | ||
| 16 | # Templates and the config shape the pages as much as the prose does, and a change to | ||
| 17 | # the renderer changes every page. Anything else — a test, a fixture — cannot. | ||
| 18 | paths: | ||
| 19 | - "docs/**" | ||
| 20 | - "src/**" | ||
| 21 | - "Cargo.toml" | ||
| 22 | - "Cargo.lock" | ||
| 23 | - ".github/workflows/docs.yml" | ||
| 24 | workflow_dispatch: | ||
| 25 | |||
| 26 | # Read the repository, write a Pages deployment. Nothing else. | ||
| 27 | permissions: | ||
| 28 | contents: read | ||
| 29 | pages: write | ||
| 30 | id-token: write | ||
| 31 | |||
| 32 | # One deployment at a time, and let a newer commit win: a queue of doc builds racing each | ||
| 33 | # other to publish is worse than the last one arriving late. | ||
| 34 | concurrency: | ||
| 35 | group: pages | ||
| 36 | cancel-in-progress: true | ||
| 37 | |||
| 38 | jobs: | ||
| 39 | build: | ||
| 40 | runs-on: ubuntu-latest | ||
| 41 | steps: | ||
| 42 | - name: Checkout | ||
| 43 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| 44 | |||
| 45 | - name: Install Rust | ||
| 46 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | ||
| 47 | with: | ||
| 48 | toolchain: stable | ||
| 49 | |||
| 50 | - name: Cache cargo | ||
| 51 | uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 | ||
| 52 | |||
| 53 | - name: Configure Pages | ||
| 54 | uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 | ||
| 55 | |||
| 56 | # Built with the release profile: the docs deploy is also the only place a release | ||
| 57 | # build runs outside a tag, so a break in it surfaces here rather than at release. | ||
| 58 | - name: Build the site | ||
| 59 | run: cargo run --release --locked -- build docs -o docs/_site --strict | ||
| 60 | |||
| 61 | - name: Upload the artifact | ||
| 62 | uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v3.0.1 | ||
| 63 | with: | ||
| 64 | path: docs/_site | ||
| 65 | |||
| 66 | deploy: | ||
| 67 | needs: build | ||
| 68 | runs-on: ubuntu-latest | ||
| 69 | environment: | ||
| 70 | name: github-pages | ||
| 71 | url: ${{ steps.deployment.outputs.page_url }} | ||
| 72 | steps: | ||
| 73 | - name: Deploy | ||
| 74 | id: deployment | ||
| 75 | uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 | ||
.github/workflows/release.yml deleted −155
| @@ -1,155 +0,0 @@ | |||
| 1 | name: Release | ||
| 2 | |||
| 3 | # Build binaries for a tag and attach them to a GitHub release. | ||
| 4 | # | ||
| 5 | # WHY BINARIES AT ALL, given `cargo install orgo` exists: installing from source needs | ||
| 6 | # a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site | ||
| 7 | # generator should be able to download one file and point it at their notes. | ||
| 8 | # | ||
| 9 | # The tag is the source of truth for the version. The build checks it against Cargo.toml | ||
| 10 | # rather than trusting them to match, because a release tagged v0.18.0 containing a binary | ||
| 11 | # that reports 0.17.0 is the kind of thing nobody notices for months. | ||
| 12 | |||
| 13 | on: | ||
| 14 | push: | ||
| 15 | tags: ["v*"] | ||
| 16 | workflow_dispatch: | ||
| 17 | inputs: | ||
| 18 | tag: | ||
| 19 | description: "Tag to build (for a re-run of a failed release)" | ||
| 20 | required: true | ||
| 21 | |||
| 22 | env: | ||
| 23 | CARGO_TERM_COLOR: always | ||
| 24 | |||
| 25 | jobs: | ||
| 26 | build: | ||
| 27 | name: ${{ matrix.target }} | ||
| 28 | runs-on: ${{ matrix.os }} | ||
| 29 | strategy: | ||
| 30 | fail-fast: false | ||
| 31 | matrix: | ||
| 32 | include: | ||
| 33 | # Apple Silicon and Intel Macs, each built natively on its own runner. The | ||
| 34 | # Intel label moves with the image: macos-13 was retired, and a job asking for | ||
| 35 | # a retired label does not fail — it sits queued until someone notices, which | ||
| 36 | # is how the v0.20.0 release spent fifteen minutes doing nothing. | ||
| 37 | - { os: macos-latest, target: aarch64-apple-darwin } | ||
| 38 | - { os: macos-15-intel, target: x86_64-apple-darwin } | ||
| 39 | # glibc for ordinary distributions, musl for containers and anything older than | ||
| 40 | # the runner's glibc — a dynamically linked binary is the usual reason a | ||
| 41 | # download does not run. | ||
| 42 | - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu } | ||
| 43 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl } | ||
| 44 | steps: | ||
| 45 | - name: Checkout | ||
| 46 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| 47 | with: | ||
| 48 | ref: ${{ github.event.inputs.tag || github.ref }} | ||
| 49 | |||
| 50 | - name: Install Rust | ||
| 51 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | ||
| 52 | with: | ||
| 53 | toolchain: stable | ||
| 54 | targets: ${{ matrix.target }} | ||
| 55 | |||
| 56 | - name: Install musl tools | ||
| 57 | if: endsWith(matrix.target, '-musl') | ||
| 58 | run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools | ||
| 59 | |||
| 60 | - name: Check the tag against Cargo.toml | ||
| 61 | shell: bash | ||
| 62 | run: | | ||
| 63 | tag="${{ github.event.inputs.tag || github.ref_name }}" | ||
| 64 | crate=$(cargo metadata --no-deps --format-version 1 \ | ||
| 65 | | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])') | ||
| 66 | if [ "$tag" != "v$crate" ]; then | ||
| 67 | echo "tag $tag does not match Cargo.toml version $crate" >&2 | ||
| 68 | exit 1 | ||
| 69 | fi | ||
| 70 | |||
| 71 | - name: Build | ||
| 72 | run: cargo build --release --locked --target ${{ matrix.target }} | ||
| 73 | |||
| 74 | # A tarball rather than a bare binary: it keeps the executable bit through GitHub's | ||
| 75 | # download path, and carries the licence with the thing it licenses. | ||
| 76 | - name: Package | ||
| 77 | shell: bash | ||
| 78 | run: | | ||
| 79 | staging="orgo-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}" | ||
| 80 | mkdir "$staging" | ||
| 81 | cp "target/${{ matrix.target }}/release/orgo" "$staging/" | ||
| 82 | cp README.md LICENSE "$staging/" | ||
| 83 | tar czf "$staging.tar.gz" "$staging" | ||
| 84 | shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256" | ||
| 85 | |||
| 86 | - name: Upload | ||
| 87 | uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| 88 | with: | ||
| 89 | name: ${{ matrix.target }} | ||
| 90 | path: | | ||
| 91 | *.tar.gz | ||
| 92 | *.tar.gz.sha256 | ||
| 93 | |||
| 94 | release: | ||
| 95 | name: publish the release | ||
| 96 | needs: build | ||
| 97 | runs-on: ubuntu-latest | ||
| 98 | permissions: | ||
| 99 | contents: write | ||
| 100 | steps: | ||
| 101 | - name: Download every build | ||
| 102 | uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| 103 | with: | ||
| 104 | merge-multiple: true | ||
| 105 | |||
| 106 | # A draft, deliberately. The release notes are written by a person, and a release | ||
| 107 | # that publishes itself before anyone has read it cannot be edited quietly. | ||
| 108 | - name: Create the draft release | ||
| 109 | uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 | ||
| 110 | with: | ||
| 111 | draft: true | ||
| 112 | tag_name: ${{ github.event.inputs.tag || github.ref_name }} | ||
| 113 | files: | | ||
| 114 | *.tar.gz | ||
| 115 | *.tar.gz.sha256 | ||
| 116 | |||
| 117 | publish: | ||
| 118 | name: publish to crates.io | ||
| 119 | needs: build | ||
| 120 | runs-on: ubuntu-latest | ||
| 121 | # Named so it can be gated. Adding a required reviewer to this environment in the | ||
| 122 | # repository settings turns a tag push into "waiting for a human", which is the right | ||
| 123 | # shape for the one step in this workflow that cannot be undone: a published version | ||
| 124 | # can be yanked but never replaced. | ||
| 125 | environment: crates-io | ||
| 126 | permissions: | ||
| 127 | # The OIDC token this mints *is* the credential — there is no API token stored in | ||
| 128 | # this repository, and nothing to leak from a compromised job beyond a token that | ||
| 129 | # crates.io revokes when the job ends. | ||
| 130 | id-token: write | ||
| 131 | contents: read | ||
| 132 | steps: | ||
| 133 | - name: Checkout | ||
| 134 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| 135 | with: | ||
| 136 | ref: ${{ github.event.inputs.tag || github.ref }} | ||
| 137 | |||
| 138 | - name: Install Rust | ||
| 139 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | ||
| 140 | with: | ||
| 141 | toolchain: stable | ||
| 142 | |||
| 143 | # Exchanges GitHub's OIDC token for a short-lived crates.io token, and revokes it | ||
| 144 | # when the job finishes. Configured on crates.io against this repository, this | ||
| 145 | # workflow's filename, and the environment above. | ||
| 146 | - name: Authenticate with crates.io | ||
| 147 | id: auth | ||
| 148 | uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 | ||
| 149 | |||
| 150 | # `--locked` publishes exactly the dependency versions the tests ran against, rather | ||
| 151 | # than whatever resolves at publish time. | ||
| 152 | - name: Publish | ||
| 153 | run: cargo publish --locked | ||
| 154 | env: | ||
| 155 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} | ||
.gitignore +1
| @@ -1,2 +1,3 @@ | |||
| 1 | /target | 1 | /target |
| 2 | _site/ | 2 | _site/ |
| 3 | dist/ | ||
Cargo.toml +1 −1
| @@ -9,7 +9,7 @@ readme = "README.md" | |||
| 9 | keywords = ["org-mode", "static-site", "ssg", "emacs", "blog"] | 9 | keywords = ["org-mode", "static-site", "ssg", "emacs", "blog"] |
| 10 | categories = ["command-line-utilities", "text-processing"] | 10 | categories = ["command-line-utilities", "text-processing"] |
| 11 | repository = "https://gitbay.org/krz/orgo" | 11 | repository = "https://gitbay.org/krz/orgo" |
| 12 | homepage = "https://orgo.krz.sh/" | 12 | homepage = "https://krazywarez.github.io/orgo/" |
| 13 | 13 | ||
| 14 | # The compiler floor. orgo's own code needs 1.82 (`Option::is_none_or`); the floor is | 14 | # The compiler floor. orgo's own code needs 1.82 (`Option::is_none_or`); the floor is |
| 15 | # 1.88 because dependencies in Cargo.lock declare it — `plist` and `time`, both by way of | 15 | # 1.88 because dependencies in Cargo.lock declare it — `plist` and `time`, both by way of |
README.md +25 −25
| @@ -14,7 +14,7 @@ heading's TODO state and tags, `#+` keywords, ID links, captions on images. orgo | |||
| 14 | of it, and its output is checked page by page against what Emacs' own exporter produces | 14 | of it, and its output is checked page by page against what Emacs' own exporter produces |
| 15 | from the same file. | 15 | from the same file. |
| 16 | 16 | ||
| 17 | **Documentation: <https://orgo.krz.sh/>** — that site is written in org and | 17 | **Documentation: <https://krazywarez.github.io/orgo/>** — that site is written in org and |
| 18 | built by orgo, so it doubles as the longest worked example available. | 18 | built by orgo, so it doubles as the longest worked example available. |
| 19 | 19 | ||
| 20 | ## Install | 20 | ## Install |
| @@ -35,7 +35,7 @@ cargo install --path . | |||
| 35 | ``` | 35 | ``` |
| 36 | 36 | ||
| 37 | Either way you get an `orgo` command on your `PATH`. Full notes, including how to run it | 37 | Either way you get an `orgo` command on your `PATH`. Full notes, including how to run it |
| 38 | without installing anything: <https://orgo.krz.sh/install.html> | 38 | without installing anything: <https://krazywarez.github.io/orgo/install.html> |
| 39 | 39 | ||
| 40 | ## Your First Site | 40 | ## Your First Site |
| 41 | 41 | ||
| @@ -76,15 +76,15 @@ Each of these is a few lines of config, and each has a page in the guide: | |||
| 76 | 76 | ||
| 77 | | Add | Documented in | | 77 | | Add | Documented in | |
| 78 | |---|---| | 78 | |---|---| |
| 79 | | A blog index, newest first | [Collections](https://orgo.krz.sh/guide/03-collections.html) | | 79 | | A blog index, newest first | [Collections](https://krazywarez.github.io/orgo/guide/03-collections.html) | |
| 80 | | Tag pages, and an index of tags | [Collections](https://orgo.krz.sh/guide/03-collections.html) | | 80 | | Tag pages, and an index of tags | [Collections](https://krazywarez.github.io/orgo/guide/03-collections.html) | |
| 81 | | An RSS feed | [Collections](https://orgo.krz.sh/guide/03-collections.html) | | 81 | | An RSS feed | [Collections](https://krazywarez.github.io/orgo/guide/03-collections.html) | |
| 82 | | Numbered pages when a list gets long | [Collections](https://orgo.krz.sh/guide/03-collections.html) | | 82 | | Numbered pages when a list gets long | [Collections](https://krazywarez.github.io/orgo/guide/03-collections.html) | |
| 83 | | A built-in theme for a blog, a wiki or a doc site | [Configuration](https://orgo.krz.sh/guide/02-configuration.html) | | 83 | | A built-in theme for a blog, a wiki or a doc site | [Configuration](https://krazywarez.github.io/orgo/guide/02-configuration.html) | |
| 84 | | Your own design, in ordinary HTML templates | [Templates](https://orgo.krz.sh/guide/04-templates.html) | | 84 | | Your own design, in ordinary HTML templates | [Templates](https://krazywarez.github.io/orgo/guide/04-templates.html) | |
| 85 | | Drafts that stay unpublished until you say so | [Authoring](https://orgo.krz.sh/guide/06-authoring.html) | | 85 | | Drafts that stay unpublished until you say so | [Authoring](https://krazywarez.github.io/orgo/guide/06-authoring.html) | |
| 86 | | A table of contents on long posts | [Authoring](https://orgo.krz.sh/guide/06-authoring.html) | | 86 | | A table of contents on long posts | [Authoring](https://krazywarez.github.io/orgo/guide/06-authoring.html) | |
| 87 | | Clean URLs that survive a renamed file | [Authoring](https://orgo.krz.sh/guide/06-authoring.html) | | 87 | | Clean URLs that survive a renamed file | [Authoring](https://krazywarez.github.io/orgo/guide/06-authoring.html) | |
| 88 | 88 | ||
| 89 | Rebuilds only touch the pages that actually changed, so saving a post on a site with | 89 | Rebuilds only touch the pages that actually changed, so saving a post on a site with |
| 90 | hundreds of them stays instant. | 90 | hundreds of them stays instant. |
| @@ -110,28 +110,28 @@ Read the numbers with three things in mind. The weblorg figures include Emacs st | |||
| 110 | loading its packages, which you pay on every publish and cannot avoid. orgo emits 13 pages | 110 | loading its packages, which you pay on every publish and cannot avoid. orgo emits 13 pages |
| 111 | weblorg does not, one per tag, so it is doing slightly more work. And the two do not | 111 | weblorg does not, one per tag, so it is doing slightly more work. And the two do not |
| 112 | produce byte-identical output — the differences are deliberate and listed under | 112 | produce byte-identical output — the differences are deliberate and listed under |
| 113 | [Org support](https://orgo.krz.sh/guide/05-org-support.html). | 113 | [Org support](https://krazywarez.github.io/orgo/guide/05-org-support.html). |
| 114 | 114 | ||
| 115 | Apple M2 Pro, 12 cores, macOS 26.6, Emacs 30.2, orgo built with `--release`. | 115 | Apple M2 Pro, 12 cores, macOS 26.6, Emacs 30.2, orgo built with `--release`. |
| 116 | 116 | ||
| 117 | ## Docs | 117 | ## Docs |
| 118 | 118 | ||
| 119 | <https://orgo.krz.sh/> | 119 | <https://krazywarez.github.io/orgo/> |
| 120 | 120 | ||
| 121 | | Page | What is in it | | 121 | | Page | What is in it | |
| 122 | |---|---| | 122 | |---|---| |
| 123 | | [Quick start](https://orgo.krz.sh/quickstart.html) | A working site in two commands, then your own writing, then your own design. | | 123 | | [Quick start](https://krazywarez.github.io/orgo/quickstart.html) | A working site in two commands, then your own writing, then your own design. | |
| 124 | | [Install](https://orgo.krz.sh/install.html) | Getting the binary, and running it without installing anything. | | 124 | | [Install](https://krazywarez.github.io/orgo/install.html) | Getting the binary, and running it without installing anything. | |
| 125 | | [Commands](https://orgo.krz.sh/guide/01-cli.html) | Every command and flag, and what each is for. | | 125 | | [Commands](https://krazywarez.github.io/orgo/guide/01-cli.html) | Every command and flag, and what each is for. | |
| 126 | | [Configuration](https://orgo.krz.sh/guide/02-configuration.html) | Every setting in `orgo.toml`, what it changes, and what it costs. | | 126 | | [Configuration](https://krazywarez.github.io/orgo/guide/02-configuration.html) | Every setting in `orgo.toml`, what it changes, and what it costs. | |
| 127 | | [Collections](https://orgo.krz.sh/guide/03-collections.html) | Blog indexes, tag pages, pagination and RSS feeds. | | 127 | | [Collections](https://krazywarez.github.io/orgo/guide/03-collections.html) | Blog indexes, tag pages, pagination and RSS feeds. | |
| 128 | | [Templates](https://orgo.krz.sh/guide/04-templates.html) | Layouts, and every variable a template can use. | | 128 | | [Templates](https://krazywarez.github.io/orgo/guide/04-templates.html) | Layouts, and every variable a template can use. | |
| 129 | | [Org support](https://orgo.krz.sh/guide/05-org-support.html) | Which org syntax is handled, which is not, and how the rest degrades. | | 129 | | [Org support](https://krazywarez.github.io/orgo/guide/05-org-support.html) | Which org syntax is handled, which is not, and how the rest degrades. | |
| 130 | | [Authoring](https://orgo.krz.sh/guide/06-authoring.html) | URLs, drafts, excerpts, tables of contents. | | 130 | | [Authoring](https://krazywarez.github.io/orgo/guide/06-authoring.html) | URLs, drafts, excerpts, tables of contents. | |
| 131 | | [Incremental builds](https://orgo.krz.sh/guide/07-incremental.html) | How it decides what to rebuild. | | 131 | | [Incremental builds](https://krazywarez.github.io/orgo/guide/07-incremental.html) | How it decides what to rebuild. | |
| 132 | | [Watching and serving](https://orgo.krz.sh/guide/08-workflow.html) | The write-save-see loop. | | 132 | | [Watching and serving](https://krazywarez.github.io/orgo/guide/08-workflow.html) | The write-save-see loop. | |
| 133 | | [Auditing](https://orgo.krz.sh/guide/09-auditing.html) | Reading a corpus before trusting a tool with it. | | 133 | | [Auditing](https://krazywarez.github.io/orgo/guide/09-auditing.html) | Reading a corpus before trusting a tool with it. | |
| 134 | | [Deploying](https://orgo.krz.sh/guide/10-deploying.html) | Producing a production build, and putting it somewhere. | | 134 | | [Deploying](https://krazywarez.github.io/orgo/guide/10-deploying.html) | Producing a production build, and putting it somewhere. | |
| 135 | 135 | ||
| 136 | ## Building | 136 | ## Building |
| 137 | 137 | ||
RELEASING.org +83 −41
| @@ -3,30 +3,65 @@ | |||
| 3 | Read the content below for the release process. | 3 | Read the content below for the release process. |
| 4 | 4 | ||
| 5 | ** Where the repository lives | 5 | ** Where the repository lives |
| 6 | =origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues and | 6 | =origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues, merge |
| 7 | merge requests are. It push-mirrors to =https://github.com/krazywarez/orgo=, tags | 7 | requests, builds and releases are. It push-mirrors to |
| 8 | included. | 8 | =https://github.com/krazywarez/orgo=, tags included, but nothing in a release depends on |
| 9 | that mirror any more — it is a copy, not a step. | ||
| 10 | |||
| 11 | ** What runs where | ||
| 12 | Three things happen automatically, and one of them happens on your Mac. | ||
| 13 | |||
| 14 | | Trigger | Where | What | | ||
| 15 | |--------------------+---------------------------+---------------------------------------------------| | ||
| 16 | | Any branch push | gitbay CI (=test= job) | =cargo test=, clippy, and a =--strict= docs build | | ||
| 17 | | A push to =main= | gitbay CI (=pages= job) | Publishes the site to the =pages= branch | | ||
| 18 | | A =v*= tag push | Your Mac (=pre-push=) | Builds and packages the two darwin tarballs | | ||
| 19 | | A =v*= tag push | gitbay CI (=release= job) | Linux tarballs, the release, then crates.io | | ||
| 20 | |||
| 21 | The darwin binaries are the one part CI cannot do. The runner is Linux, and gitbay's | ||
| 22 | =runner next= claims the oldest pending build with no platform targeting, so a second | ||
| 23 | runner could not be aimed at macOS jobs. Building them in =pre-push= is the better place | ||
| 24 | anyway: a build failure aborts the push, so a tag whose Mac binaries do not compile is | ||
| 25 | never published. | ||
| 26 | |||
| 27 | Uploading them has to wait, because =release asset add= needs a release and the release is | ||
| 28 | created by the job the tag push triggers. =pre-push= hands that to a detached | ||
| 29 | =.githooks/upload-macos=, which waits for the release and then attaches the tarballs. Its | ||
| 30 | log and the tarballs stay in =dist/macos/<tag>/=. | ||
| 9 | 31 | ||
| 10 | That mirror is what makes a release work. The automation is GitHub Actions and nothing | 32 | ** Before the first publish |
| 11 | triggers it directly: a tag pushed to gitbay reaches GitHub within a minute, and | 33 | Once per clone: |
| 12 | =.github/workflows/release.yml= runs there. =docs.yml= deploys the documentation site to | ||
| 13 | Pages the same way, on a push to =main= that touches =docs/=, =src/=, =Cargo.toml= or | ||
| 14 | =Cargo.lock=. | ||
| 15 | 34 | ||
| 16 | Neither forge runs the tests. =ci.yml= is gone and there is no =.gitbay/ci.yml=, so the | 35 | #+begin_src sh |
| 17 | checks in step 2 are the only gate a release passes. | 36 | git config core.hooksPath .githooks |
| 37 | #+end_src | ||
| 18 | 38 | ||
| 19 | ** Before the first publish | 39 | Once on the Mac that cuts releases — Homebrew's =rust= cannot add targets, so the |
| 20 | Nothing to install and no token to store. The release workflow mints a short-lived | 40 | toolchain has to be rustup: |
| 21 | crates.io token with OIDC, configured on crates.io against the GitHub repository, the | 41 | |
| 22 | =release.yml= workflow file and the =crates-io= environment that job runs in. | 42 | #+begin_src sh |
| 43 | brew install rustup && rustup default stable | ||
| 44 | rustup target add x86_64-apple-darwin | ||
| 45 | #+end_src | ||
| 46 | |||
| 47 | Once on the VPS runner: a Rust toolchain with =x86_64-unknown-linux-musl= added and | ||
| 48 | =musl-gcc= installed, and an SSH key registered on gitbay that can push to the repository | ||
| 49 | (the =pages= job) and run =release create= (the =release= job). | ||
| 50 | |||
| 51 | Once on the repository — the crates.io token, which is the one credential this setup | ||
| 52 | stores: | ||
| 23 | 53 | ||
| 24 | =repository= in =Cargo.toml= points at gitbay; =homepage= points at the documentation | 54 | #+begin_src sh |
| 25 | site on Pages. | 55 | gitbay repo secret set krz/orgo CARGO_REGISTRY_TOKEN |
| 56 | #+end_src | ||
| 57 | |||
| 58 | =repository= in =Cargo.toml= points at gitbay; =homepage= points at | ||
| 59 | [[https://orgo.krz.sh]], the documentation site the =pages= job publishes. | ||
| 26 | 60 | ||
| 27 | ** Every release | 61 | ** Every release |
| 28 | 1. Bump the version in =Cargo.toml=, and build once so =Cargo.lock= follows. | 62 | 1. Bump the version in =Cargo.toml=, and build once so =Cargo.lock= follows. |
| 29 | 2. Test and package the release. | 63 | 2. Test and package the release. CI runs the first two on every push, but a release is |
| 64 | worth checking before you tag rather than after. | ||
| 30 | 65 | ||
| 31 | #+begin_src sh | 66 | #+begin_src sh |
| 32 | cargo test | 67 | cargo test |
| @@ -37,49 +72,56 @@ site on Pages. | |||
| 37 | 72 | ||
| 38 | 3. Build a site you know with =--no-cache= and diff the output against the previous | 73 | 3. Build a site you know with =--no-cache= and diff the output against the previous |
| 39 | version's. | 74 | version's. |
| 40 | 4. Commit, tag, push. | 75 | 4. Commit and push. The =test= and =pages= jobs run. |
| 41 | 76 | ||
| 42 | #+begin_src sh | 77 | #+begin_src sh |
| 43 | git commit -am "0.18: <what changed>" | 78 | git commit -am "0.24: <what changed>" |
| 44 | git tag -a v0.18.0 -m "0.18.0" | 79 | git push |
| 45 | git push && git push --tags | ||
| 46 | #+end_src | 80 | #+end_src |
| 47 | 81 | ||
| 48 | 5. The tag push is the release, by way of the mirror. It builds binaries for macOS | 82 | 5. Write the release notes, then tag with them. The =release= job uses the tag's own |
| 49 | (arm64 and x86_64) and Linux (gnu and musl), opens a /draft/ GitHub release with them | 83 | annotation as the release notes, so the notes are written at the moment you decide to |
| 50 | attached, and runs =cargo publish --locked= — there is nothing to publish by hand, and | 84 | cut the release rather than afterwards. |
| 51 | running =cargo publish= locally now only fails on a version crates.io already has. The | ||
| 52 | build checks the tag against =Cargo.toml= rather than trusting the two to match. | ||
| 53 | 85 | ||
| 54 | =gh= talks to the mirror, so it is how you watch the run: | 86 | #+begin_src sh |
| 87 | $EDITOR notes.md | ||
| 88 | git tag -a v0.24.0 -F notes.md | ||
| 89 | git push --tags | ||
| 90 | #+end_src | ||
| 91 | |||
| 92 | The push blocks while your Mac builds both darwin targets. If either fails, the push | ||
| 93 | is refused and nothing is tagged. | ||
| 94 | |||
| 95 | 6. The tag push is the release. It checks the tag against =Cargo.toml= rather than | ||
| 96 | trusting the two to match, builds the gnu and musl tarballs, creates the release with | ||
| 97 | your notes, attaches the tarballs, and runs =cargo publish --locked=. The macOS | ||
| 98 | tarballs arrive a few minutes later from the detached uploader. | ||
| 55 | 99 | ||
| 56 | #+begin_src sh | 100 | #+begin_src sh |
| 57 | gh run list -R krazywarez/orgo --limit 3 | 101 | gitbay build list krz/orgo |
| 102 | gitbay release show krz/orgo v0.24.0 | ||
| 58 | #+end_src | 103 | #+end_src |
| 59 | 104 | ||
| 60 | Publishing is the one step that cannot be undone: a version can be yanked but never | 105 | Publishing is the one step that cannot be undone: a version can be yanked but never |
| 61 | replaced. The publish job runs in the =crates-io= environment so it can be held — | 106 | replaced. It runs last, after the release exists and the binaries are attached, so a |
| 62 | add a required reviewer to that environment in the GitHub repository settings and a | 107 | failure earlier in the job costs you a tag rather than a version number. |
| 63 | tag push waits for a human before it reaches crates.io. | ||
| 64 | 108 | ||
| 65 | A release that fails halfway is re-run from the Actions tab: the workflow takes the | 109 | 7. If the macOS upload did not land — check =dist/macos/v0.24.0/upload.log= — rerun it. |
| 66 | tag to build as an input, so it does not need a second tag. | 110 | The tarballs are already built, and uploading one twice replaces it. |
| 67 | |||
| 68 | 6. Write the release notes and publish the draft GitHub release. | ||
| 69 | 7. Release on gitbay, which has no automation of its own. Same notes, same binaries. | ||
| 70 | 111 | ||
| 71 | #+begin_src sh | 112 | #+begin_src sh |
| 72 | gitbay release create v0.18.0 --title 0.18.0 --file - < notes.md | 113 | .githooks/upload-macos v0.24.0 |
| 73 | gh release download v0.18.0 -R krazywarez/orgo -D dist | ||
| 74 | for f in dist/*; do gitbay release asset add v0.18.0 "$(basename "$f")" < "$f"; done | ||
| 75 | #+end_src | 114 | #+end_src |
| 76 | 115 | ||
| 77 | ** If a release goes wrong | 116 | ** If a release goes wrong |
| 78 | Yank rather than delete, and ship a fix as a new version: | 117 | Yank rather than delete, and ship a fix as a new version: |
| 79 | 118 | ||
| 80 | #+begin_src sh | 119 | #+begin_src sh |
| 81 | cargo yank --version 0.18.0 | 120 | cargo yank --version 0.24.0 |
| 82 | #+end_src | 121 | #+end_src |
| 83 | 122 | ||
| 84 | Yanking stops new dependents from selecting it; anyone who already has it keeps working. | 123 | Yanking stops new dependents from selecting it; anyone who already has it keeps working. |
| 85 | Then release =0.18.1= with the fix. | 124 | Then release =0.24.1= with the fix. |
| 125 | |||
| 126 | A build that failed halfway is re-run with =gitbay build trigger=, which does not need a | ||
| 127 | second tag. | ||
src/site.rs +7 −20
| @@ -1332,10 +1332,7 @@ fn discover( | |||
| 1332 | }; | 1332 | }; |
| 1333 | let rel = path.strip_prefix(src).unwrap_or(path); | 1333 | let rel = path.strip_prefix(src).unwrap_or(path); |
| 1334 | // The source root itself always passes; `filter_entry` prunes whole subtrees. | 1334 | // The source root itself always passes; `filter_entry` prunes whole subtrees. |
| 1335 | if rel.as_str().is_empty() { | 1335 | rel.as_str().is_empty() || !is_excluded(rel, &skip_dirs) |
| 1336 | return true; | ||
| 1337 | } | ||
| 1338 | !is_excluded(rel, &skip_dirs) && !is_build_output(e) | ||
| 1339 | }) { | 1336 | }) { |
| 1340 | let entry = entry.with_context(|| format!("walking {src}"))?; | 1337 | let entry = entry.with_context(|| format!("walking {src}"))?; |
| 1341 | if !entry.file_type().is_file() { | 1338 | if !entry.file_type().is_file() { |
| @@ -1482,6 +1479,9 @@ fn excluded_dirs(src: &Utf8Path, config: &Config, out: Option<&Utf8Path>) -> Vec | |||
| 1482 | dirs | 1479 | dirs |
| 1483 | } | 1480 | } |
| 1484 | 1481 | ||
| 1482 | /// Is this source-relative path excluded from discovery? | ||
| 1483 | /// | ||
| 1484 | /// Dot-entries are skipped wholesale. That is the conventional rule for site generators, | ||
| 1485 | /// Is this path component a dot-entry that must not be published? | 1485 | /// Is this path component a dot-entry that must not be published? |
| 1486 | /// | 1486 | /// |
| 1487 | /// Dot-directories are excluded because a source directory is very often a git repository, | 1487 | /// Dot-directories are excluded because a source directory is very often a git repository, |
| @@ -1499,22 +1499,9 @@ fn is_hidden(component: &str) -> bool { | |||
| 1499 | /// The one dot-directory the web expects to be published. | 1499 | /// The one dot-directory the web expects to be published. |
| 1500 | const WELL_KNOWN: &str = ".well-known"; | 1500 | const WELL_KNOWN: &str = ".well-known"; |
| 1501 | 1501 | ||
| 1502 | /// Is this directory a site orgo built earlier? | 1502 | /// and the reason is safety rather than tidiness: a source directory is very often a git |
| 1503 | /// | 1503 | /// repository, and publishing `.git` — or `.env` — is a way to leak a project's entire |
| 1504 | /// Every build writes `.orgo-cache.json` into its output directory, so a directory in the | 1504 | /// history alongside its homepage. |
| 1505 | /// source carrying one is output rather than content someone wrote. `excluded_dirs` only | ||
| 1506 | /// covers an output directory nested in the source; without this, building into the | ||
| 1507 | /// source (`-o _site`) and then previewing elsewhere (`-o /tmp/preview`) copies the whole | ||
| 1508 | /// first site into the second, one asset at a time. | ||
| 1509 | fn is_build_output(entry: &walkdir::DirEntry) -> bool { | ||
| 1510 | entry.file_type().is_dir() && entry.path().join(".orgo-cache.json").is_file() | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | /// Is this source-relative path excluded from discovery? | ||
| 1514 | /// | ||
| 1515 | /// Dot-entries are skipped wholesale, and the reason is safety rather than tidiness: a | ||
| 1516 | /// source directory is very often a git repository, and publishing `.git` — or `.env` — | ||
| 1517 | /// is a way to leak a project's entire history alongside its homepage. | ||
| 1518 | fn is_excluded(rel: &Utf8Path, skip_dirs: &[Utf8PathBuf]) -> bool { | 1505 | fn is_excluded(rel: &Utf8Path, skip_dirs: &[Utf8PathBuf]) -> bool { |
| 1519 | if rel.components().any(|c| is_hidden(c.as_str())) { | 1506 | if rel.components().any(|c| is_hidden(c.as_str())) { |
| 1520 | return true; | 1507 | return true; |
tests/incremental.rs −29
| @@ -513,32 +513,3 @@ fn editing_one_template_rebuilds_only_the_pages_that_use_it() { | |||
| 513 | r.rendered | 513 | r.rendered |
| 514 | ); | 514 | ); |
| 515 | } | 515 | } |
| 516 | |||
| 517 | /// A previous build's output, left in the source, is not content. Building into the | ||
| 518 | /// source and then previewing elsewhere used to copy the whole first site into the | ||
| 519 | /// second — 2 assets became 21 on orgo's own documentation. | ||
| 520 | #[test] | ||
| 521 | fn a_previous_build_output_is_not_copied_as_assets() { | ||
| 522 | let src = tmpdir("prev-output-src"); | ||
| 523 | write(&src, "index.org", "#+TITLE: Home\n\nHome.\n"); | ||
| 524 | write(&src, "logo.svg", "<svg/>"); | ||
| 525 | |||
| 526 | // Build into the source, as docs/guide/10-deploying.org does. | ||
| 527 | let nested = src.join("_site"); | ||
| 528 | build_site(&src, &nested, &BuildOptions::default()).unwrap(); | ||
| 529 | assert!(manifest_path(&nested).exists(), "the build wrote its cache manifest"); | ||
| 530 | |||
| 531 | // Then preview elsewhere, as docs/guide/09-auditing.org does. | ||
| 532 | let preview = tmpdir("prev-output-preview"); | ||
| 533 | let r = build_site(&src, &preview, &BuildOptions::default()).unwrap(); | ||
| 534 | |||
| 535 | assert_eq!( | ||
| 536 | r.assets, | ||
| 537 | vec![Utf8PathBuf::from("logo.svg")], | ||
| 538 | "only the real asset is copied, not the earlier build's output" | ||
| 539 | ); | ||
| 540 | assert!( | ||
| 541 | !preview.join("_site").exists(), | ||
| 542 | "the earlier site must not be nested inside the new one" | ||
| 543 | ); | ||
| 544 | } | ||