diff --git a/constructs/playwright-check.mdx b/constructs/playwright-check.mdx
index 332ccc78..be29b8d6 100644
--- a/constructs/playwright-check.mdx
+++ b/constructs/playwright-check.mdx
@@ -44,7 +44,7 @@ import { Engine, PlaywrightCheck } from "checkly/constructs"
new PlaywrightCheck("critical-e2e-monitor", {
name: "Critical E2E Monitor",
description: "Runs the **critical path** e2e suite against production.",
- include: [".npmrc", "test-files/**"],
+ include: ["test-files/**"],
playwrightConfigPath: "./playwright.config.ts",
engine: Engine.node("24"), // Optional: override the auto-detected engine.
installCommand: "npm ci",
@@ -227,7 +227,7 @@ Defines the glob patterns for files and directories that should be included when
new PlaywrightCheck("critical-e2e-monitor", {
name: "Critical E2E Monitor",
playwrightConfigPath: "../playwright.config.ts",
- include: [".npmrc", "test-files/**"],
+ include: ["test-files/**"],
})
```
diff --git a/constructs/project.mdx b/constructs/project.mdx
index 121230b7..f877ccb6 100644
--- a/constructs/project.mdx
+++ b/constructs/project.mdx
@@ -74,6 +74,8 @@ export default defineConfig({
| `repoUrl` | `string` | ❌ | - | Optional URL to a Git repository |
| `checks` | `object` | ❌ | - | Top-level defaults for all checks in this project |
| `cli` | `object` | ❌ | - | Defaults for CLI commands |
+| `bundle` | `object` | ❌ | - | Dependency embedding and pruning for Playwright Check Suite code bundles. See [Private Packages and Internal Registries](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries) |
+| `runner` | `object` | ❌ | - | Settings applied on Checkly runners for Playwright Check Suite installs: [registry routing](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries#route-runner-installs-to-other-registries) and the [dependency cache version](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#force-a-reinstall) |
### Core Project Options
diff --git a/detect/synthetic-monitoring/playwright-checks/custom-dependencies.mdx b/detect/synthetic-monitoring/playwright-checks/custom-dependencies.mdx
index 31c61aff..ef40d54b 100644
--- a/detect/synthetic-monitoring/playwright-checks/custom-dependencies.mdx
+++ b/detect/synthetic-monitoring/playwright-checks/custom-dependencies.mdx
@@ -13,7 +13,7 @@ Playwright Check Suites do not use Checkly runtimes. Browser Checks and Multiste
## Using JavaScript/Node.js dependencies
-Checkly installs dependencies from your `package.json` and lock files. It works with [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/), and [pnpm](https://pnpm.io/).
+Checkly installs dependencies from your `package.json` and lock files. It works with [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), and [bun](https://bun.sh/).
By default, Checkly installs all dependencies (both `dependencies` and `devDependencies`).
@@ -36,6 +36,7 @@ Checkly detects your package manager from your lock file:
| `package-lock.json` | npm |
| `pnpm-lock.yaml` | pnpm |
| `yarn.lock` | yarn |
+| `bun.lock` | bun |
### Workspace support
@@ -79,8 +80,11 @@ Checkly supports private packages. To authenticate with your private registry, c
You need to:
1. Add authentication configuration to your `.npmrc` file
-2. Include the `.npmrc` file in your `checkly.config.ts`
-3. Store authentication tokens as [environment variables](/platform/variables) in Checkly
+2. Store authentication tokens as [environment variables](/platform/variables) in Checkly
+
+
+If your private packages come from an internal registry that Checkly can't reach, such as a registry behind your firewall or VPN, see [Private Packages and Internal Registries](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries) to embed them in the code bundle instead.
+
### Configure authentication to private dependencies
@@ -93,21 +97,14 @@ Store authentication tokens as Checkly [environment variables](/platform/variabl
#### Using private npm packages
```txt
-# Using an environment variable (recommended)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
-
-# Hard-coded token (not recommended)
-//registry.npmjs.org/:_authToken=npm_abc...
```
#### Using JFrog Artifactory
```txt
-# Using an environment variable (recommended)
-registry=https://yourcompany.jfrog.io/artifactory/api/npm/npm-local/:_authToken=${ARTIFACTORY_TOKEN}
-
-# Hard-coded token (not recommended)
-registry=https://yourcompany.jfrog.io/artifactory/api/npm/npm-local/:_authToken=abc...
+registry=https://yourcompany.jfrog.io/artifactory/api/npm/npm-local/
+//yourcompany.jfrog.io/artifactory/api/npm/npm-local/:_authToken=${ARTIFACTORY_TOKEN}
```
#### Using GitHub Packages
@@ -123,25 +120,11 @@ registry=https://yourcompany.jfrog.io/artifactory/api/npm/npm-local/:_authToken=
### Include authentication files
-Add your `.npmrc` file to the `checks.include` property in your `checkly.config.ts`.
-
-When you run `npx checkly test` or `npx checkly deploy`, the CLI bundles and uploads these files. This makes them available when Checkly runs `installCommand` to install your dependencies.
+When you run `npx checkly test` or `npx checkly deploy`, the CLI bundles and uploads your `.npmrc` files automatically: the one at the workspace root and any next to a workspace member's `package.json`. This makes them available when Checkly runs `installCommand` to install your dependencies.
-```typescript checkly.config.ts highlight={6}
-import { defineConfig } from 'checkly'
-
-export default defineConfig({
- checks: {
- playwrightConfigPath: './playwright.config.ts',
- // Include .npmrc for authentication with private registries
- // Works with npm, yarn, and pnpm
- include: ['.npmrc'],
- playwrightChecks: [
- // ...
- ]
- }
-})
-```
+
+Because your `.npmrc` files are uploaded with the code bundle, a token written in plain text is uploaded too. Reference tokens through environment variables, as in the examples above.
+
#### Using lifecycle scripts
@@ -186,21 +169,47 @@ Learn more about [customizing install commands](/detect/synthetic-monitoring/pla
## Dependency Caching
-Checkly caches installed packages between check runs to speed up execution and reduce installation time. Dependencies are installed once and reused for subsequent runs until your lock files (`package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`), your `installCommand`, your working directory, or your [`engine`](/constructs/playwright-check#param-engine) change.
+Checkly caches installed packages between check runs to speed up execution and reduce installation time. Dependencies are installed once and reused for subsequent runs until the files that define your dependencies change, such as your lock file and your `package.json` and `.npmrc` files, or until your `installCommand`, your working directory, or your [`engine`](/constructs/playwright-check#param-engine) change. Changes to [embedded or pruned packages and registry routing](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries) also invalidate the cache.
### How caching works
When dependency caching is enabled:
1. On the first run, Checkly installs all packages from your lock file and stores them in cache
-2. On subsequent runs, Checkly checks if your lock file, `package.json` files, `installCommand`, working directory, or `engine` have changed
+2. On subsequent runs, Checkly checks whether any of the inputs described above have changed
3. If unchanged, Checkly uses the cached packages, skipping the installation step
4. If changed, Checkly installs packages again and updates the cache
Caching significantly reduces check execution time, especially for projects with many or large dependencies.
+When the cache can't be used, the install takes longer the more packages your project declares. To make those installs faster, remove dependencies your checks don't use, such as linters or Storybook, from the code bundle with [`bundle.packages.prune`](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries#speed-up-dependency-installs).
+
The dependency cache uses a least-recently-used (LRU) policy. Active Playwright Check Suites automatically refresh their cache entry, so caches only expire for check suites that have not run in the last 30 days.
+### Force a reinstall
+
+To make Checkly reinstall dependencies without changing any of these files, set `runner.cache.install.version` at the top level of your `checkly.config.ts` and change its value whenever you want a fresh install. Deployed checks pick up the new value on the next `npx checkly deploy`.
+
+```typescript checkly.config.ts highlight={6-12}
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ runner: {
+ cache: {
+ install: {
+ version: 'v2',
+ },
+ },
+ },
+})
+```
+
+The value can be a string or an integer. An unset or empty value leaves the cache untouched, so a value read from an environment variable, such as `version: process.env.DEPENDENCY_CACHE_VERSION`, is safe when the variable isn't set.
+
+For a one-off fresh install, pass `--refresh-cache` to [`checkly test`](/cli/checkly-test), [`checkly pw-test`](/cli/checkly-pw-test), [`checkly trigger`](/cli/checkly-trigger), or [`checkly checks run`](/cli/checkly-checks) instead.
+
### Automatic retries
Checkly automatically retries transient failures during setup steps. Cache downloads retry up to 3 times with exponential backoff. If all attempts fail, the run falls back to a clean install.
@@ -234,7 +243,8 @@ Browser caching requires Private Location agent v8.2.0 or later.
If you see errors like `404 Not Found` or `Package '@your-org/package' not found`:
-- Verify your `.npmrc` file is included in `checks.include`
+- Verify your `.npmrc` file is at the workspace root or next to the `package.json` that needs it
+- If the registry is only reachable from your own network, see [Private Packages and Internal Registries](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries)
- Check that the registry URL is correct
- Ensure your package name matches exactly (including scope)
@@ -250,7 +260,7 @@ If you see `401 Unauthorized` or `403 Forbidden` errors:
If Checkly uses the wrong package manager:
-- Verify the correct lock file is present (`package-lock.json`, `yarn.lock`, or `pnpm-lock.json`)
+- Verify the correct lock file is present (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, or `bun.lock`)
- Remove any conflicting lock files
- Override with a custom `installCommand` if needed (see [customize install commands](/detect/synthetic-monitoring/playwright-checks/configuration#customize-install-and-test-commands))
@@ -259,6 +269,7 @@ If Checkly uses the wrong package manager:
If package installation exceeds time limits:
- Check for large dependencies that can be optimized
+- Remove dependencies your checks don't use from the code bundle with [`bundle.packages.prune`](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries#speed-up-dependency-installs)
- Consider using a custom `installCommand` to install only required packages
### Memory usage exceeded
@@ -266,6 +277,7 @@ If package installation exceeds time limits:
If you see errors indicating that memory limits were exceeded during installation:
- Optimize your dependencies to reduce memory consumption
+- Remove dependencies your checks don't use from the code bundle with [`bundle.packages.prune`](/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries#speed-up-dependency-installs)
- Consider using a custom `installCommand` to install only necessary packages
### General installation failures
diff --git a/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries.mdx b/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries.mdx
new file mode 100644
index 00000000..f227e217
--- /dev/null
+++ b/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries.mdx
@@ -0,0 +1,468 @@
+---
+title: 'Private Packages and Internal Registries in Playwright Check Suites'
+description: 'Run Playwright Check Suites that depend on private packages from internal registries Checkly cannot reach, and speed up dependency installs, by embedding and pruning dependencies in the code bundle.'
+sidebarTitle: 'Internal Registries'
+canonical: 'https://www.checklyhq.com/docs/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries/'
+---
+
+When you run `npx checkly test` or `npx checkly deploy`, the CLI packs your Playwright tests into a code bundle together with the files your package manager needs: `package.json` files, the lockfile, and `.npmrc`. Before each run, Checkly installs your dependencies from that bundle, fetching packages from the registries your project uses.
+
+That install fails when your project depends on private packages from an internal registry that Checkly can't reach, such as an Artifactory, Nexus, or Verdaccio instance behind your firewall or VPN, or an intranet-only mirror of the public npm registry. This page covers the CLI options for those setups. The pruning options also [speed up dependency installs](#speed-up-dependency-installs) in any project, because the runner installs fewer packages.
+
+
+If your private registry is reachable from the internet and only needs a token, such as GitHub Packages or a hosted npm organization, you don't need to embed or route packages. Configure your `.npmrc` as described in [Using private dependencies](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#using-private-dependencies).
+
+
+Choose the option that matches your setup:
+
+| Situation | Option |
+|-----------|--------|
+| Your private packages are only available from an internal registry Checkly can't reach | [Embed the packages](#embed-private-packages) with `bundle.packages.embed` |
+| Your `.npmrc` points at an internal mirror, but the packages are also available from registries Checkly can reach | [Route runner installs](#route-runner-installs-to-other-registries) with `runner.registries` |
+| Your monorepo's lockfile includes dependencies of workspace members your checks don't import | [Lockfile pruning](#lockfile-pruning) leaves them out automatically |
+| The install pulls in packages your checks don't use, such as unused peer dependencies from your internal registry, or linters that slow the install down | [Prune manifest dependencies](#prune-manifest-dependencies) with `bundle.packages.prune` |
+
+All of these options apply to Playwright Check Suites only, not to Browser Checks or Multistep Checks. You configure them at the top level of `checkly.config.ts`, not per check.
+
+## Embed private packages
+
+If some of your dependencies come from a private registry that Checkly's infrastructure can't reach, list them in `bundle.packages.embed`. The CLI downloads those packages on the machine where you run it, verifies each tarball against the integrity hash in your lockfile, and ships them inside the code bundle. The runner then installs them without contacting your registry.
+
+```typescript checkly.config.ts highlight={6-14}
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ bundle: {
+ packages: {
+ embed: [
+ '@acme/*',
+ '!@acme/public-sdk',
+ 'legacy-private-pkg@2.1.0',
+ ],
+ },
+ },
+ checks: {
+ playwrightConfigPath: './playwright.config.ts',
+ playwrightChecks: [
+ // ...
+ ],
+ },
+})
+```
+
+This config embeds every package in the `@acme` scope that your lockfile contains, except `@acme/public-sdk`, which the runner must then fetch itself. If your `.npmrc` sends the whole `@acme` scope to your internal registry, route `@acme/public-sdk` to a registry the runner can reach with [`runner.registries`](#route-runner-installs-to-other-registries). It also embeds version 2.1.0 of the unscoped `legacy-private-pkg`. Every other package installs from its usual registry.
+
+Each entry is one of:
+
+- A package name, such as `'@acme/private-utils'`. Embeds every version of the package found in the lockfile.
+- An exact version pin, such as `'legacy-private-pkg@2.1.0'`. Checkly lists only the embedded versions of an embedded package, so a pin must cover every version of that package in your lockfile. Otherwise the install can fail; npm, for example, can't find the missing version. `checkly test` and `checkly deploy` warn when an entry leaves versions of a package in your lockfile unembedded. Embed the package by name to include every version in your lockfile.
+- A name [pattern](#package-name-patterns), such as `'@acme/*'`, optionally prefixed with `!` to exclude packages that earlier entries selected.
+
+List every package the runner can't fetch, including private packages that are only dependencies of other private packages. Embedding a package doesn't embed its dependencies; see [Dependencies of dependencies](#dependencies-of-dependencies).
+
+### Supported lockfiles
+
+The CLI resolves entries against the lockfile at the workspace root:
+
+| Lockfile | Supported |
+|----------|-----------|
+| `pnpm-lock.yaml` | ✅ |
+| `package-lock.json` | ✅ |
+| `bun.lock` (text format) | ✅ |
+| `yarn.lock` (Yarn Berry, v2 and later) | ✅ |
+| `yarn.lock` (Yarn Classic, v1) | ❌ |
+| `bun.lockb` (binary format) | ❌ Run `bun install --save-text-lockfile` to create a `bun.lock` |
+
+Yarn Berry lockfiles don't record npm tarball hashes, so for Yarn Berry the CLI looks them up in your registry's package metadata each time it bundles your checks. Downloaded tarballs are still cached.
+
+### Validation
+
+The CLI validates the list before deploying:
+
+- An entry that matches no package in the lockfile fails validation and blocks the deploy. Only list packages your project actually uses.
+- An entry whose only matches can't be embedded also fails validation. Packages installed from git, a local path, or a URL, and lockfile entries without an integrity hash, can't be embedded; the runner must fetch them itself.
+- Workspace members that a pattern matches are skipped, because they're bundled as source code rather than embedded. An entry that matches only workspace members fails validation.
+- A `!` exclusion that matches nothing is ignored.
+- A list that ends up selecting no packages at all only prints a warning.
+
+### Registry credentials
+
+Embedded packages need registry credentials only on the machine running the CLI, which downloads them. The runner installs them from the code bundle and never contacts your registry for them, so you don't need to store your registry token in Checkly; a token reference in the bundled `.npmrc` whose variable isn't set in Checkly doesn't break the install. The CLI reads credentials from:
+
+- The project, workspace root, and user `.npmrc` files
+- `npm_config_*` environment variables
+- pnpm's global `auth.ini`, where `pnpm login` stores tokens on pnpm 11 and later
+
+Scope tokens to the registry (`//registry.example.com/:_authToken=${NPM_TOKEN}`) rather than setting a bare `_authToken`. Because `.npmrc` is uploaded with the code bundle, reference tokens through environment variables instead of writing them in plain text, and set those variables wherever you run `checkly test` or `checkly deploy`. Only packages the runner fetches itself need their token stored in Checkly, as described in [Using private dependencies](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#using-private-dependencies). If you use [`runner.registries`](#route-runner-installs-to-other-registries), set those tokens in its `auth` entries instead.
+
+If you use bun or Yarn Berry and your credentials only live in `bunfig.toml` or `.yarnrc.yml`, copy them into `.npmrc` or set them as `npm_config_*` variables; the CLI doesn't read those files. bun and Yarn Berry projects must also keep their registry URLs, including scoped registries, in `.npmrc`.
+
+If a download fails to authenticate, the error names the credential source the CLI used.
+
+### Code bundle size
+
+The Checkly API accepts code bundles of up to 30 MB, compressed. Embedded packages ship inside the code bundle, so embedding large packages, or many packages that add up, can push the bundle over that limit. `checkly test`, `checkly pw-test`, and `checkly deploy` then fail when they upload the bundle:
+
+```text Terminal
+The code bundle is too large to upload: the compressed bundle is 34.2 MB, but the Checkly API accepts at most 30 MB. Reduce the bundle size by removing large files from the Playwright project, narrowing any 'include' patterns, or embedding fewer private packages ('bundle.packages.embed').
+```
+
+To stay under the limit, embed only the packages the runner can't fetch from a registry it can reach, and let [lockfile pruning](#lockfile-pruning) and [`bundle.packages.prune`](#prune-manifest-dependencies) drop the packages your checks don't use. Packages that pruning removes from the bundled lockfile aren't embedded.
+
+Enterprise plans can raise the limit to 100 MB. Contact your account executive or [contact us in the Checkly Web App](https://app.checklyhq.com/?support=true).
+
+## Pruning
+
+Pruning reduces what the runner installs to what your code bundle needs. This keeps internal packages your checks never use out of the install, so you don't have to embed them or make their registry reachable, and it makes the install faster. The CLI never modifies the files on disk; it only rewrites the copies in the code bundle.
+
+### Lockfile pruning
+
+In a monorepo, your code bundle often contains only some of the workspace members. Installing from the full lockfile would make the runner fetch dependencies of members that aren't in the bundle, and fail if any of those come from a registry it can't reach. The CLI therefore prunes the bundled lockfile to the bundle's contents automatically. You don't need to configure anything.
+
+Lockfile pruning supports the same lockfiles as [embedding](#supported-lockfiles), and requires your package manager to be installed on the machine running the CLI. pnpm and npm reuse their local caches and only contact your registry for packages they haven't cached; Yarn Berry and bun prune offline.
+
+If pruning is needed but can't run or can't verify its result, the CLI ships the original lockfile unchanged and prints a note explaining why. Embedding follows the pruned lockfile: a package that only workspace members outside the bundle depend on is not embedded, even if an entry matches it, because the runner never installs it.
+
+Control lockfile pruning with these environment variables:
+
+| Variable | Description |
+|----------|-------------|
+| `CHECKLY_LOCKFILE_PRUNE` | Set to `0` or `false` to turn lockfile pruning off. This also turns off [`bundle.packages.prune`](#prune-manifest-dependencies) when a lockfile is bundled. |
+| `CHECKLY_LOCKFILE_PRUNE_TIMEOUT` | Time budget for pruning, in seconds. Defaults to `30`. Raise it if pruning times out, for example `CHECKLY_LOCKFILE_PRUNE_TIMEOUT=120`. Set to `0` to turn the timeout off. |
+
+If a package you need stops being embedded after pruning, the workspace member that depends on it is probably not part of the bundle. Import that member from your test code instead of turning pruning off.
+
+### Prune manifest dependencies
+
+Use `bundle.packages.prune` to remove dependencies from the `package.json` files in the code bundle: the workspace root's and every bundled workspace member's. The CLI rewrites the manifests before it prunes the lockfile, so the removed packages drop out of the bundled lockfile too. Besides the peer dependency case below, you can use it to [speed up dependency installs](#speed-up-dependency-installs).
+
+The most common use is unused peer dependencies. With pnpm's `auto-install-peers` setting, which is on by default, a member's peer dependencies end up in the lockfile even when your tests never import them. If those peers come from a registry Checkly can't reach, the install fails:
+
+```typescript checkly.config.ts highlight={6-10}
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ bundle: {
+ packages: {
+ prune: { peerDependencies: true },
+ },
+ },
+})
+```
+
+This config removes every entry under `peerDependencies` from the workspace root's and each bundled member's `package.json`. When the CLI then prunes the lockfile, the peers that were only installed to satisfy those entries drop out, so the runner doesn't try to fetch them.
+
+`prune` accepts two forms:
+
+
+
+
+An array of package name [patterns](#package-name-patterns). Matching packages are removed from every dependency class of every bundled `package.json`:
+
+```typescript checkly.config.ts
+bundle: {
+ packages: {
+ // Remove the @acme scope, except @acme/runtime
+ prune: ['@acme/*', '!@acme/runtime', 'storybook'],
+ },
+},
+```
+
+This removes every `@acme` package except `@acme/runtime`, plus `storybook`, from `dependencies`, `devDependencies`, `peerDependencies`, and `optionalDependencies`.
+
+
+
+
+An object keyed by dependency class: `dependencies`, `devDependencies`, `peerDependencies`, or `optionalDependencies`. Each value is `true` to remove the whole class, or a pattern list to remove only matching packages from that class:
+
+```typescript checkly.config.ts
+bundle: {
+ packages: {
+ prune: {
+ peerDependencies: true,
+ devDependencies: ['@storybook/**', 'eslint*'],
+ },
+ },
+},
+```
+
+This removes all peer dependencies, and removes the `@storybook` scope and unscoped packages starting with `eslint` from `devDependencies` only. `dependencies` and `optionalDependencies` are left as they are.
+
+`true` can't be combined with `!` exclusions. To remove a whole class except some packages, use `['**', '!package-to-keep']`.
+
+
+
+
+Unlike `embed`, prune patterns don't accept version pins. A pattern that matches nothing is ignored. Removing a peer dependency also removes its `peerDependenciesMeta` entry. Patterns only match dependencies declared in `package.json` files; see [Dependencies of dependencies](#dependencies-of-dependencies).
+
+#### Target specific workspace members
+
+To prune only some workspace members, use member-scoped entries in the array form. `member` is a pattern list matched against each manifest's `name`; use `'.'` for the workspace root. Each entry has exactly one of `remove` or `keep`, which take a pattern list or a per-class object:
+
+```typescript checkly.config.ts
+bundle: {
+ packages: {
+ prune: [
+ // Remove packages from matching members only
+ {
+ member: ['@acme/web'],
+ remove: { peerDependencies: true },
+ },
+ // Declare the member's complete dependency set
+ {
+ member: ['@acme/design-system'],
+ keep: { dependencies: ['@acme/tokens'] },
+ },
+ ],
+ },
+},
+```
+
+The first entry removes the peer dependencies of `@acme/web` and leaves every other member's peers in place. The second entry reduces `@acme/design-system` to a single dependency, `@acme/tokens`: its other `dependencies` and all of its `devDependencies`, `peerDependencies`, and `optionalDependencies` are removed.
+
+- `remove` works like the global patterns, but only for the matched members. Entries apply in the order you list them.
+- `keep` declares a member's entire remaining dependency set. Anything it doesn't select is removed, classes a per-class `keep` doesn't mention are emptied, and no other entry can remove a kept package. `keep: []` removes every dependency.
+
+Prefer exact member names with `keep`, since a wildcard can match a member you add later. The CLI warns when a `member` selector matches no workspace member, or when a `keep` pattern keeps nothing in a bundled member.
+
+#### Requirements and caveats
+
+- When the bundle contains a lockfile, `prune` only takes effect if lockfile pruning succeeds in the same run, so the manifests and lockfile stay in sync. Otherwise the original `package.json` files ship unchanged and the CLI prints a warning. A bundle without a lockfile always ships the pruned manifests.
+- The CLI doesn't check your code against the pruned dependencies. Don't remove anything your tests need at runtime, or anything the install itself needs. For example, keep `typescript` when a package's `prepare` or `postinstall` script compiles TypeScript during the install (see [lifecycle scripts](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#using-lifecycle-scripts)).
+
+### Speed up dependency installs
+
+The runner installs your dependencies whenever the [dependency cache](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#dependency-caching) can't be used: on the first run, after your dependencies change, and on every run in Private Locations that don't have dependency caching turned on. Every package it installs adds to that time, so pruning packages your checks never load makes those runs start faster.
+
+In a monorepo, [lockfile pruning](#lockfile-pruning) already leaves out the dependencies of workspace members outside the bundle. To go further, remove the development tooling that your `package.json` declares but your tests don't need, such as linters, formatters, and component workshops:
+
+```typescript checkly.config.ts highlight={6-19}
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ bundle: {
+ packages: {
+ prune: {
+ devDependencies: [
+ 'eslint*',
+ '@eslint/*',
+ '@typescript-eslint/*',
+ 'prettier',
+ 'storybook',
+ '@storybook/*',
+ ],
+ },
+ },
+ },
+ checks: {
+ playwrightConfigPath: './playwright.config.ts',
+ playwrightChecks: [
+ { name: 'Acme E2E', logicalId: 'acme-e2e' },
+ ],
+ },
+})
+```
+
+This config removes ESLint and its plugins, Prettier, and Storybook from the `devDependencies` of the bundled `package.json` files. The CLI then regenerates the bundled lockfile, so everything those tools depend on drops out too, and the runner installs only what remains. Your local `package.json` is unchanged, so linting and Storybook keep working on your machine.
+
+Pruning works the same way in a single-package project as in a monorepo. Only remove packages that neither your tests nor the install use: keep build tools such as `typescript` when an install script needs them. The CLI doesn't check this for you.
+
+## Route runner installs to other registries
+
+If your `.npmrc` points at a registry URL that only works on your network, such as a VPN-only mirror, but the same packages are available to Checkly from other registries, use `runner.registries` instead of embedding. It tells the runner which registries to install from, replacing the registries named in the bundled configuration files:
+
+```typescript checkly.config.ts highlight={6-20}
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ runner: {
+ registries: {
+ upstreams: {
+ acme: {
+ url: 'https://npm.acme.example.com/',
+ auth: { type: 'bearer', token: '${ACME_NPM_TOKEN}' },
+ },
+ npmjs: { url: 'https://registry.npmjs.org/' },
+ },
+ packages: [
+ { pattern: '@acme/*', upstreams: ['acme'] },
+ { pattern: '**', upstreams: ['npmjs'] },
+ ],
+ },
+ },
+})
+```
+
+With this config, the runner installs `@acme` packages only from `https://npm.acme.example.com/`, authenticating with the `ACME_NPM_TOKEN` environment variable, and every other package from the public npm registry. If an `@acme` package is missing from the Acme registry, the install fails instead of asking the public registry for it.
+
+- `upstreams` names the registries the runner may use. A `token` must be a single `${VARIABLE}` reference to a Checkly [environment variable](/platform/variables), which the runner resolves at install time. Literal tokens are rejected.
+- Once `runner.registries` is set, the runner ignores the registry credentials and TLS settings, such as `strict-ssl` and `cafile`, in your bundled `.npmrc`, `pnpm-workspace.yaml`, and `.yarnrc.yml`. Give every upstream that needs authentication its own `auth` entry.
+- `packages` routes package names to upstreams. The first rule whose [pattern](#package-name-patterns) matches wins. The runner tries the rule's upstreams in order, but never falls through to a later rule, so private package names aren't sent to a broader registry. The last rule must be `{ pattern: '**', ... }`.
+- Routing patterns don't accept `!` exclusions or version pins.
+- [Embedded packages](#embed-private-packages) never go through routing rules: the runner installs them from the code bundle and doesn't ask any routed registry about them.
+
+`runner.registries` only affects the install on Checkly. Embedding and lockfile pruning still run on your machine and use your `.npmrc` credentials.
+
+## Dependencies of dependencies
+
+Your lockfile records every package the install needs, including dependencies of your dependencies (transitive dependencies). Each option handles them differently:
+
+| Option | Dependencies of dependencies |
+|--------|------------------------------|
+| `bundle.packages.embed` | Entries match packages at any depth in the lockfile, so you can embed a transitive dependency by name. Embedding a package doesn't embed its dependencies. |
+| Lockfile pruning | Removes the dependencies of workspace members outside the bundle at every depth, except packages that something in the bundle still needs. |
+| `bundle.packages.prune` | Patterns only match dependencies declared in `package.json` files; a pattern that names a package that is only a transitive dependency has no effect. Removing a declared dependency also removes the transitive dependencies that only it needed from the bundled lockfile. |
+| `runner.registries` | Routing rules apply to every package the runner fetches from a registry, at any depth. Embedded packages never reach them. |
+
+### Embed private dependency chains
+
+The runner installs the dependencies of an embedded package like any other package, from the registries your project uses. That works for public dependencies, but if an embedded package depends on other private packages from the same unreachable registry, you must embed those too. The CLI doesn't detect a missing one; the install on Checkly fails when it can't fetch it.
+
+For example, if `@acme/ui` depends on the private `@acme/tokens` and the public `react`, embed both private packages and let the runner fetch `react` from npm:
+
+```typescript checkly.config.ts
+bundle: {
+ packages: {
+ embed: ['@acme/ui', '@acme/tokens'],
+ },
+},
+```
+
+If your private packages share a scope, a wildcard such as `@acme/*` embeds the whole scope at every depth, including private packages you only depend on indirectly.
+
+## Examples
+
+### Monorepo with an internal registry
+
+Your tests live in one member of a pnpm workspace, and your `.npmrc` points the `@acme` scope at a registry that's only reachable from your network:
+
+```text
+acme/
+├── .npmrc # Points @acme at the internal registry
+├── package.json
+├── pnpm-lock.yaml
+└── packages/
+ ├── e2e/ # Tests and checkly.config.ts
+ ├── design-system/ # Imported by the tests
+ └── admin/ # Not used by the tests
+```
+
+`design-system` depends on the private `@acme/tokens` and declares the private `@acme/analytics` as a peer dependency, which the tests don't use. `admin` depends on the private `@acme/billing-sdk`.
+
+```typescript packages/e2e/checkly.config.ts
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ bundle: {
+ packages: {
+ embed: ['@acme/*'],
+ prune: [
+ {
+ member: ['@acme/design-system'],
+ remove: { peerDependencies: ['@acme/analytics'] },
+ },
+ ],
+ },
+ },
+ checks: {
+ playwrightConfigPath: './playwright.config.ts',
+ playwrightChecks: [
+ { name: 'Acme E2E', logicalId: 'acme-e2e' },
+ ],
+ },
+})
+```
+
+When you run `npx checkly deploy`, the CLI:
+
+1. Bundles `e2e` and `design-system`, because the tests import `design-system`. `admin` is left out.
+2. Removes `@acme/analytics` from the bundled copy of `design-system`'s `package.json`.
+3. Prunes the bundled lockfile to the bundle's contents. `@acme/billing-sdk` drops out because only `admin` needs it, and `@acme/analytics` drops out because nothing declares it anymore.
+4. Embeds the `@acme` packages that remain in the pruned lockfile, here `@acme/tokens`, downloading them through your `.npmrc`. The pattern also matches the workspace members, such as `design-system`; those are bundled as source code and skipped.
+
+On Checkly, the runner installs `@acme/tokens` from the bundle and all public packages from npm. It never contacts your internal registry.
+
+The `prune` entry isn't required for the install to work: without it, `@acme/*` would also embed `@acme/analytics`. Pruning the unused peer saves downloading and shipping a package the tests never load. It becomes required when the runner can neither install the peer from the bundle nor fetch it itself, for example a peer installed from an internal Git server.
+
+### Private scope behind a VPN-only mirror
+
+Your `.npmrc` sends every install through a mirror that's only reachable on your VPN. The mirror proxies the public npm registry and also hosts your `@acme` packages:
+
+```text .npmrc
+registry=https://npm-mirror.vpn.acme.example.com/
+```
+
+```typescript checkly.config.ts
+import { defineConfig } from 'checkly'
+
+export default defineConfig({
+ projectName: 'Acme Website',
+ logicalId: 'acme-website',
+ bundle: {
+ packages: {
+ embed: ['@acme/*'],
+ },
+ },
+ runner: {
+ registries: {
+ upstreams: {
+ npmjs: { url: 'https://registry.npmjs.org/' },
+ },
+ packages: [
+ { pattern: '**', upstreams: ['npmjs'] },
+ ],
+ },
+ },
+})
+```
+
+The two options split the work:
+
+- `embed` downloads the `@acme` packages through the mirror on your machine, where the VPN is available, and ships them in the code bundle.
+- `runner.registries` sends every other package to the public npm registry instead of the mirror. Embedded packages take priority over routing rules, so the `@acme` packages never reach the `**` rule.
+
+Run `checkly test` and `checkly deploy` with the VPN connected, so the CLI can download the embedded packages.
+
+## Package name patterns
+
+`bundle.packages.embed`, `bundle.packages.prune`, and `runner.registries` share the same pattern syntax:
+
+| Pattern | Matches |
+|---------|---------|
+| `@acme/utils` | Exactly `@acme/utils` |
+| `@acme/*` | Every package in the `@acme` scope |
+| `acme-*` | Unscoped packages starting with `acme-` |
+| `*` | Every unscoped package. A single `*` never crosses the `/` in a scoped name. |
+| `**` | Every package, scoped or not |
+| `**/utils` | `utils` and `utils` in any scope, such as `@acme/utils` |
+| `!@acme/legacy` | Excludes `@acme/legacy` from what earlier entries selected (`embed` and `prune` only) |
+
+Exclusions only remove packages selected by entries before them, so order matters: `['@acme/*', '!@acme/legacy']` selects the scope except `@acme/legacy`, while `['!@acme/legacy', '@acme/*']` selects the whole scope.
+
+## Troubleshooting
+
+### See what was embedded or pruned
+
+Run the CLI with debug logging to see which packages each `embed` entry matched, and why lockfile pruning was skipped:
+
+```bash Terminal
+DEBUG='checkly:cli:services:embedded-packages,checkly:cli:services:check-parser:*' npx checkly test
+```
+
+### The code bundle is too large to upload
+
+The compressed bundle exceeds the limit the Checkly API accepts for your account: 30 MB, unless it has been raised. Enterprise plans can raise it to 100 MB. Embedded packages count toward it; see [Code bundle size](#code-bundle-size).
+
+### Dependencies are reinstalled after a change
+
+Changing the set of embedded packages, the pruned manifests or lockfile, or `runner.registries` invalidates the runner's [dependency cache](/detect/synthetic-monitoring/playwright-checks/custom-dependencies#dependency-caching). The next run installs dependencies from scratch. A prune pattern or a `!` exclusion that matches nothing changes none of these, so it leaves the cache untouched.
diff --git a/docs.json b/docs.json
index 9a7bccc7..43291c98 100644
--- a/docs.json
+++ b/docs.json
@@ -259,6 +259,7 @@
"detect/synthetic-monitoring/playwright-checks/performance-metrics",
"detect/synthetic-monitoring/playwright-checks/add-to-group",
"detect/synthetic-monitoring/playwright-checks/custom-dependencies",
+ "detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries",
"detect/synthetic-monitoring/playwright-checks/environment-variables",
"detect/synthetic-monitoring/playwright-checks/timeouts"
]
diff --git a/sitemap.xml b/sitemap.xml
index 10811eeb..df29b749 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -1197,6 +1197,9 @@
https://www.checklyhq.com/docs/detect/synthetic-monitoring/playwright-checks/performance-metrics/
+
+ https://www.checklyhq.com/docs/detect/synthetic-monitoring/playwright-checks/private-packages-and-internal-registries/
+
https://www.checklyhq.com/docs/detect/synthetic-monitoring/playwright-checks/quickstart/