Skip to content

chore(release): update version and changelog for v1.8.1 #43

chore(release): update version and changelog for v1.8.1

chore(release): update version and changelog for v1.8.1 #43

Workflow file for this run

name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Existing tag to release (e.g. v1.2.3). Tag must already exist — use only for emergency re-runs.'
required: true
type: string
permissions:
contents: read
actions: read
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: true
jobs:
detect-tag:
name: Detect release tag and readiness
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
actions: read
outputs:
release_tag: ${{ steps.tag.outputs.release_tag }}
ready: ${{ steps.tag.outputs.ready }}
is_prerelease: ${{ steps.tag.outputs.is_prerelease }}
steps:
- name: Resolve tag from successful workflow runs
id: tag
uses: actions/github-script@v8
env:
RELEASE_TAG_INPUT: ${{ inputs.tag }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
let tagName;
if (context.eventName === 'workflow_dispatch') {
const tagInput = process.env.RELEASE_TAG_INPUT?.trim() ?? '';
if (!tagInput) {
core.setFailed('tag input is required for workflow_dispatch.');
return;
}
tagName = tagInput.startsWith('v') ? tagInput : `v${tagInput}`;
} else {
tagName = context.ref.replace('refs/tags/', '');
}
if (!tagName || !tagName.startsWith('v')) {
core.info(`Ref '${context.ref}' is not a release tag; skipping.`);
core.setOutput('release_tag', '');
core.setOutput('ready', 'false');
return;
}
const refData = await github.rest.git.getRef({
owner,
repo,
ref: `tags/${tagName}`,
});
const refObj = refData.data.object;
const taggedSha =
refObj.type === 'tag'
? (await github.rest.git.getTag({ owner, repo, tag_sha: refObj.sha })).data.object.sha
: refObj.sha;
const main = await github.rest.repos.getBranch({ owner, repo, branch: 'main' });
const mainSha = main.data.commit.sha;
if (context.eventName !== 'workflow_dispatch' && taggedSha !== mainSha) {
core.setFailed(
`Release blocked: tag ${tagName} points to ${taggedSha}, but latest main is ${mainSha}.`
);
return;
}
// Guard: fail if a GitHub Release already exists for this tag.
try {
await github.rest.repos.getReleaseByTag({ owner, repo, tag: tagName });
core.setFailed(`Release ${tagName} already exists on GitHub. Delete it first if you want to re-release.`);
return;
} catch (e) {
if (e.status !== 404) throw e;
// 404 = no release yet, proceed
}
// Verify that the CI workflow passed for this commit.
const ciRuns = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'ci.yml',
head_sha: taggedSha,
status: 'completed',
per_page: 5,
});
const successfulRun = ciRuns.data.workflow_runs.find(r => r.conclusion === 'success');
if (!successfulRun) {
core.setFailed(
`Release blocked: no successful CI run found for commit ${taggedSha}. Push to main/tag and wait for CI to pass before releasing.`
);
return;
}
core.info(`CI run ${successfulRun.id} passed for commit ${taggedSha}.`);
core.info(`Release tag detected: ${tagName} on commit ${taggedSha}; tag verified.`);
const isPrerelease = tagName.includes('-');
core.setOutput('release_tag', tagName);
core.setOutput('ready', 'true');
core.setOutput('is_prerelease', String(isPrerelease));
release:
name: Build and Release
needs: detect-tag
if: needs.detect-tag.outputs.release_tag != '' && needs.detect-tag.outputs.ready == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
deployments: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ needs.detect-tag.outputs.release_tag }}
fetch-depth: 0
- name: Generate release notes for changelog
id: changelog_notes
uses: actions/github-script@v8
env:
RELEASE_TAG: ${{ needs.detect-tag.outputs.release_tag }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const currentTag = process.env.RELEASE_TAG?.trim() ?? '';
const tagsResp = await github.rest.repos.listTags({
owner,
repo,
per_page: 100,
});
const tags = tagsResp.data;
const index = tags.findIndex(tag => tag.name === currentTag);
const previousTag =
index >= 0
? (tags.slice(index + 1).find(tag => tag.name.startsWith('v'))?.name ?? '')
: '';
const notesResponse = await github.rest.repos.generateReleaseNotes({
owner,
repo,
tag_name: currentTag,
...(previousTag ? { previous_tag_name: previousTag } : {}),
target_commitish: 'main',
});
core.info(
previousTag
? `Generated changelog notes from ${previousTag} to ${currentTag}.`
: `Generated changelog notes for ${currentTag} (no previous tag found).`
);
core.setOutput('previous_tag', previousTag);
core.setOutput('notes', notesResponse.data.body ?? '');
- name: Write release notes to file
run: printf '%s' "$RELEASE_NOTES" > release-notes.md
env:
RELEASE_NOTES: ${{ steps.changelog_notes.outputs.notes }}
# NOTE: Version/changelog mutation and commit/tag operations were moved to
# local automation (`scripts/deploy.sh`) to comply with branch rules that
# require PR-based changes on main.
- name: Skip repo mutation in release workflow
run: echo "Version/changelog updates are performed locally before tagging."
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'pnpm'
- name: Setup task runner
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
with:
version: "3.x"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build extension package
run: xvfb-run -a task compile
- name: Package extension
run: |
if [ "${{ needs.detect-tag.outputs.is_prerelease }}" = "true" ]; then
pnpm dlx @vscode/vsce package --pre-release --no-dependencies --out opilot.vsix
else
pnpm dlx @vscode/vsce package --no-dependencies --out opilot.vsix
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.detect-tag.outputs.release_tag }}
body_path: release-notes.md
files: 'opilot.vsix'
draft: false
prerelease: ${{ needs.detect-tag.outputs.is_prerelease == 'true' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create VS Code Marketplace deployment (pre-release)
id: deploy_vsce_pre
if: needs.detect-tag.outputs.is_prerelease == 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: '${{ needs.detect-tag.outputs.release_tag }}',
environment: 'vscode-marketplace',
description: 'Publish pre-release to VS Code Marketplace',
auto_merge: false,
required_contexts: [],
production_environment: false,
});
core.setOutput('deployment_id', deployment.data.id);
- name: Publish pre-release to VS Code Marketplace
id: publish_vsce_pre
if: needs.detect-tag.outputs.is_prerelease == 'true'
run: pnpm dlx @vscode/vsce publish --pre-release --allow-all-proposed-apis --packagePath opilot.vsix -p ${{ secrets.VSCE_PAT }}
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
- name: Update VS Code Marketplace deployment status (pre-release)
if: always() && needs.detect-tag.outputs.is_prerelease == 'true' && steps.deploy_vsce_pre.outputs.deployment_id != ''
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const outcome = '${{ steps.publish_vsce_pre.outcome }}';
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deploy_vsce_pre.outputs.deployment_id }},
state: outcome === 'success' ? 'success' : 'failure',
description: `VS Code Marketplace pre-release publish ${outcome}`,
environment_url: 'https://marketplace.visualstudio.com/items?itemName=selfagency.opilot',
auto_inactive: false,
});
- name: Create Open VSX deployment (pre-release)
id: deploy_ovsx_pre
if: needs.detect-tag.outputs.is_prerelease == 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: '${{ needs.detect-tag.outputs.release_tag }}',
environment: 'open-vsx',
description: 'Publish pre-release to Open VSX Registry',
auto_merge: false,
required_contexts: [],
production_environment: false,
});
core.setOutput('deployment_id', deployment.data.id);
- name: Publish pre-release to Open VSX
id: publish_ovsx_pre
if: needs.detect-tag.outputs.is_prerelease == 'true'
run: pnpm dlx ovsx publish opilot.vsix --pre-release -p ${{ secrets.OVSX_PAT }}
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
- name: Update Open VSX deployment status (pre-release)
if: always() && needs.detect-tag.outputs.is_prerelease == 'true' && steps.deploy_ovsx_pre.outputs.deployment_id != ''
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const outcome = '${{ steps.publish_ovsx_pre.outcome }}';
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deploy_ovsx_pre.outputs.deployment_id }},
state: outcome === 'success' ? 'success' : 'failure',
description: `Open VSX pre-release publish ${outcome}`,
environment_url: 'https://open-vsx.org/extension/selfagency/opilot',
auto_inactive: false,
});
- name: Create VS Code Marketplace deployment
id: deploy_vsce
if: needs.detect-tag.outputs.is_prerelease != 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: '${{ needs.detect-tag.outputs.release_tag }}',
environment: 'vscode-marketplace',
description: 'Publish to VS Code Marketplace',
auto_merge: false,
required_contexts: [],
production_environment: true,
});
core.setOutput('deployment_id', deployment.data.id);
- name: Publish to VS Code Marketplace
id: publish_vsce
if: needs.detect-tag.outputs.is_prerelease != 'true'
run: pnpm dlx @vscode/vsce publish --allow-all-proposed-apis --packagePath opilot.vsix -p ${{ secrets.VSCE_PAT }}
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
- name: Update VS Code Marketplace deployment status
if: always() && needs.detect-tag.outputs.is_prerelease != 'true' && steps.deploy_vsce.outputs.deployment_id != ''
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const outcome = '${{ steps.publish_vsce.outcome }}';
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deploy_vsce.outputs.deployment_id }},
state: outcome === 'success' ? 'success' : 'failure',
description: `VS Code Marketplace publish ${outcome}`,
environment_url: 'https://marketplace.visualstudio.com/items?itemName=selfagency.opilot',
auto_inactive: false,
});
- name: Create Open VSX deployment
id: deploy_ovsx
if: needs.detect-tag.outputs.is_prerelease != 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: '${{ needs.detect-tag.outputs.release_tag }}',
environment: 'open-vsx',
description: 'Publish to Open VSX Registry',
auto_merge: false,
required_contexts: [],
production_environment: true,
});
core.setOutput('deployment_id', deployment.data.id);
- name: Publish to Open VSX
id: publish_ovsx
if: needs.detect-tag.outputs.is_prerelease != 'true'
run: pnpm dlx ovsx publish opilot.vsix -p ${{ secrets.OVSX_PAT }}
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
- name: Update Open VSX deployment status
if: always() && needs.detect-tag.outputs.is_prerelease != 'true' && steps.deploy_ovsx.outputs.deployment_id != ''
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const outcome = '${{ steps.publish_ovsx.outcome }}';
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deploy_ovsx.outputs.deployment_id }},
state: outcome === 'success' ? 'success' : 'failure',
description: `Open VSX publish ${outcome}`,
environment_url: 'https://open-vsx.org/extension/selfagency/opilot',
auto_inactive: false,
});