From 50b0d50defd78628a0fce71eb98db44f5077db31 Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 9 Aug 2026 17:04:00 +0800 Subject: [PATCH 1/7] docs(rfc): deploy docs on release with a main preview site Production viteplus.dev deploys move from push-to-main to the release workflow, after the npm packages and the GitHub release are published. Pushes to main deploy to a dedicated main.viteplus.dev preview instead. --- rfcs/deploy-docs-on-release.md | 252 +++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 rfcs/deploy-docs-on-release.md diff --git a/rfcs/deploy-docs-on-release.md b/rfcs/deploy-docs-on-release.md new file mode 100644 index 0000000000..cf63fd789d --- /dev/null +++ b/rfcs/deploy-docs-on-release.md @@ -0,0 +1,252 @@ +# RFC: Deploy docs on release + +- Motivating example: [#2346](https://github.com/voidzero-dev/vite-plus/pull/2346) (XDG directory layout, rewrites `install.sh` / `install.ps1`) + +## Summary + +Stop deploying viteplus.dev on every push to `main`. Deploy it from the release +workflow after the npm packages and the GitHub release are published, and keep +`workflow_dispatch` for manual deploys. The site and the install scripts then +always describe the latest released `vp`. + +Pushes to `main` keep an automatic deploy, but to a dedicated preview project +at `https://main.viteplus.dev` (a CNAME to `viteplus-main.void.app`), so +developers can read the latest docs on `main` before the next release. + +## Current behavior + +`deploy-docs.yml` runs on every push to `main` that touches `docs/**`, +`packages/cli/install.sh`, `packages/cli/install.ps1`, or the workflow file. It +builds the VitePress site and deploys it to the `viteplus` void.app project. + +The docs build copies the install scripts into the site +(`docs/package.json` `build` script copies them into `docs/public/`). +`https://vite.plus` redirects to `https://viteplus.dev/install.sh`, and +`https://vite.plus/ps1` to `install.ps1`. The docs deploy is therefore also the +production channel for the installer. + +The release pipeline is separate. Merging a release PR bumps +`packages/cli/package.json`; `release.yml` verifies the version changed, builds, +waits for manual approval in the `release` environment, then publishes the npm +packages, the GitHub release, and the Docker image. + +This creates two failure modes: + +1. **Feature docs go live before the release exists.** A PR that adds code and + documents it deploys the docs at merge time. Users read about commands and + flags the released `vp` does not have. The gap between merge and release can + be days, since releases need a version bump and manual approval. +2. **Install-script changes go live before the binaries that match them.** + [#2346](https://github.com/voidzero-dev/vite-plus/pull/2346) is the concrete + case: merging it would serve an `install.sh` that installs into the split + XDG layout, while `vp` from the latest release still resolves the legacy + `~/.vite-plus` layout. Fresh installs break until the next release ships. + +## Proposal + +Three parts: + +1. Change `deploy-docs.yml` from a push-triggered workflow into a reusable one. +2. Call it from `release.yml` after the release is published. +3. Add `deploy-docs-main.yml`, which takes over the push trigger and deploys + `main` to the `viteplus-main` preview project. + +### `deploy-docs.yml` + +1. Remove the `push` trigger. Keep `workflow_dispatch`. Add `workflow_call` + with `VOID_TOKEN` declared as a required secret. +2. Move the `deploy-docs` concurrency group from the workflow level to the + `deploy` job. Jobs of a called workflow run inside the caller's run, so + workflow-level concurrency in the called file does not apply there. + Job-level concurrency serializes production deploys across both entry + paths (`cancel-in-progress: false` as today). + +```yaml +on: + workflow_dispatch: + workflow_call: + secrets: + VOID_TOKEN: + required: true + +jobs: + deploy: + if: github.repository == 'voidzero-dev/vite-plus' + runs-on: ubuntu-latest + concurrency: + group: deploy-docs + cancel-in-progress: false + permissions: + contents: read + env: + VOID_PROJECT: viteplus + # ... existing steps unchanged +``` + +### `release.yml` + +Add one job: + +```yaml + deploy-docs: + name: Deploy docs + needs: [check, Release] + if: >- + needs.check.outputs.version_changed == 'true' && + !contains(needs.check.outputs.version, '-') + permissions: + contents: read + uses: ./.github/workflows/deploy-docs.yml + secrets: + VOID_TOKEN: ${{ secrets.VOID_TOKEN }} +``` + +- The called workflow checks out `github.sha`, which in the release run is the + release commit. The deployed site matches the released version. +- `needs: [check, Release]` runs the deploy once npm and the GitHub release are + out, in parallel with `publish-docker`. Docs do not depend on the image. +- The `!contains(version, '-')` guard skips prereleases. An alpha publish must + not overwrite the production site with docs for unreleased behavior. +- A docs-deploy failure does not undo the release. Re-run the job or dispatch + the workflow manually. + +### `deploy-docs-main.yml`: standing preview of `main` + +A new workflow takes over the push trigger that `deploy-docs.yml` loses. It +runs the same build steps and deploys to a dedicated `viteplus-main` void.app +project instead of production: + +```yaml +name: Deploy Docs Main Preview + +permissions: {} + +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'packages/cli/install.sh' + - 'packages/cli/install.ps1' + - '.github/workflows/deploy-docs-main.yml' + +concurrency: + group: deploy-docs-main + cancel-in-progress: true + +jobs: + deploy: + if: github.repository == 'voidzero-dev/vite-plus' + runs-on: ubuntu-latest + permissions: + contents: read + env: + VOID_PROJECT: viteplus-main + # ... same build and deploy steps as deploy-docs.yml +``` + +- `https://main.viteplus.dev` always shows the docs at the head of `main`, + including unreleased features. Developers get a stable link to share, + without a release or a manual dispatch. +- A dedicated project, not the shared `viteplus-staging` one: PR previews + deploy there and would overwrite the `main` preview on every PR push. +- `cancel-in-progress: true`: only the newest `main` deploy matters for a + preview. Production keeps `false`. +- The workflow can keep the `vite-task-docs-*-main-*` cache keys that + `deploy-docs.yml` uses today, since both build `main`; the release-run + deploy restores from the same key family. +- Setup before the workflow lands: create the `viteplus-main` project on the + void platform (the deploy uses the same `VOID_TOKEN` secret), add the DNS + CNAME `main.viteplus.dev` -> `viteplus-main.void.app`, and attach the + custom domain to the project. + +`deploy-docs-preview.yml` stays unchanged. Per-PR staging deploys to +`viteplus-staging.void.app` remain the place to review docs changes before +merge. + +### Manual deploys + +`workflow_dispatch` covers urgent updates outside the release cycle: + +- Main carries no unreleased docs since the last release commit: merge the fix + to `main`, dispatch Deploy Docs on `main`. +- Main already carries unreleased docs: cut a branch from the release tag, + cherry-pick the fix, dispatch Deploy Docs on that branch. `workflow_dispatch` + accepts any branch or tag as the ref. + +A dispatch on `main` publishes everything on `main`, including unreleased docs +if present. The operator has to check for that; the release-tag branch is the +safe path. + +### No chicken-and-egg on the installer + +The docs build in the release run installs `vp` through `setup-vp`, which +fetches the install script currently deployed on viteplus.dev. The run builds +the site with the previous script, then the deploy replaces it. Fresh installs +after the deploy get the new script together with the new binaries. + +## Why `workflow_call` and not another trigger + +- `on: release: types: [published]` does not fire. `release.yml` publishes the + release with the default `GITHUB_TOKEN` (`gh release edit --draft=false`), + and events created with `GITHUB_TOKEN` do not start workflow runs. A PAT or + GitHub App token would work around this at the cost of another credential. +- `workflow_run` on Release completion runs at the head of the default branch, + not at the release commit. Docs merged after the release commit would deploy + with it, which reintroduces problem 1. +- `gh workflow run` at the end of the Release job works (`workflow_dispatch` + is exempt from the `GITHUB_TOKEN` restriction) but needs `actions: write` + and detaches the deploy from the release run in the Actions UI. + `workflow_call` keeps the deploy visible and gated inside the release run. + +## Behavior changes + +| Event | Before | After | +| --- | --- | --- | +| Docs change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | +| `install.sh` / `install.ps1` change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | +| Stable release published | No docs deploy | Production deploy from the release commit | +| Prerelease published | No docs deploy | No docs deploy (`main` preview already current) | +| Manual dispatch | Redundant with push deploys | The escape hatch for urgent production updates | + +## Drawbacks + +- Docs-only fixes (typos, clarifications) reach production with the next + release unless someone dispatches a deploy. Today they go live within + minutes of merge. They do reach `viteplus-main` within minutes. +- The production site lags `main` by design. Contributors who expect merged + docs on viteplus.dev must link to `main.viteplus.dev` until the next + release. +- One more job in `release.yml`, and the release run gains the docs build time + (a few minutes, in parallel with the Docker publish). +- Two sites can index in search engines. The preview project should send + `noindex` (or the theme should emit it when the site URL is not + viteplus.dev) so `main.viteplus.dev` does not compete with production. + +## Alternatives considered + +- **Gate only the install scripts, keep push deploys for `docs/**`.** Fixes + problem 2 but not problem 1, and lets `docs/guide/install.md` drift from the + script it documents. Two freshness channels on one site. +- **Versioned docs.** Publish `main` but hide unreleased sections until their + release. Needs authoring conventions and theme/tooling support; out of scope. +- **One workflow file for production and the `main` preview, with the project + chosen by trigger.** Selecting `VOID_PROJECT` from `github.event_name` does + not work: a called workflow inherits the caller's event, and `release.yml` + itself runs on `push`, so the release-run deploy would look like a push and + target the preview project. A `workflow_call` input can disambiguate, but + the fallback logic for the direct-push case is easy to get wrong. Two small + files with explicit projects read clearer. +- **Deploy `main` preview to the existing `viteplus-staging` project.** PR + previews deploy there and would overwrite the `main` preview on every PR + push. +- **Factor the shared build and deploy steps into one reusable workflow with a + `project` input**, called by the production, `main`-preview, and PR-preview + workflows. Removes the step duplication that already exists between + `deploy-docs.yml` and `deploy-docs-preview.yml`. Reasonable follow-up + cleanup; kept out of this change to keep the diff reviewable. + +## Open questions + +- Should the deploy also wait for `publish-docker`, so Docker install docs + never precede the image? Waiting adds a few minutes to the deploy. From dcfff455d3cb5156122d9d18c922a18c0286e9ad Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 9 Aug 2026 17:10:48 +0800 Subject: [PATCH 2/7] ci: deploy production docs on release, main preview on push Production viteplus.dev (which also serves install.sh behind https://vite.plus) now deploys from release.yml after the npm packages and the GitHub release are published, at the release commit, skipped for prereleases. Pushes to main deploy to main.viteplus.dev instead, via the new deploy-docs-main.yml. workflow_dispatch on deploy-docs.yml remains for urgent production updates. --- .github/workflows/deploy-docs-main.yml | 65 ++++++++++++++++++++++++++ .github/workflows/deploy-docs.yml | 25 +++++----- .github/workflows/release.yml | 16 +++++++ 3 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/deploy-docs-main.yml diff --git a/.github/workflows/deploy-docs-main.yml b/.github/workflows/deploy-docs-main.yml new file mode 100644 index 0000000000..95e8770050 --- /dev/null +++ b/.github/workflows/deploy-docs-main.yml @@ -0,0 +1,65 @@ +name: Deploy Docs Main Preview + +permissions: {} + +# Deploys the docs at the head of main to main.viteplus.dev (the viteplus-main +# void.app project) so developers can preview unreleased docs. Production +# viteplus.dev deploys from release.yml instead. +# See rfcs/deploy-docs-on-release.md. +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'packages/cli/install.sh' + - 'packages/cli/install.ps1' + - '.github/workflows/deploy-docs-main.yml' + +concurrency: + group: deploy-docs-main + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + deploy: + if: github.repository == 'voidzero-dev/vite-plus' + runs-on: ubuntu-latest + permissions: + contents: read + env: + VOID_PROJECT: viteplus-main + steps: + - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 + + - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # main + with: + cache: true + working-directory: docs + cache-dependency-path: docs/pnpm-lock.yaml + + - name: Restore docs Vite Task cache + id: vite-task-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: docs/node_modules/.vite/task-cache + key: vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main-${{ github.sha }} + # Restore the latest main cache; Vite Task fingerprints decide reuse. + restore-keys: | + vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main- + + - run: vp run build + working-directory: docs + + - name: Save docs Vite Task cache + if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: docs/node_modules/.vite/task-cache + key: ${{ steps.vite-task-cache.outputs.cache-primary-key }} + + - run: vpx void deploy --dir docs/.vitepress/dist + env: + VOID_TOKEN: ${{ secrets.VOID_TOKEN }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 6d365252cf..3f5a8e3ee9 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,19 +2,17 @@ name: Deploy Docs permissions: {} +# Deploys viteplus.dev, which also serves install.sh / install.ps1 behind +# https://vite.plus. Runs from release.yml after a stable release is published, +# or manually via workflow_dispatch for urgent updates. Pushes to main deploy +# to main.viteplus.dev via deploy-docs-main.yml instead. +# See rfcs/deploy-docs-on-release.md. on: - push: - branches: [main] - paths: - - 'docs/**' - - 'packages/cli/install.sh' - - 'packages/cli/install.ps1' - - '.github/workflows/deploy-docs.yml' workflow_dispatch: - -concurrency: - group: deploy-docs - cancel-in-progress: false + workflow_call: + secrets: + VOID_TOKEN: + required: true defaults: run: @@ -24,6 +22,11 @@ jobs: deploy: if: github.repository == 'voidzero-dev/vite-plus' runs-on: ubuntu-latest + # Job-level so it also applies when release.yml calls this workflow + # (workflow-level concurrency in a called workflow has no effect). + concurrency: + group: deploy-docs + cancel-in-progress: false permissions: contents: read env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62581f316c..e851982358 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -213,6 +213,22 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release edit "v${VERSION}" --draft=false --repo "${{ github.repository }}" + # Deploy viteplus.dev from the release commit after the npm packages and the + # GitHub release are published, so the site and the install scripts always + # match the released vp. Skipped for prereleases: production docs must keep + # describing the latest stable release. See rfcs/deploy-docs-on-release.md. + deploy-docs: + name: Deploy docs + needs: [check, Release] + if: >- + needs.check.outputs.version_changed == 'true' && + !contains(needs.check.outputs.version, '-') + permissions: + contents: read + uses: ./.github/workflows/deploy-docs.yml + secrets: + VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + # Build and push the official toolchain Docker image to GHCR after the npm # release is published (the image installs vp from npm, so the version must # exist first). See docker/Dockerfile and docs/guide/docker.md. From b2b9856da24cd269ef640e0679ac388fa32d52ac Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 9 Aug 2026 17:21:26 +0800 Subject: [PATCH 3/7] ci: extract docs deploy steps into a shared composite action Replace the workflow_call design with a .github/actions/deploy-docs composite, following the repo convention for shared step sequences. deploy-docs.yml (manual production deploy), deploy-docs-main.yml, deploy-docs-preview.yml, and the release deploy-docs job are now thin callers; the preview passes per-PR cache keys via the new cache-ref / cache-sha inputs. RFC updated to match. --- .github/actions/deploy-docs/action.yml | 77 +++++++++++++ .github/workflows/deploy-docs-main.yml | 34 +----- .github/workflows/deploy-docs-preview.yml | 36 +----- .github/workflows/deploy-docs.yml | 56 +++------ .github/workflows/release.yml | 16 ++- rfcs/deploy-docs-on-release.md | 131 ++++++++++++++-------- 6 files changed, 196 insertions(+), 154 deletions(-) create mode 100644 .github/actions/deploy-docs/action.yml diff --git a/.github/actions/deploy-docs/action.yml b/.github/actions/deploy-docs/action.yml new file mode 100644 index 0000000000..89ab8f4271 --- /dev/null +++ b/.github/actions/deploy-docs/action.yml @@ -0,0 +1,77 @@ +name: 'Build and deploy docs' +description: >- + Build the VitePress docs site (which bundles install.sh / install.ps1 into + its public assets) and deploy it to a void.app project. Run on a Linux + runner after checkout. See rfcs/deploy-docs-on-release.md for which + workflow deploys to which project. + +inputs: + void-project: + description: 'void.app project to deploy to (e.g. viteplus, viteplus-main).' + required: true + void-token: + description: 'void.app deploy token (pass secrets.VOID_TOKEN).' + required: true + cache-ref: + description: >- + Ref-scoped segment of the Vite Task cache key: main, or pr- for + PR previews. Non-main refs fall back to the main cache on restore. + required: false + default: 'main' + cache-sha: + description: 'Commit sha that scopes the primary cache key.' + required: false + default: ${{ github.sha }} + +runs: + using: 'composite' + steps: + - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # main + with: + cache: true + working-directory: docs + cache-dependency-path: docs/pnpm-lock.yaml + + - name: Compute Vite Task cache keys + id: cache-keys + shell: bash + env: + CACHE_REF: ${{ inputs.cache-ref }} + CACHE_SHA: ${{ inputs.cache-sha }} + run: | + prefix="vite-task-docs-${RUNNER_OS}-${RUNNER_ARCH}" + { + echo "key=${prefix}-${CACHE_REF}-${CACHE_SHA}" + echo 'restore-keys<> "$GITHUB_OUTPUT" + + - name: Restore docs Vite Task cache + id: vite-task-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: docs/node_modules/.vite/task-cache + key: ${{ steps.cache-keys.outputs.key }} + # Prefer this ref's newest cache; Vite Task fingerprints decide reuse. + restore-keys: ${{ steps.cache-keys.outputs.restore-keys }} + + - run: vp run build + shell: bash + working-directory: docs + + - name: Save docs Vite Task cache + if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: docs/node_modules/.vite/task-cache + key: ${{ steps.vite-task-cache.outputs.cache-primary-key }} + + - run: vpx void deploy --dir docs/.vitepress/dist + shell: bash + env: + VOID_PROJECT: ${{ inputs.void-project }} + VOID_TOKEN: ${{ inputs.void-token }} diff --git a/.github/workflows/deploy-docs-main.yml b/.github/workflows/deploy-docs-main.yml index 95e8770050..6a1ef77330 100644 --- a/.github/workflows/deploy-docs-main.yml +++ b/.github/workflows/deploy-docs-main.yml @@ -14,6 +14,7 @@ on: - 'packages/cli/install.sh' - 'packages/cli/install.ps1' - '.github/workflows/deploy-docs-main.yml' + - '.github/actions/deploy-docs/**' concurrency: group: deploy-docs-main @@ -29,37 +30,10 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - env: - VOID_PROJECT: viteplus-main steps: - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # main + - uses: ./.github/actions/deploy-docs with: - cache: true - working-directory: docs - cache-dependency-path: docs/pnpm-lock.yaml - - - name: Restore docs Vite Task cache - id: vite-task-cache - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: docs/node_modules/.vite/task-cache - key: vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main-${{ github.sha }} - # Restore the latest main cache; Vite Task fingerprints decide reuse. - restore-keys: | - vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main- - - - run: vp run build - working-directory: docs - - - name: Save docs Vite Task cache - if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: docs/node_modules/.vite/task-cache - key: ${{ steps.vite-task-cache.outputs.cache-primary-key }} - - - run: vpx void deploy --dir docs/.vitepress/dist - env: - VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + void-project: viteplus-main + void-token: ${{ secrets.VOID_TOKEN }} diff --git a/.github/workflows/deploy-docs-preview.yml b/.github/workflows/deploy-docs-preview.yml index 2267f3192b..6de12af2a2 100644 --- a/.github/workflows/deploy-docs-preview.yml +++ b/.github/workflows/deploy-docs-preview.yml @@ -9,6 +9,7 @@ on: - 'packages/cli/install.sh' - 'packages/cli/install.ps1' - '.github/workflows/deploy-docs-preview.yml' + - '.github/actions/deploy-docs/**' concurrency: group: deploy-docs-preview-${{ github.event.pull_request.number }} @@ -28,41 +29,16 @@ jobs: contents: read pull-requests: write env: - VOID_PROJECT: viteplus-staging PREVIEW_URL: https://viteplus-staging.void.app/ steps: - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # main + - uses: ./.github/actions/deploy-docs with: - cache: true - working-directory: docs - cache-dependency-path: docs/pnpm-lock.yaml - - - name: Restore docs Vite Task cache - id: vite-task-cache - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: docs/node_modules/.vite/task-cache - key: vite-task-docs-${{ runner.os }}-${{ runner.arch }}-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} - # Prefer this PR's newest cache, then fall back to main for new PRs. - restore-keys: | - vite-task-docs-${{ runner.os }}-${{ runner.arch }}-pr-${{ github.event.pull_request.number }}- - vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main- - - - run: vp run build - working-directory: docs - - - name: Save docs Vite Task cache - if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: docs/node_modules/.vite/task-cache - key: ${{ steps.vite-task-cache.outputs.cache-primary-key }} - - - run: vpx void deploy --dir docs/.vitepress/dist - env: - VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + void-project: viteplus-staging + void-token: ${{ secrets.VOID_TOKEN }} + cache-ref: pr-${{ github.event.pull_request.number }} + cache-sha: ${{ github.event.pull_request.head.sha }} - name: Comment on PR uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 3f5a8e3ee9..66e0057187 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,17 +2,19 @@ name: Deploy Docs permissions: {} -# Deploys viteplus.dev, which also serves install.sh / install.ps1 behind -# https://vite.plus. Runs from release.yml after a stable release is published, -# or manually via workflow_dispatch for urgent updates. Pushes to main deploy -# to main.viteplus.dev via deploy-docs-main.yml instead. +# Deploys production viteplus.dev, which also serves install.sh / install.ps1 +# behind https://vite.plus. Runs from release.yml after a stable release is +# published, or manually here for urgent updates. Pushes to main deploy to +# main.viteplus.dev via deploy-docs-main.yml instead. # See rfcs/deploy-docs-on-release.md. on: workflow_dispatch: - workflow_call: - secrets: - VOID_TOKEN: - required: true + +# Shared with the deploy-docs job in release.yml so production deploys +# serialize across both entry paths. +concurrency: + group: deploy-docs + cancel-in-progress: false defaults: run: @@ -22,44 +24,12 @@ jobs: deploy: if: github.repository == 'voidzero-dev/vite-plus' runs-on: ubuntu-latest - # Job-level so it also applies when release.yml calls this workflow - # (workflow-level concurrency in a called workflow has no effect). - concurrency: - group: deploy-docs - cancel-in-progress: false permissions: contents: read - env: - VOID_PROJECT: viteplus steps: - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # main - with: - cache: true - working-directory: docs - cache-dependency-path: docs/pnpm-lock.yaml - - - name: Restore docs Vite Task cache - id: vite-task-cache - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + - uses: ./.github/actions/deploy-docs with: - path: docs/node_modules/.vite/task-cache - key: vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main-${{ github.sha }} - # Restore the latest main cache; Vite Task fingerprints decide reuse. - restore-keys: | - vite-task-docs-${{ runner.os }}-${{ runner.arch }}-main- - - - run: vp run build - working-directory: docs - - - name: Save docs Vite Task cache - if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: docs/node_modules/.vite/task-cache - key: ${{ steps.vite-task-cache.outputs.cache-primary-key }} - - - run: vpx void deploy --dir docs/.vitepress/dist - env: - VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + void-project: viteplus + void-token: ${{ secrets.VOID_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e851982358..347192acc2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,15 +219,25 @@ jobs: # describing the latest stable release. See rfcs/deploy-docs-on-release.md. deploy-docs: name: Deploy docs + runs-on: ubuntu-latest needs: [check, Release] if: >- needs.check.outputs.version_changed == 'true' && !contains(needs.check.outputs.version, '-') + # Shared with deploy-docs.yml so production deploys serialize across both + # entry paths. + concurrency: + group: deploy-docs + cancel-in-progress: false permissions: contents: read - uses: ./.github/workflows/deploy-docs.yml - secrets: - VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + steps: + - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 + + - uses: ./.github/actions/deploy-docs + with: + void-project: viteplus + void-token: ${{ secrets.VOID_TOKEN }} # Build and push the official toolchain Docker image to GHCR after the npm # release is published (the image installs vp from npm, so the version must diff --git a/rfcs/deploy-docs-on-release.md b/rfcs/deploy-docs-on-release.md index cf63fd789d..22503e52cb 100644 --- a/rfcs/deploy-docs-on-release.md +++ b/rfcs/deploy-docs-on-release.md @@ -44,43 +44,62 @@ This creates two failure modes: ## Proposal -Three parts: - -1. Change `deploy-docs.yml` from a push-triggered workflow into a reusable one. -2. Call it from `release.yml` after the release is published. -3. Add `deploy-docs-main.yml`, which takes over the push trigger and deploys +Four parts: + +1. Extract the docs build and deploy steps into a + `.github/actions/deploy-docs` composite action, the repo convention for + shared step sequences (see `.github/actions/clone`, `build-windows-cli`). +2. Reduce `deploy-docs.yml` to a manual (`workflow_dispatch`) production + deploy that runs the composite. +3. Add a `deploy-docs` job to `release.yml` that runs the composite after the + release is published. +4. Add `deploy-docs-main.yml`, which takes over the push trigger and deploys `main` to the `viteplus-main` preview project. +5. Switch `deploy-docs-preview.yml` to the composite; its trigger, staging + target, and PR comment step stay as they are. + +### `.github/actions/deploy-docs` + +The composite action holds the steps shared by every docs deploy: `setup-vp`, +the Vite Task cache restore/save, `vp run build`, and `vpx void deploy`. Its +inputs: + +- `void-project`: the deploy target. +- `void-token`: composite actions cannot read secrets, so the caller passes + `secrets.VOID_TOKEN`. +- `cache-ref` / `cache-sha` (optional, default `main` / `github.sha`): scope + the Vite Task cache key. PR previews pass `pr-` and the head sha, + which reproduces their current per-PR keys with a fallback to the `main` + cache. + +Callers check out the repo first, then run the action. ### `deploy-docs.yml` -1. Remove the `push` trigger. Keep `workflow_dispatch`. Add `workflow_call` - with `VOID_TOKEN` declared as a required secret. -2. Move the `deploy-docs` concurrency group from the workflow level to the - `deploy` job. Jobs of a called workflow run inside the caller's run, so - workflow-level concurrency in the called file does not apply there. - Job-level concurrency serializes production deploys across both entry - paths (`cancel-in-progress: false` as today). +Remove the `push` trigger; keep `workflow_dispatch` only. The build and +deploy steps move to the composite: ```yaml on: workflow_dispatch: - workflow_call: - secrets: - VOID_TOKEN: - required: true + +concurrency: + group: deploy-docs + cancel-in-progress: false jobs: deploy: if: github.repository == 'voidzero-dev/vite-plus' runs-on: ubuntu-latest - concurrency: - group: deploy-docs - cancel-in-progress: false permissions: contents: read - env: - VOID_PROJECT: viteplus - # ... existing steps unchanged + steps: + - uses: taiki-e/checkout-action@... # v1.4.2 + + - uses: ./.github/actions/deploy-docs + with: + void-project: viteplus + void-token: ${{ secrets.VOID_TOKEN }} ``` ### `release.yml` @@ -90,19 +109,29 @@ Add one job: ```yaml deploy-docs: name: Deploy docs + runs-on: ubuntu-latest needs: [check, Release] if: >- needs.check.outputs.version_changed == 'true' && !contains(needs.check.outputs.version, '-') + concurrency: + group: deploy-docs + cancel-in-progress: false permissions: contents: read - uses: ./.github/workflows/deploy-docs.yml - secrets: - VOID_TOKEN: ${{ secrets.VOID_TOKEN }} + steps: + - uses: taiki-e/checkout-action@... # v1.4.2 + + - uses: ./.github/actions/deploy-docs + with: + void-project: viteplus + void-token: ${{ secrets.VOID_TOKEN }} ``` -- The called workflow checks out `github.sha`, which in the release run is the - release commit. The deployed site matches the released version. +- The job checks out `github.sha`, which in the release run is the release + commit. The deployed site matches the released version. +- The job-level `deploy-docs` concurrency group is shared with + `deploy-docs.yml`, so production deploys serialize across both entry paths. - `needs: [check, Release]` runs the deploy once npm and the GitHub release are out, in parallel with `publish-docker`. Docs do not depend on the image. - The `!contains(version, '-')` guard skips prereleases. An alpha publish must @@ -113,7 +142,7 @@ Add one job: ### `deploy-docs-main.yml`: standing preview of `main` A new workflow takes over the push trigger that `deploy-docs.yml` loses. It -runs the same build steps and deploys to a dedicated `viteplus-main` void.app +runs the same composite and deploys to a dedicated `viteplus-main` void.app project instead of production: ```yaml @@ -129,6 +158,7 @@ on: - 'packages/cli/install.sh' - 'packages/cli/install.ps1' - '.github/workflows/deploy-docs-main.yml' + - '.github/actions/deploy-docs/**' concurrency: group: deploy-docs-main @@ -140,9 +170,13 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - env: - VOID_PROJECT: viteplus-main - # ... same build and deploy steps as deploy-docs.yml + steps: + - uses: taiki-e/checkout-action@... # v1.4.2 + + - uses: ./.github/actions/deploy-docs + with: + void-project: viteplus-main + void-token: ${{ secrets.VOID_TOKEN }} ``` - `https://main.viteplus.dev` always shows the docs at the head of `main`, @@ -160,9 +194,9 @@ jobs: CNAME `main.viteplus.dev` -> `viteplus-main.void.app`, and attach the custom domain to the project. -`deploy-docs-preview.yml` stays unchanged. Per-PR staging deploys to -`viteplus-staging.void.app` remain the place to review docs changes before -merge. +`deploy-docs-preview.yml` keeps its trigger, its `viteplus-staging.void.app` +target, and its PR comment step, and now runs the same composite. Per-PR +staging deploys remain the place to review docs changes before merge. ### Manual deploys @@ -185,7 +219,7 @@ fetches the install script currently deployed on viteplus.dev. The run builds the site with the previous script, then the deploy replaces it. Fresh installs after the deploy get the new script together with the new binaries. -## Why `workflow_call` and not another trigger +## Why a job in the release run and not another trigger - `on: release: types: [published]` does not fire. `release.yml` publishes the release with the default `GITHUB_TOKEN` (`gh release edit --draft=false`), @@ -196,8 +230,8 @@ after the deploy get the new script together with the new binaries. with it, which reintroduces problem 1. - `gh workflow run` at the end of the Release job works (`workflow_dispatch` is exempt from the `GITHUB_TOKEN` restriction) but needs `actions: write` - and detaches the deploy from the release run in the Actions UI. - `workflow_call` keeps the deploy visible and gated inside the release run. + and detaches the deploy from the release run in the Actions UI. A job in + the release run keeps the deploy visible and gated inside it. ## Behavior changes @@ -230,21 +264,22 @@ after the deploy get the new script together with the new binaries. script it documents. Two freshness channels on one site. - **Versioned docs.** Publish `main` but hide unreleased sections until their release. Needs authoring conventions and theme/tooling support; out of scope. +- **A reusable workflow (`workflow_call`) called from `release.yml`, instead + of a composite action.** `reusable-release-build.yml` sets a precedent, but + it reuses a whole job matrix; the docs deploy reuses a step sequence inside + jobs with different triggers, gates, and concurrency, which is what the + `.github/actions` composites are for. `workflow_call` also carries + subtleties: the called workflow inherits the caller's `github` context and + event, its workflow-level concurrency has no effect, and secrets must be + declared and forwarded. The composite avoids all three and also serves + `deploy-docs-preview.yml`. - **One workflow file for production and the `main` preview, with the project - chosen by trigger.** Selecting `VOID_PROJECT` from `github.event_name` does - not work: a called workflow inherits the caller's event, and `release.yml` - itself runs on `push`, so the release-run deploy would look like a push and - target the preview project. A `workflow_call` input can disambiguate, but - the fallback logic for the direct-push case is easy to get wrong. Two small - files with explicit projects read clearer. + chosen by trigger.** Selecting `VOID_PROJECT` from `github.event_name` is + implicit and fragile, and `release.yml` still needs its own release-gated + job. Thin wrappers with explicit projects read clearer. - **Deploy `main` preview to the existing `viteplus-staging` project.** PR previews deploy there and would overwrite the `main` preview on every PR push. -- **Factor the shared build and deploy steps into one reusable workflow with a - `project` input**, called by the production, `main`-preview, and PR-preview - workflows. Removes the step duplication that already exists between - `deploy-docs.yml` and `deploy-docs-preview.yml`. Reasonable follow-up - cleanup; kept out of this change to keep the diff reviewable. ## Open questions From 513eca971a1313a30b0b19c5f23890ec643bccef Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 9 Aug 2026 17:21:47 +0800 Subject: [PATCH 4/7] ci: announce the release only after the docs deploy discord-notify now also needs deploy-docs. It gates on success-or-skipped because deploy-docs is skipped for prereleases: prerelease announcements still go out, while a failed docs deploy on a stable release holds the announcement back. --- .github/workflows/release.yml | 17 ++++++++++++----- rfcs/deploy-docs-on-release.md | 8 ++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 347192acc2..65a1b45bdc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -292,15 +292,22 @@ jobs: VP_VERSION=${{ env.VERSION }} provenance: false - # Announce the release on Discord last, after the Docker images are published, - # so the message can include the GHCR image. Runs after the npm release - # (Release) and the images (publish-docker). + # Announce the release on Discord last, after the Docker images and the docs + # are published, so the message never points at a missing image or a site + # that still shows the previous release. discord-notify: name: Notify Discord runs-on: ubuntu-latest # publish-docker already needs Release, so depending on it orders this last. - needs: [check, publish-docker] - if: needs.check.outputs.version_changed == 'true' + # deploy-docs is skipped for prereleases; gate on its result explicitly so + # prerelease announcements still go out, while a failed docs deploy on a + # stable release holds the announcement back. + needs: [check, publish-docker, deploy-docs] + if: >- + !cancelled() && + needs.check.outputs.version_changed == 'true' && + needs.publish-docker.result == 'success' && + (needs.deploy-docs.result == 'success' || needs.deploy-docs.result == 'skipped') env: VERSION: ${{ needs.check.outputs.version }} IMAGE: ghcr.io/voidzero-dev/vite-plus diff --git a/rfcs/deploy-docs-on-release.md b/rfcs/deploy-docs-on-release.md index 22503e52cb..21bfe38b3f 100644 --- a/rfcs/deploy-docs-on-release.md +++ b/rfcs/deploy-docs-on-release.md @@ -136,8 +136,12 @@ Add one job: out, in parallel with `publish-docker`. Docs do not depend on the image. - The `!contains(version, '-')` guard skips prereleases. An alpha publish must not overwrite the production site with docs for unreleased behavior. -- A docs-deploy failure does not undo the release. Re-run the job or dispatch - the workflow manually. +- `discord-notify` adds `deploy-docs` to its `needs` and gates on + `result == 'success' || result == 'skipped'`, so a stable release announces + only after the site is updated, while prereleases (where `deploy-docs` is + skipped) still announce. +- A docs-deploy failure does not undo the release; it holds back the Discord + announcement. Re-run the job or dispatch the workflow manually. ### `deploy-docs-main.yml`: standing preview of `main` From 2affd3481a2cff35b2857459f283fbcbdb3a5e44 Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 9 Aug 2026 17:30:27 +0800 Subject: [PATCH 5/7] docs(rfc): apply vp fmt to deploy-docs-on-release --- rfcs/deploy-docs-on-release.md | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/rfcs/deploy-docs-on-release.md b/rfcs/deploy-docs-on-release.md index 21bfe38b3f..49c55e81ea 100644 --- a/rfcs/deploy-docs-on-release.md +++ b/rfcs/deploy-docs-on-release.md @@ -107,25 +107,25 @@ jobs: Add one job: ```yaml - deploy-docs: - name: Deploy docs - runs-on: ubuntu-latest - needs: [check, Release] - if: >- - needs.check.outputs.version_changed == 'true' && - !contains(needs.check.outputs.version, '-') - concurrency: - group: deploy-docs - cancel-in-progress: false - permissions: - contents: read - steps: - - uses: taiki-e/checkout-action@... # v1.4.2 - - - uses: ./.github/actions/deploy-docs - with: - void-project: viteplus - void-token: ${{ secrets.VOID_TOKEN }} +deploy-docs: + name: Deploy docs + runs-on: ubuntu-latest + needs: [check, Release] + if: >- + needs.check.outputs.version_changed == 'true' && + !contains(needs.check.outputs.version, '-') + concurrency: + group: deploy-docs + cancel-in-progress: false + permissions: + contents: read + steps: + - uses: taiki-e/checkout-action@... # v1.4.2 + + - uses: ./.github/actions/deploy-docs + with: + void-project: viteplus + void-token: ${{ secrets.VOID_TOKEN }} ``` - The job checks out `github.sha`, which in the release run is the release @@ -239,13 +239,13 @@ after the deploy get the new script together with the new binaries. ## Behavior changes -| Event | Before | After | -| --- | --- | --- | -| Docs change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | -| `install.sh` / `install.ps1` change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | -| Stable release published | No docs deploy | Production deploy from the release commit | -| Prerelease published | No docs deploy | No docs deploy (`main` preview already current) | -| Manual dispatch | Redundant with push deploys | The escape hatch for urgent production updates | +| Event | Before | After | +| ---------------------------------------------------- | --------------------------- | ----------------------------------------------- | +| Docs change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | +| `install.sh` / `install.ps1` change merges to `main` | Production deploy | Preview deploy to `main.viteplus.dev` | +| Stable release published | No docs deploy | Production deploy from the release commit | +| Prerelease published | No docs deploy | No docs deploy (`main` preview already current) | +| Manual dispatch | Redundant with push deploys | The escape hatch for urgent production updates | ## Drawbacks From 57c68541b7c25ca0a99b19a257b7232d4678e539 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 10 Aug 2026 14:23:42 +0800 Subject: [PATCH 6/7] docs: document single-pending-slot concurrency semantics for docs deploys A queued production deploy replaces a pending one in the shared deploy-docs group; the site converges to the newest queued deploy, and a replaced release deploy holds back discord-notify. Note this in the workflow comments and the RFC. --- .github/workflows/deploy-docs.yml | 4 +++- .github/workflows/release.yml | 4 +++- rfcs/deploy-docs-on-release.md | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 66e0057187..c205ab9e60 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -11,7 +11,9 @@ on: workflow_dispatch: # Shared with the deploy-docs job in release.yml so production deploys -# serialize across both entry paths. +# serialize across both entry paths. GitHub keeps only the newest pending run +# in the group (a queued deploy replaces a pending one), so production +# converges to the newest queued content; the running deploy always completes. concurrency: group: deploy-docs cancel-in-progress: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65a1b45bdc..207d308550 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -225,7 +225,9 @@ jobs: needs.check.outputs.version_changed == 'true' && !contains(needs.check.outputs.version, '-') # Shared with deploy-docs.yml so production deploys serialize across both - # entry paths. + # entry paths. Only the newest pending run survives in the group; if this + # job's pending run is replaced by a manual dispatch, the canceled job + # holds back discord-notify and the deploy must be re-run. concurrency: group: deploy-docs cancel-in-progress: false diff --git a/rfcs/deploy-docs-on-release.md b/rfcs/deploy-docs-on-release.md index 49c55e81ea..100bdd95d0 100644 --- a/rfcs/deploy-docs-on-release.md +++ b/rfcs/deploy-docs-on-release.md @@ -132,6 +132,12 @@ deploy-docs: commit. The deployed site matches the released version. - The job-level `deploy-docs` concurrency group is shared with `deploy-docs.yml`, so production deploys serialize across both entry paths. +- GitHub keeps one pending run per concurrency group: a newer queued deploy + replaces a pending one, while the running deploy always completes. So + production converges to the newest queued deploy. If a release's pending + deploy is the one replaced, the canceled job shows in the release run and + holds back `discord-notify`; re-run it if the replacing deploy carried + older content. - `needs: [check, Release]` runs the deploy once npm and the GitHub release are out, in parallel with `publish-docker`. Docs do not depend on the image. - The `!contains(version, '-')` guard skips prereleases. An alpha publish must From 5e1e04eb3155a8a3d444e392fbe870bebd6cdbf5 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 10 Aug 2026 15:32:07 +0800 Subject: [PATCH 7/7] docs(site): point install URLs at the deploy origin on preview builds Preview deploys (main.viteplus.dev, PR staging) serve their own install scripts, but the docs hardcode the production https://vite.plus shortcuts. The deploy-docs composite now passes a site-origin input to the build as DOCS_SITE_ORIGIN. When set, a markdown-it rule rewrites the install URLs in markdown content, and the homepage install command and AI copy prompt read __DOCS_*__ define constants from the same origin (including the llms-full.txt link). The build:site task tracks the variable in env so each deploy target keeps its own Vite Task cache entry. Production builds are unchanged. Known gap: the llms dumps copy raw markdown and keep production URLs on previews. --- .github/actions/deploy-docs/action.yml | 10 ++++ .github/workflows/deploy-docs-main.yml | 1 + .github/workflows/deploy-docs-preview.yml | 1 + docs/.vitepress/config.mts | 50 +++++++++++++++++++ docs/.vitepress/env.d.ts | 10 ++++ .../theme/components/CopyPrompt.vue | 17 ++++--- .../theme/components/home/InstallCommand.vue | 4 +- docs/vite.config.ts | 3 ++ rfcs/deploy-docs-on-release.md | 24 +++++++++ 9 files changed, 112 insertions(+), 8 deletions(-) diff --git a/.github/actions/deploy-docs/action.yml b/.github/actions/deploy-docs/action.yml index 89ab8f4271..e03892e6b7 100644 --- a/.github/actions/deploy-docs/action.yml +++ b/.github/actions/deploy-docs/action.yml @@ -22,6 +22,14 @@ inputs: description: 'Commit sha that scopes the primary cache key.' required: false default: ${{ github.sha }} + site-origin: + description: >- + Origin of this deploy when it is not production viteplus.dev + (e.g. https://main.viteplus.dev). When set, the docs build rewrites the + https://vite.plus installer URLs to this origin's install scripts. + Leave empty for production. + required: false + default: '' runs: using: 'composite' @@ -62,6 +70,8 @@ runs: - run: vp run build shell: bash working-directory: docs + env: + DOCS_SITE_ORIGIN: ${{ inputs.site-origin }} - name: Save docs Vite Task cache if: success() && steps.vite-task-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/deploy-docs-main.yml b/.github/workflows/deploy-docs-main.yml index 6a1ef77330..9b7a309d28 100644 --- a/.github/workflows/deploy-docs-main.yml +++ b/.github/workflows/deploy-docs-main.yml @@ -37,3 +37,4 @@ jobs: with: void-project: viteplus-main void-token: ${{ secrets.VOID_TOKEN }} + site-origin: https://main.viteplus.dev diff --git a/.github/workflows/deploy-docs-preview.yml b/.github/workflows/deploy-docs-preview.yml index 6de12af2a2..2579e08698 100644 --- a/.github/workflows/deploy-docs-preview.yml +++ b/.github/workflows/deploy-docs-preview.yml @@ -39,6 +39,7 @@ jobs: void-token: ${{ secrets.VOID_TOKEN }} cache-ref: pr-${{ github.event.pull_request.number }} cache-sha: ${{ github.event.pull_request.head.sha }} + site-origin: https://viteplus-staging.void.app - name: Comment on PR uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 413fc85b26..293673f43f 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -7,6 +7,26 @@ import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-i import llmstxt from 'vitepress-plugin-llms'; import { withMermaid } from 'vitepress-plugin-mermaid'; +// Non-production deploys (the main preview, PR staging) serve their own +// copies of the install scripts and llms dumps, so the https://vite.plus +// installer shortcuts and absolute site URLs must point at the deploy's +// origin instead of production. The deploy workflows set DOCS_SITE_ORIGIN via +// the deploy-docs composite action; markdown content is rewritten through +// markdown-it below, and Vue components read the __DOCS_*__ define constants. +const siteOrigin = process.env.DOCS_SITE_ORIGIN; +const docsOrigin = siteOrigin || 'https://viteplus.dev'; +const installShUrl = siteOrigin ? `${siteOrigin}/install.sh` : 'https://vite.plus'; +const installPs1Url = siteOrigin ? `${siteOrigin}/install.ps1` : 'https://vite.plus/ps1'; + +function rewriteInstallUrls(text: string): string { + if (!siteOrigin) { + return text; + } + return text + .replaceAll('https://vite.plus/ps1', installPs1Url) + .replaceAll('https://vite.plus', installShUrl); +} + const taskRunnerGuideItems = [ { text: 'Run', @@ -113,6 +133,11 @@ export default extendConfig( ['meta', { name: 'twitter:site', content: '@voidzerodev' }], ], vite: { + define: { + __DOCS_ORIGIN__: JSON.stringify(docsOrigin), + __DOCS_INSTALL_SH_URL__: JSON.stringify(installShUrl), + __DOCS_INSTALL_PS1_URL__: JSON.stringify(installPs1Url), + }, optimizeDeps: { include: ['mermaid > @braintree/sanitize-url'], }, @@ -258,6 +283,31 @@ export default extendConfig( markdown: { config(md) { md.use(groupIconMdPlugin); + if (siteOrigin) { + md.core.ruler.push('rewrite-install-urls', (state) => { + const walk = (tokens: typeof state.tokens) => { + for (const token of tokens) { + if ( + token.type === 'fence' || + token.type === 'code_inline' || + token.type === 'text' + ) { + token.content = rewriteInstallUrls(token.content); + } + if (token.type === 'link_open') { + const href = token.attrGet('href'); + if (href) { + token.attrSet('href', rewriteInstallUrls(href)); + } + } + if (token.children) { + walk(token.children); + } + } + }; + walk(state.tokens); + }); + } }, }, }), diff --git a/docs/.vitepress/env.d.ts b/docs/.vitepress/env.d.ts index adcc6abf4e..23ce8f7581 100644 --- a/docs/.vitepress/env.d.ts +++ b/docs/.vitepress/env.d.ts @@ -1,3 +1,13 @@ +// Build-time constants injected via vite.define in config.mts. They point at +// the current deploy's origin (production, main preview, or PR staging). The +// dunder names follow the Vite convention for compile-time replaced globals. +// oxlint-disable-next-line no-underscore-dangle +declare const __DOCS_ORIGIN__: string; +// oxlint-disable-next-line no-underscore-dangle +declare const __DOCS_INSTALL_SH_URL__: string; +// oxlint-disable-next-line no-underscore-dangle +declare const __DOCS_INSTALL_PS1_URL__: string; + // Vue SFC module declaration declare module '*.vue' { import type { DefineComponent } from 'vue'; diff --git a/docs/.vitepress/theme/components/CopyPrompt.vue b/docs/.vitepress/theme/components/CopyPrompt.vue index 1520b16379..1b9de196d7 100644 --- a/docs/.vitepress/theme/components/CopyPrompt.vue +++ b/docs/.vitepress/theme/components/CopyPrompt.vue @@ -7,11 +7,11 @@ import { computed, onBeforeUnmount, ref, useId } from 'vue'; // live llms-full.txt docs dump. const DEFAULT_PROMPT = `I want to use Vite+ in my project. Vite+ is the unified toolchain for the web behind the \`vp\` CLI — one tool combining Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task, plus runtime and package-manager management. -First, read https://viteplus.dev/llms-full.txt to learn Vite+'s commands and configuration. +First, read ${__DOCS_ORIGIN__}/llms-full.txt to learn Vite+'s commands and configuration. Install the \`vp\` CLI if it's not already on the system: -- macOS / Linux: curl -fsSL https://vite.plus | bash -- Windows (PowerShell): irm https://vite.plus/ps1 | iex +- macOS / Linux: curl -fsSL ${__DOCS_INSTALL_SH_URL__} | bash +- Windows (PowerShell): irm ${__DOCS_INSTALL_PS1_URL__} | iex Then open a new terminal and run \`vp help\`. To scaffold a new project run \`vp create\`; to move an existing Vite project onto Vite+ run \`vp migrate\`. @@ -19,17 +19,22 @@ Day-to-day commands: \`vp install\` (dependencies), \`vp dev\` (dev server), \`v Help me get set up and explain anything I should know.`; +// DEFAULT_PROMPT interpolates the __DOCS_*__ define constants, so it is not a +// static literal and cannot be a withDefaults() default (defineProps is +// hoisted out of setup). Resolve the fallback in promptText instead. const props = withDefaults( defineProps<{ prompt?: string; label?: string; }>(), { - prompt: DEFAULT_PROMPT, + prompt: '', label: 'View Prompt', }, ); +const promptText = computed(() => props.prompt || DEFAULT_PROMPT); + const titleId = useId(); const dialogEl = ref(null); const state = ref<'idle' | 'copied' | 'error'>('idle'); @@ -71,7 +76,7 @@ const blurPointerTarget = (event: MouseEvent) => { const copyPrompt = async (event: MouseEvent) => { blurPointerTarget(event); try { - await navigator.clipboard.writeText(props.prompt); + await navigator.clipboard.writeText(promptText.value); flash('copied'); } catch { flash('error'); @@ -132,7 +137,7 @@ onBeforeUnmount(() => {
{{ prompt }}
+ >{{ promptText }}