Skip to content

feat(cli): global -C flag for working-directory switching #5

feat(cli): global -C flag for working-directory switching

feat(cli): global -C flag for working-directory switching #5

name: Publish preview build
# Publishes a labeled PR's commit build to the registry bridge
# (https://github.com/voidzero-dev/pkg-pr-registry-bridge), which serves it as
# the npm version 0.0.0-commit.<sha>. Registered builds:
# https://registry-bridge.viteplus.dev/-/refs
# Triggered by adding the `preview-build` label.
permissions: {}
on:
pull_request:
types: [labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
prepare:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'preview-build')
name: Compute snapshot version
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
steps:
# Use the exact version the registry bridge serves (0.0.0-commit.<head-sha>,
# same head SHA the bridge registers below), so the built Rust binary and JS
# dist carry the version that actually gets installed: CARGO_PKG_VERSION and
# cliPkg.version both equal it, instead of the build-time release placeholder.
# `napi pre-publish` reuses this for consistent optionalDependencies entries.
- name: Compute version
id: version
run: echo "version=0.0.0-commit.${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
build-rust:
name: Build bindings and binaries
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'preview-build')
needs: prepare
permissions:
contents: read
uses: ./.github/workflows/reusable-release-build.yml
with:
version: ${{ needs.prepare.outputs.version }}
cache-key: pkg-pr-new
publish:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'preview-build')
name: Pkg Preview
runs-on: ubuntu-latest
needs:
- prepare
- build-rust
permissions:
contents: read
# The "Comment bridge version" step creates/updates a sticky comment via
# github.rest.issues.createComment/updateComment; comment writes on PRs
# are gated on pull-requests:write, and creating an issue comment is
# gated on issues:write (the create path runs on PRs without one yet).
pull-requests: write
issues: write
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- uses: ./.github/actions/clone
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .node-version
package-manager-cache: false
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Download cli dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/cli/dist
pattern: cli
merge-multiple: true
- name: Download cli docs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/cli/docs
pattern: cli-docs
merge-multiple: true
- name: Download cli binding
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/cli/artifacts
pattern: vite-plus-native-*
- name: Download core dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/core/dist
pattern: core
merge-multiple: true
- name: Download prompts dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: packages/prompts/dist
pattern: prompts
merge-multiple: true
- uses: ./.github/actions/download-rolldown-binaries
with:
github-token: ${{ github.token }}
target: x86_64-unknown-linux-gnu
upload: 'false'
- name: Download Rust CLI binaries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: rust-cli-artifacts
pattern: vite-global-cli-*
# Stops short of `npm publish` and leaves packages/cli/{npm,cli-npm}/*
# on disk for the bridge action to pack.
- name: Prepare native addon and CLI binary packages
run: node ./packages/cli/publish-native-addons.ts --mode pkg-pr-new
# Publish this commit build to the registry bridge so it can be installed
# as the npm version 0.0.0-commit.<sha>
# (https://github.com/voidzero-dev/pkg-pr-registry-bridge). The bridge
# action packs the locally built package directories prepared above (the
# two preview packages and every platform binary) with `pnpm pack`,
# re-packs them under the commit version with matching integrity, uploads
# them to the bridge, and registers the ref. This build publishes under
# the PR head commit, so pass that SHA (not the merge commit github.sha).
# Restricted to same-repo PRs because fork PRs do not receive the admin
# token secret. A bridge failure fails the job: install.sh and the Docker
# preview install PR builds through the bridge, so a silently missing ref
# would only surface later as a broken install.
- name: Register commit build with the registry bridge
id: bridge
if: github.event.pull_request.head.repo.full_name == github.repository
uses: voidzero-dev/pkg-pr-registry-bridge@711e22a2e1ae55d809fc5fab514c1180a41ac91e # main
with:
sha: ${{ github.event.pull_request.head.sha }}
admin-token: ${{ secrets.PKG_PR_BRIDGE_ADMIN_TOKEN }}
# The locally built directories to pack and publish. (This is also
# the action's default, passed explicitly so the published set is
# reviewable here.)
packages: |
packages/cli/npm/*
packages/cli/cli-npm/*
packages/cli
packages/core
packages/prompts
# Surfaced by the bridge's /-/refs so a registered ref links back to its
# PR. Always present here (this workflow only runs on pull_request).
pr-url: ${{ github.event.pull_request.html_url }}
# Once the bridge has the commit build, post (or update) a sticky PR comment
# with the resolved npm versions and per-package-manager registry config, so
# reviewers can install the build directly. The outcome gate skips this on
# fork PRs, where the bridge step itself is skipped.
- name: Comment bridge version on the PR
if: steps.bridge.outcome == 'success'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
# The synthetic version (0.0.0-commit.<sha>) the bridge action
# published, so the version scheme has one source of truth (the action).
BRIDGE_VERSION: ${{ steps.bridge.outputs.version }}
with:
script: |
const version = process.env.BRIDGE_VERSION;
const shortSha = context.payload.pull_request.head.sha.slice(0, 7);
const pr = context.payload.pull_request.number;
const marker = '<!-- pkg-pr-bridge-version -->';
const bridge = 'https://registry-bridge.viteplus.dev/';
// Built as a line array (not a template literal) so the inline
// backticks and the fenced json block don't need backslash-escaping.
const body = [
marker,
'',
`### Registry bridge build (\`${shortSha}\`)`,
'',
'This commit build is published to the [registry bridge](https://github.com/voidzero-dev/pkg-pr-registry-bridge), which serves these as ordinary npm versions (every other package proxies to npmjs):',
'',
'| Package | Version |',
'| --- | --- |',
`| \`vite-plus\` | \`${version}\` |`,
`| \`@voidzero-dev/vite-plus-core\` | \`${version}\` |`,
'',
'**Install the Vite+ CLI built from this commit, then migrate a project:**',
'',
'```bash',
'# macOS / Linux',
`curl -fsSL https://vite.plus | VP_PR_VERSION=${pr} bash`,
'```',
'```powershell',
'# Windows (PowerShell)',
`$env:VP_PR_VERSION="${pr}"; irm https://vite.plus/ps1 | iex`,
'```',
'',
`After installing, upgrade the current project's \`vite-plus\` to this test build with:`,
'',
'```bash',
'vp migrate',
'```',
'',
`**Or** point your package manager at the bridge registry \`${bridge}\`:`,
'',
'| Package manager | Registry config |',
'| --- | --- |',
`| npm / pnpm / Bun | \`.npmrc\`: \`registry=${bridge}\` |`,
`| Yarn (v2+) | \`.yarnrc.yml\`: \`npmRegistryServer: "${bridge}"\` |`,
'',
'Then pin the build (`vite` aliases to vite-plus-core; pnpm can use a catalog, npm an `overrides` entry):',
'',
'```json',
'{',
' "devDependencies": {',
` "vite-plus": "${version}",`,
` "vite": "npm:@voidzero-dev/vite-plus-core@${version}"`,
' }',
'}',
'```',
].join('\n');
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
});
const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
body,
});
}
# Build and push a preview Docker image from the registry bridge build so the
# image can be verified before a real release. Tagged `pr-<number>`; never
# `latest`. See docker/Dockerfile and docs/guide/docker.md.
publish-docker-preview:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'preview-build')
name: Docker preview image
runs-on: ubuntu-latest
needs: publish
permissions:
contents: read
packages: write
env:
IMAGE: ghcr.io/voidzero-dev/vite-plus
TAG: pr-${{ github.event.pull_request.number }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Builds from this PR's registry bridge build (VP_PR_VERSION resolves it
# through the bridge). The bridge must have the commit build first, hence
# `needs: publish`.
# amd64-only: this throwaway preview avoids the slow arm64 QEMU leg; arm64
# is covered by the release build and the test-install-sh-arm64 job.
- name: Build and push preview image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: docker
file: docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.IMAGE }}:${{ env.TAG }}
build-args: |
VP_PR_VERSION=${{ github.event.pull_request.number }}
# Single-manifest image (no attestation index): simpler for consumers
# and lets the comment job read the size via `docker manifest inspect`.
provenance: false
# Post (or update) a single sticky PR comment with the preview image tag after
# it publishes. Re-runs reuse the same comment via the hidden marker instead of
# creating a new one.
comment-docker-preview:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'preview-build')
name: Comment Docker preview
runs-on: ubuntu-latest
needs: publish-docker-preview
permissions:
contents: read
packages: read
pull-requests: write
env:
IMAGE: ghcr.io/voidzero-dev/vite-plus
PR_NUMBER: ${{ github.event.pull_request.number }}
steps:
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Compressed download size from the registry manifest (config + layers),
# without pulling the image. Matches what `docker pull` transfers and what
# the GHCR package page shows.
- name: Measure image size
id: size
run: |
bytes=$(docker manifest inspect "${IMAGE}:pr-${PR_NUMBER}" | jq '[.config.size] + [.layers[].size] | add')
echo "compressed=$(numfmt --to=si --suffix=B "$bytes")" >> "$GITHUB_OUTPUT"
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
COMPRESSED_SIZE: ${{ steps.size.outputs.compressed }}
with:
script: |
const image = process.env.IMAGE;
const pr = context.payload.pull_request.number;
const marker = '<!-- docker-preview -->';
// Built as a line array (not a template literal) so the fenced code
// blocks don't collide with the YAML block-scalar indentation.
const body = [
marker,
'## 🐳 Docker preview image',
'',
"Built from this PR's registry bridge build:",
'',
'| Image | Compressed size |',
'| --- | --- |',
`| \`${image}:pr-${pr}\` | ${process.env.COMPRESSED_SIZE} |`,
'',
'```bash',
`# remove any stale local copy from a previous run, then pull fresh`,
`docker rmi ${image}:pr-${pr} 2>/dev/null; docker pull ${image}:pr-${pr}`,
'```',
'',
'Quick check:',
'',
'```bash',
`docker run --rm ${image}:pr-${pr} vp --version`,
'```',
'',
'See [docs/guide/docker.md](https://github.com/voidzero-dev/vite-plus/blob/main/docs/guide/docker.md) for usage.',
].join('\n');
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
});
const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
body,
});
}