Skip to content

fix(cli): bundled workspace packaging for release-runtime#128

Open
stoicneko wants to merge 2 commits intohappier-dev:devfrom
stoicneko:fix/cli-bundle-release-runtime
Open

fix(cli): bundled workspace packaging for release-runtime#128
stoicneko wants to merge 2 commits intohappier-dev:devfrom
stoicneko:fix/cli-bundle-release-runtime

Conversation

@stoicneko
Copy link

@stoicneko stoicneko commented Mar 13, 2026

Summary

  • Add a shared bundledWorkspacePackages.mjs module as single source of truth for all bundled workspace packages
  • Build and sync release-runtime alongside the other bundled workspace packages (it was previously missing from the build:shared tsc step)
  • Deep-freeze entry objects in the canonical list to prevent desync mutations
  • Extend post-build artifact check to cover all bundled packages (not just protocol)
  • Add a regression test that keeps bundledDependencies, build targets, and bundle targets aligned

Target branch

dev

How to test

  1. From repo root: cd apps/cli
  2. Run the regression test: node --test scripts/prepack-script.test.mjs
    • All 3 tests should pass
  3. (Optional) Full prepack smoke test: yarn workspace @happier-dev/cli prepack
    • Verify release-runtime dist is present under apps/cli/node_modules/@happier-dev/release-runtime/dist

Checklist

  • Tests added / updated
  • No hardcoded secrets
  • JSDoc added to all new exports
  • Existing tests pass
  • PR targets dev branch

Summary by CodeRabbit

  • Chores

    • Centralized CLI workspace bundle configuration and replaced hard-coded package lists with a single source of truth; build and bundling now iterate dynamically over that canonical set.
  • Tests

    • Added and extended tests to verify the prepack process includes bundling and to ensure workspace bundle definitions stay synchronized across build and bundle steps.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Walkthrough

Adds a centralized module exporting canonical bundled workspace package metadata and a factory to compute bundle descriptors; build and bundling scripts now consume these dynamic definitions; tests were extended to verify synchronization between package.json and the new configuration.

Changes

Cohort / File(s) Summary
Workspace Configuration
apps/cli/scripts/bundledWorkspacePackages.mjs
New module exporting frozen arrays: bundledWorkspacePackages, bundledWorkspaceDirNames, bundledWorkspacePackageNames, and createBundledWorkspaceBundles({ repoRoot, happyCliDir }) which returns bundle descriptors with packageName, srcDir, and destDir.
Build Script
apps/cli/scripts/buildSharedDeps.mjs
Replaced hard-coded package list with imported bundledWorkspaceDirNames (exported as bundledWorkspaceDirs), iterating over it for tsc builds and dist existence checks; removed unused realpathSync import and moved TS compilation/dist validation into the loop.
Bundling Script
apps/cli/scripts/bundleWorkspaceDeps.mjs
Replaced inline bundle construction with createBundledWorkspaceBundles({ repoRoot, happyCliDir }); remaining bundling and vendoring flow unchanged but now driven by the centralized bundle descriptors.
Tests
apps/cli/scripts/prepack-script.test.mjs
Added imports for new exports and extended tests: assert prepack includes bundling step, compare bundledDependencies from package.json to bundledWorkspacePackageNames, verify bundledWorkspaceDirs vs bundledWorkspaceDirNames, and validate a generated release-runtime bundle's src/dest paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description follows the template with all required sections completed: Summary, target branch, testing steps, and checklist with relevant items checked off.
Title check ✅ Passed The title directly reflects the main objective: consolidating bundled workspace packaging definitions and ensuring release-runtime is included in the CLI build alongside other bundled packages.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes.

Add @coderabbitai placeholder anywhere in the title of your PR and CodeRabbit will replace it with a title based on the changes in the PR. You can change the placeholder by changing the reviews.auto_title_placeholder setting.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR fixes the release-runtime package being missed by the buildSharedDeps tsc compilation step during prepack, even though it was already being bundled, by extracting a new shared bundledWorkspacePackages.mjs canonical list that both the build and bundle scripts now source from.

Key changes:

  • bundledWorkspacePackages.mjs (new): single source of truth for all four bundled workspace packages (agents, cli-common, protocol, release-runtime), exposing a frozen array, derived dirNames/packageNames exports, and a createBundledWorkspaceBundles factory.
  • buildSharedDeps.mjs: replaces the three hardcoded runTsc calls in main() with a loop over the shared list, and updates the syncBundledWorkspaceDist default to use the same list — release-runtime is now compiled in the build:shared step for the first time.
  • bundleWorkspaceDeps.mjs: the inline bundles array literal is replaced by a call to createBundledWorkspaceBundles; functional behavior is unchanged (all four packages including release-runtime were already listed here).
  • prepack-script.test.mjs: adds a regression test that keeps bundledDependencies in package.json, the build targets, and the bundle targets in sync.

Two minor style observations:

  • buildSharedDeps.mjs now adds release-runtime to the compilation loop but does not add a post-build artifact check to match the existing protocol sanity check.
  • The new test assertion assert.deepEqual([...bundledWorkspaceDirs], [...bundledWorkspaceDirNames]) compares a direct re-export to its source, making it trivially true in the current implementation; a clarifying comment would improve future maintainability.

Confidence Score: 4/5

  • Safe to merge — the fix is correct and behavioral changes are intentional; only minor style improvements remain.
  • The core logic is sound: extracting a canonical shared list eliminates the drift that caused release-runtime to be bundled without being compiled. All existing tests continue to pass via explicit packages: overrides, and the new regression test validates bundledDependencies alignment. The two style-level observations (missing post-build artifact check for release-runtime, and the tautological test assertion) do not represent correctness issues.
  • apps/cli/scripts/buildSharedDeps.mjs — consider adding a release-runtime dist artifact check to match the existing protocol sanity check after the compilation loop.

Important Files Changed

Filename Overview
apps/cli/scripts/bundledWorkspacePackages.mjs New canonical shared-list module — defines all four bundled workspace packages (agents, cli-common, protocol, release-runtime) and exports helper factory; clean, no issues.
apps/cli/scripts/buildSharedDeps.mjs Switches to the shared canonical list for tsc compilation loop and syncBundledWorkspaceDist defaults; release-runtime is now compiled in main() but lacks a post-build artifact sanity check (unlike protocol). Minor: bundledWorkspaceDirs is a trivial re-export, which makes the regression test assertion tautological in practice.
apps/cli/scripts/bundleWorkspaceDeps.mjs Replaces inline bundle-list literal with factory call from shared module; all four packages including release-runtime were already present before, behavior is unchanged.
apps/cli/scripts/prepack-script.test.mjs New regression test verifying bundledDependencies, build targets, and bundle targets stay aligned. The assert.deepEqual([...bundledWorkspaceDirs], [...bundledWorkspaceDirNames]) assertion is effectively checking a re-export against itself, making it tautological in the current implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[yarn prepack] --> B[yarn build]
    B --> C[prebuild: build:shared\nbuildSharedDeps.mjs main]
    C --> D{loop bundledWorkspaceDirs}
    D --> E[runTsc packages/agents]
    D --> F[runTsc packages/cli-common]
    D --> G[runTsc packages/protocol]
    D --> H[runTsc packages/release-runtime\n🆕 now included]
    E & F & G & H --> I[existsSync protocol/dist check]
    I --> J[syncBundledWorkspaceDist\nall 4 packages]
    J --> K[pkgroll CLI build]
    K --> L[node bundleWorkspaceDeps.mjs]
    L --> M[createBundledWorkspaceBundles\n🆕 from shared canonical list]
    M --> N[bundleWorkspacePackages\nagents, cli-common, protocol, release-runtime]
    N --> O[vendorBundledPackageRuntimeDependencies\nper bundle]
    O --> P[npm pack ready]

    subgraph bundledWorkspacePackages.mjs [bundledWorkspacePackages.mjs 🆕]
        Q[bundledWorkspacePackages array\nagents / cli-common / protocol / release-runtime]
        R[bundledWorkspaceDirNames]
        S[bundledWorkspacePackageNames]
        T[createBundledWorkspaceBundles factory]
        Q --> R & S & T
    end

    M --> bundledWorkspacePackages.mjs
    D --> bundledWorkspacePackages.mjs
Loading

Comments Outside Diff (1)

  1. apps/cli/scripts/buildSharedDeps.mjs, line 140-148 (link)

    No post-build artifact check for release-runtime

    main() now compiles all four packages in the bundledWorkspaceDirs loop, but the only artifact sanity check after the loop is for @happier-dev/protocol's dist/index.js. release-runtime is now equally critical to the prepack pipeline (it was previously missing from the build step entirely, which is exactly the bug being fixed here), yet a silent misconfiguration in its tsconfig.json (outDir pointing somewhere unexpected, or include matching nothing) could result in an empty dist being bundled without any diagnostic.

    Consider adding a parallel check:

    const releaseRuntimeDist = resolve(repoRoot, 'packages', 'release-runtime', 'dist', 'index.js');
    if (!existsSync(releaseRuntimeDist)) {
      throw new Error(`Expected @happier-dev/release-runtime build output missing: ${releaseRuntimeDist}`);
    }

    This is a non-blocking suggestion since runTsc already throws on non-zero exit codes — but the explicit check on protocol sets a precedent that is worth following for the newly added package.

Last reviewed commit: 3919fb0

: [];

assert.deepEqual(bundledDependencies, [...bundledWorkspacePackageNames].sort());
assert.deepEqual([...bundledWorkspaceDirs], [...bundledWorkspaceDirNames]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Tautological assertion — re-export compared to itself

bundledWorkspaceDirs in buildSharedDeps.mjs is defined as:

export const bundledWorkspaceDirs = bundledWorkspaceDirNames;

…a direct re-export of bundledWorkspaceDirNames. So when the test imports both and spreads them, it is comparing the same underlying frozen array to itself — deepEqual([...x], [...x]) — which will always pass regardless of what value x holds.

The intent of the assertion appears to be guarding against a future divergence where buildSharedDeps.mjs might override bundledWorkspaceDirs with a manually maintained subset. That guard is real and useful, but the assertion can only protect against that if the two symbols are independently defined. Consider adding a comment to the assertion explaining its forward-looking intent, or alternatively move the assertion's value closer to what it actually tests — that the main() loop in buildSharedDeps compiles every entry in the canonical list:

// Verify buildSharedDeps re-exports the canonical list without filtering.
// If buildSharedDeps.mjs ever defines bundledWorkspaceDirs independently,
// this assertion catches any divergence from the source of truth.
assert.deepEqual([...bundledWorkspaceDirs], [...bundledWorkspaceDirNames]);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
apps/cli/scripts/bundledWorkspacePackages.mjs (1)

3-16: Freeze the definition entries too.

Object.freeze([...]) only locks the array shell. The exported { dirName, packageName } objects are still mutable, so an accidental write would desync this canonical list from the already-derived bundledWorkspaceDirNames and bundledWorkspacePackageNames.

Optional hardening
 export const bundledWorkspacePackages = Object.freeze([
-  { dirName: 'agents', packageName: '@happier-dev/agents' },
-  { dirName: 'cli-common', packageName: '@happier-dev/cli-common' },
-  { dirName: 'protocol', packageName: '@happier-dev/protocol' },
-  { dirName: 'release-runtime', packageName: '@happier-dev/release-runtime' },
+  Object.freeze({ dirName: 'agents', packageName: '@happier-dev/agents' }),
+  Object.freeze({ dirName: 'cli-common', packageName: '@happier-dev/cli-common' }),
+  Object.freeze({ dirName: 'protocol', packageName: '@happier-dev/protocol' }),
+  Object.freeze({ dirName: 'release-runtime', packageName: '@happier-dev/release-runtime' }),
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/scripts/bundledWorkspacePackages.mjs` around lines 3 - 16, The
exported bundledWorkspacePackages array currently freezes only the array shell
but leaves each entry mutable; update the initializer for
bundledWorkspacePackages so each entry object is frozen as well (e.g., wrap each
{ dirName, packageName } with Object.freeze or map to Object.freeze for the
entries) so bundledWorkspacePackages contains immutable objects; keep
bundledWorkspaceDirNames and bundledWorkspacePackageNames as they are but ensure
they continue to derive from the now-immutable bundledWorkspacePackages.
apps/cli/scripts/buildSharedDeps.mjs (1)

141-143: Validate every bundled workspace emits dist/.

This loop now builds release-runtime, but the postcondition below still only checks packages/protocol/dist/index.js. If any other bundled workspace stops emitting dist/, syncBundledWorkspaceDist() just swallows the copy failure and leaves stale artifacts under apps/cli/node_modules.

Suggested hardening
 export function main() {
   for (const pkg of bundledWorkspaceDirs) {
     runTsc(resolve(repoRoot, 'packages', pkg, 'tsconfig.json'));
+    const distDir = resolve(repoRoot, 'packages', pkg, 'dist');
+    if (!existsSync(distDir)) {
+      throw new Error(`Expected `@happier-dev/`${pkg} build output missing: ${distDir}`);
+    }
   }
-
-  const protocolDist = resolve(repoRoot, 'packages', 'protocol', 'dist', 'index.js');
-  if (!existsSync(protocolDist)) {
-    throw new Error(`Expected `@happier-dev/protocol` build output missing: ${protocolDist}`);
-  }
 
   // If the CLI currently has bundled workspace deps under apps/cli/node_modules,
   // keep their dist outputs in sync so local builds/tests do not consume stale artifacts.
   syncBundledWorkspaceDist({ repoRoot });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/scripts/buildSharedDeps.mjs` around lines 141 - 143, The loop that
runs runTsc over bundledWorkspaceDirs can succeed without emitting a dist/, but
the later postcondition only checks packages/protocol/dist/index.js; update the
build verification to confirm each package produced a dist directory (e.g. check
resolve(repoRoot, 'packages', pkg, 'dist') exists and contains expected
artifacts such as index.js or package.json main) immediately after runTsc for
each pkg, and if missing throw or exit non‑zero so syncBundledWorkspaceDist
cannot continue silently; modify the logic around bundledWorkspaceDirs, runTsc
and syncBundledWorkspaceDist to surface copy failures instead of swallowing
them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/cli/scripts/buildSharedDeps.mjs`:
- Around line 141-143: The loop that runs runTsc over bundledWorkspaceDirs can
succeed without emitting a dist/, but the later postcondition only checks
packages/protocol/dist/index.js; update the build verification to confirm each
package produced a dist directory (e.g. check resolve(repoRoot, 'packages', pkg,
'dist') exists and contains expected artifacts such as index.js or package.json
main) immediately after runTsc for each pkg, and if missing throw or exit
non‑zero so syncBundledWorkspaceDist cannot continue silently; modify the logic
around bundledWorkspaceDirs, runTsc and syncBundledWorkspaceDist to surface copy
failures instead of swallowing them.

In `@apps/cli/scripts/bundledWorkspacePackages.mjs`:
- Around line 3-16: The exported bundledWorkspacePackages array currently
freezes only the array shell but leaves each entry mutable; update the
initializer for bundledWorkspacePackages so each entry object is frozen as well
(e.g., wrap each { dirName, packageName } with Object.freeze or map to
Object.freeze for the entries) so bundledWorkspacePackages contains immutable
objects; keep bundledWorkspaceDirNames and bundledWorkspacePackageNames as they
are but ensure they continue to derive from the now-immutable
bundledWorkspacePackages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c141c39-70e3-417c-840b-9749a8d06a96

📥 Commits

Reviewing files that changed from the base of the PR and between cd992ae and 3919fb0.

📒 Files selected for processing (4)
  • apps/cli/scripts/buildSharedDeps.mjs
  • apps/cli/scripts/bundleWorkspaceDeps.mjs
  • apps/cli/scripts/bundledWorkspacePackages.mjs
  • apps/cli/scripts/prepack-script.test.mjs

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 13, 2026
- Add JSDoc to bundledWorkspacePackages.mjs (file header + all exports/functions)
- Deep-freeze entry objects so mutations cannot desync derived exports
- Extend post-build artifact check to cover all bundled workspace packages
  (previously only checked packages/protocol/dist; now checks dist/ for each
  package immediately after runTsc so a misconfigured tsconfig is caught early)
@stoicneko stoicneko changed the title Fix CLI bundled workspace packaging for release-runtime fix(cli): bundled workspace packaging for release-runtime Mar 14, 2026
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