Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8de16d8
fix(check-parser): carry real member versions in faux package manifes…
sorccu Aug 22, 2026
f860523
feat(check-parser): bundle the workspace-root pnpmfile [RED-886]
sorccu Aug 22, 2026
cfcf324
feat(check-parser): add lockfile-only install commands for pnpm and n…
sorccu Aug 22, 2026
da07140
feat(check-parser): add a bundle lockfile pruner [RED-886]
sorccu Aug 22, 2026
5a07519
feat(check-parser): prune the bundled lockfile to match the bundle [R…
sorccu Aug 23, 2026
1e48f7c
docs(cli): document bundled lockfile pruning [RED-886]
sorccu Aug 23, 2026
30ad15e
fix(check-parser): harden lockfile pruning edge cases [RED-886]
sorccu Aug 23, 2026
bacc344
fix(cli): pin the pruned-lockfile write location and harden prune ski…
sorccu Aug 23, 2026
4c96d5b
refactor(cli): consolidate the pnpmfile checksum gate and document it…
sorccu Aug 23, 2026
34c41c3
chore(cli): surface the lockfile-prune opt-out in the unpruned note […
sorccu Aug 23, 2026
0ad95db
docs(cli): clarify pnpmfile gating and lockfile-prune skip reporting …
sorccu Aug 23, 2026
3f19eed
feat(cli): add content-based lockfile parsing and a tarball lockfile …
sorccu Aug 23, 2026
b43a41d
feat(cli): materialize embedded tarballs from the pruned lockfile at …
sorccu Aug 23, 2026
7272b24
test(cli): cover pruned-lockfile embedded filtering end to end [RED-886]
sorccu Aug 23, 2026
a224ed2
docs(cli): describe pruned-lockfile filtering of embedded packages [R…
sorccu Aug 23, 2026
87830fa
chore(cli): demote bundling progress lines to debug logs [RED-886]
sorccu Aug 23, 2026
7728eae
ci: provision bun for the unit test jobs [RED-886]
sorccu Aug 23, 2026
192e92a
feat(cli): prune bun text lockfiles when bundling [RED-886]
sorccu Aug 23, 2026
e6496d4
feat(cli): resolve embedded packages from bun lockfiles [RED-886]
sorccu Aug 23, 2026
e1b4f60
docs(cli): document bun lockfile support [RED-886]
sorccu Aug 23, 2026
bf69d64
fix(cli): report a missing package manager binary correctly on Window…
sorccu Aug 23, 2026
c4a36e8
test(cli): keep the env-stripping sentinel safe from case-variant col…
sorccu Aug 23, 2026
2344201
feat(cli): prune bundled yarn.lock for Yarn Berry [RED-886]
sorccu Aug 23, 2026
4af07b0
ci: provision Corepack yarn for the real-yarn pruner tests [RED-886]
sorccu Aug 23, 2026
4d4ca1d
fix(cli): make yarn prune below-floor timeout check deterministic [RE…
sorccu Aug 23, 2026
79ddb9a
feat(cli): embed packages from a Yarn Berry yarn.lock [RED-886]
sorccu Aug 23, 2026
09c4373
docs(cli): document Yarn Berry lockfile pruning and embed support [RE…
sorccu Aug 23, 2026
de965f2
test(cli): fix the Windows fake-yarn Classic-skip shim quoting [RED-886]
sorccu Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,54 @@ jobs:
- uses: pnpm/action-setup@v5
with:
version: 10
# The lockfile-pruner tests exercising a real bun install skip themselves
# when bun is not on PATH, so without this step they would silently never
# run in CI. The version is pinned because the tests regenerate a
# committed bun.lock fixture and a future bun may serialize it
# differently.
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.11'
- uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: "pnpm"
# Provisions a corepack-managed yarn for the real-yarn lockfile-pruner
# tests (the fixture's packageManager field pins the exact Yarn Berry
# version). Scoped to yarn on purpose: a bare `corepack enable` would
# also shim pnpm into the setup-node bin dir, which precedes PNPM_HOME
# in PATH and would silently replace the pinned pnpm for every later
# step (this repo has no root packageManager field for corepack to
# resolve).
- run: corepack enable yarn
# Downloads the fixture-pinned Yarn versions (Berry 4 and 3) now, so
# a registry or corepack failure surfaces here with an obvious
# message instead of inside the pruner's timed subprocess during the
# test run. The env var suppresses corepack's download notice/prompt
# as defense in depth (Actions has no TTY, so corepack would not
# prompt anyway).
- run: yarn --version
working-directory: packages/cli/src/services/check-parser/__tests__/lockfile-pruner-fixtures/yarn-workspace
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
- run: yarn --version
working-directory: packages/cli/src/services/check-parser/__tests__/lockfile-pruner-fixtures/yarn3-workspace
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
- run: pnpm install --frozen-lockfile
- run: pnpm run prepack
- run: pnpm run test
env:
# Makes the test suite assert that bun is actually on PATH, so a
# lost setup-bun step fails loudly instead of silently skipping
# the real-bun lockfile-pruner tests.
CHECKLY_EXPECT_BUN: '1'
# Same assertion for the corepack-managed Yarn Berry. The suite's
# probe checks the resolved major version from the fixture
# directory, so a preinstalled Classic yarn shadowing the corepack
# shim fails the assertion rather than silently skipping the
# real-yarn lockfile-pruner tests.
CHECKLY_EXPECT_YARN: '1'
- name: Save LLM rules as an artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -112,13 +153,34 @@ jobs:
- uses: pnpm/action-setup@v5
with:
version: 10
# See test-ubuntu: provisions bun for the real-bun lockfile-pruner tests,
# which otherwise skip themselves.
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.11'
- uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: "pnpm"
# See test-ubuntu: yarn-scoped so the corepack pnpm shim cannot shadow
# the pinned pnpm, and pre-downloaded so a corepack failure surfaces
# here instead of inside the timed pruner subprocess.
- run: corepack enable yarn
- run: yarn --version
working-directory: packages/cli/src/services/check-parser/__tests__/lockfile-pruner-fixtures/yarn-workspace
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
- run: yarn --version
working-directory: packages/cli/src/services/check-parser/__tests__/lockfile-pruner-fixtures/yarn3-workspace
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
- run: pnpm install --frozen-lockfile
- run: pnpm run prepack
- run: pnpm run test
env:
# See test-ubuntu.
CHECKLY_EXPECT_BUN: '1'
CHECKLY_EXPECT_YARN: '1'
- name: Save LLM rules as an artifact
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,5 @@ Source in `src/ai-context/`, built during `prepare`. Generates examples from fix
- `CHECKLY_ENV` — target environment (`production`, `staging`, `development`, `local`)
- `CHECKLY_API_URL` — override API base URL (used when `CHECKLY_ENV=local`)
- `CHECKLY_CLI_VERSION` — override reported CLI version (useful for testing `create-checkly`)
- `CHECKLY_CACHE_DIR` — override the CLI's cache directory (embedded-package tarball downloads)
- `CHECKLY_LOCKFILE_PRUNE` — set to `0` to disable pruning the bundled lockfile to the code bundle's contents
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
- For private packages or custom registries, `.npmrc` is bundled automatically — the workspace-root `.npmrc` and any `.npmrc` beside a package's `package.json` are included by default. You do not need to add `.npmrc` to `include`.
- The `.npmrc` should reference a Checkly environment variable such as `${NPM_TOKEN}`. Tell the user that the token must exist in Checkly before `deploy` or `trigger`. Because `.npmrc` is uploaded automatically, warn users to reference credentials via environment variables (`${NPM_TOKEN}`) rather than embedding plaintext tokens.
- Use `installCommand` only when the default package-manager install command is not enough.
- Checkly caches installed dependencies between runs, keyed off the lock file, `package.json` and `.npmrc` contents, and the resolved `bundle.packages.embed` tarball set. To force a reinstall declaratively, set `caching.dependencyCache.version` (a string or a safe integer) at the top level of `checkly.config.ts` (not per check — one code bundle serves all Playwright Check Suites) and change its value whenever the cache should be invalidated; scheduled checks pick up the change on the next `checkly deploy`. Unset or empty-string values leave the cache key unchanged, so a dynamic value such as `version: process.env.DEPENDENCY_CACHE_VERSION` is safe when the variable is not always set. For a one-off reinstall during an ad-hoc run, use the `--refresh-cache` flag available on the run/test commands (`checkly test`, `checkly pw-test`, `checkly trigger`, `checkly checks run`) instead; the config value is the persistent knob that also applies to deployed, scheduled checks.
- For pnpm projects, the workspace-root pnpmfile (`.pnpmfile.cjs` / `.pnpmfile.mjs`) is bundled automatically so the remote install reproduces the lockfile's recorded `pnpmfileChecksum` — but only when two conditions hold. First, the lockfile must record a `pnpmfileChecksum` (pnpm writes one when `pnpm install` last ran with the pnpmfile in place); without one there is nothing for the remote install to reproduce and no pnpmfile is bundled. Second, the pnpmfile must be self-contained: it may load only side-effect-free Node.js builtins (`assert`, `buffer`, `crypto`, `events`, `path`, `punycode`, `querystring`, `string_decoder`, `url`, `util`) via literal `require('...')`/`import` specifiers, and must not reference `process`, `__dirname`, `__filename`, `import.meta`, `globalThis`, `global`, `eval`, `Function`, `require.resolve` or dynamically computed module paths — the remote install loads the pnpmfile before any dependencies exist and in a different environment. A pnpmfile that does not satisfy this (e.g. loads `fs`, local helper files or npm packages) is skipped with a warning and the remote install may re-resolve dependencies instead of using the lockfile; make the pnpmfile self-contained to avoid this. Since the pnpmfile is uploaded, avoid embedding secrets in it.
- In a workspace (monorepo) whose code bundle covers only part of the workspace, the bundled lockfile is pruned automatically: the CLI regenerates it (via `pnpm install --lockfile-only` / `npm install --package-lock-only` / `bun install --lockfile-only` / `yarn install --mode=update-lockfile` in a temp dir) so it only references the packages actually in the bundle — otherwise the remote install would try to fetch dependencies of workspace members that were omitted or shipped as dependency-free placeholder manifests, which fails outright for private packages. Supported for `pnpm-lock.yaml` versions 6/9, `package-lock.json` versions 2/3, the text `bun.lock` version 1 and Yarn Berry `yarn.lock` files (for bun projects, keep registry configuration in `.npmrc`, which bun reads: `bunfig.toml` is not carried into the regeneration — recorded resolutions keep their URLs, but whenever bun declines to reuse the lockfile — it is out of date with a manifest, or a workspace member's name collides with a registry dependency — bun re-resolves those entries against the wrong registry, disclosing the package names to it (typically the public registry), and pruning rejects the result with a warning; for yarn projects, `.yarnrc.yml` is likewise not carried into the regeneration, which is safe because Berry lockfiles are registry-agnostic and the regeneration reuses recorded resolutions without the network — settings like `approvedGitRepositories` and `npmScopes` only affect new resolutions, which pruning never performs — and the regeneration runs with yarn's network access disabled outright, since it never needs it: a lockfile that is out of date with a manifest then fails fast with a warning instead of resolving the missing package against the wrong registry and disclosing its name; yarn's hardened mode is disabled for the same reason; `yarn patch` files under `.yarn/patches` are bundled automatically because the regeneration reads them; a `yarn` binary that resolves to Yarn Classic on a Berry project is refused before it can run, because Classic would silently perform a full install); when a bundled lockfile over-describes a partial-workspace bundle but pruning cannot run — other lockfile formats, Yarn Classic v1 lockfiles, a `yarn` binary that resolves to Yarn Classic on a Berry project (set the `packageManager` field so Corepack provisions Yarn 2+), bun's binary `bun.lockb` (regenerate a text lockfile with `bun install --save-text-lockfile`), the package manager binary not being installed on the machine running the CLI, `excludeLinksFromLockfile`, a recorded pnpmfile checksum without a bundled pnpmfile, a workspace member whose version cannot be determined, among others — the original lockfile ships unchanged and the CLI prints a note saying so. Other skips are silent: nothing to prune (the bundle contains the full workspace, or regeneration produced identical bytes), no bundled lockfile to prune, or pruning disabled via `CHECKLY_LOCKFILE_PRUNE=0`; silent skip reasons are visible via `DEBUG='checkly:cli:services:check-parser:*'`. When pruning runs but cannot produce a provably pruned copy of the original — the lockfile is out of date with a `package.json`, the package manager could not run or timed out, the lockfile could not be read or written, or verification failed, among others — the original ships unchanged with a warning. Set `CHECKLY_LOCKFILE_PRUNE=0` to disable pruning.
- Checkly caches installed dependencies between runs, keyed off the workspace's lock file, every workspace member's `package.json` and `.npmrc` (whether or not the member is in the bundle), bundled pnpmfile contents, and the resolved `bundle.packages.embed` tarball set (filtered to what the pruned lockfile still references when pruning applied) — plus, as additional inputs, any synthesized placeholder manifests shipped in the bundle and the pruned lockfile when pruning applied. Because the bundle-specific inputs follow the bundle, the key can change without a file edit — e.g. when a different set of workspace members ends up in the bundle. To force a reinstall declaratively, set `caching.dependencyCache.version` (a string or a safe integer) at the top level of `checkly.config.ts` (not per check — one code bundle serves all Playwright Check Suites) and change its value whenever the cache should be invalidated; scheduled checks pick up the change on the next `checkly deploy`. Unset or empty-string values leave the cache key unchanged, so a dynamic value such as `version: process.env.DEPENDENCY_CACHE_VERSION` is safe when the variable is not always set. For a one-off reinstall during an ad-hoc run, use the `--refresh-cache` flag available on the run/test commands (`checkly test`, `checkly pw-test`, `checkly trigger`, `checkly checks run`) instead; the config value is the persistent knob that also applies to deployed, scheduled checks.
- In Checkly CLI v8.0.0 and later, `include` patterns resolve relative to the Playwright config directory, not the project root. If `playwrightConfigPath` points to a subdirectory, adjust `include` globs. Example: `playwrightConfigPath: "./e2e/playwright.config.ts"` with a root fixture at `fixtures/data.json` needs `include: ["../fixtures/data.json"]`.
- If dependencies come from a private registry that Checkly's infrastructure cannot reach (for example an intranet-only Nexus mirror), list them in `bundle.packages.embed` in `checkly.config.ts` — a top-level section: `bundle: { packages: { embed: ['@acme/private-utils', 'legacy-private-pkg@2.1.0'] } }`. Each entry is a package name (embeds every version found in the lockfile) or an exact `name@version` pin; names may contain `*` wildcards (`@acme/*`, `acme-*`, `@acme/*-utils`) where each `*` matches any run of characters except `/` (never crossing the scope separator); as long as a spec matches at least one registry package, matches that cannot be embedded are skipped (workspace members silently, git/file/URL dependencies and integrity-less entries with a warning since the runner must fetch those itself), while a spec whose only matches cannot be embedded — or that matches nothing at all — is an error; a pattern embeds every lockfile version of every package it matches, so scope it to the packages the runner genuinely cannot fetch. List every unreachable package by name, including private packages that only appear as transitive dependencies of other private packages — dependencies of listed packages are not embedded automatically. The CLI resolves entries against the workspace-root lockfile (`pnpm-lock.yaml` or `package-lock.json`), reuses tarballs from local caches (its own, then npm's) or downloads them from the registry configured in `.npmrc`, verifies each against the lockfile's integrity hash, and ships them inside the code bundle at `.checkly/embedded-packages/*.tgz`, where the runner serves them through a local registry during install. Downloads are cached under the workspace root's `node_modules/.cache/checkly` (in a monorepo that is the repo root, not the member package; override with `CHECKLY_CACHE_DIR`; a per-user cache dir is the fallback when the project location isn't writable), so nothing lands in the project outside `node_modules`. CI setups that cache `node_modules` — or platforms that preserve `node_modules/.cache` — persist the tarballs automatically; otherwise persist `CHECKLY_CACHE_DIR` in CI to avoid re-downloading (note `npm ci` deletes `node_modules` wholesale, unlike incremental pnpm installs). The machine running `checkly deploy`/`test` needs registry access on a cold cache. Changing the resolved set of embedded packages invalidates the runner's dependency cache, so the next run reinstalls with the new tarballs. Applies to Playwright Check Suites only, not browser or multistep checks.
- If dependencies come from a private registry that Checkly's infrastructure cannot reach (for example an intranet-only Nexus mirror), list them in `bundle.packages.embed` in `checkly.config.ts` — a top-level section: `bundle: { packages: { embed: ['@acme/private-utils', 'legacy-private-pkg@2.1.0'] } }`. Each entry is a package name (embeds every version found in the lockfile) or an exact `name@version` pin; names may contain `*` wildcards (`@acme/*`, `acme-*`, `@acme/*-utils`) where each `*` matches any run of characters except `/` (never crossing the scope separator); as long as a spec matches at least one registry package, matches that cannot be embedded are skipped (workspace members silently, git/file/URL dependencies and integrity-less entries with a warning since the runner must fetch those itself), while a spec whose only matches cannot be embedded — or that matches nothing at all — is an error; a pattern embeds every lockfile version of every package it matches, so scope it to the packages the runner genuinely cannot fetch. List every unreachable package by name, including private packages that only appear as transitive dependencies of other private packages — dependencies of listed packages are not embedded automatically. The CLI resolves entries against the workspace-root lockfile (`pnpm-lock.yaml`, `package-lock.json`, the text `bun.lock` or a Yarn Berry `yarn.lock` — Yarn Classic v1 lockfiles are not supported), reuses tarballs from local caches (its own, then npm's) or downloads them from the registry configured in `.npmrc` (only `.npmrc` — bun or yarn users whose registry credentials live solely in `bunfig.toml` or `.yarnrc.yml` must duplicate them into `.npmrc`, or downloads fail with an auth error), verifies each against the lockfile's integrity hash, and ships them inside the code bundle at `.checkly/embedded-packages/*.tgz`, where the runner serves them through a local registry during install. Yarn Berry lockfiles record no npm tarball integrity (Berry checksums cover yarn's own cache format), so the CLI resolves the tarball integrity from the registry's package metadata instead — one small metadata request per embedded package on every deploy (the per-version route, falling back to the full packument), even when the tarballs themselves come from a warm cache, so a yarn embed needs registry reachability at deploy time even on a warm cache. When the bundled lockfile is pruned to the code bundle's contents (see the pruning bullet above), the embedded set follows it: packages the pruned lockfile no longer references — dependencies of workspace members that are not part of the bundle — are neither embedded nor downloaded, even if an entry matches them. If a package unexpectedly stops being embedded, the usual cause is that only a workspace member outside the bundle depends on it, in which case the runner never installs it and nothing is wrong; if the checks genuinely need it, make the depending member part of the bundle (import it from check code) rather than disabling pruning — `CHECKLY_LOCKFILE_PRUNE=0` restores the unfiltered set but reintroduces the over-describing lockfile that pruning exists to prevent, so treat it as a last resort. Downloads are cached under the workspace root's `node_modules/.cache/checkly` (in a monorepo that is the repo root, not the member package; override with `CHECKLY_CACHE_DIR`; a per-user cache dir is the fallback when the project location isn't writable), so nothing lands in the project outside `node_modules`. CI setups that cache `node_modules` — or platforms that preserve `node_modules/.cache` — persist the tarballs automatically; otherwise persist `CHECKLY_CACHE_DIR` in CI to avoid re-downloading (note `npm ci` deletes `node_modules` wholesale, unlike incremental pnpm installs). The machine running `checkly deploy`/`test` needs registry access on a cold cache — but only for the tarballs actually shipped, not for pruned-away ones. Changing the resolved set of embedded packages invalidates the runner's dependency cache, so the next run reinstalls with the new tarballs. Applies to Playwright Check Suites only, not browser or multistep checks.

## Install troubleshooting

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/debug/parse-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export default class ParseProjectCommand extends Command {
const bundler = await Bundler.createForWorkspace(Session.workspace.unwrap(), {
dependencyCacheVersion: checklyConfig.caching?.dependencyCache?.version,
embeddedPackagesMaterializer: Session.getEmbeddedPackagesMaterializer(),
packageManager: Session.packageManager,
})

const bundleStartedAt = performance.now()
Expand Down
Loading
Loading