Skip to content

chore(cli): bundle with bun target node and publish from staged dir#2637

Open
RomanHotsiy wants to merge 11 commits intomainfrom
codex/bun-node-publish-dir
Open

chore(cli): bundle with bun target node and publish from staged dir#2637
RomanHotsiy wants to merge 11 commits intomainfrom
codex/bun-node-publish-dir

Conversation

@RomanHotsiy
Copy link
Copy Markdown
Member

@RomanHotsiy RomanHotsiy commented Mar 4, 2026

What/Why/How?

  • Switch CLI build output to Bun bundling for Node (bun build --target node) instead of the previous TypeScript-only compile flow.
  • Fix bundled-runtime resolution issues by replacing runtime createRequire('../../package.json') patterns with static JSON imports in CLI runtime paths.
  • Add a built-in build-docs template fallback so bundled artifacts do not depend on template.hbs being present as a filesystem asset.
  • Replace publish-time package.json mutation/restore with a staged publish directory (packages/cli/.publish) and publishConfig.directory so changeset publish publishes from the staged manifest/artifacts.
  • Keep the staged directory minimal: bin/cli.js, bundled lib/index.js, README.md, LICENSE, and a stripped package.json (no deps/devDeps/scripts).
  • Update local pack/smoke/perf/release snapshot flows to use the staged directory.
  • Update CI jobs that execute e2e/pack/publish flows to ensure Bun is installed.

Reference

  • N/A

Testing

  • npm --workspace packages/cli run compile
  • node packages/cli/lib/index.js --help
  • npm run typecheck
  • npm run pack:prepare
  • npm run release (no unpublished packages in this run; verified publish-dir prepare/cleanup path)

Screenshots (optional)

  • N/A

Check yourself

  • Code changed? - Tested with Redoc/Realm/Reunite (internal)
  • All new/updated code is covered by tests
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Medium risk because it replaces the CLI build and npm publish mechanics (Bun bundling + staged publish directory), which can break installs, releases, and runtime asset resolution if misconfigured.

Overview
Bundles the CLI into a single Node-targeted output using Bun (with an npx bun fallback) to reduce install time and avoid runtime require-style package.json lookups.

Changes publishing/packing to a staged packages/cli/.publish directory via publishConfig.directory and new prepare:publish-dir/cleanup scripts, and updates release/snapshot/local pack flows (plus .gitignore) to publish/pack from that directory.

Hardens bundled runtime behavior by embedding a fallback build-docs Handlebars template when template.hbs isn’t available on disk, and wiring redoc version lookup through a static JSON import. CI workflows are updated to install Bun and e2e now bundles the CLI before running.

Reviewed by Cursor Bugbot for commit 1b232c3. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: 1b232c3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/cli Patch
@redocly/openapi-core Patch
@redocly/respect-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@RomanHotsiy RomanHotsiy added the snapshot Create experimental release PR label Mar 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 79.92% (🎯 79%) 6693 / 8374
🔵 Statements 79.33% (🎯 79%) 6920 / 8722
🔵 Functions 83.06% (🎯 82%) 1354 / 1630
🔵 Branches 71.64% (🎯 71%) 4554 / 6356
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/build-docs/index.ts 93.33% 50% 100% 93.33% 53
packages/cli/src/commands/build-docs/utils.ts 59.45% 33.33% 85.71% 61.11% 51-72, 127, 134, 149
packages/cli/src/utils/package.ts 100% 100% 100% 100%
Generated in workflow #9288 for commit 1b232c3 by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

CLI Version Mean Time ± Std Dev (s) Relative Performance (Lower is Faster)
cli-latest 3.426s ± 0.045s ▓ 1.03x
cli-next 3.318s ± 0.009s ▓ 1.00x (Fastest)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

📦 A new experimental 🧪 version v0.0.0-snapshot.1772635755 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1772635755
# or
npm install @redocly/openapi-core@0.0.0-snapshot.1772635755
# or
npm install @redocly/respect-core@0.0.0-snapshot.1772635755

⚠️ Note: This is a development build and may contain unstable features.

@RomanHotsiy RomanHotsiy removed the snapshot Create experimental release PR label Mar 4, 2026
@RomanHotsiy RomanHotsiy marked this pull request as ready for review March 12, 2026 03:43
@RomanHotsiy RomanHotsiy requested review from a team as code owners March 12, 2026 03:43
Copy link
Copy Markdown
Collaborator

@tatomyr tatomyr left a comment

Choose a reason for hiding this comment

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

Had a brief look. Will give it another pass later.

import { performance } from 'node:perf_hooks';
import { default as redoc } from 'redoc';

import packageJson from '../../../package.json' with { type: 'json' };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It probably makes more sense to import it from utils/package.js.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

solved

}

function cleanUpStyledComponentWarnings(str: string): string {
return str.replace(/^styled-components: it looks like an unknown prop .*?(?:\r?\n|$)/gm, '');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't see the message anywhere. Why do wee need this?

const result = spawnSync(command, args, {
cwd: rootDir,
stdio: 'inherit',
shell: process.platform === 'win32',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are we going to publish from Win?

"prepublishOnly": "npm run copy-assets && cp ../../README.md ."
"compile": "bun build src/index.ts --target node --define process.env.NODE_ENV=process.env.NODE_ENV --outfile lib/index.js",
"prepare:publish-dir": "node ./scripts/prepare-publish-dir.mjs",
"clean:publish-dir": "node ./scripts/clean-publish-dir.mjs"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not simply use rm?


try {
run('npm', ['--workspace', 'packages/cli', 'run', 'prepare:publish-dir']);
run('npm', ['exec', 'changeset', 'publish']);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How does it know it should publish from packages/cli/.publish/ and not from packages/cli/?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

  "publishConfig": {
    "directory": ".publish"
  },

in cli/package.json

Comment on lines +80 to +85
if (!value || typeof value !== 'object') {
return value;
}

const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);
return Object.fromEntries(entries);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (!value || typeof value !== 'object') {
return value;
}
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);
return Object.fromEntries(entries);
if (isPlainObject(value)) {
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);
return Object.fromEntries(entries);
}
return value

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Anyways, why do we need to clean up the constructed package json programmatically? Let's just construct it the way we want.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1b232c3. Configure here.

}

if (result.status !== 0) {
process.exit(result.status ?? 1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

process.exit() bypasses finally cleanup in release script

Medium Severity

The run() function calls process.exit() when a command returns a non-zero exit code. In Node.js, process.exit() terminates the process immediately without unwinding the call stack, so the finally block on line 36 that runs clean:publish-dir never executes. Only the throw path (for spawn errors) is catchable. This means if changeset publish fails with a non-zero exit code, the .publish directory is never cleaned up. Changing the non-zero-status path to throw instead of process.exit() would let the try/catch/finally work as intended.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b232c3. Configure here.

Copy link
Copy Markdown
Collaborator

@tatomyr tatomyr left a comment

Choose a reason for hiding this comment

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

Left a minor comment. Please also check this comment: #2637 (comment).
Otherwise LGTM.

}

console.warn('Bun binary not found on PATH; falling back to `npx bun@1.3.10`.');
return spawnSync('npx', ['--yes', 'bun@1.3.10', ...bunBuildArgs], {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not just use bun as a dev dependency?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think it's overkill, we need it only for publishing, I would keep it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants