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
5 changes: 3 additions & 2 deletions .github/workflows/release-react.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# 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.
name: 📦 Release react

on:
push:
branches:
- main
- stable
paths:
# React declares `superdoc` in dependencies (not peerDependencies), so
# existing consumers with lockfiles won't pick up a new core version
Expand Down
40 changes: 25 additions & 15 deletions scripts/__tests__/release-local.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,18 +176,23 @@ test('stable orchestrator prunes before snapshot and reports would-release previ
);
});

test('stable orchestrator releases tools chain (CLI, SDK, MCP) and core chain (superdoc) in order', async () => {
test('stable orchestrator releases tools chain (CLI, SDK, MCP) and core chain (superdoc, react) 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)');
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',
);
assert.ok(
content.includes("name: 'react'"),
'scripts/release-local-stable.mjs: orchestrator must release react after superdoc so consumers see them ship together',
);
assert.equal(
content.includes("name: 'esign'") || content.includes("name: 'react'") || content.includes("name: 'template-builder'") || content.includes("name: 'vscode-ext'"),
content.includes("name: 'esign'") || content.includes("name: 'template-builder'") || content.includes("name: 'vscode-ext'"),
false,
'scripts/release-local-stable.mjs: react, vscode-ext, esign, and template-builder are added in follow-up PRs',
'scripts/release-local-stable.mjs: vscode-ext, esign, and template-builder are added in follow-up PRs',
);
});

Expand Down Expand Up @@ -258,11 +263,10 @@ test('stable release workflows serialize on the shared release-stable concurrenc
);

// Per-package workflows that still auto-fire on stable directly.
// superdoc is excluded because release-stable.yml drives its stable
// releases now. The remaining workflows have not yet been brought into
// the orchestrator.
// 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.
const perPackageStableWorkflows = [
'.github/workflows/release-react.yml',
'.github/workflows/release-esign.yml',
'.github/workflows/release-template-builder.yml',
'.github/workflows/release-vscode-ext.yml',
Expand All @@ -275,14 +279,20 @@ test('stable release workflows serialize on the shared release-stable concurrenc
);
}

// superdoc no longer auto-fires on stable - the orchestrator is its
// single stable release path.
const superdocWorkflow = await readRepoFile('.github/workflows/release-superdoc.yml');
assert.equal(
/branches:\s*\n\s*-\s*main\s*\n\s*-\s*stable/.test(superdocWorkflow),
false,
'.github/workflows/release-superdoc.yml: stable releases are driven by release-stable.yml; this workflow only fires on main',
);
// Workflows that no longer auto-fire on stable - the orchestrator is
// their single stable release path.
const orchestratorOnlyOnStable = [
'.github/workflows/release-superdoc.yml',
'.github/workflows/release-react.yml',
];
for (const file of orchestratorOnlyOnStable) {
const content = await readRepoFile(file);
assert.equal(
/branches:\s*\n\s*-\s*main\s*\n\s*-\s*stable/.test(content),
false,
`${file}: stable releases are driven by release-stable.yml; this workflow only fires on main`,
);
}
});

test('MCP releaserc builds the package before publish so the tarball ships dist/', async () => {
Expand Down
39 changes: 34 additions & 5 deletions scripts/release-local-stable.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +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:
* Currently superdoc only. react and vscode-ext still ship from their
* per-package stable workflows; pulling them into this chain is a
* separate refactor and is what makes docs-stable promotion (keyed off
* superdoc's v* tag) live in this workflow.
* 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.
*
* Per-package adapters live on the descriptor (resumePublish,
* preparePythonSnapshot). The recovery engine is generic; new packages
Expand Down Expand Up @@ -679,6 +680,25 @@ function prepareSdkPythonSnapshot(workspaceRoot, tag) {
return copySdkPythonArtifacts(workspaceRoot, tag);
}

function resumeReactPublish(workspaceRoot, distTag, options = {}) {
const { skipBuild = workspaceRoot === REPO_ROOT } = options;
// react's `prepublishOnly` runs `vite build`, whose `dts` plugin rolls up
// types imported from `superdoc`. That import resolves through
// packages/superdoc/dist via the workspace symlink. The snapshot only ran
// `pnpm install`, so build superdoc first; in REPO_ROOT it is already on
// disk from the workflow's `Build packages` step.
if (!skipBuild) {
runInWorkspace(workspaceRoot, 'pnpm', ['run', 'build:superdoc']);
}
runInWorkspace(workspaceRoot, 'node', [
join(workspaceRoot, 'scripts/npm-publish-package.cjs'),
'--package-dir',
'packages/react',
Comment on lines +693 to +696
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build React before publishing recovered snapshots

When recovery runs from an older tagged snapshot (!tagAtHead in recoverPackageRelease), that temporary worktree only runs pnpm install; packages/react/dist is gitignored and is not present in the tag checkout. This adapter then invokes the generic publish helper directly, so an incomplete already-tagged React release can be recovered by publishing a tarball without the compiled dist/index.js/types instead of rebuilding first. Please mirror the MCP/superdoc recovery paths and build React when publishing from a snapshot worktree.

Useful? React with 👍 / 👎.

'--tag',
distTag,
]);
}

function resumeSuperdocPublish(workspaceRoot, distTag, options = {}) {
const { skipBuild = workspaceRoot === REPO_ROOT } = options;
const args = [join(workspaceRoot, 'scripts/publish-superdoc.cjs'), '--dist-tag', distTag];
Expand Down Expand Up @@ -872,6 +892,15 @@ const packages = [
npmPackages: SUPERDOC_NPM_PACKAGES,
resumePublish: resumeSuperdocPublish,
},
{
name: 'react',
chain: 'core',
packageCwd: 'packages/react',
tagPrefix: 'react-v',
tagPattern: 'react-v*',
npmPackages: ['@superdoc-dev/react'],
resumePublish: resumeReactPublish,
},
];

/**
Expand Down
Loading