Skip to content

fix(cli): build release-runtime before packaging#137

Open
w-gitops wants to merge 2 commits intohappier-dev:devfrom
w-gitops:fix/cli-release-runtime-build
Open

fix(cli): build release-runtime before packaging#137
w-gitops wants to merge 2 commits intohappier-dev:devfrom
w-gitops:fix/cli-release-runtime-build

Conversation

@w-gitops
Copy link

@w-gitops w-gitops commented Mar 22, 2026

Summary

  • Add workspaceBundleManifest.mjs as single source of truth for the bundled workspace package list
  • Include release-runtime in the shared workspace build list used by apps/cli
  • Verify build output for every bundled workspace package before packaging
  • Add a low-dependency contract test that guards the default bundled package sync list

Why

@happier-dev/cli imports @happier-dev/release-runtime at runtime and declares it as a bundled dependency, but apps/cli/scripts/buildSharedDeps.mjs only built agents, cli-common, and protocol.

That made CLI packaging depend on pre-existing local packages/release-runtime/dist state. In practice this can make npm release artifacts nondeterministic and matches the ERR_MODULE_NOT_FOUND failure reported in #103.

Testing

  • node --test apps/cli/scripts/buildSharedDeps.contract.test.mjs
  • node --test apps/cli/scripts/prepack-script.test.mjs

Refs #103

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests for the CLI shared-dependencies build pipeline, validating deterministic bundle generation, build-entry resolution, and dist sync behavior.
  • Refactor

    • Centralized workspace package list for consistent bundling (now includes four packages).
    • Improved build-entry resolution to prefer package metadata with a deterministic fallback and updated dist-sync defaulting to the centralized package list.

- Add workspaceBundleManifest.mjs as single source of truth for bundled packages
- Include release-runtime in the shared workspace build list used by apps/cli
- Verify build output for every bundled workspace package before packaging
- Add contract test that guards the default bundled package sync list

@happier-dev/cli imports @happier-dev/release-runtime at runtime and declares
it as a bundled dependency, but buildSharedDeps.mjs only built agents,
cli-common, and protocol. This made CLI packaging depend on pre-existing local
packages/release-runtime/dist state, causing nondeterministic npm release
artifacts and ERR_MODULE_NOT_FOUND failures (refs happier-dev#103).

Refs happier-dev#127
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Walkthrough

Centralized the CLI workspace bundle configuration into a new manifest module and updated build/bundle scripts to consume it. Added a contract test verifying the manifest, bundle descriptor generation, build-entry resolution (package.json main fallback), and dist sync behavior.

Changes

Cohort / File(s) Summary
Workspace bundle manifest
apps/cli/scripts/workspaceBundleManifest.mjs
New module exporting bundledWorkspacePackages (agents, cli-common, protocol, release-runtime) and createBundledWorkspaceBundles({ repoRoot, targetRoot }) that produces bundle descriptors with resolved srcDir/destDir.
Build shared deps
apps/cli/scripts/buildSharedDeps.mjs
Import/re-export bundledWorkspacePackages; add resolveBundledWorkspaceBuildEntry(pkg, opts = {}) which reads package package.json.main or falls back to packages/<pkg>/dist/index.js; iterate bundledWorkspacePackages for TypeScript build and per-package existence checks; use bundledWorkspacePackages as default in syncBundledWorkspaceDist.
Bundle workspace deps
apps/cli/scripts/bundleWorkspaceDeps.mjs
Replace inline construction of bundles with createBundledWorkspaceBundles({ repoRoot, targetRoot }) and pass resulting bundles into existing bundling/vendoring steps.
Contract tests
apps/cli/scripts/buildSharedDeps.contract.test.mjs
New test suite asserting manifest package list, deterministic createBundledWorkspaceBundles output, resolveBundledWorkspaceBuildEntry behavior with and without main in package.json, and syncBundledWorkspaceDist copy + destination package.json update using stubbed FS/process functions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title accurately summarizes the main change: ensuring release-runtime is built before CLI packaging, addressing a critical build dependency issue.
Description check ✅ Passed The description covers all required template sections with clear summaries, motivations, testing steps, and issue references; all critical information is present.
Linked Issues check ✅ Passed The PR description clearly references issue #103 which describes the ERR_MODULE_NOT_FOUND failure being fixed.
Out of Scope Changes check ✅ Passed All changes are focused on the build pipeline refactoring; no unrelated modifications or scope creep detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 suggest fixes for GitHub Check annotations.

Configure the reviews.tools.github-checks setting to adjust the time to wait for GitHub Checks to complete.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR fixes a build-time ordering bug where @happier-dev/release-runtime was imported by the CLI at runtime but never compiled before packaging, causing non-deterministic release artifacts and the ERR_MODULE_NOT_FOUND failure from #103. The fix introduces workspaceBundleManifest.mjs as a single source of truth for the bundled workspace package list and wires it into both the shared-dep build script and the bundle-workspace-deps script, eliminating the prior divergence. A per-package build-output verification step and a set of hermetic contract tests are added to guard against future sync drift.

  • workspaceBundleManifest.mjs (new): single authoritative list (agents, cli-common, protocol, release-runtime) plus a factory for bundle metadata
  • buildSharedDeps.mjs: switches default package list to the manifest, adds resolveBundledWorkspaceBuildEntry to resolve each package's main entry for post-build verification, and generalises the main() build loop to cover all four packages
  • bundleWorkspaceDeps.mjs: replaces the previously hard-coded bundle array with createBundledWorkspaceBundles from the manifest
  • buildSharedDeps.contract.test.mjs (new): contract tests covering the manifest list, bundle metadata derivation, build-entry resolution, and syncBundledWorkspaceDist default inclusion of release-runtime

Confidence Score: 5/5

  • Safe to merge — the fix is targeted, well-tested, and carries no risk of regression beyond the style nit flagged.
  • The change is mechanically straightforward: it extracts a hard-coded list into a manifest file and adds release-runtime to it, then verifies build outputs with a new function. All existing behaviour for agents, cli-common, and protocol is preserved. The new resolveBundledWorkspaceBuildEntry function correctly handles ./-prefixed main paths via path.resolve semantics (confirmed by the test assertions and cross-checked against the real packages/release-runtime/package.json). The contract tests are hermetic and cover the principal code paths. The only open item is a minor redundant import + re-export pair (P2 style).
  • No files require special attention.

Important Files Changed

Filename Overview
apps/cli/scripts/workspaceBundleManifest.mjs New single source of truth for the bundled workspace package list; clean, minimal file with no issues.
apps/cli/scripts/buildSharedDeps.mjs Adds resolveBundledWorkspaceBuildEntry for per-package build verification and switches default package list to the new manifest; has a minor redundant import+re-export pair (P2).
apps/cli/scripts/bundleWorkspaceDeps.mjs Removes hard-coded bundle list and delegates to createBundledWorkspaceBundles; straightforward cleanup with no issues.
apps/cli/scripts/buildSharedDeps.contract.test.mjs Focused contract tests covering the manifest list, bundle metadata derivation, main-entry resolution logic, and syncBundledWorkspaceDist default inclusion of release-runtime; well-scoped with injected I/O for hermeticity.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[buildSharedDeps.main] --> B[workspaceBundleManifest.mjs\nbundledWorkspacePackages]
    B --> C{for each pkg\nin bundledWorkspacePackages}
    C --> D[runTsc packages/pkg/tsconfig.json]
    D --> E{for each pkg\nverify build output}
    E --> F[resolveBundledWorkspaceBuildEntry\nreads pkg/package.json main field]
    F --> G{dist entry\nexistsSync?}
    G -- No --> H[throw Error\nbuild output missing]
    G -- Yes --> I[syncBundledWorkspaceDist\ncopy dist + sanitize package.json]

    J[bundleWorkspaceDeps.mjs] --> B
    B --> K[createBundledWorkspaceBundles\nrepoRoot + targetRoot]
    K --> L[bundleWorkspacePackages\nvendorBundledPackageRuntimeDependencies]
Loading

Last reviewed commit: "fix(cli): build rele..."

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
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/workspaceBundleManifest.mjs (1)

3-3: Consider freezing the exported package list to prevent accidental mutation.

Since this is the canonical source of truth, making it immutable avoids subtle cross-script side effects.

Suggested tweak
-export const bundledWorkspacePackages = ['agents', 'cli-common', 'protocol', 'release-runtime'];
+export const bundledWorkspacePackages = Object.freeze(['agents', 'cli-common', 'protocol', 'release-runtime']);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/scripts/workspaceBundleManifest.mjs` at line 3, The exported array
bundledWorkspacePackages should be made immutable to prevent accidental runtime
mutation; update the export of bundledWorkspacePackages so the array is frozen
(use Object.freeze on the array value or create a frozen copy) ensuring any
attempts to push/pop will fail, and keep the export name
bundledWorkspacePackages so other modules consume the same frozen list.
apps/cli/scripts/buildSharedDeps.mjs (1)

109-121: Add validation to resolveBundledWorkspaceBuildEntry to guard against non-bundled main paths.

The function currently accepts package.json#main without validating it remains within dist/. While all packages correctly point to dist/ entries today, a misconfigured main (e.g., ../src/index.js, ./lib/index.js) would resolve successfully during the existence check but fail once packaged—since bundling only ships the dist/ directory.

Add a guard to reject or warn when main resolves outside dist/, preventing silent runtime failures in published artifacts.

🤖 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 109 - 121, In
resolveBundledWorkspaceBuildEntry, validate packageJson.main so it cannot point
outside the package's dist/ folder: after computing mainEntry from
packageJson.main, resolve it to an absolute path (based on
repoRoot/packages/pkg) and check that the resolved path has a path segment
starting with the package's dist directory (e.g., ends with or contains
"/packages/<pkg>/dist" or is a child of that dist directory); if the check
fails, either fallback to the default dist entry (resolve(repoRoot, 'packages',
pkg, 'dist', 'index.js')) and log or throw a clear error/warning via the
existing logging mechanism — ensure this logic is applied before returning the
resolved path so misconfigured mains like "../src/index.js" or "./lib/index.js"
are rejected.
🤖 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 109-121: In resolveBundledWorkspaceBuildEntry, validate
packageJson.main so it cannot point outside the package's dist/ folder: after
computing mainEntry from packageJson.main, resolve it to an absolute path (based
on repoRoot/packages/pkg) and check that the resolved path has a path segment
starting with the package's dist directory (e.g., ends with or contains
"/packages/<pkg>/dist" or is a child of that dist directory); if the check
fails, either fallback to the default dist entry (resolve(repoRoot, 'packages',
pkg, 'dist', 'index.js')) and log or throw a clear error/warning via the
existing logging mechanism — ensure this logic is applied before returning the
resolved path so misconfigured mains like "../src/index.js" or "./lib/index.js"
are rejected.

In `@apps/cli/scripts/workspaceBundleManifest.mjs`:
- Line 3: The exported array bundledWorkspacePackages should be made immutable
to prevent accidental runtime mutation; update the export of
bundledWorkspacePackages so the array is frozen (use Object.freeze on the array
value or create a frozen copy) ensuring any attempts to push/pop will fail, and
keep the export name bundledWorkspacePackages so other modules consume the same
frozen list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57a721ca-f914-4d7d-a14d-5351d2a26288

📥 Commits

Reviewing files that changed from the base of the PR and between cd992ae and 6580efc.

📒 Files selected for processing (4)
  • apps/cli/scripts/buildSharedDeps.contract.test.mjs
  • apps/cli/scripts/buildSharedDeps.mjs
  • apps/cli/scripts/bundleWorkspaceDeps.mjs
  • apps/cli/scripts/workspaceBundleManifest.mjs

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 (1)
apps/cli/scripts/buildSharedDeps.mjs (1)

108-121: LGTM!

The function correctly resolves the build entry with appropriate fallback behavior. The dependency injection for readFileSync and repoRoot makes it testable.

Optional nit: Line 116 validates that main is non-empty after trimming but uses the untrimmed value. For extra robustness, consider using the trimmed value:

-    const mainEntry = typeof packageJson?.main === 'string' && packageJson.main.trim() ? packageJson.main : './dist/index.js';
+    const trimmedMain = typeof packageJson?.main === 'string' ? packageJson.main.trim() : '';
+    const mainEntry = trimmedMain || './dist/index.js';
🤖 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 108 - 121, In
resolveBundledWorkspaceBuildEntry, capture and use the trimmed packageJson.main
instead of reusing the untrimmed value: compute a trimmedMain (e.g., const
trimmedMain = typeof packageJson?.main === 'string' ? packageJson.main.trim() :
'') and then use trimmedMain for the truthy check and as the mainEntry value
(falling back to './dist/index.js' when trimmedMain is empty); this ensures the
returned path uses the sanitized main string.
🤖 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 108-121: In resolveBundledWorkspaceBuildEntry, capture and use the
trimmed packageJson.main instead of reusing the untrimmed value: compute a
trimmedMain (e.g., const trimmedMain = typeof packageJson?.main === 'string' ?
packageJson.main.trim() : '') and then use trimmedMain for the truthy check and
as the mainEntry value (falling back to './dist/index.js' when trimmedMain is
empty); this ensures the returned path uses the sanitized main string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20cc027a-f107-42ac-b698-5a63a406cc6d

📥 Commits

Reviewing files that changed from the base of the PR and between 6580efc and c9558d1.

📒 Files selected for processing (1)
  • apps/cli/scripts/buildSharedDeps.mjs

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