Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 51 additions & 7 deletions .github/workflows/opencode-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ on:
default: '30'
workflow_call:
inputs:
tag:
artifact:
type: string
required: false
default: ''
version:
type: string
required: true
timeout:
Expand All @@ -29,18 +33,58 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- name: Download packed artifact
if: inputs.artifact != ''
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: ${{ inputs.artifact }}
path: ${{ runner.temp }}/package

- name: Resolve plugin version
id: version
env:
ARTIFACT_NAME: ${{ inputs.artifact }}
REQUESTED_VERSION: ${{ github.event_name == 'workflow_call' && inputs.version || '' }}
REQUESTED_TAG: ${{ inputs.tag }}
run: |
VERSION=$(npm view opencode-synced@${{ inputs.tag }} version)
if [ -n "$ARTIFACT_NAME" ]; then
TARBALL=$(find "${RUNNER_TEMP}/package" -maxdepth 1 -name '*.tgz' -print -quit)
if [ -z "$TARBALL" ]; then
echo "Missing package artifact."
exit 1
fi
VERSION=$(tar -xOf "$TARBALL" package/package.json | jq -r '.version')
if [ "$VERSION" != "$REQUESTED_VERSION" ]; then
echo "Expected package version $REQUESTED_VERSION, found $VERSION."
exit 1
fi
echo "spec=opencode-synced@file:$TARBALL" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
exit 0
fi

PACKAGE_SPEC="opencode-synced@${REQUESTED_VERSION:-$REQUESTED_TAG}"
VERSION=""
for attempt in 1 2 3 4 5 6; do
VERSION=$(npm view "$PACKAGE_SPEC" version 2>/dev/null || true)
if [ -n "$VERSION" ]; then
break
fi
echo "Waiting for npm registry to expose $PACKAGE_SPEC (attempt $attempt/6)"
sleep 10
done
if [ -z "$VERSION" ]; then
echo "No version found for tag: ${{ inputs.tag }}"
echo "No version found for: $PACKAGE_SPEC"
exit 1
fi
if [ -n "$REQUESTED_VERSION" ] && [ "$VERSION" != "$REQUESTED_VERSION" ]; then
echo "Expected exact version $REQUESTED_VERSION, resolved $VERSION"
exit 1
fi
echo "spec=opencode-synced@$VERSION" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Install opencode
Expand All @@ -53,7 +97,7 @@ jobs:
- name: Configure clean opencode home
env:
opencode_home: ${{ runner.temp }}/opencode-home
PLUGIN_VERSION: ${{ steps.version.outputs.version }}
PLUGIN_SPEC: ${{ steps.version.outputs.spec }}
run: |
export HOME="$opencode_home"
export XDG_CONFIG_HOME="$HOME/.config"
Expand All @@ -65,7 +109,7 @@ jobs:
cat > "$XDG_CONFIG_HOME/opencode/opencode.json" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-synced@${PLUGIN_VERSION}"]
"plugin": ["${PLUGIN_SPEC}"]
}
EOF

Expand Down Expand Up @@ -155,7 +199,7 @@ jobs:

- name: Upload opencode logs (on failure)
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: opencode-smoke-logs-${{ matrix.os }}
path: ${{ runner.temp }}/opencode-smoke.log
238 changes: 192 additions & 46 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,124 @@ on:
options:
- latest
- next
ref:
description: 'Exact full commit SHA to publish'
required: true
type: string
repository_dispatch:
types: [publish-package]

permissions:
id-token: write
contents: read

jobs:
publish:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
ref: ${{ steps.request.outputs.ref }}
tag: ${{ steps.request.outputs.tag }}
steps:
- name: Validate canonical publish ref
id: request
env:
EVENT_NAME: ${{ github.event_name }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_TAG: ${{ inputs.tag }}
INPUT_REF: ${{ inputs.ref }}
PAYLOAD_REF: ${{ github.event.client_payload.ref }}
PAYLOAD_TAG: ${{ github.event.client_payload.tag }}
run: |
DEFAULT_BRANCH=$(gh api "repos/${GITHUB_REPOSITORY}" --jq '.default_branch')
DEFAULT_SHA=$(gh api \
"repos/${GITHUB_REPOSITORY}/commits/${DEFAULT_BRANCH}" \
--jq '.sha')

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
TAG="$INPUT_TAG"
REF="$INPUT_REF"
else
TAG="$PAYLOAD_TAG"
REF="$PAYLOAD_REF"
fi

if [[ ! "$REF" =~ ^[0-9a-f]{40}$ ]]; then
echo "Publishing requires a full commit SHA."
exit 1
fi

case "$TAG" in
latest|next) ;;
*)
echo "Unsupported npm tag: $TAG"
exit 1
;;
esac

if [ "$EVENT_NAME" = "workflow_dispatch" ] && \
[ "$TAG" = "next" ] && [ "$REF" != "$DEFAULT_SHA" ]; then
echo "Manual next publishing requires the current default-branch SHA."
exit 1
fi

if [ "$TAG" = "latest" ]; then
PACKAGE_VERSION=$(gh api \
-H "Accept: application/vnd.github.raw+json" \
"repos/${GITHUB_REPOSITORY}/contents/package.json?ref=${REF}" \
--jq '.version')
if [[ ! "$PACKAGE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Latest publishing requires a stable semantic version."
exit 1
fi
TAG_OBJECT=$(gh api \
"repos/${GITHUB_REPOSITORY}/git/ref/tags/v${PACKAGE_VERSION}")
TAG_TYPE=$(jq -r '.object.type' <<<"$TAG_OBJECT")
TAG_SHA=$(jq -r '.object.sha' <<<"$TAG_OBJECT")
while [ "$TAG_TYPE" = "tag" ]; do
TAG_OBJECT=$(gh api \
"repos/${GITHUB_REPOSITORY}/git/tags/${TAG_SHA}")
TAG_TYPE=$(jq -r '.object.type' <<<"$TAG_OBJECT")
TAG_SHA=$(jq -r '.object.sha' <<<"$TAG_OBJECT")
done
if [ "$TAG_TYPE" != "commit" ] || [ "$REF" != "$TAG_SHA" ]; then
echo "Latest publishing requires the commit behind v${PACKAGE_VERSION}."
exit 1
fi
fi

if [ "$EVENT_NAME" = "repository_dispatch" ] && [ "$TAG" = "next" ]; then
MATCHING_RELEASE_PRS=$(gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/pulls?state=open&base=${DEFAULT_BRANCH}&per_page=100" | \
jq --arg ref "$REF" '[.[][] | select(
.head.sha == $ref and
.user.login == "github-actions[bot]" and
(.head.ref | startswith("release-please--branches--")) and
(.title | startswith("chore(main): release "))
)] | length')
if [ "$MATCHING_RELEASE_PRS" != "1" ]; then
echo "Next publishing requires the exact SHA of the open release-please PR."
exit 1
fi
fi

echo "ref=$REF" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

package:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
requested_tag: ${{ steps.publish.outputs.requested_tag }}
artifact: opencode-synced-package
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
ref: ${{ needs.validate.outputs.ref }}
persist-credentials: false

- uses: jdx/mise-action@d6e32c1796099e0f1f3ac741c220a8b7eae9e5dd
with:
Expand All @@ -35,67 +138,110 @@ jobs:
- name: Setup
run: mise run setup

- name: Build
run: mise run build

- id: inputs
uses: simenandre/setup-inputs@v1

- name: Publish candidate to npm with OIDC
id: publish
- name: Prepare package version
id: version
env:
REQUESTED_TAG: ${{ needs.validate.outputs.tag }}
run: |
TAG="${{ steps.inputs.outputs.tag }}"
if [ -z "$TAG" ]; then
TAG="latest"
BASE_VERSION=$(node --print "require('./package.json').version")
if [ -z "$BASE_VERSION" ]; then
echo "Missing package version."
exit 1
fi
if [[ ! "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid semantic package version: $BASE_VERSION"
exit 1
fi

echo "requested_tag=$TAG" >> "$GITHUB_OUTPUT"
VERSION="$BASE_VERSION"
if [ "$REQUESTED_TAG" = "next" ]; then
BASE_VERSION_WITHOUT_PRERELEASE="${BASE_VERSION%%[-+]*}"
if [[ ! "$BASE_VERSION_WITHOUT_PRERELEASE" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid package version for prerelease: $BASE_VERSION"
exit 1
fi

VERSION="${BASE_VERSION_WITHOUT_PRERELEASE}-next.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}"
npm version "$VERSION" \
--no-git-tag-version \
--ignore-scripts \
--allow-same-version
fi

CANDIDATE_TAG="next"
echo "Publishing candidate with tag: $CANDIDATE_TAG (requested: $TAG)"
mise run publish --tag "$CANDIDATE_TAG"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Resolve published version
id: version
- name: Build
run: mise run build

- name: Pack exact artifact
run: |
VERSION=$(npm view opencode-synced@next version)
if [ -z "$VERSION" ]; then
echo "Failed to resolve version from npm tag: next"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
mkdir -p "${RUNNER_TEMP}/package"
npm pack --pack-destination "${RUNNER_TEMP}/package"

smoke:
needs: publish
- name: Upload exact artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: opencode-synced-package
path: ${{ runner.temp }}/package/*.tgz
if-no-files-found: error
retention-days: 1

prepublish-smoke:
needs: package
permissions:
actions: read
contents: read
uses: ./.github/workflows/opencode-smoke.yml
with:
tag: next
artifact: ${{ needs.package.outputs.artifact }}
version: ${{ needs.package.outputs.version }}
timeout: 20
continue-on-error: true

promote_latest:
needs: [publish, smoke]
if: needs.publish.outputs.requested_tag == 'latest'
publish:
needs: [validate, package, prepublish-smoke]
runs-on: ubuntu-latest
permissions:
actions: read
id-token: write
contents: read
outputs:
version: ${{ needs.package.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
with:
node-version: 24

- uses: jdx/mise-action@d6e32c1796099e0f1f3ac741c220a8b7eae9e5dd
- name: Install trusted-publishing npm version
run: npm install --global --ignore-scripts npm@11.6.3

- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
install: true
cache: true
experimental: true
name: ${{ needs.package.outputs.artifact }}
path: ${{ runner.temp }}/package

- name: Promote latest dist-tag
- name: Publish exact artifact to npm with OIDC
env:
EXPECTED_VERSION: ${{ needs.package.outputs.version }}
REQUESTED_TAG: ${{ needs.validate.outputs.tag }}
run: |
VERSION="${{ needs.publish.outputs.version }}"
if [ -z "$VERSION" ]; then
echo "Missing published version."
TARBALL=$(find "${RUNNER_TEMP}/package" -maxdepth 1 -name '*.tgz' -print -quit)
if [ -z "$TARBALL" ]; then
echo "Missing package artifact."
exit 1
fi
ACTUAL_VERSION=$(tar -xOf "$TARBALL" package/package.json | jq -r '.version')
if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then
echo "Expected package version $EXPECTED_VERSION, found $ACTUAL_VERSION."
exit 1
fi
npm publish "$TARBALL" --access public --tag "$REQUESTED_TAG" --ignore-scripts

echo "Promoting opencode-synced@$VERSION to latest"
npm dist-tag add "opencode-synced@$VERSION" latest
postpublish-smoke:
# Trusted publishing authorizes npm publish only. A failed post-publish smoke cannot roll back
# an immutable npm version, so the exact packed artifact must pass prepublish-smoke first.
needs: publish
permissions:
contents: read
uses: ./.github/workflows/opencode-smoke.yml
with:
version: ${{ needs.publish.outputs.version }}
timeout: 20
Loading
Loading