Skip to content

chore!: bundle CLI with esbuild#2866

Open
AlbinaBlazhko17 wants to merge 34 commits into
mainfrom
feat/esbuild-bundle-cli
Open

chore!: bundle CLI with esbuild#2866
AlbinaBlazhko17 wants to merge 34 commits into
mainfrom
feat/esbuild-bundle-cli

Conversation

@AlbinaBlazhko17

@AlbinaBlazhko17 AlbinaBlazhko17 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Switched @redocly/cli to bundle with esbuild and produce one dependency-free file.

  • Added scripts/build.mjs - esbuild config that bundles src/index.tslib/index.js
  • Added scripts/prepare-publish-dir.mjs - creates a .publish staging directory with a minimal package.json (no dependencies) and only the files that need to be published
  • Changed scripts/local-pack.sh - pack:prepare now builds and packs from .publish
  • Removed packages/cli from tsconfig.build.json references - tsc no longer compiles the CLI
  • Inlined template.hbs as a string constant in build-docs/utils.ts as a fallback - esbuild does not copy assets, so resolveTemplateSource tries to read the file first and falls back to the embedded template
  • Simplified package.ts - replaced createRequire with a static JSON import, which esbuild handles natively
  • Removed pin-intersecting-deps.sh and all yarn cache workarounds from smoke tests - no transitive deps means no version conflicts to pin

Reference

Testing

Locally, in CI, in other products.

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

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

Note

High Risk
Major packaging and distribution change: published CLI has no node_modules dependencies, which can break plugins and integrators that relied on hoisting; esbuild bundling also changes how commands and assets load at runtime.

Overview
@redocly/cli is now built with esbuild and published without runtime dependencies, so installs are faster and the tarball only contains bin/, bundled lib/, README, LICENSE, and generated THIRD_PARTY_NOTICES.

The build pipeline adds packages/cli/scripts/build.mjs (ESM bundle + code splitting, redoc leak guard on the main entry) and prepare-publish-dir.mjs (stages .publish with a minimal package.json). Root compile runs the CLI bundle instead of tsc for the CLI; packages/cli is removed from tsconfig.build.json. Release and snapshot flows publish from .publish via scripts/release-publish.mjs and updated local-pack.sh.

Breaking for plugin authors: packages hoisted from the CLI (e.g. @redocly/openapi-core) are no longer available unless declared in the plugin’s own dependencies (documented in the changeset).

build-docs is dynamically imported so heavy redoc/React code stays out of the main bundle; the default Handlebars template is inlined in utils.ts (file-based templates still work). Scorecard plugin evaluation sets __redocly_dirname from an optional config directory via pathToFileURL. Smoke CI drops yarn cache/pin workarounds; Docker build no longer copies template.hbs manually.

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

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c525d5e

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 Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

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

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 81.37% (🎯 81%) 7394 / 9086
🔵 Statements 80.73% (🎯 80%) 7687 / 9521
🔵 Functions 84.5% (🎯 84%) 1473 / 1743
🔵 Branches 73.05% (🎯 72%) 5002 / 6847
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 56.66% 35.48% 83.33% 58.62% 46-67, 133
packages/cli/src/commands/scorecard-classic/validation/plugin-evaluator.ts 83.33% 76.19% 100% 82.75% 17, 59-62
packages/cli/src/commands/scorecard-classic/validation/validate-scorecard.ts 80% 73.68% 100% 79.16% 52-54, 72-92
packages/cli/src/utils/package.ts 100% 100% 100% 100%
Generated in workflow #10315 for commit c525d5e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓▓▓▓▓▓▓▓▓ 1.27x ± 0.01 ▓▓▓▓▓▓▓▓▓▓▓▓▓ 1.41x ± 0.01 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 1.82x ± 0.02
cli-next ▓ 1.00x (Fastest) ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)

"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed order of the exports to avoid warnings. Made the same changes for respect-core.
Image

Comment thread Dockerfile
WORKDIR /build
COPY . .
RUN apk add --no-cache jq git && \
npm ci --no-optional --ignore-scripts && \

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't need to skip deps under optionalDependencies, because we don't have deps at all.

Comment thread package.json
"release": "node ./scripts/release-publish.mjs",
"pack:prepare": "./scripts/local-pack.sh",
"respect:parser:generate": "pegjs --format es --output packages/respect-core/lib/modules/runtime-expressions/abnf-parser.js packages/respect-core/src/modules/runtime-expressions/abnf-parser.pegjs && cp packages/respect-core/lib/modules/runtime-expressions/abnf-parser.js packages/respect-core/src/modules/runtime-expressions/abnf-parser.js",
"build-docs:copy-assets": "cp packages/cli/src/commands/build-docs/template.hbs packages/cli/lib/commands/build-docs/template.hbs ",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't need any templates inside lib folder. We only have lib/index.js.

@AlbinaBlazhko17 AlbinaBlazhko17 self-assigned this Jun 11, 2026
@AlbinaBlazhko17 AlbinaBlazhko17 added the snapshot Create experimental release PR label Jun 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1781184257

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

@AlbinaBlazhko17 AlbinaBlazhko17 marked this pull request as ready for review June 12, 2026 07:36
@AlbinaBlazhko17 AlbinaBlazhko17 requested review from a team as code owners June 12, 2026 07:36
Comment thread packages/cli/scripts/build.mjs
Comment thread packages/cli/scripts/build.mjs
Comment thread .github/workflows/release.yaml
Comment thread packages/cli/scripts/build.mjs Outdated
);

const seenPkgRoots = new Set();
const licenseGroups = new Map(); // spdx -> { text, packages: ['name@version — Copyright ...'] }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We cannot just point to our public repo, so i picked different approach to show license and packages to what it belongs. The current THIRD_PARTY_NOTICES has 772 rows.

metafile: true,
// Avoid errors when external dependencies use CJS syntax.
banner: {
js: "import { createRequire as __createRequire } from 'node:module';\nconst require = __createRequire(import.meta.url);",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If i remove this it will not throw an error, but i will leave this as a guard. @tatomyr WDYT?

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.

Let's try without it first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did more deep testing and received an error, so we leave this banner prop.

Comment thread .dockerignore
!tsconfig.json
!package-lock.json
!README.md
!LICENSE.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We have root LICENSE.md file with this extension.

Comment thread packages/cli/scripts/build.mjs Outdated
@tatomyr tatomyr changed the title chore: bundle CLI with esbuild chore!: bundle CLI with esbuild Jun 16, 2026
Comment thread packages/cli/src/commands/build-docs/index.ts Outdated
const templateSource = customTemplate
? readFileSync(customTemplate, 'utf-8')
: DEFAULT_TEMPLATE_SOURCE;
const template = handlebars.compile(templateSource);

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.

Do we still need to convert it to string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, because we use 'utf-8'.

Comment thread packages/cli/package.json
"Roman Hotsiy <roman@redocly.com> (https://redocly.com/)"
],
"dependencies": {
"devDependencies": {

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.

Please check if npx changeset version will still correctly bump the CLI version when bumping its dev dependencies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I gave it a shot and looks like it updated CLI.

Comment thread packages/cli/scripts/prepare-publish-dir.mjs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort 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 c525d5e. Configure here.


for (const relInput of allInputs) {
const absInput = path.resolve(packageDir, relInput);
const pkgRootMatch = absInput.match(/^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Windows path breaks license scan

Low Severity

Third-party notice generation matches node_modules using a regex that only allows forward slashes, while path.resolve on Windows produces backslash paths. Bundled dependency discovery can be skipped on Windows, yielding incomplete or misleading THIRD_PARTY_NOTICES output.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c525d5e. Configure here.

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

Labels

snapshot Create experimental release PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants