← reports

#2546 · bb plugin new scaffolds a plugin that cannot import @get-bb/plugin-sdk/testing: cron-parser is undeclared

Bug Priority: Medium Effort: Low cli plugins open on GitHub 2026-08-27 · base ad79bbb5ec909524f8f281e62d860c588a86f332

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

A new plugin cannot load the backend test harness after a normal development install.

The harness imports cron-parser, but the scaffold does not declare that package.

The SDK marks the package as an optional peer, so npm does not install it for the consumer.

The test file fails during module load, before Vitest can register its first test.

2. Claims vs findings

Claim from the issueStatusEvidence
A fresh scaffold cannot import @get-bb/plugin-sdk/testing. Verified The base run failed with the reported Cannot find package 'cron-parser' error. See base-vitest.log.
The backend harness imports better-sqlite3, cron-parser, hono, and zod. Verified The imports occur on lines 4 through 7 of fake-plugin-host.ts.
The SDK declares all four packages as optional peers. Verified The SDK manifest lists each peer and gives each peer optional: true.
The scaffold declares three packages but omits cron-parser. Verified The scaffold has better-sqlite3 and hono in development dependencies. It has zod in dependencies.
A stale lockfile causes the fault. Refuted A new scaffold and a new npm install produced the fault. The test passed after one cron-parser install.
The frontend harness does not have this missing-peer fault. Verified The frontend entry imports React and Testing Library. The author guide tells users to install React, React DOM, Testing Library, and jsdom. See frontend harness imports and the author guide.

3. Environment

4. Minimal reproduction

The published SDK version was unavailable on 2026-08-27. The source check therefore used a local archive from the same base commit.

  1. Prepare and build the base checkout.
    git checkout ad79bbb5ec909524f8f281e62d860c588a86f332
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build
  2. Create the scaffold and make a local SDK archive.
    REPO="$(pwd)"
    mkdir -p "$REPO/.repro-2546"
    NODE_ENV=development node "$REPO/packages/scripts/dist/commands/run-cli.js" plugin new probe
    npm pack "$REPO/packages/plugin-sdk" --pack-destination "$REPO/.repro-2546"

    The CLI still creates the scaffold when its automatic npm install cannot find the unpublished SDK.

  3. Point the scaffold at that archive and install its development packages.
    cd "$REPO/bb-plugin-probe"
    npm pkg set 'devDependencies.@get-bb/plugin-sdk=file:'"$REPO"'/.repro-2546/get-bb-plugin-sdk-0.4.24.tgz'
    npm install --include=dev
    npm install --save-dev vitest
  4. Add this test as server.test.ts.
    import { it } from "vitest";
    import { createFakePluginHost } from "@get-bb/plugin-sdk/testing";
    import plugin from "./server";
    
    it("loads", async () => {
      const { bb } = createFakePluginHost({ pluginId: "probe" });
      await plugin(bb);
    });

    Saved test: server.test.ts.

  5. Add a local Vitest configuration. This prevents Vitest from loading the bb monorepo configuration above the scaffold.
    cat > vitest.config.ts <<'TS'
    import { defineConfig } from "vitest/config";
    
    export default defineConfig({});
    TS

    Saved configuration: vitest.config.ts.

  6. Run the test.
    npx vitest run --config vitest.config.ts

    Expected:

    Test Files  1 passed (1)
    Tests       1 passed (1)

    Actual:

     RUN  v4.1.11 /var/tmp/bb-2546-revise2-xYrQCz/bb-plugin-probe
    
     ❯ server.test.ts (0 test)
    
    ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
    
     FAIL  server.test.ts [ server.test.ts ]
    Error: Cannot find package 'cron-parser' imported from /var/tmp/bb-2546-revise2-xYrQCz/bb-plugin-probe/node_modules/@get-bb/plugin-sdk/dist/testing/index.js
    Did you mean to import "cron-parser/dist/index.js"?
    ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
    
     Test Files  1 failed (1)
          Tests  no tests
       Start at  17:47:08
       Duration  100ms (transform 11ms, setup 0ms, import 0ms, tests 0ms, environment 0ms)
    
    vitest_exit=1

    Full output: base-vitest.log.

  7. Install the missing peer and run the same test again.
    npm install --save-dev cron-parser@^5.5.0
    npx vitest run --config vitest.config.ts

    Actual:

     RUN  v4.1.11 /var/tmp/bb-2546-revise2-xYrQCz/bb-plugin-probe
    
     Test Files  1 passed (1)
          Tests  1 passed (1)
       Start at  17:47:14
       Duration  155ms (transform 21ms, setup 0ms, import 76ms, tests 3ms, environment 0ms)
    
    vitest_exit=0

    Full output: base-after-cron-parser.log.

5. Root cause

The backend harness uses four bare package imports at module scope.

import Database from "better-sqlite3";
import { CronExpressionParser } from "cron-parser";
import { Hono } from "hono";
import { z } from "zod";

Source: fake-plugin-host.ts lines 4–7.

The SDK leaves these packages for the consumer and marks each package as optional.

"peerDependencies": {
  "better-sqlite3": ">=12",
  "cron-parser": "^5.5.0",
  "hono": "^4.11.9",
  "zod": "^4.3.6"
},
"peerDependenciesMeta": {
  "better-sqlite3": { "optional": true },
  "cron-parser": { "optional": true },
  "hono": { "optional": true },
  "zod": { "optional": true }
}

Source: package.json lines 151–188.

The scaffold has no cron-parser entry.

dependencies: {
  ...PLUGIN_STARTER_DEPENDENCIES,
  zod: "^4.3.6",
},
devDependencies: {
  "@get-bb/plugin-sdk": PLUGIN_SDK_VERSION,
  "better-sqlite3": "^12.0.0",
  hono: "^4.11.9",
  typescript: "^5.7.0"
}

Source: plugin-scaffold.ts lines 1892–1925.

npm omits an optional peer unless the consumer declares it.

Node then reaches the unresolved import during module evaluation.

The test body never starts, which explains the zero-test result.

The deeper fault is contract drift between the harness imports and the scaffold manifest.

6. Proposed fix (first principles)

Add cron-parser to the scaffold development dependencies at the SDK peer range.

Add a contract test that checks every optional backend-harness peer against the generated manifest.

The test must read peerDependenciesMeta. It must not treat a required peer as optional.

An end-to-end test should pack the local SDK, install a new scaffold, and import the backend harness.

This change does not alter the server and daemon wire contract. It does not need a protocol version change.

7. PR review

PR #2550 · Unblock first-plugin authoring

The PR adds the correct cron-parser declaration for this issue.

The same test passed after that package was present, so the change addresses the root cause.

The PR also changes the thread-list guide and npm error output for issues #2547 and #2548.

Medium · npm stdout can hide the real stderr reason

npmFailureDetail joins stderr before stdout, then keeps only the last eight lines.

Twelve stdout lines therefore removed the complete stderr error from the warning.

The hostile test failed the PR assertion and showed only progress lines 5 through 12.

Evidence: pr-2550-mixed-output-test.log.

Location: apps/cli/src/commands/plugin.ts:496-507.

Fix: prefer stderr, or keep a separate tail from each stream. Add a mixed-stream test.

Low · the optional-peer test reads all peers

sdkOptionalPeers returns every key from peerDependencies.

It does not inspect peerDependenciesMeta[name].optional.

A future required peer would create a false failure because npm installs required peers.

Location: apps/cli/src/__tests__/plugin-scaffold-dependencies.test.ts:102-108.

Low · the scaffold repeats the harness comment

The same five-line comment appears before cron-parser and before hono.

Location: packages/templates/src/plugin-scaffold.ts:1915-1925.

Tests: The two CLI files passed 39 tests. The two server files passed 16 tests.

Turbo type checks passed for @bb/cli, @bb/server, and @bb/templates.

Verdict: REQUEST CHANGES.

The #2546 fix is correct. The broader PR needs the mixed-stream error fix before merge.

8. Related issues

9. Appendix

Reproduction evidence

PR evidence

Commands run

gh issue view 2546 --comments --json ...
gh pr view 2550 --comments --json ...
gh pr diff 2550
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
npm pack packages/plugin-sdk
npm install --include=dev
npm install --save-dev vitest
cp /tmp/bb-reports/issues/2546/repro/vitest.config.ts vitest.config.ts
npx vitest run --config vitest.config.ts
npm install --save-dev cron-parser@^5.5.0
npx vitest run --config vitest.config.ts
gh pr checkout 2550 --detach
pnpm exec turbo run test --filter=@bb/cli --force -- ...
pnpm exec turbo run test --filter=@bb/server --force -- ...
pnpm exec turbo run typecheck --filter=@bb/cli --filter=@bb/server --filter=@bb/templates --force
git fetch origin main
git log ad79bbb5ec90..origin/main -- packages/templates/src/plugin-scaffold.ts

No later origin/main commit changed the relevant source path.

Verification

The verifier ran the original literal steps and reached a monorepo Vitest configuration error.

With the saved local configuration, the verifier saw the reported cron-parser failure and the passing direct-install result.

This revision adds that configuration step and links its saved file.

It also corrects the frontend claim and expands the scaffold evidence to include zod.