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
66 changes: 48 additions & 18 deletions .github/workflows/kiro-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,27 +174,57 @@ jobs:

gh release upload "$TAG" release/*.vsix --clobber

# Publishes the moment THIS platform is ready, without waiting for the
# others. Only ever runs from a release tag.
# Uploading to the registry is an HTTP request, not a build, so it does not need
# the platform it targets. Splitting it out buys two things. The secret can live
# under a GitHub environment, which is only readable by a job declaring
# `environment:`, and no half-published version is possible: Open VSX treats a
# version as immutable, so if one platform failed to build after another had
# already published, that gap could never be filled, only superseded by a
# higher version.
publish:
name: publish ${{ matrix.target }}
needs: release
if: startsWith(github.ref, 'refs/tags/kiro-extension-v')
runs-on: ubuntu-22.04
timeout-minutes: 15

# Only needs to read back the build artifact. Stated explicitly so a
# restrictive repository default cannot fail the download confusingly.
permissions:
contents: read
actions: read

# Names the environment holding the OVSX_PAT secret. This must match the
# environment exactly or the secret below silently resolves to an empty
# string. The environment happens to be named after the secret it carries.
environment: OVSX_PAT

strategy:
# One platform failing to upload must not strand the others.
fail-fast: false
matrix:
target: [darwin-arm64, darwin-x64, win32-x64, linux-x64]

defaults:
run:
working-directory: kiro-extension

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- uses: actions/download-artifact@v4
with:
name: vsix-${{ matrix.target }}
path: kiro-extension/release

- name: Publish ${{ matrix.target }} to Open VSX
if: startsWith(github.ref, 'refs/tags/kiro-extension-v')
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
shell: bash
run: |
if [ -z "$OVSX_PAT" ]; then
echo "OVSX_PAT resolved to an empty string, refusing to publish." >&2
echo "" >&2
echo "If you added it under Settings > Environments, it is only" >&2
echo "visible to a job that declares 'environment:'. This job does" >&2
echo "not, deliberately: an environment with protection rules would" >&2
echo "also gate every pull request build." >&2
echo "" >&2
echo "Add it as a repository secret instead:" >&2
echo "Settings > Secrets and variables > Actions > New repository secret" >&2
exit 1
fi
node scripts/release.mjs --publish
run: node scripts/publish.mjs --target=${{ matrix.target }}

# A platform-specific VSIX declares TargetPlatform, and a mismatched host
# refuses to install it. This produces one file with every companion binary and
Expand Down
94 changes: 94 additions & 0 deletions kiro-extension/scripts/publish.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Publishes an already-packaged VSIX to Open VSX, the registry Kiro installs
* from. Packaging happens on the platform that can compile the native mascot;
* uploading is just an HTTP request, so it is split out and runs anywhere.
*
* node scripts/publish.mjs --target darwin-arm64
*
* Expects the matching VSIX to already exist in release/, which is how the CI
* publish job consumes the artifact the build job produced.
*/
import { spawnSync } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const manifest = JSON.parse(await fs.readFile(path.join(root, 'package.json'), 'utf-8'));

const target = process.argv.find((arg) => arg.startsWith('--target='))?.split('=')[1];
if (!target) {
console.error('[doraemon] usage: node scripts/publish.mjs --target=<platform>');
process.exit(1);
}

if (!process.env['OVSX_PAT']) {
console.error('[doraemon] OVSX_PAT resolved to an empty string, refusing to publish.');
console.error('');
console.error('[doraemon] In CI this usually means the secret lives under an');
console.error('[doraemon] environment, but the job did not declare "environment:".');
console.error('[doraemon] Locally, create a token at:');
console.error('[doraemon] https://open-vsx.org/user-settings/tokens');
process.exit(1);
}

/*
* The tag names the release but the version comes from package.json. Publishing a
* mismatch is unrecoverable: Open VSX treats a version as immutable, so the wrong
* number cannot be replaced afterwards, only superseded by a higher one.
*/
const tag = process.env['GITHUB_REF_NAME'];
if (tag?.startsWith('kiro-extension-v')) {
const tagged = tag.slice('kiro-extension-v'.length);
if (tagged !== manifest.version) {
console.error(`[doraemon] tag ${tag} does not match package.json ${manifest.version}.`);
console.error('[doraemon] bump the version and retag, rather than publishing a mismatch.');
process.exit(1);
}
}

// Mirrors the name release.mjs packages under, so no globbing is needed.
const vsix = path.join(root, 'release', `${manifest.name}-${manifest.version}-${target}.vsix`);
try {
await fs.access(vsix);
} catch {
console.error(`[doraemon] ${path.relative(root, vsix)} is missing.`);
console.error('[doraemon] package it first, or check the build artifact was downloaded.');
process.exit(1);
}

const { size } = await fs.stat(vsix);
console.log(`[doraemon] publishing ${path.relative(root, vsix)} (${(size / 1024 / 1024).toFixed(2)} MB)`);

/*
* Publishing into a namespace that does not exist fails outright. Creating it is
* idempotent, so doing it here means the first ever release needs no manual step.
* Four platforms race to create the same namespace and three will lose, which is
* why an "already exists" result is treated as success.
*/
console.log(`[doraemon] ensuring namespace "${manifest.publisher}" exists`);
const namespace = spawnSync('npx', ['--yes', 'ovsx', 'create-namespace', manifest.publisher], {
cwd: root,
encoding: 'utf-8',
shell: process.platform === 'win32',
});
const namespaceOutput = `${namespace.stdout ?? ''}${namespace.stderr ?? ''}`;
if (namespace.status !== 0 && !/already (exists|owned)/i.test(namespaceOutput)) {
console.error('[doraemon] could not create the namespace:');
console.error(namespaceOutput.trim());
console.error('[doraemon] claim it manually at https://open-vsx.org and retry.');
process.exit(1);
}

console.log(`[doraemon] uploading ${target} as "${manifest.publisher}"`);
const publish = spawnSync('npx', ['--yes', 'ovsx', 'publish', vsix, '--target', target], {
cwd: root,
stdio: 'inherit',
shell: process.platform === 'win32',
});
if (publish.status !== 0) {
console.error(`[doraemon] publishing ${target} failed`);
process.exit(publish.status ?? 1);
}

console.log(`[doraemon] published ${manifest.name} ${manifest.version} (${target})`);