@rivi-software/workers-bundle-check 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RIVI Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,258 @@
1
- # Temporary Holding Version
1
+ # @rivi-software/workers-bundle-check
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@rivi-software/workers-bundle-check?label=npm&color=0b6b3a)](https://www.npmjs.com/package/@rivi-software/workers-bundle-check) [![CI status](https://img.shields.io/github/actions/workflow/status/rivi-software/tools/ci.yml?branch=main&label=CI)](https://github.com/rivi-software/tools/actions/workflows/ci.yml) [![MIT License](https://img.shields.io/badge/license-MIT-0b6b3a)](LICENSE)
4
+
5
+ Fails CI on what a Cloudflare Worker's build should not contain.
6
+
7
+ `wrangler check startup` reports a Worker's size and its startup time, but nothing checks what is inside the build. These mistakes ship without anyone noticing:
8
+ - **A module that does not belong in the Worker.** A dev-only mock, a browser-only library or a whole icon set gets pulled in through a chain of imports. The build stays under the size limit, so nothing fails.
9
+ - **Source maps served publicly.** The build writes `.map` files into the static assets directory. Unless `.assetsignore` leaves them out, anyone can download the original source.
10
+ - **A secret inlined at build time.** A `define` reads an API key or a private key from the environment and writes it into the code.
11
+
12
+ This package checks a build for all three, from the command line or from a script.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install --save-dev @rivi-software/workers-bundle-check
18
+ ```
19
+
20
+ It needs Node 22 or later, and runs after the build, not in the Worker. A configuration file in TypeScript needs Node 22.18 or later. The package is ESM only.
21
+
22
+ It reads the build's output, so it works with any bundler that writes source maps: the Cloudflare Vite plugin, `wrangler deploy --outdir`, or esbuild of your own. The tests build Workers with Vite 8 (Rolldown) and with Wrangler 4.156.2 (esbuild).
23
+
24
+ ## Usage
25
+
26
+ Run it after the build, with a configuration file in the project's directory:
27
+
28
+ ```ts
29
+ // workers-bundle-check.config.ts
30
+ import type { WorkerBundleCheckOptions } from "@rivi-software/workers-bundle-check";
31
+
32
+ export default {
33
+ serverDir: "dist/server",
34
+ assetsDir: "dist/client",
35
+ forbidden: [
36
+ { module: "msw", reason: "test-only" },
37
+ { module: "**/icons/all.js", reason: "import single icons" },
38
+ { marker: "__DEV_TOOLBAR__", reason: "dev-only code" },
39
+ ],
40
+ } satisfies WorkerBundleCheckOptions;
41
+ ```
42
+
43
+ ```json
44
+ {
45
+ "scripts": {
46
+ "build": "vite build && workers-bundle-check"
47
+ }
48
+ }
49
+ ```
50
+
51
+ The command looks for `workers-bundle-check.config.ts`, `.mts`, `.js`, `.mjs` or `.json` in the current directory, and takes the first it finds. `--config <file>` names another one. `--server <dir>` and `--assets <dir>` override `serverDir` and `assetsDir`, and are enough on their own for the source map and secret checks:
52
+
53
+ ```bash
54
+ npx workers-bundle-check --server dist/server --assets dist/client
55
+ ```
56
+
57
+ Or call the function from a build script:
58
+
59
+ ```ts
60
+ import { checkWorkerBundle } from "@rivi-software/workers-bundle-check";
61
+
62
+ await checkWorkerBundle({
63
+ serverDir: "dist/server",
64
+ assetsDir: "dist/client",
65
+ forbidden: [{ module: "msw", reason: "test-only" }],
66
+ });
67
+ ```
68
+
69
+ When it finds a problem, the command prints the problems and exits with `1`, and the function rejects with a `WorkerBundleCheckError`. Its message lists the problems, one per line:
70
+
71
+ ```text
72
+ The Worker bundle check found 3 problems:
73
+ dist/server/index.js holds 14 modules (52.1 kB) that "msw" forbids: test-only. The largest is node_modules/.pnpm/msw@2.7.0/node_modules/msw/lib/core/index.mjs (12.0 kB).
74
+ dist/client/assets/index-4f2a.js.map is a source map Wrangler would serve: leave it out with dist/client/.assetsignore, or delete it before the deploy.
75
+ dist/client/assets/index-4f2a.js:1 holds a value shaped like a Stripe secret key (fingerprint 9c1e5b0a7d3f2e48).
76
+ ```
77
+
78
+ When it finds none, the function resolves to a report of what it read, and the command prints it.
79
+
80
+ ### Options
81
+
82
+ | Option | What | Default |
83
+ | --- | --- | --- |
84
+ | `serverDir` | The Worker's build output: the directory with its JavaScript and source maps | (required) |
85
+ | `assetsDir` | The static assets directory, the one `assets.directory` names in the Wrangler config | none: no assets |
86
+ | `forbidden` | The modules and markers the Worker must not carry | `[]` |
87
+ | `allowedSecrets` | Findings of secrets that are known to be safe | `[]` |
88
+ | `secretPatterns` | Kinds of secret to look for besides the built-in ones | `[]` |
89
+ | `cwd` | The directory paths are relative to, and module paths are named from | `process.cwd()` |
90
+
91
+ With the Cloudflare Vite plugin, `serverDir` is the directory that holds the build's `wrangler.json`, such as `dist/server` in a TanStack Start app, and `assetsDir` is the one its `assets.directory` names, such as `dist/client`. With `wrangler deploy --outdir dist`, `serverDir` is `dist`, and `assetsDir` is the directory the Wrangler config's `assets.directory` names.
92
+
93
+ ## Forbidden modules and markers
94
+
95
+ A rule names a module, a marker or both, and says why. The Worker breaks it when it holds either.
96
+
97
+ ```ts
98
+ forbidden: [
99
+ // A package, wherever it is installed: msw's every module.
100
+ { module: "msw", reason: "test-only" },
101
+ // A glob over module paths.
102
+ { module: "src/mocks/**", reason: "dev-only" },
103
+ { module: "**/icons/all.js", reason: "import single icons" },
104
+ // A regular expression over module paths.
105
+ { module: /\/browser\//, reason: "browser-only code" },
106
+ // A string the code must not contain, such as one only dev code has.
107
+ { marker: "__DEV_TOOLBAR__", reason: "dev-only code" },
108
+ // Both: the package, or its marker where no source map names it.
109
+ { module: "yjs", marker: "Yjs was already imported", reason: "the editor loads it lazily" },
110
+ ],
111
+ ```
112
+
113
+ - **A package name**, such as `msw` or `@faker-js/faker`, names every module in that package, wherever it is installed. That covers npm's and pnpm's layouts, and a package nested in another.
114
+ - **Any other string** is a glob, matched against the module's path. A module path is relative to `cwd`: `src/server.ts`, or `node_modules/.pnpm/msw@2.7.0/node_modules/msw/lib/core/index.mjs`. `**` matches dot directories such as `.pnpm` too. A module outside `cwd`, such as one in a monorepo's root `node_modules`, is named with its leading `../`, but matched without it. A leading `./` in a glob is left out, so `./src/mocks/**` and `src/mocks/**` are the same. To name a directory named like a package, such as `src`, write `src/**`.
115
+ - **A regular expression** is matched against the same path.
116
+ - **A marker** is a string or a regular expression that the Worker's code must not contain. It finds code that no source map names, such as a module the bundler inlined, or a bundle built without maps. Names are minified away, so pick a string literal.
117
+
118
+ The report names each file that breaks a rule, how many of the rule's modules it holds, their size, and the largest one.
119
+
120
+ ### Rules that guard nothing
121
+
122
+ A rule whose module or marker no longer exists guards nothing. When a library renames `icons/all.js`, the rule against it matches nothing, and the whole icon set could ship again without a failure. The check therefore fails on a rule that matches nothing the project has:
123
+ - **A package name**, when the package is not installed where Node would find it from `cwd`. That includes a monorepo's root `node_modules`, and pnpm's store.
124
+ - **A glob or a regular expression**, when no file matches it. It looks at the files under `cwd`, and at those in the `node_modules` of each directory above it, following symlinks.
125
+ - **A marker with a module pattern**, when none of the files the pattern matches contains it.
126
+ - **A marker on its own**, when no file of the project under `cwd`, outside `node_modules`, contains it. For a marker only a package has, name the package as the rule's module too.
127
+
128
+ What the Worker holds is never stale. The check walks the files only for a rule the Worker does not match, and stops as soon as every rule has matched something.
129
+
130
+ ## Served source maps
131
+
132
+ The check reads the assets directory's `.assetsignore` the way Wrangler does: in gitignore syntax, with the [`ignore`](https://www.npmjs.com/package/ignore) package Wrangler uses. It leaves out `.assetsignore`, `_headers` and `_redirects` as Wrangler does. It then fails on:
133
+ - each `.map` file Wrangler would upload
134
+ - each served script or style that carries its source map inline, as a `data:` URL
135
+
136
+ Any `.assetsignore` that leaves the maps out passes, such as `*.map`, `**/*.map` or `assets/`. A script that names a map Wrangler leaves out is fine: the map is not there to load.
137
+
138
+ ## Values shaped like secrets
139
+
140
+ The check looks for these key formats in two places:
141
+ - the Worker's scripts, HTML, JSON, text, XML and SVG files
142
+ - every file the assets would serve, a stray `.env` included, but source maps and binary files such as images and fonts
143
+
144
+ | Name | Shape |
145
+ | --- | --- |
146
+ | private key | `-----BEGIN … PRIVATE KEY-----` and the key after it |
147
+ | AWS access key ID | `AKIA…`, `ASIA…` (not AWS's `…EXAMPLE` IDs) |
148
+ | GitHub token | `ghp_…`, `gho_…`, `ghu_…`, `ghs_…`, `ghr_…`, `github_pat_…` |
149
+ | GitLab token | `glpat-…` |
150
+ | npm token | `npm_…` |
151
+ | Slack token | `xoxb-…`, `xoxp-…` and the other `xox` tokens |
152
+ | Slack webhook URL | `hooks.slack.com/services/…` |
153
+ | Stripe secret key | `sk_live_…`, `sk_test_…`, `rk_live_…`, `rk_test_…` |
154
+ | webhook signing secret | `whsec_…` (Stripe, Svix, Clerk) |
155
+ | OpenAI API key | `sk-…T3BlbkFJ…` |
156
+ | Anthropic API key | `sk-ant-api03-…`, `sk-ant-admin01-…` |
157
+ | PostHog personal API key | `phx_…` |
158
+ | PostHog project secret key | `phs_…` |
159
+ | Supabase secret key | `sb_secret_…` |
160
+ | SendGrid API key | `SG.….…` |
161
+ | Shopify access token | `shpat_…`, `shpca_…`, `shppa_…`, `shpss_…` |
162
+ | Linear API key | `lin_api_…` |
163
+ | Hugging Face token | `hf_…` |
164
+
165
+ Keys that apps ship to browsers on purpose are not in the list: Stripe's publishable `pk_` keys, PostHog's `phc_` project tokens, Supabase's publishable keys, and Google's `AIza` API keys. Add other formats with `secretPatterns`:
166
+
167
+ ```ts
168
+ secretPatterns: [{ name: "internal service token", pattern: /\bsvc_[A-Za-z0-9]{32}\b/ }],
169
+ ```
170
+
171
+ The report never prints a value. It gives the file, the line, the module the value comes from when the file has a source map, and a fingerprint: the first 16 hexadecimal digits of the value's SHA-256. The fingerprint stays the same from build to build, while the names of hashed chunks change.
172
+
173
+ ### Allowed secrets
174
+
175
+ Some findings are safe, such as an example key a library ships. Allow each one by its fingerprint, by the module it comes from, or by both, and say why:
176
+
177
+ ```ts
178
+ allowedSecrets: [
179
+ { fingerprint: "9c1e5b0a7d3f2e48", reason: "Stripe's example key, in our docs page" },
180
+ { module: "node-forge", reason: "its test vectors" },
181
+ ],
182
+ ```
183
+
184
+ `module` takes the same patterns as a forbidden rule. An allowed entry that matches no finding fails the check, so the list stays current.
185
+
186
+ ## How it works
187
+
188
+ - **Which modules the Worker holds.** It reads every `.js`, `.mjs` and `.cjs` file in `serverDir` with its source map. It uses the map the file's `sourceMappingURL` names, inline or in a file, or else `<file>.map` beside it, which is where Vite's `hidden` maps are. A module counts when its mappings cover code in the file. A module the bundler tree-shook away is not in the map, or maps only to the call that initialized it (`init_x()`), which the check leaves out. The size it reports is the length of the module's code in the file.
189
+ - **Module paths.** It resolves each source from the map's directory and `sourceRoot`, and names it by its real path, as bundlers do. A package that pnpm links into `node_modules` is named by its path in pnpm's store. Wrangler writes a `sourceRoot` that its own sources do not use. When no file is where `sourceRoot` points, the check looks for the source without it.
190
+ - **Matching `.assetsignore`.** It builds the matcher Wrangler builds in `createAssetsIgnoreFunction` (workers-sdk, `packages/workers-shared/utils/helpers.ts`), and tests each file's path in the assets directory against it. The tests compare its result with what `wrangler deploy --dry-run` leaves out, for a dozen `.assetsignore` files.
191
+
192
+ ## Limits
193
+
194
+ - **The modules need source maps.** When a rule names a module and a Worker file has no source map, the check fails, because it cannot see what the file holds. Markers need no maps. Index maps (with `sections`) are not read.
195
+ - **It names the module, not the import that pulled it in.** Source maps do not record imports. To find the chain, use the bundler's own analysis, such as esbuild's metafile or a bundle visualizer.
196
+ - **Rules check the Worker.** The browser's code in `assetsDir` is checked for source maps and secrets only.
197
+ - **Installed is not imported.** In a monorepo, a package installed in the root counts for the stale check even when this app does not depend on it.
198
+ - **Yarn Plug'n'Play.** Its packages sit in zip files, which the stale check cannot see into, so it calls a package rule stale unless the Worker holds the package.
199
+ - **Secrets have to have a shape.** A key without a prefix, such as an AWS secret access key, a Cloudflare API token or a password, has no shape to find. Neither does a value that the build encodes or splits. The check complements a secret scanner on the source, and does not replace it.
200
+ - **`assetsDir` is not read from the Wrangler config.** Pass it, or the assets are not checked at all.
201
+
202
+ ## Exports
203
+
204
+ From `@rivi-software/workers-bundle-check`:
205
+
206
+ | Export | What |
207
+ | --- | --- |
208
+ | `checkWorkerBundle(options)` | Checks the build: resolves to a `WorkerBundleReport`, or rejects with a `WorkerBundleCheckError` |
209
+ | `WorkerBundleCheckError` | The problems: `report`, with `report.problems`; its message lists them |
210
+ | `SECRET_PATTERNS` | The built-in secret patterns: `{ name, pattern }[]` |
211
+ | Types | `WorkerBundleCheckOptions`, `WorkerBundleReport`, `WorkerBundleProblem`, `ForbiddenRule`, `ModulePattern`, `AllowedSecret`, `SecretPattern`, `BundledModule` |
212
+
213
+ A `WorkerBundleReport` holds `serverDir`, `workerFiles`, `modules` (how many the maps name), `assetsDir`, `servedAssets`, `ignoredAssets`, `allowedSecrets` (how many findings the allow-list let through) and `problems`. Each problem has a `kind` and a one-line `message`:
214
+
215
+ | `kind` | Fields |
216
+ | --- | --- |
217
+ | `forbidden-module` | `rule`, `file`, `modules` (`{ path, bytes }`, largest first) |
218
+ | `forbidden-marker` | `rule`, `file` |
219
+ | `stale-rule` | `rule`, `part` (`module` or `marker`) |
220
+ | `missing-source-map` | `file` |
221
+ | `served-source-map` | `file` |
222
+ | `inline-source-map` | `file` |
223
+ | `secret` | `file`, `line`, `name`, `fingerprint`, `module` when known |
224
+ | `stale-allowed-secret` | `allowed` |
225
+
226
+ The `workers-bundle-check` command exits with `0` when the build passes, with `1` when it finds a problem or cannot read the build, and with `2` on a usage error. `workers-bundle-check --help` prints its options.
227
+
228
+ ## Development
229
+
230
+ The tests in `tests/` cover:
231
+ - reading source maps: the mappings, a dropped module's residue, hidden and inline maps, and Wrangler's `sourceRoot`
232
+ - matching package names, globs and regular expressions against npm's and pnpm's layouts
233
+ - the walk over a monorepo's files, through symlinks
234
+ - the agreement with `wrangler deploy --dry-run` on which assets `.assetsignore` leaves out
235
+ - each secret pattern, and keys meant to be public
236
+ - the whole check on Workers built with Vite (Rolldown) and with Wrangler (esbuild)
237
+ - the command and its configuration files
238
+
239
+ They build and run locally, and call no Cloudflare API. In this directory, run `pnpm run test`, `pnpm run check` and `pnpm run build`.
240
+
241
+ ## License
242
+
243
+ Licensed under the [MIT License](LICENSE). Copyright © 2026 RIVI Software.
244
+
245
+ <br>
246
+
247
+ <p align="center">
248
+ <a href="https://rivi.software">
249
+ <picture>
250
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rivi-software/tools/main/assets/rivi-mark-on-dark.svg">
251
+ <img src="https://raw.githubusercontent.com/rivi-software/tools/main/assets/rivi-mark.svg" alt="RIVI Software" width="36" height="36">
252
+ </picture>
253
+ </a>
254
+ </p>
255
+
256
+ <p align="center">
257
+ <sub>Part of <a href="https://github.com/rivi-software/tools#readme">RIVI tools</a>, the packages <a href="https://rivi.software">RIVI Software</a> builds its own apps with.</sub>
258
+ </p>
@@ -0,0 +1,35 @@
1
+ /** A file in the assets directory. */
2
+ export type AssetFile = {
3
+ /** Its path in the assets directory, with `/` between segments: the one `.assetsignore` matches. */
4
+ asset: string;
5
+ /** Its absolute path. */
6
+ file: string;
7
+ };
8
+ /** The assets directory's files, split into those Wrangler uploads and those it leaves out. */
9
+ export type Assets = {
10
+ served: AssetFile[];
11
+ ignored: AssetFile[];
12
+ };
13
+ /**
14
+ * Whether Wrangler leaves an asset out of the upload, for the `.assetsignore` text (undefined
15
+ * when there is none). Wrangler matches each path in the directory with the `ignore` package
16
+ * (gitignore syntax), against the file's lines and its own patterns for `.assetsignore`,
17
+ * `_redirects` and `_headers` (workers-sdk, packages/workers-shared/utils/helpers.ts,
18
+ * `createAssetsIgnoreFunction`). This builds the same matcher.
19
+ */
20
+ export declare function assetsIgnoreMatcher(assetsIgnore: string | undefined): (asset: string) => boolean;
21
+ /** The files of the assets directory, as Wrangler's upload sees them. */
22
+ export declare function readAssets(dir: string): Promise<Assets>;
23
+ /** A source map the assets would serve. */
24
+ export type ServedSourceMap = {
25
+ kind: "served-source-map" | "inline-source-map";
26
+ /** The asset's absolute path. */
27
+ file: string;
28
+ };
29
+ /** Whether a script or style ends with its source map inline. */
30
+ export declare function hasInlineSourceMap(code: string): boolean;
31
+ /**
32
+ * The source maps among the served assets: a `.map` file, or a script or style that carries its
33
+ * map inline. `text` reads a served script or style.
34
+ */
35
+ export declare function findServedSourceMaps(served: readonly AssetFile[], text: (file: string) => Promise<string>): Promise<ServedSourceMap[]>;
package/dist/assets.js ADDED
@@ -0,0 +1,63 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import ignore from "ignore";
4
+ import { posixRelative } from "./source-map.js";
5
+ /**
6
+ * Whether Wrangler leaves an asset out of the upload, for the `.assetsignore` text (undefined
7
+ * when there is none). Wrangler matches each path in the directory with the `ignore` package
8
+ * (gitignore syntax), against the file's lines and its own patterns for `.assetsignore`,
9
+ * `_redirects` and `_headers` (workers-sdk, packages/workers-shared/utils/helpers.ts,
10
+ * `createAssetsIgnoreFunction`). This builds the same matcher.
11
+ */
12
+ export function assetsIgnoreMatcher(assetsIgnore) {
13
+ const patterns = ["/.assetsignore", "/_redirects", "/_headers"];
14
+ if (assetsIgnore !== undefined)
15
+ patterns.push(...assetsIgnore.split("\n"));
16
+ const matcher = ignore.default().add(patterns);
17
+ return (asset) => matcher.ignores(asset);
18
+ }
19
+ /** The files of the assets directory, as Wrangler's upload sees them. */
20
+ export async function readAssets(dir) {
21
+ const entries = await readdir(dir, { recursive: true, withFileTypes: true });
22
+ const assetsIgnore = await readFile(path.join(dir, ".assetsignore"), "utf8").catch((error) => {
23
+ if (error.code === "ENOENT")
24
+ return undefined;
25
+ throw error;
26
+ });
27
+ const ignored = assetsIgnoreMatcher(assetsIgnore);
28
+ const assets = { served: [], ignored: [] };
29
+ for (const entry of entries) {
30
+ if (!entry.isFile())
31
+ continue;
32
+ const file = path.join(entry.parentPath, entry.name);
33
+ const asset = { asset: posixRelative(dir, file), file };
34
+ (ignored(asset.asset) ? assets.ignored : assets.served).push(asset);
35
+ }
36
+ const order = (a, b) => a.asset < b.asset ? -1 : a.asset > b.asset ? 1 : 0;
37
+ assets.served.sort(order);
38
+ assets.ignored.sort(order);
39
+ return assets;
40
+ }
41
+ /** Scripts and styles, which can carry a source map inline. */
42
+ const SCRIPT_OR_STYLE = /\.(?:[cm]?js|css)$/i;
43
+ /** The `sourceMappingURL` comment that ends a file and holds the map itself. */
44
+ const INLINE_SOURCE_MAP = /^\/[/*][#@] sourceMappingURL=data:[^\s*]*[\s*/]*$/;
45
+ /** Whether a script or style ends with its source map inline. */
46
+ export function hasInlineSourceMap(code) {
47
+ const at = code.lastIndexOf("sourceMappingURL=data:");
48
+ return at >= 4 && INLINE_SOURCE_MAP.test(code.slice(at - 4));
49
+ }
50
+ /**
51
+ * The source maps among the served assets: a `.map` file, or a script or style that carries its
52
+ * map inline. `text` reads a served script or style.
53
+ */
54
+ export async function findServedSourceMaps(served, text) {
55
+ const found = [];
56
+ for (const { asset, file } of served) {
57
+ if (/\.map$/i.test(asset))
58
+ found.push({ kind: "served-source-map", file });
59
+ else if (SCRIPT_OR_STYLE.test(asset) && hasInlineSourceMap(await text(file)))
60
+ found.push({ kind: "inline-source-map", file });
61
+ }
62
+ return found;
63
+ }
@@ -0,0 +1,103 @@
1
+ import { type BundledModule, type ForbiddenRule } from "./forbidden.js";
2
+ import { type AllowedSecret, type SecretPattern } from "./secrets.js";
3
+ export type WorkerBundleCheckOptions = {
4
+ /**
5
+ * The Worker's build output: the directory with its JavaScript modules and their source maps,
6
+ * such as `dist/server`. Relative to `cwd`.
7
+ */
8
+ serverDir: string;
9
+ /**
10
+ * The Worker's static assets directory, the one `assets.directory` names in the Wrangler
11
+ * config, such as `dist/client`. Relative to `cwd`. Leave it out for a Worker without assets.
12
+ */
13
+ assetsDir?: string;
14
+ /** The modules and markers the Worker must not carry. */
15
+ forbidden?: readonly ForbiddenRule[];
16
+ /** Findings of values shaped like secrets that are known to be safe. */
17
+ allowedSecrets?: readonly AllowedSecret[];
18
+ /** Kinds of secret to look for besides {@link SECRET_PATTERNS}. */
19
+ secretPatterns?: readonly SecretPattern[];
20
+ /**
21
+ * The directory paths are relative to, and module paths are named from: the project's, with
22
+ * its `node_modules`. Default `process.cwd()`.
23
+ */
24
+ cwd?: string;
25
+ };
26
+ /** A problem the check found. Paths are relative to `cwd`, with `/` between segments. */
27
+ export type WorkerBundleProblem = ({
28
+ /** A Worker file holds modules a rule forbids, largest first. */
29
+ kind: "forbidden-module";
30
+ rule: ForbiddenRule;
31
+ file: string;
32
+ modules: BundledModule[];
33
+ } | {
34
+ /** A Worker file contains a forbidden marker. */
35
+ kind: "forbidden-marker";
36
+ rule: ForbiddenRule;
37
+ file: string;
38
+ } | {
39
+ /** A rule's module pattern or marker matches nothing the project has. */
40
+ kind: "stale-rule";
41
+ rule: ForbiddenRule;
42
+ part: "module" | "marker";
43
+ } | {
44
+ /** A Worker file has no source map, so the rules cannot see its modules. */
45
+ kind: "missing-source-map";
46
+ file: string;
47
+ } | {
48
+ /** A `.map` file the assets would serve. */
49
+ kind: "served-source-map";
50
+ file: string;
51
+ } | {
52
+ /** A script or style the assets would serve with its source map inline. */
53
+ kind: "inline-source-map";
54
+ file: string;
55
+ } | {
56
+ /** A value shaped like a secret, in a Worker file or a served asset. */
57
+ kind: "secret";
58
+ file: string;
59
+ /** The pattern's name. */
60
+ name: string;
61
+ /** The first 16 hexadecimal digits of the value's SHA-256; the value is never reported. */
62
+ fingerprint: string;
63
+ line: number;
64
+ /** The module the value comes from, when the file has a source map. */
65
+ module?: string;
66
+ } | {
67
+ /** An allowed secret that matches no finding. */
68
+ kind: "stale-allowed-secret";
69
+ allowed: AllowedSecret;
70
+ }) & {
71
+ /** The problem in one line, for a report. */
72
+ message: string;
73
+ };
74
+ /** What the check read, and what it found. */
75
+ export type WorkerBundleReport = {
76
+ /** The Worker's build output, as given. */
77
+ serverDir: string;
78
+ /** The Worker's JavaScript files. */
79
+ workerFiles: string[];
80
+ /** How many modules the Worker's source maps name. */
81
+ modules: number;
82
+ /** The assets directory, when one was given. */
83
+ assetsDir?: string;
84
+ /** How many files Wrangler would upload from the assets directory. */
85
+ servedAssets: number;
86
+ /** How many files its `.assetsignore` leaves out. */
87
+ ignoredAssets: number;
88
+ /** How many findings of secrets the allow-list let through. */
89
+ allowedSecrets: number;
90
+ problems: WorkerBundleProblem[];
91
+ };
92
+ /** Thrown by {@link checkWorkerBundle} when it finds problems: they are in `report.problems`. */
93
+ export declare class WorkerBundleCheckError extends Error {
94
+ readonly report: WorkerBundleReport;
95
+ constructor(report: WorkerBundleReport);
96
+ }
97
+ /**
98
+ * Checks a Worker's build: no forbidden module or marker in the Worker, no source map among the
99
+ * assets Wrangler would serve, and no value shaped like a secret in either. Resolves to the
100
+ * report when it finds nothing; rejects with a {@link WorkerBundleCheckError} that lists the
101
+ * problems otherwise. Run it after the build.
102
+ */
103
+ export declare function checkWorkerBundle(options: WorkerBundleCheckOptions): Promise<WorkerBundleReport>;