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
3 changes: 3 additions & 0 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# vsce needs VSCE_PAT to authenticate to the VS Code Marketplace
# during the vscode-ext step of the core chain.
VSCE_PAT: ${{ secrets.VSCE_PAT }}
GITHUB_REF_NAME: stable
run: node scripts/release-local-stable.mjs

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-vscode-ext.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Auto-releases on push to main (@next) and stable (@latest).
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml so that
# every stable release shares one concurrency slot and one git push lane.
# Note: VS Code Marketplace doesn't support semver prerelease versions, so
# main pushes only build a .vsix and attach it to the GitHub release.
name: 📦 Release vscode-ext

on:
push:
branches:
- main
- stable
paths:
- 'apps/vscode-ext/**'
- 'packages/superdoc/**'
Expand Down
19 changes: 12 additions & 7 deletions scripts/__tests__/release-local.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,12 @@ test('stable orchestrator prunes before snapshot and reports would-release previ
);
});

test('stable orchestrator releases tools chain (CLI, SDK, MCP) and core chain (superdoc, react) in order', async () => {
test('stable orchestrator releases tools chain (CLI, SDK, MCP) and core chain (superdoc, react, vscode-ext) in order', async () => {
const content = await readRepoFile('scripts/release-local-stable.mjs');
assertOrder(content, "name: 'cli'", "name: 'sdk'", 'scripts/release-local-stable.mjs (cli before sdk)');
assertOrder(content, "name: 'sdk'", "name: 'mcp'", 'scripts/release-local-stable.mjs (sdk before mcp)');
assertOrder(content, "name: 'superdoc'", "name: 'react'", 'scripts/release-local-stable.mjs (superdoc before react)');
assertOrder(content, "name: 'react'", "name: 'vscode-ext'", 'scripts/release-local-stable.mjs (react before vscode-ext)');
assert.ok(
content.includes("name: 'superdoc'"),
'scripts/release-local-stable.mjs: orchestrator must release superdoc so the v* tag drives docs-stable promotion in the same workflow',
Expand All @@ -189,10 +190,14 @@ test('stable orchestrator releases tools chain (CLI, SDK, MCP) and core chain (s
content.includes("name: 'react'"),
'scripts/release-local-stable.mjs: orchestrator must release react after superdoc so consumers see them ship together',
);
assert.ok(
content.includes("name: 'vscode-ext'") && content.includes("vsCodeExtensionId: 'superdoc-dev.superdoc-vscode-ext'"),
'scripts/release-local-stable.mjs: orchestrator must release vscode-ext with its marketplace extension id so publishComplete checks the marketplace, not npm',
);
assert.equal(
content.includes("name: 'esign'") || content.includes("name: 'template-builder'") || content.includes("name: 'vscode-ext'"),
content.includes("name: 'esign'") || content.includes("name: 'template-builder'"),
false,
'scripts/release-local-stable.mjs: vscode-ext, esign, and template-builder are added in follow-up PRs',
'scripts/release-local-stable.mjs: esign and template-builder are not yet brought into the orchestrator',
);
});

Expand Down Expand Up @@ -263,13 +268,12 @@ test('stable release workflows serialize on the shared release-stable concurrenc
);

// Per-package workflows that still auto-fire on stable directly.
// superdoc and react are excluded because release-stable.yml drives
// their stable releases now. The remaining workflows have not yet been
// brought into the orchestrator.
// superdoc, react, and vscode-ext are excluded because release-stable.yml
// drives their stable releases now. The remaining workflows have not yet
// been brought into the orchestrator.
const perPackageStableWorkflows = [
'.github/workflows/release-esign.yml',
'.github/workflows/release-template-builder.yml',
'.github/workflows/release-vscode-ext.yml',
];
for (const file of perPackageStableWorkflows) {
const content = await readRepoFile(file);
Expand All @@ -284,6 +288,7 @@ test('stable release workflows serialize on the shared release-stable concurrenc
const orchestratorOnlyOnStable = [
'.github/workflows/release-superdoc.yml',
'.github/workflows/release-react.yml',
'.github/workflows/release-vscode-ext.yml',
];
for (const file of orchestratorOnlyOnStable) {
const content = await readRepoFile(file);
Expand Down
56 changes: 46 additions & 10 deletions scripts/release-local-stable.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
* These three share artifacts (SDK packages CLI native binaries; MCP
* imports SDK + engine code), so they must release in this order.
*
* Core chain (superdoc -> react):
* superdoc is the npm core; react consumes it. They release in order so
* react is never published against an older superdoc than what just
* shipped. docs-stable promotion is keyed off superdoc's v* tag and
* lives in this workflow as a result. vscode-ext still ships from its
* per-package stable workflow and joins the chain in a separate refactor.
* Core chain (superdoc -> react -> vscode-ext):
* superdoc is the npm core; react consumes it; vscode-ext bundles the
* editor and ships a .vsix to the VS Code Marketplace. They release in
* order so downstream packages are never published against an older
* superdoc than what just shipped. docs-stable promotion is keyed off
* superdoc's v* tag and lives in this workflow as a result.
*
* Per-package adapters live on the descriptor (resumePublish,
* preparePythonSnapshot). The recovery engine is generic; new packages
Expand Down Expand Up @@ -421,11 +421,14 @@ async function generateGitHubReleaseNotes({ tag, targetCommit, previousTag }) {
}

function getExpectedReleaseAssets(pkg, workspaceRoot) {
if (pkg.name !== 'vscode-ext') {
// Only vscode-ext currently has release assets (the .vsix). Other
// descriptors don't set vsCodeExtensionId, so they get an empty list and
// ensureGitHubReleaseAssets becomes a no-op.
if (!pkg.vsCodeExtensionId) {
return [];
}

const extensionDir = join(workspaceRoot, 'apps/vscode-ext');
const extensionDir = join(workspaceRoot, pkg.packageCwd);
let assets = readdirSync(extensionDir)
.filter((entry) => entry.endsWith('.vsix'))
.map((entry) => join(extensionDir, entry));
Expand Down Expand Up @@ -477,7 +480,7 @@ function isGitHubReleaseComplete(pkg, release) {
return false;
}

if (pkg.name === 'vscode-ext') {
if (pkg.vsCodeExtensionId) {
return Array.isArray(release.assets) && release.assets.some((asset) => asset.name.endsWith('.vsix'));
}

Expand Down Expand Up @@ -506,7 +509,7 @@ async function ensureGitHubRelease(pkg, { tag, targetCommit, previousTag, worksp
});
}

if (pkg.name === 'vscode-ext') {
if (pkg.vsCodeExtensionId) {
await ensureGitHubReleaseAssets(release, pkg, workspaceRoot);
release = await getGitHubReleaseByTag(tag);
}
Expand Down Expand Up @@ -680,6 +683,27 @@ function prepareSdkPythonSnapshot(workspaceRoot, tag) {
return copySdkPythonArtifacts(workspaceRoot, tag);
}

function resumeVscodeExtPublish(workspaceRoot, _distTag, options = {}) {
const { skipBuild = workspaceRoot === REPO_ROOT } = options;
// The vscode-ext webview imports `superdoc` and `superdoc/style.css`, both
// of which resolve to packages/superdoc/dist. In a tagged snapshot only
// `pnpm install` has run, so build superdoc first or esbuild's webview
// bundling fails to resolve those imports. In REPO_ROOT the workflow's
// `Build packages` step already produced the dist.
if (!skipBuild) {
runInWorkspace(workspaceRoot, 'pnpm', ['run', 'build:superdoc']);
}
// VS Code Marketplace publish needs the VSCE_PAT env var (passed in by the
// orchestrator workflow). `pnpm run package` recreates the .vsix from
// whatever is on disk and `vsce publish --skip-duplicate` is idempotent
// against the marketplace, so recovery and primary release use the same
// commands. The .vsix asset upload to the GitHub release is handled by
// ensureGitHubReleaseAssets earlier in recoverPackageRelease.
const extensionRoot = join(workspaceRoot, 'apps/vscode-ext');
runInWorkspace(extensionRoot, 'pnpm', ['run', 'package']);
runInWorkspace(extensionRoot, 'pnpm', ['run', 'publish:vsce']);
}

function resumeReactPublish(workspaceRoot, distTag, options = {}) {
const { skipBuild = workspaceRoot === REPO_ROOT } = options;
// react's `prepublishOnly` runs `vite build`, whose `dts` plugin rolls up
Expand Down Expand Up @@ -901,6 +925,18 @@ const packages = [
npmPackages: ['@superdoc-dev/react'],
resumePublish: resumeReactPublish,
},
{
name: 'vscode-ext',
chain: 'core',
packageCwd: 'apps/vscode-ext',
tagPrefix: 'vscode-v',
tagPattern: 'vscode-v*',
// VS Code Marketplace is the publish target instead of npm; the script's
// generic release-state inspection already special-cases vsCodeExtensionId
// for publishComplete and .vsix asset upload.
vsCodeExtensionId: 'superdoc-dev.superdoc-vscode-ext',
resumePublish: resumeVscodeExtPublish,
},
];

/**
Expand Down
Loading