Skip to content

feat: facet-cli#51

Merged
yashmehrotra merged 22 commits into
mainfrom
facet-cli
Jun 24, 2026
Merged

feat: facet-cli#51
yashmehrotra merged 22 commits into
mainfrom
facet-cli

Conversation

@yashmehrotra

@yashmehrotra yashmehrotra commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added @flanksource/facet-cli for npm-based CLI installs and published per-platform standalone binaries.
    • Added stricter render preflight checks for html/pdf and enhanced facet doctor reporting.
  • Bug Fixes
    • Improved Node-based rendering pipeline reliability (server, SSR/dev loaders) and PDF placeholder/signature handling.
  • Documentation
    • Updated installation and binary download instructions to use the new CLI package and architecture-specific filenames.
  • Tests
    • Migrated CLI tests to Vitest and expanded unit coverage for packaging and preflight behavior.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR migrates the CLI from Bun to Node.js, adds Node SEA binary generation and a new @flanksource/facet-cli package, and updates CLI execution, tests, CI, release, and Docker flows to use Node 22 and pnpm-based tooling.

Changes

Node SEA CLI migration

Layer / File(s) Summary
Runtime utility foundations
cli/src/utils/*, cli/test/*
Adds shared shell execution, asset resolution, and self-exec helpers, plus supporting test updates and small utility adjustments for the Node-based runtime.
Preflight checks and CLI entry routing
cli/src/commands/doctor.ts, cli/src/commands/doctor.test.ts, cli/src/cli.ts
Adds aggregated preflight checks, wires them into render commands, and changes CLI entry routing to dispatch loader modes from FACET_LOADER.
SSR and dev loader modules
cli/src/loaders/*, cli/src/bundler/renderer.ts
Converts SSR and dev loader behavior into exported Node entrypoints and removes the old React SSR export.
Builder and dev-server integration
cli/src/bundler/*, cli/src/builders/facet-directory.*, cli/src/types.ts
Reworks the Vite builder and dev server to re-exec the CLI, updates facet directory rebuild behavior, and removes copied loader artifacts.
HTTP preview server runtime
cli/src/server/*, cli/src/utils/pdf-*.ts
Replaces the preview server with Node HTTP handling, normalizes response bodies, and updates archive and PDF helpers for the new runtime.
SEA build and test tooling
cli/package.json, cli/tsdown*.ts, cli/vitest.config.ts, Taskfile.yml, .github/workflows/test.yml, Dockerfile, vite.lib.config.ts
Adds SEA build configuration and switches scripts, tasks, tests, and container/runtime setup to Node 22, pnpm, and Vitest.
Packaged CLI distribution
cli/scripts/pack-npm-cli.cjs, cli/scripts/pack-npm-cli.test.ts, cli/npm/facet-cli/*, README.md
Adds the publishable CLI package manifest, packaging script, tests, and installation docs for the npm distribution and standalone binaries.
Release workflow and container updates
.github/workflows/release.yml, .github/workflows/test.yml, Dockerfile
Updates release, CI, and Docker configuration to build SEA binaries, publish the CLI package, and run on Node 22.

Possibly related PRs

  • flanksource/facet#24: Both PRs propagate GIT_COMMIT through the Docker and release build flow.
  • flanksource/facet#43: Both PRs update .github/workflows/release.yml around release job dependencies and npm publishing coordination.
  • flanksource/facet#44: This PR continues the same Bun-to-Node CLI runtime migration in shared CLI modules and loaders.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the PR and identifies the main area changed, even though it is broad.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch facet-cli
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch facet-cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​vitest@​4.1.9981007998100
Addednpm/​postject@​1.0.0-alpha.610010010084100

View full report

@socket-security

socket-security Bot commented Jun 24, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @emnapi/runtime is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: cli/pnpm-lock.yamlnpm/vitest@4.1.9npm/@emnapi/runtime@1.11.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@emnapi/runtime@1.11.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm commander is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: cli/pnpm-lock.yamlnpm/postject@1.0.0-alpha.6npm/commander@9.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/commander@9.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@coderabbitai coderabbitai 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.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/test.yml (1)

27-30: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Node version drift: CI tests on 20, release/Docker build on 22.

This job still pins node-version: "20" while release.yml and the Dockerfile now use Node 22. Since the “Build (library + SEA binary)” + smoke-test steps below build a SEA binary that embeds the host Node, CI validates a Node-20 binary while production ships Node 22. Aligning to 22 keeps the smoke test representative of what’s released.

Suggested change
         with:
-          node-version: "20"
+          node-version: "22"
           cache: "pnpm"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test.yml around lines 27 - 30, The CI workflow is still
pinned to Node 20 while the release and Docker build use Node 22, so update the
Setup Node.js step in the test workflow to match the Node version used by the
release path. Make the change in the workflow job that runs the Build (library +
SEA binary) and smoke-test steps, using the existing actions/setup-node
configuration, so the embedded Node version tested in CI matches what ships.
cli/src/utils/pdf-multipass.ts (1)

459-521: 🎯 Functional Correctness | 🟠 Major

Restore a fallback for placeholder overlays cli/src/utils/pdf-multipass.ts:487-513
When headerHasPlaceholders/footerHasPlaceholders is true but browser or html is missing, embedded stays unset and the overlay is skipped entirely. Keep the old stream-rewrite path here, or make marker-bearing overlays require browser/html at the call sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/utils/pdf-multipass.ts` around lines 459 - 521, The placeholder
overlay handling in pdf-multipass currently drops header/footer content when
headerHasPlaceholders or footerHasPlaceholders is true but browser or html is
unavailable. Update the page loop in pdf-multipass so marker-bearing overlays
still render via the existing fallback path instead of leaving embedded unset,
or enforce browser/html availability earlier at the call sites that invoke
renderElementPdf and embedOverlay.
cli/src/bundler/vite-server.ts (1)

67-72: 🩺 Stability & Availability | 🟠 Major

Bound dev-server shutdown. close() waits on exit with no timeout, so cleanup can hang indefinitely if the child ignores SIGTERM. Add a timeout and fall back to SIGKILL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/bundler/vite-server.ts` around lines 67 - 72, The close() path in
vite-server’s dev-server process cleanup can hang forever because it waits
unconditionally on proc’s exit event after proc.kill(). Update the close logic
to add a bounded timeout around the await once(proc, 'exit') in the proc
shutdown flow, and if the process has not exited by then, force-terminate it
with SIGKILL before continuing cleanup. Keep the fix localized to the close()
implementation that manages proc.exitCode and proc.signalCode.
🧹 Nitpick comments (2)
cli/scripts/build-sea.cjs (2)

13-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: fail fast if the bundle is missing.

bundle (dist-sea/cli.cjs) is assumed to exist; if build:bundle was skipped, --experimental-sea-config fails with an opaque error referencing main. A quick existsSync(bundle) check with a clear message improves the build-time DX.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/scripts/build-sea.cjs` around lines 13 - 33, The SEA build step in
build-sea.cjs assumes the cli.cjs bundle already exists, which leads to an
opaque failure when --experimental-sea-config runs without it. Add an existsSync
check for the bundle path before writing sea-config.json or calling
execFileSync, and throw a clear error message that tells the user the bundle is
missing and that build:bundle must be run first; use the bundle, seaConfigPath,
and execFileSync flow in build-sea.cjs to place the guard in the right spot.

38-53: 🩺 Stability & Availability | 🔵 Trivial

Windows release signing is optional. facet-windows-x64.exe is built in the release workflow, but the SEA injection step doesn’t re-sign it. That won’t prevent the binary from running; add a signtool/re-sign step only if signed Windows artifacts are required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/scripts/build-sea.cjs` around lines 38 - 53, The SEA injection in
build-sea.cjs only re-signs the binary on darwin, so Windows release artifacts
built by the same flow remain unsigned after postject injection. Update the
build-sea.cjs flow around the execFileSync injection and the existing macOS
codesign block to either explicitly add a Windows signing step using the release
signing toolchain or leave the behavior unchanged if signed Windows binaries are
not required. Keep the platform-specific logic in build-sea.cjs clear by
branching on process.platform near the postject and codesign handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Line 243: The release workflow step currently interpolates
needs.version-bump.outputs.new_version directly into the run command, which
should be moved to an env binding instead. Update the job step in the release
workflow so the version is passed via an environment variable and then consumed
by the pack-npm-cli.cjs invocation, rather than using shell template expansion.
Use the existing version-bump output and the pack-npm-cli.cjs step as the key
symbols to locate the change.

In `@cli/src/builders/facet-directory.ts`:
- Around line 982-989: The local build invocation in facet-directory’s spawnSync
call can fail with ENOBUFS because the default stdout/stderr buffer is too small
for chatty pnpm scripts. Update the spawnSync options in the local build path to
set a larger maxBuffer alongside cwd and timeout, and keep the existing
stdout/stderr logging behavior in place so build:components and build:css output
can be captured safely.

In `@cli/src/bundler/vite-builder.ts`:
- Around line 126-129: The spawned SSR loader process in runLoaderOnce currently
leaves stdout as a pipe while only stderr is consumed, which can cause the child
to block if it writes enough output. Update the spawn call for child in
vite-builder.ts to either ignore stdout or actively drain/read it, and keep the
fix localized around runLoaderOnce and the child process setup so the loader
cannot hang the build.

In `@cli/src/cli.ts`:
- Line 348: The floating run() call in cli.ts should handle promise rejections
instead of allowing unhandled rejections from the dynamic loaders or
runSsrLoader/runDevLoader. Update the run() invocation to attach an error
handler or await it from an async entrypoint so failures are caught, logged with
a readable message, and translated into a controlled non-zero process exit. Use
the run() symbol and its loader call sites as the place to add the top-level
rejection handling.

In `@cli/src/commands/doctor.ts`:
- Around line 96-101: The preflight flow in doctor.ts is skipping required ids
when run(id) returns undefined, so unknown required checks are treated as
passing. Update the loop in the preflight logic to treat an unresolved required
id as a failure instead of ignoring it, and make sure CHECK_REGISTRY coverage is
enforced for all ids passed from cli.ts or any other caller. Use the run and
requiredIds handling in doctor.ts to locate the fix.

In `@cli/src/loaders/ssr.ts`:
- Around line 87-91: Remove the NODE_PATH and Module._initPaths() mutation from
the SSR loader because facetRequire already handles resolving Vite, React, and
the externalized bundle relative to .facet. Update the setup in ssr.ts to rely
on facetRequire’s existing resolution behavior and delete the
facetNodeModules/originalNodePath logic so the loader no longer depends on the
private Node API.

In `@cli/src/server/archive.ts`:
- Line 5: The archive handling in archive.ts still depends on shelling out
through the imported shell helper, which breaks on Windows. Update the archive
extraction flow in the relevant archive extraction function to use Node-native
directory creation and a cross-platform tar extraction approach, or gate the
feature off on Windows explicitly. Keep the fix localized around the existing
archive upload/extraction logic so the behavior is portable across shipped
binaries.

In `@cli/src/server/preview.ts`:
- Line 101: Bind the preview server explicitly to loopback instead of all
interfaces: update the httpServer.listen call in preview startup so it uses
127.0.0.1 by default unless there is an explicit host configuration. Keep the
existing config.port behavior, and make the binding consistent with the
localhost preview logging and any optional API-key auth behavior in preview.ts.
- Around line 88-95: The request error handling in
createHttpServer/handleRequest only wraps the promise chain, so
nodeToWebRequest(req) can still throw before catch runs. Move the adapter call
into the same request-level try/catch or convert it into a guarded promise flow
so malformed request metadata is handled by the existing logger.error and 500
JSON response path instead of crashing the server.

In `@cli/src/utils/data-loader.ts`:
- Line 4: The data-loader shell command currently invokes bare tsx via the shell
helper, so it depends on tsx being available on PATH. Update the logic in
data-loader’s loader execution path to use the resolved tsx package executable
explicitly (or otherwise ensure the dependency is invoked without relying on
PATH), and keep the behavior in sync with the existing shell-based execution in
this module.

In `@cli/src/utils/remote-resolver.ts`:
- Line 6: The remote clone flow in remote-resolver.ts is leaking credentials
because authedUrl is embedded in a shell-rendered git clone command via $ from
shell.js. Update the clone logic in the relevant resolver functions to avoid
putting GIT_TOKEN into the command string; use a credential helper/askpass-based
auth flow or otherwise pass credentials outside the rendered shell command, and
ensure any propagated shell errors are redacted so the token cannot appear in
logs or process listings.

In `@cli/src/utils/shell.ts`:
- Around line 73-77: ShellPromise.text() does not match its JSDoc because it
returns stdout without trimming; update the implementation in text() or revise
the docstring so they agree. Locate the fix in ShellPromise.text() and either
apply .trim() to the result.stdout string if trimmed output is intended, or
change the comment to describe the actual behavior.

In `@cli/src/utils/tailwind.ts`:
- Line 9: The verbose path in the Tailwind helper is still using the shell
wrapper’s captured output, so diagnostics are hidden just like quiet mode.
Update the Tailwind command runner in tailwind.ts so the opts.verbose branch
either writes the captured stdout/stderr buffers after execution or uses a
shell-helper mode that inherits stdio instead of capturing it. Use the existing
shell helper imported as $ and the verbose branch around the Tailwind invocation
as the main location to fix.

---

Outside diff comments:
In @.github/workflows/test.yml:
- Around line 27-30: The CI workflow is still pinned to Node 20 while the
release and Docker build use Node 22, so update the Setup Node.js step in the
test workflow to match the Node version used by the release path. Make the
change in the workflow job that runs the Build (library + SEA binary) and
smoke-test steps, using the existing actions/setup-node configuration, so the
embedded Node version tested in CI matches what ships.

In `@cli/src/bundler/vite-server.ts`:
- Around line 67-72: The close() path in vite-server’s dev-server process
cleanup can hang forever because it waits unconditionally on proc’s exit event
after proc.kill(). Update the close logic to add a bounded timeout around the
await once(proc, 'exit') in the proc shutdown flow, and if the process has not
exited by then, force-terminate it with SIGKILL before continuing cleanup. Keep
the fix localized to the close() implementation that manages proc.exitCode and
proc.signalCode.

In `@cli/src/utils/pdf-multipass.ts`:
- Around line 459-521: The placeholder overlay handling in pdf-multipass
currently drops header/footer content when headerHasPlaceholders or
footerHasPlaceholders is true but browser or html is unavailable. Update the
page loop in pdf-multipass so marker-bearing overlays still render via the
existing fallback path instead of leaving embedded unset, or enforce
browser/html availability earlier at the call sites that invoke renderElementPdf
and embedOverlay.

---

Nitpick comments:
In `@cli/scripts/build-sea.cjs`:
- Around line 13-33: The SEA build step in build-sea.cjs assumes the cli.cjs
bundle already exists, which leads to an opaque failure when
--experimental-sea-config runs without it. Add an existsSync check for the
bundle path before writing sea-config.json or calling execFileSync, and throw a
clear error message that tells the user the bundle is missing and that
build:bundle must be run first; use the bundle, seaConfigPath, and execFileSync
flow in build-sea.cjs to place the guard in the right spot.
- Around line 38-53: The SEA injection in build-sea.cjs only re-signs the binary
on darwin, so Windows release artifacts built by the same flow remain unsigned
after postject injection. Update the build-sea.cjs flow around the execFileSync
injection and the existing macOS codesign block to either explicitly add a
Windows signing step using the release signing toolchain or leave the behavior
unchanged if signed Windows binaries are not required. Keep the
platform-specific logic in build-sea.cjs clear by branching on process.platform
near the postject and codesign handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 443967b6-49cc-4791-8d06-db70979037da

📥 Commits

Reviewing files that changed from the base of the PR and between 0e52904 and 7c57434.

⛔ Files ignored due to path filters (1)
  • cli/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (51)
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • Dockerfile
  • README.md
  • Taskfile.yml
  • cli/.gitignore
  • cli/npm/facet-cli/README.md
  • cli/npm/facet-cli/package.json
  • cli/package.json
  • cli/scripts/build-sea.cjs
  • cli/scripts/gen-version.cjs
  • cli/scripts/pack-npm-cli.cjs
  • cli/scripts/pack-npm-cli.test.ts
  • cli/src/builders/facet-directory.test.ts
  • cli/src/builders/facet-directory.ts
  • cli/src/bundler/renderer.ts
  • cli/src/bundler/vite-builder.test.ts
  • cli/src/bundler/vite-builder.ts
  • cli/src/bundler/vite-server.ts
  • cli/src/cli.ts
  • cli/src/commands/doctor.test.ts
  • cli/src/commands/doctor.ts
  • cli/src/loaders/dev.ts
  • cli/src/loaders/ssr.ts
  • cli/src/server/archive.ts
  • cli/src/server/preview.ts
  • cli/src/server/routes.ts
  • cli/src/types.ts
  • cli/src/utils/assets.test.ts
  • cli/src/utils/assets.ts
  • cli/src/utils/css-scoper.ts
  • cli/src/utils/data-loader.ts
  • cli/src/utils/package-manager.test.ts
  • cli/src/utils/package-manager.ts
  • cli/src/utils/pdf-multipass.ts
  • cli/src/utils/pdf-security.ts
  • cli/src/utils/remote-resolver.test.ts
  • cli/src/utils/remote-resolver.ts
  • cli/src/utils/self-exec.ts
  • cli/src/utils/shell.ts
  • cli/src/utils/tailwind.test.ts
  • cli/src/utils/tailwind.ts
  • cli/test/jsx-parser.test.ts
  • cli/test/lint-rules.test.ts
  • cli/test/resolve-output.test.ts
  • cli/tsconfig.json
  • cli/tsdown.config.ts
  • cli/tsdown.sea.config.ts
  • cli/vite-dev-loader.ts
  • cli/vitest.config.ts
  • vite.lib.config.ts
💤 Files with no reviewable changes (1)
  • cli/vite-dev-loader.ts

Comment thread .github/workflows/release.yml Outdated
Comment thread cli/src/builders/facet-directory.ts
Comment thread cli/src/bundler/vite-builder.ts
Comment thread cli/src/cli.ts Outdated
Comment thread cli/src/commands/doctor.ts
Comment thread cli/src/server/preview.ts Outdated
Comment thread cli/src/utils/data-loader.ts
Comment thread cli/src/utils/remote-resolver.ts
Comment thread cli/src/utils/shell.ts
Comment thread cli/src/utils/tailwind.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/src/server/archive.ts (1)

24-38: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Cap extracted archive size, not just uploaded bytes.

Line 24 only limits the compressed upload size, but Line 38 expands the archive without a cumulative output limit. A small tar.gz bomb can fill disk or tie up the preview server. Use a streaming extractor/preflight that enforces uncompressed byte and entry limits before writing files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/server/archive.ts` around lines 24 - 38, The archive handling in
archive() only checks the compressed upload size and then blindly expands it
with tar, so add protection for the uncompressed content as well. Update the
extraction flow after writeFile/extractDir to use a streaming or preflight
validation step that enforces maximum total extracted bytes and entry/file-count
limits before any files are written. Keep the existing ARCHIVE_ERROR path in
RenderError, and adjust the tar extraction logic in archive.ts so oversized
archives are rejected before disk fill or excessive expansion can occur.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/utils/pdf-multipass.ts`:
- Around line 469-475: The stream rewrite in pdf-multipass is still replacing
the stream body even when the `/Length` value was not actually updated, which
can corrupt PDFs. Update the logic around the dictionary parsing in the stream
rebuild path so `result = Buffer.concat(...)` only runs when a direct `/Length`
match was found and replaced, or handle indirect `/Length` references before
rebuilding. Use the existing `dictStart`, `beforeDict`, `dict`, `afterStream`,
and stream rewrite block to locate the fix.

---

Outside diff comments:
In `@cli/src/server/archive.ts`:
- Around line 24-38: The archive handling in archive() only checks the
compressed upload size and then blindly expands it with tar, so add protection
for the uncompressed content as well. Update the extraction flow after
writeFile/extractDir to use a streaming or preflight validation step that
enforces maximum total extracted bytes and entry/file-count limits before any
files are written. Keep the existing ARCHIVE_ERROR path in RenderError, and
adjust the tar extraction logic in archive.ts so oversized archives are rejected
before disk fill or excessive expansion can occur.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cde72421-759f-4642-b6d7-ce7aa6291ca8

📥 Commits

Reviewing files that changed from the base of the PR and between 7c57434 and d06f3f4.

📒 Files selected for processing (16)
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • cli/scripts/build-sea.cjs
  • cli/src/builders/facet-directory.ts
  • cli/src/bundler/vite-builder.ts
  • cli/src/bundler/vite-server.ts
  • cli/src/cli.ts
  • cli/src/commands/doctor.ts
  • cli/src/loaders/ssr.ts
  • cli/src/server/archive.ts
  • cli/src/server/preview.ts
  • cli/src/utils/data-loader.ts
  • cli/src/utils/pdf-multipass.ts
  • cli/src/utils/remote-resolver.ts
  • cli/src/utils/shell.ts
  • cli/src/utils/tailwind.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • .github/workflows/test.yml
  • cli/scripts/build-sea.cjs
  • cli/src/utils/shell.ts
  • cli/src/cli.ts
  • cli/src/server/preview.ts
  • cli/src/commands/doctor.ts
  • .github/workflows/release.yml
  • cli/src/bundler/vite-server.ts
  • cli/src/builders/facet-directory.ts
  • cli/src/loaders/ssr.ts
  • cli/src/bundler/vite-builder.ts

Comment on lines +469 to +475
const dictStart = result.lastIndexOf('<<', streamPos);
if (dictStart !== -1) {
const beforeDict = result.subarray(0, dictStart);
const dict = result.subarray(dictStart, dataStart).toString('latin1')
.replace(/\/Length\s+\d+/, `/Length ${deflated.length}`);
const afterStream = result.subarray(dataEnd);
result = Buffer.concat([beforeDict, Buffer.from(dict, 'latin1'), deflated, afterStream]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid rewriting streams when /Length was not updated.

If the stream uses an indirect or missing /Length, the regex leaves the dictionary unchanged while Line 475 still writes a newly deflated stream. That can produce a malformed PDF. Guard on a matched direct length, or implement indirect length updates before rebuilding the stream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/utils/pdf-multipass.ts` around lines 469 - 475, The stream rewrite in
pdf-multipass is still replacing the stream body even when the `/Length` value
was not actually updated, which can corrupt PDFs. Update the logic around the
dictionary parsing in the stream rebuild path so `result = Buffer.concat(...)`
only runs when a direct `/Length` match was found and replaced, or handle
indirect `/Length` references before rebuilding. Use the existing `dictStart`,
`beforeDict`, `dict`, `afterStream`, and stream rewrite block to locate the fix.

@yashmehrotra yashmehrotra merged commit 8b210bf into main Jun 24, 2026
17 checks passed
@yashmehrotra yashmehrotra deleted the facet-cli branch June 24, 2026 19:10
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.

1 participant