Files
t8y2--dbx/.github/workflows/plugin-cli-release.yml

364 lines
13 KiB
YAML

name: Plugin CLI Release
on:
push:
tags:
- "plugin-cli-v*"
workflow_dispatch:
inputs:
version:
description: "Plugin CLI version to publish, for example 0.1.0"
required: true
default: "0.1.0"
npm-tag:
description: "npm distribution tag"
required: true
default: "latest"
type: choice
options:
- latest
- next
permissions:
attestations: write
contents: write
id-token: write
concurrency:
group: plugin-cli-release
cancel-in-progress: false
jobs:
prepare:
name: Validate plugin CLI release
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.release.outputs.tag }}
version: ${{ steps.release.outputs.version }}
npm-tag: ${{ steps.release.outputs.npm-tag }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 22.13.0
registry-url: https://registry.npmjs.org
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-go@v6
with:
go-version: "1.22.x"
cache: false
- name: Validate release branch and versions
id: release
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_NPM_TAG: ${{ inputs.npm-tag }}
run: |
node <<'NODE'
const fs = require("fs");
let version;
let tag;
let npmTag;
if (process.env.GITHUB_EVENT_NAME === "push") {
tag = process.env.GITHUB_REF_NAME;
const match = /^plugin-cli-v(.+)$/.exec(tag);
if (!match) throw new Error(`Unexpected plugin CLI tag: ${tag}`);
version = match[1];
npmTag = version.includes("-") ? "next" : "latest";
} else {
if (process.env.GITHUB_REF !== "refs/heads/main") {
throw new Error(`Manual plugin CLI releases must run from main, received ${process.env.GITHUB_REF}.`);
}
version = process.env.INPUT_VERSION.trim();
tag = `plugin-cli-v${version}`;
npmTag = process.env.INPUT_NPM_TAG;
}
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
throw new Error(`Invalid semantic version: ${version}`);
}
if (version.includes("-") && npmTag === "latest") {
throw new Error(`Prerelease ${version} cannot use the npm latest tag.`);
}
const packagePaths = [
"packages/plugin-cli/package.json",
"packages/plugin-cli-darwin-arm64/package.json",
"packages/plugin-cli-darwin-x64/package.json",
"packages/plugin-cli-linux-arm64-gnu/package.json",
"packages/plugin-cli-linux-x64-gnu/package.json",
"packages/plugin-cli-win32-arm64/package.json",
"packages/plugin-cli-win32-x64/package.json",
];
for (const path of packagePaths) {
const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
if (pkg.version !== version) {
throw new Error(`${path} has version ${pkg.version}; expected ${version}.`);
}
}
const cliCargo = fs.readFileSync("plugins/sdk/cli/Cargo.toml", "utf8");
const cliVersion = cliCargo.match(/^version\s*=\s*"([^"]+)"/m)?.[1];
if (cliVersion !== version) {
throw new Error(`plugins/sdk/cli/Cargo.toml has version ${cliVersion}; expected ${version}.`);
}
const launcher = JSON.parse(fs.readFileSync(packagePaths[0], "utf8"));
for (const [dependency, dependencyVersion] of Object.entries(launcher.optionalDependencies ?? {})) {
if (dependencyVersion !== version) {
throw new Error(`${dependency} is pinned to ${dependencyVersion}; expected ${version}.`);
}
}
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tag}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `npm-tag=${npmTag}\n`);
NODE
- name: Check npm token
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "${NODE_AUTH_TOKEN}" ]; then
echo "::error::NPM_TOKEN secret is required to publish @dbx-app/plugin-cli."
exit 1
fi
- name: Test Rust CLI
run: cargo test --locked --manifest-path plugins/sdk/cli/Cargo.toml
- name: Test npm launcher
run: npm --prefix packages/plugin-cli test
- name: Verify packed npm installation
env:
DBX_PLUGIN_CLI_VERIFY_NATIVE: "1"
run: node scripts/verify-plugin-cli-package.mjs
- name: Create immutable source tag
if: github.event_name == 'workflow_dispatch'
env:
TAG: ${{ steps.release.outputs.tag }}
run: |
git fetch origin --tags --force
if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then
TAG_SHA="$(git rev-list -n 1 "refs/tags/${TAG}")"
HEAD_SHA="$(git rev-parse HEAD)"
if [ "${TAG_SHA}" != "${HEAD_SHA}" ]; then
echo "::error::${TAG} already points to ${TAG_SHA}, not ${HEAD_SHA}."
exit 1
fi
echo "${TAG} already points to this commit."
else
git tag "${TAG}" HEAD
git push origin "refs/tags/${TAG}:refs/tags/${TAG}"
fi
publish-platforms:
name: Publish ${{ matrix.package-name }}
needs: prepare
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
target: aarch64-apple-darwin
package-dir: plugin-cli-darwin-arm64
package-name: "@dbx-app/plugin-cli-darwin-arm64"
binary: dbx-plugin
- runner: macos-15-intel
target: x86_64-apple-darwin
package-dir: plugin-cli-darwin-x64
package-name: "@dbx-app/plugin-cli-darwin-x64"
binary: dbx-plugin
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
package-dir: plugin-cli-linux-arm64-gnu
package-name: "@dbx-app/plugin-cli-linux-arm64-gnu"
binary: dbx-plugin
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
package-dir: plugin-cli-linux-x64-gnu
package-name: "@dbx-app/plugin-cli-linux-x64-gnu"
binary: dbx-plugin
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
package-dir: plugin-cli-win32-arm64
package-name: "@dbx-app/plugin-cli-win32-arm64"
binary: dbx-plugin.exe
- runner: windows-2025
target: x86_64-pc-windows-msvc
package-dir: plugin-cli-win32-x64
package-name: "@dbx-app/plugin-cli-win32-x64"
binary: dbx-plugin.exe
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.tag }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: actions/setup-python@v6
if: runner.os == 'Linux'
with:
python-version: "3.x"
- name: Install Linux compatibility builder
if: runner.os == 'Linux'
run: pip install ziglang==0.14.0 cargo-zigbuild==0.23.0
- name: Build precompiled CLI
shell: bash
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target/plugin-cli
run: |
if [[ "${{ runner.os }}" == "Linux" ]]; then
cargo zigbuild --locked --release --manifest-path plugins/sdk/cli/Cargo.toml --target "${{ matrix.target }}.2.31"
else
cargo build --locked --release --manifest-path plugins/sdk/cli/Cargo.toml --target "${{ matrix.target }}"
fi
- name: Stage platform package
shell: bash
run: |
mkdir -p "packages/${{ matrix.package-dir }}/bin"
cp "target/plugin-cli/${{ matrix.target }}/release/${{ matrix.binary }}" "packages/${{ matrix.package-dir }}/bin/${{ matrix.binary }}"
if [[ "${{ runner.os }}" != "Windows" ]]; then
chmod +x "packages/${{ matrix.package-dir }}/bin/${{ matrix.binary }}"
fi
- uses: actions/setup-node@v6
with:
node-version: 22.13.0
registry-url: https://registry.npmjs.org
- name: Publish platform package
shell: bash
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ needs.prepare.outputs.version }}
NPM_TAG: ${{ needs.prepare.outputs.npm-tag }}
run: |
if npm view "${{ matrix.package-name }}@${VERSION}" version >/dev/null 2>&1; then
echo "${{ matrix.package-name }}@${VERSION} already exists; refreshing ${NPM_TAG}."
npm dist-tag add "${{ matrix.package-name }}@${VERSION}" "${NPM_TAG}"
else
npm publish "./packages/${{ matrix.package-dir }}" --access public --provenance --tag "${NPM_TAG}"
fi
publish-launcher:
name: Publish @dbx-app/plugin-cli
needs: [prepare, publish-platforms]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.tag }}
- uses: actions/setup-node@v6
with:
node-version: 22.13.0
registry-url: https://registry.npmjs.org
- name: Wait for platform packages
env:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
wait_for_package() {
package="$1"
for attempt in $(seq 1 60); do
if npm view "${package}@${VERSION}" version >/dev/null 2>&1; then
return 0
fi
echo "Waiting for ${package}@${VERSION} (${attempt}/60)."
sleep 10
done
return 1
}
for package in \
@dbx-app/plugin-cli-darwin-arm64 \
@dbx-app/plugin-cli-darwin-x64 \
@dbx-app/plugin-cli-linux-arm64-gnu \
@dbx-app/plugin-cli-linux-x64-gnu \
@dbx-app/plugin-cli-win32-arm64 \
@dbx-app/plugin-cli-win32-x64; do
wait_for_package "${package}" || {
echo "::error::${package}@${VERSION} is unavailable; refusing to publish the launcher."
exit 1
}
done
- name: Publish launcher and bundled SDK
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ needs.prepare.outputs.version }}
NPM_TAG: ${{ needs.prepare.outputs.npm-tag }}
run: |
if npm view "@dbx-app/plugin-cli@${VERSION}" version >/dev/null 2>&1; then
echo "@dbx-app/plugin-cli@${VERSION} already exists; refreshing ${NPM_TAG}."
npm dist-tag add "@dbx-app/plugin-cli@${VERSION}" "${NPM_TAG}"
else
npm publish ./packages/plugin-cli --access public --provenance --tag "${NPM_TAG}"
fi
verify-published-package:
name: Verify clean npm installation
needs: [prepare, publish-launcher]
runs-on: ubuntu-24.04
steps:
- uses: actions/setup-node@v6
with:
node-version: 22.13.0
registry-url: https://registry.npmjs.org
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-go@v6
with:
go-version: "1.22.x"
cache: false
- name: Install from npm and package every template
env:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
work="$(mktemp -d)"
cd "${work}"
npm init -y >/dev/null
for attempt in $(seq 1 60); do
if npm view "@dbx-app/plugin-cli@${VERSION}" version >/dev/null 2>&1; then
break
fi
if [ "${attempt}" -eq 60 ]; then
echo "::error::@dbx-app/plugin-cli@${VERSION} did not become visible on npm."
exit 1
fi
sleep 10
done
npm install --ignore-scripts --no-audit --no-fund "@dbx-app/plugin-cli@${VERSION}"
./node_modules/.bin/dbx-plugin --version
for template in frontend rust go; do
./node_modules/.bin/dbx-plugin create "${template}-plugin" \
--template "${template}" \
--yes \
--id "com.example.npm-${template}" \
--name "npm ${template} smoke" \
--publisher example \
--description "Published npm package smoke"
./node_modules/.bin/dbx-plugin package "${template}-plugin"
test "$(find "${template}-plugin/dist" -maxdepth 1 -name '*.dbxp' | wc -l)" -eq 1
done