From a4485c6c33f669155619189119d8c8233670cfb9 Mon Sep 17 00:00:00 2001 From: Alefita Date: Sat, 12 Sep 2026 15:56:42 -0300 Subject: [PATCH 1/3] ci: add pull-request checks and a tag-driven release pipeline `publish.yml` owns the npm release but only runs on tags and only in the upstream repository, so pull requests get no checks and a fork cannot hand out an installable build. - `ci.yml` runs `pnpm run check` on every pull request and on `main`. - `release.yml` packs the tarball for a tag or a manually requested version (`workflow_dispatch`), keeps it as a workflow artifact, and attaches it to a GitHub release, marked pre-release when the version carries a suffix. The npm release stays owned by `publish.yml`; this workflow only builds the artifact, so a fork can produce release candidates without publishing anything to a registry. --- .github/workflows/ci.yml | 29 +++++++++++ .github/workflows/release.yml | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31821d0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + with: + version: 11.21.0 + - uses: actions/setup-node@v6 + with: + node-version: '24' + package-manager-cache: false + - run: pnpm --config.minimum-release-age=0 install --frozen-lockfile + - run: pnpm run check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4410146 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,91 @@ +name: Release + +# Publishes an installable tarball for a release candidate or a release, so a +# fork (or the maintainer, before npm is ready) can hand out +# dsh plugin --profile web add +# without waiting for a publishable version. The npm release stays owned by +# publish.yml; this workflow only builds and attaches the artifact. + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: Version to pack, with or without a leading v (for example 0.2.10-rc.1) + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + pack: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Resolve the version to pack + id: version + shell: bash + run: | + set -euo pipefail + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + raw="${{ inputs.version }}" + else + raw="$GITHUB_REF_NAME" + fi + version="${raw#v}" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "Version '$version' is not a release or release-candidate semver" >&2 + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + if [[ "$version" == *-* ]]; then + echo "prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "prerelease=false" >> "$GITHUB_OUTPUT" + fi + + - uses: pnpm/action-setup@v4 + with: + version: 11.21.0 + - uses: actions/setup-node@v6 + with: + node-version: '24' + package-manager-cache: false + - run: pnpm --config.minimum-release-age=0 install --frozen-lockfile + - name: Stamp the requested version into the package + run: pnpm version "${{ steps.version.outputs.version }}" --no-git-tag-version + - run: pnpm run check + - run: pnpm --config.minimum-release-age=0 pack + + - name: Keep the packed tarball as a workflow artifact + uses: actions/upload-artifact@v4 + with: + name: dsh-codex-${{ steps.version.outputs.version }} + path: dsh-codex-*.tgz + if-no-files-found: error + + - name: Publish the GitHub release with the installable tarball + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + tag="v${{ steps.version.outputs.version }}" + prerelease="${{ steps.version.outputs.prerelease }}" + if gh release view "$tag" >/dev/null 2>&1; then + gh release upload "$tag" dsh-codex-*.tgz --clobber + exit 0 + fi + args=(--title "$tag" --generate-notes) + if [[ "$prerelease" == "true" ]]; then + args+=(--prerelease) + fi + gh release create "$tag" dsh-codex-*.tgz "${args[@]}" From 306509a5e8f8039346f4065d0e446481a760af6d Mon Sep 17 00:00:00 2001 From: Alefita Date: Sat, 12 Sep 2026 15:58:45 -0300 Subject: [PATCH 2/3] ci: use actions/upload-artifact v7 (Node 24) in the release pipeline --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4410146..d170634 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,7 +66,7 @@ jobs: - run: pnpm --config.minimum-release-age=0 pack - name: Keep the packed tarball as a workflow artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: dsh-codex-${{ steps.version.outputs.version }} path: dsh-codex-*.tgz From e01b48ddbdfaa61780d061c253a675b5b5555edd Mon Sep 17 00:00:00 2001 From: Yan <1964649083@qq.com> Date: Sun, 13 Sep 2026 15:56:20 +0800 Subject: [PATCH 3/3] ci: automate releases from package version changes Run the release path only when package.json changes version on main. Check, pack, retain, and publish one tarball before creating the annotated version tag and GitHub release. Fold the separate release workflow into publish.yml, remove manual ref/version stamping, keep upstream-only publication, and make partial reruns fail closed on package integrity. --- .github/workflows/publish.yml | 120 +++++++++++++++++++++++++++++++--- .github/workflows/release.yml | 91 -------------------------- 2 files changed, 110 insertions(+), 101 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fc36cb6..6bf95d6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,11 +2,13 @@ name: Publish to npm on: push: - tags: - - 'v*' + branches: + - main + paths: + - package.json permissions: - contents: read + contents: write id-token: write concurrency: @@ -19,22 +21,120 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Verify tag matches package version + with: + fetch-depth: 0 + - name: Detect a package version change + id: version shell: bash run: | + set -euo pipefail + before="${{ github.event.before }}" version="$(node -p "require('./package.json').version")" - if [[ "$GITHUB_REF_NAME" != "v$version" ]]; then - echo "Tag $GITHUB_REF_NAME does not match package version $version" >&2 - exit 1 + if [[ "$before" =~ ^0+$ ]] || ! git cat-file -e "${before}:package.json"; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "No previous package.json is available; skipping automatic release." + exit 0 + fi + previous="$(git show "${before}:package.json" | node -e "let text=''; process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => text += chunk); process.stdin.on('end', () => process.stdout.write(JSON.parse(text).version))")" + if [[ "$version" == "$previous" ]]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "Package version remains $version; skipping automatic release." + exit 0 + fi + tag="v$version" + if git rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then + target="$(git rev-list -n 1 "$tag")" + if [[ "$target" != "$GITHUB_SHA" ]]; then + echo "Tag $tag already points to $target instead of $GITHUB_SHA" >&2 + exit 1 + fi + echo "tag_exists=true" >> "$GITHUB_OUTPUT" + else + echo "tag_exists=false" >> "$GITHUB_OUTPUT" + fi + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + if [[ "$version" == *-* ]]; then + echo "prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "prerelease=false" >> "$GITHUB_OUTPUT" fi - uses: pnpm/action-setup@v4 + if: steps.version.outputs.changed == 'true' with: version: 11.21.0 - uses: actions/setup-node@v6 + if: steps.version.outputs.changed == 'true' with: node-version: '24' registry-url: https://registry.npmjs.org/ package-manager-cache: false - - run: pnpm --config.minimum-release-age=0 install --frozen-lockfile - - run: pnpm run check - - run: npm publish + - if: steps.version.outputs.changed == 'true' + run: pnpm --config.minimum-release-age=0 install --frozen-lockfile + - if: steps.version.outputs.changed == 'true' + run: pnpm run check + - if: steps.version.outputs.changed == 'true' + run: pnpm --config.minimum-release-age=0 pack + - name: Keep the installable tarball as a workflow artifact + if: steps.version.outputs.changed == 'true' + uses: actions/upload-artifact@v7 + with: + name: dsh-codex-${{ steps.version.outputs.version }} + path: dsh-codex-*.tgz + if-no-files-found: error + - name: Inspect the npm release state + if: steps.version.outputs.changed == 'true' + id: npm + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + tarballs=(dsh-codex-*.tgz) + if [[ "${#tarballs[@]}" -ne 1 ]]; then + echo "Expected exactly one packed tarball" >&2 + exit 1 + fi + package="$(node -p "require('./package.json').name")" + version="${{ steps.version.outputs.version }}" + local_integrity="$(node -e "const { createHash } = require('node:crypto'); const { readFileSync } = require('node:fs'); process.stdout.write('sha512-' + createHash('sha512').update(readFileSync(process.argv[1])).digest('base64'))" "${tarballs[0]}")" + if registry_integrity="$(npm view "$package@$version" dist.integrity 2>/dev/null)" && [[ -n "$registry_integrity" ]]; then + if [[ "$registry_integrity" != "$local_integrity" ]]; then + echo "$package@$version already exists with different package bytes" >&2 + exit 1 + fi + echo "published=true" >> "$GITHUB_OUTPUT" + else + echo "published=false" >> "$GITHUB_OUTPUT" + fi + - name: Publish the packed artifact to npm + if: steps.version.outputs.changed == 'true' && steps.npm.outputs.published != 'true' + run: npm publish dsh-codex-*.tgz + - name: Create the release tag + if: steps.version.outputs.changed == 'true' && steps.version.outputs.tag_exists != 'true' + env: + TAG: ${{ steps.version.outputs.tag }} + shell: bash + run: | + set -euo pipefail + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git tag --annotate "$TAG" --message "Release $TAG" "$GITHUB_SHA" + git push origin "refs/tags/$TAG" + - name: Attach the installable tarball to the GitHub release + if: steps.version.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.version.outputs.tag }} + shell: bash + run: | + set -euo pipefail + if gh release view "$TAG" >/dev/null 2>&1; then + gh release upload "$TAG" dsh-codex-*.tgz --clobber + exit 0 + fi + args=(--title "$TAG" --generate-notes --verify-tag) + if [[ "${{ steps.version.outputs.prerelease }}" == "true" ]]; then + args+=(--prerelease) + fi + gh release create "$TAG" dsh-codex-*.tgz "${args[@]}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index d170634..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Release - -# Publishes an installable tarball for a release candidate or a release, so a -# fork (or the maintainer, before npm is ready) can hand out -# dsh plugin --profile web add -# without waiting for a publishable version. The npm release stays owned by -# publish.yml; this workflow only builds and attaches the artifact. - -on: - push: - tags: - - 'v*' - workflow_dispatch: - inputs: - version: - description: Version to pack, with or without a leading v (for example 0.2.10-rc.1) - required: true - type: string - -permissions: - contents: write - -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - -jobs: - pack: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Resolve the version to pack - id: version - shell: bash - run: | - set -euo pipefail - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - raw="${{ inputs.version }}" - else - raw="$GITHUB_REF_NAME" - fi - version="${raw#v}" - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then - echo "Version '$version' is not a release or release-candidate semver" >&2 - exit 1 - fi - echo "version=$version" >> "$GITHUB_OUTPUT" - if [[ "$version" == *-* ]]; then - echo "prerelease=true" >> "$GITHUB_OUTPUT" - else - echo "prerelease=false" >> "$GITHUB_OUTPUT" - fi - - - uses: pnpm/action-setup@v4 - with: - version: 11.21.0 - - uses: actions/setup-node@v6 - with: - node-version: '24' - package-manager-cache: false - - run: pnpm --config.minimum-release-age=0 install --frozen-lockfile - - name: Stamp the requested version into the package - run: pnpm version "${{ steps.version.outputs.version }}" --no-git-tag-version - - run: pnpm run check - - run: pnpm --config.minimum-release-age=0 pack - - - name: Keep the packed tarball as a workflow artifact - uses: actions/upload-artifact@v7 - with: - name: dsh-codex-${{ steps.version.outputs.version }} - path: dsh-codex-*.tgz - if-no-files-found: error - - - name: Publish the GitHub release with the installable tarball - env: - GH_TOKEN: ${{ github.token }} - shell: bash - run: | - set -euo pipefail - tag="v${{ steps.version.outputs.version }}" - prerelease="${{ steps.version.outputs.prerelease }}" - if gh release view "$tag" >/dev/null 2>&1; then - gh release upload "$tag" dsh-codex-*.tgz --clobber - exit 0 - fi - args=(--title "$tag" --generate-notes) - if [[ "$prerelease" == "true" ]]; then - args+=(--prerelease) - fi - gh release create "$tag" dsh-codex-*.tgz "${args[@]}"