chore: remove old bundles (#40135)

This commit is contained in:
Pavel Feldman
2026-04-09 15:41:38 -07:00
committed by GitHub
parent a120dec6dc
commit 979a70bc7f
66 changed files with 2604 additions and 12840 deletions
+1 -1
View File
@@ -12,5 +12,5 @@ See [CLAUDE.md](../../../CLAUDE.md) for monorepo structure, build/test/lint comm
- [Library Architecture](library.md) — client/server/dispatcher structure, protocol layer, DEPS rules
- [Adding and Modifying APIs](api.md) — define API docs, implement client/server, add tests
- [MCP Tools and CLI Commands](tools.md) — add MCP tools, CLI commands, config options
- [Vendoring Dependencies](vendor.md) — bundle third-party npm packages into playwright-core or playwright
- [Vendor Dependencies & Bundling](vendor.md) — utilsBundle, coreBundle, babelBundle; adding vendored npm packages; DEPS.list; `check_deps`
- [Updating WebKit Safari Version](webkit-safari-version.md) — update the Safari version string in the WebKit user-agent
+4
View File
@@ -23,6 +23,8 @@ packages/playwright-core/src/
Each directory has a `DEPS.list` constraining its imports. These are enforced by `npm run flint`.
Entries can be relative paths, alias paths (`@isomorphic/**`, `@utils/**`), or `node_modules/<pkg>` to allow a specific npm package import. The `"strict"` marker disables inheritance from parent folders. Section headers like `[filename.ts]` scope rules to a single file.
**client/** can import from:
- `../protocol/` — validators and channel types
- `../utils/isomorphic` — shared utilities
@@ -39,6 +41,8 @@ Each directory has a `DEPS.list` constraining its imports. These are enforced by
**Key rule:** Client code NEVER imports server code. Server code NEVER imports client code. They communicate only through the protocol.
**Vendored npm packages** (anything under `node_modules/`) go through `src/utilsBundle.ts` — a single bundled file that re-exports the vendored symbols. Adding a new dep or changing a DEPS.list entry for vendored code: see [vendor.md](vendor.md).
## Protocol Layer
### protocol.yml
+222 -172
View File
@@ -1,190 +1,240 @@
# Vendoring (Bundling) a New Dependency
# Vendor Dependencies & Bundling
Playwright vendors third-party npm packages by bundling them with esbuild into self-contained files.
This isolates dependencies, prevents version conflicts, and keeps the published packages lean.
Playwright ships a small number of node_modules inlined into a handful of
pre-built "bundle" files under `lib/`. Everything else is either a source
file compiled per-file, or loaded at runtime from one of the bundles. This
doc covers how the bundling works, how to add or move a vendored package,
and how the dependency checker enforces the contract.
## Architecture Overview
## The Bundles
Each bundle lives under `packages/<package>/bundles/<name>/` and consists of three parts:
### playwright-core
1. **Bundle directory** (`bundles/<name>/`) — has its own `package.json` with the dependencies to vendor, plus a `src/<name>BundleImpl.ts` entry point that imports and re-exports them.
2. **Build configuration** in `utils/build/build.js` — an esbuild entry that bundles the impl file into a single minified CJS file.
3. **Wrapper file** (`src/<name>Bundle.ts`) — a thin typed wrapper that `require()`s the built bundle impl and re-exports symbols with TypeScript types.
| Output | Entry | Purpose |
|---|---|---|
| `lib/utilsBundle.js` | `src/utilsBundle.ts` | Vendored npm packages (`debug`, `mime`, `ws`, `yauzl`, `yazl`, `@modelcontextprotocol/sdk`, `graceful-fs`, …). The single home for third-party runtime code in playwright-core. |
| `lib/coreBundle.js` | `src/coreBundle.ts` | Re-exports of playwright-core's own modules (`client`, `iso`, `utils`, `cli`, `server`, `registry`, …) as namespaces. Inlines almost all playwright-core source except `utilsBundle`. |
| `lib/server/electron/loader.js` | `src/server/electron/loader.ts` | Tiny Electron preload shim. |
Data flow:
```
bundles/<name>/package.json (declares npm deps)
→ npm ci → node_modules/
bundles/<name>/src/<name>BundleImpl.ts (imports from node_modules, re-exports)
→ esbuild (bundle + minify) →
lib/<name>BundleImpl.js (single self-contained file)
←
src/<name>Bundle.ts (typed wrapper, require('./...BundleImpl'))
→ esbuild (normal compile) →
lib/<name>Bundle.js (used by application code)
The `dynamicImportToRequirePlugin` in `utils/build/build.js` rewrites
vendored npm imports at **bundle time**. For example, a playwright-core
source file containing
```ts
import debug from 'debug';
```
## Step-by-Step: Adding a New Bundle
gets rewritten to
### Decide which package it belongs to
- `packages/playwright-core/bundles/` — for core browser automation deps (networking, compression, protocols, etc.)
- `packages/playwright/bundles/` — for test runner deps (assertion libs, transpilers, file watchers, etc.)
### 1. Create the bundle directory
```
packages/<package>/bundles/<name>/
├── package.json
└── src/
└── <name>BundleImpl.ts
```js
const debug = require('./utilsBundle').debug;
```
### 2. Create `package.json`
before the bundler sees it — so the vendored package never gets inlined
into `coreBundle.js`. The mapping from npm package name to utilsBundle
export key lives in `utils/build/utilsBundleMapping.js`.
Minimal private package with only the deps you want to bundle:
### playwright
```json
{
"name": "<name>-bundle",
"version": "0.0.1",
"private": true,
"dependencies": {
"some-lib": "^1.2.3"
},
"devDependencies": {
"@types/some-lib": "^1.2.0"
}
}
| Output | Entry | Purpose |
|---|---|---|
| `lib/transform/babelBundle.js` | `src/transform/babelBundle.ts` | Wraps `@babel/core`, `@babel/traverse`, `@babel/code-frame`, plugins. Shared by every consumer that needs babel. |
| `lib/transform/esmLoader.js` | `src/transform/esmLoader.ts` | Node ESM loader registered via `node:module.register()`. Output sits next to `babelBundle.js` so its `./babelBundle` sibling require resolves correctly. |
| `lib/common/index.js` | `src/common/index.ts` | Barrel of `common/*` + `transform/*` (compilationCache, test, configLoader, fixtures, globals, …). State-holding singletons (currentTestInfo, memoryCache, …) live here. |
| `lib/runner/index.js` | `src/runner/index.ts` | Barrel of `runner/*` + `reporters/*` + `plugins/*`. |
| `lib/matchers/expect.js` | `src/matchers/expect.ts` | Jest-style matchers with `expect` inlined. |
| `lib/worker/workerProcessEntry.js` | `src/worker/workerProcessEntry.ts` | Entry point spawned per test worker. |
| `lib/loader/loaderProcessEntry.js` | `src/loader/loaderProcessEntry.ts` | Entry point for the test file loader sub-process. |
| `lib/runner/uiModeReporter.js` | `src/runner/uiModeReporter.ts` | Loaded by `require.resolve` from testServer; passed to child workers as a file path. |
The `common` and `runner` bundles externalize `../transform/babelBundle`
(among other things) so babel code is not duplicated across them. The
`lib/transform/transform.ts` module uses `libPath('transform', 'babelBundle')`
(absolute path via `package.ts` root) to load the babel bundle at runtime,
so it works regardless of which bundle has inlined it.
### Per-file emits (no bundle)
Files outside the bundled entries are compiled 1:1 by esbuild and land
under `lib/` mirroring their source layout. The per-file step in
`utils/build/build.js` lists the specific directories for the
`playwright` package (`cli/`, `agents/`, `mcp/`, root `*.ts`, and a few
targeted files like `runner/uiModeReporter.ts`). Other packages
(`playwright-test`, `html-reporter`, `trace-viewer`, …) are compiled by
the generic per-package loop.
## Bundle Sidecars
Every bundled output has two sidecar files next to it:
- **`<bundle>.js.txt`** — human-readable report listing inlined files
(sorted by path, with per-file KB sizes), externals, and total bytes.
Written by `utils/build/bundle_report.js`.
- **`<bundle>.js.LICENSE`** — third-party license texts for every npm
package whose source got inlined. Populated from `license-checker`,
memoized once per build invocation. Consumed by the top-level
`ThirdPartyNotices.txt` files, which just point readers at the
per-bundle sidecars.
Both sidecars are included in the published npm package (controlled by
`packages/*/.npmignore`).
## Adding a Vendored NPM Dependency
Three pieces need to line up when adding a new npm package that you want
inlined into `utilsBundle` (i.e., loaded through `require('./utilsBundle').<key>`):
1. **Install the package.** Add it to the root `package.json`
`devDependencies`. The monorepo root is where esbuild resolves modules
from; the workspace root's `node_modules/<pkg>` is what gets inlined
into `utilsBundle.js`.
2. **Export it from `src/utilsBundle.ts`.** Pick one of:
```ts
import fooLibrary from 'foo';
export const foo = fooLibrary; // default
import * as fooLibrary from 'foo';
export const foo = fooLibrary; // namespace
export { namedSymbol } from 'foo'; // named
```
Type-only exports (`export type { X } from 'foo'`) are valid and
don't affect runtime.
3. **Add a mapping entry to `utils/build/utilsBundleMapping.js`**:
```js
'foo': { default: 'foo' },
// or:
'foo': { namespace: 'foo' },
// or:
'foo': { named: { namedSymbol: 'fooNamedSymbol' } },
```
- `default` — matches `import foo from 'foo'` and rewrites to
`require('./utilsBundle').foo`.
- `namespace` — matches `import * as foo from 'foo'`.
- `named` — matches `import { namedSymbol } from 'foo'` and rewrites
to `const { fooNamedSymbol: namedSymbol } = require('./utilsBundle')`.
- Multiple forms can coexist in one entry (see `yauzl`).
- The map key is the exact npm specifier as written in source
(including subpaths like `'@babel/core'` or `'colors/safe'`).
4. **Update DEPS.list.** The file or its enclosing folder's `DEPS.list`
must authorize `node_modules/<pkg>` — otherwise `npm run flint`'s
`check_deps` step complains about the disallowed external dependency.
If the DEPS.list authorizes it, the package.json-dependencies check
also gets skipped for that file.
5. **Run `npm run flint`.** It runs `check_deps`, `tsc`, `eslint`, and
`doc` in parallel. A missing mapping typically surfaces as `node_modules/`
references leaking into `coreBundle.js` — the build fails hard via
`assertCoreBundleHasNoNodeModules()`.
## In-tree Third-Party Helpers
Some vendored code isn't a published npm package but lives in-tree at
`packages/playwright-core/src/server/utils/third_party/` (e.g.
`extractZip.ts`, `lockfile.ts`). These are TypeScript files, not
node_modules. They're exposed to callers via two different routes:
- **Through `coreBundle.utils`.** Re-exported from
`src/server/utils/index.ts` via `export * from './third_party/extractZip'`
etc. Callers import via the `@utils/*` path alias:
```ts
import { extractZip } from '@utils/third_party/extractZip';
```
The alias is rewritten at bundle time to
`require('playwright-core/lib/coreBundle').utils.extractZip`.
- **Transitive npm deps via utilsBundle.** When a third_party TS file
imports an npm package (e.g., `lockfile.ts` imports `graceful-fs`,
`retry`, `signal-exit`), those are still rewritten through
`utilsBundle` — so the mapping in `utilsBundleMapping.js` must list
them too.
## DEPS.list
Every directory under `packages/*/src/` has a `DEPS.list` constraining
its imports. Three kinds of entries:
| Syntax | Meaning |
|---|---|
| `./somefile.ts`, `@isomorphic/**` | Relative or alias source import allowed |
| `node_modules/<pkg>` | npm package import allowed (exact specifier match) |
| `"strict"` | No other DEPS inherited; only what's listed is allowed |
Section headers `[filename.ts]` scope rules to a single file. The
top-level `[*]` (or no header) applies to everything in the folder plus
subfolders that don't have their own DEPS.list.
A DEPS.list entry of `node_modules/<pkg>` now shortcuts both layers of
the check: the "disallowed external dependency" error AND the
"dependencies not declared in package.json" report. The per-file
allowlist is the contract — no need to also list the dep in
`packages/<pkg>/package.json` if only one file uses it and it's
authorized there.
### check_deps.js
`utils/check_deps.js` walks the TypeScript program, visits every
`import` in `src/**`, and for each npm specifier:
1. Skips if the source file's DEPS.list authorizes `node_modules/<specifier>`.
2. Otherwise records the top-level package name along with the file
path that imported it.
3. Subtracts `peerDependencies`, `VENDORED_PACKAGES` (from
`utilsBundleMapping.js`), and any package that resolves without
`node_modules/` (a core module or a local file).
4. Subtracts packages listed in `packages/<pkg>/package.json`
`dependencies`.
5. Anything left is reported with the specific file(s) that import it.
The missing-dep error now includes file paths:
```
Dependencies are not declared in package.json:
expect
src/matchers/expect.ts
@babel/core
src/transform/babelBundle.ts
```
Then run `npm install` inside the bundle directory to generate `package-lock.json`.
## Bundle-Level Externalization (onResolve plugins)
### 3. Create `src/<name>BundleImpl.ts`
Two onResolve plugins in `utils/build/build.js` normalize relative
imports to the sibling bundle at consumer output level:
This is the esbuild entry point. Import from `node_modules` and re-export:
- **`externalizeUtilsBundlePlugin`** — matches any relative specifier
ending in `/utilsBundle` or `/utilsBundle.js` (at any depth: `./utilsBundle`,
`../utilsBundle`, `../../utilsBundle`) and marks it external with the
single spelling `./utilsBundle`. This only applies to the coreBundle
build because coreBundle inlines source files from all over
`playwright-core/src/` (different depths) and needs a single consistent
external specifier that resolves correctly at runtime from
`lib/coreBundle.js`.
- The **babelBundle** case is handled differently — instead of a plugin,
consumers' source/output depths are aligned:
- `esmLoader` bundle output is placed at `lib/transform/esmLoader.js`
(same folder as `babelBundle.js`), so `./babelBundle` from
`transform.ts` resolves correctly.
- `common` and `runner` bundles declare `'../transform/babelBundle'`
as a static external; their outputs are at `lib/common/index.js` and
`lib/runner/index.js`, both at depth 1, so the source-relative
specifier resolves naturally.
- `transform.ts`'s own `require('./babelBundle')` was replaced with
`require(libPath('transform', 'babelBundle'))` — an absolute path
computed at runtime via `package.ts`, which works from any bundle.
```typescript
// For default exports:
import someLibrary from 'some-lib';
export const someLib = someLibrary;
## Quick Reference
// For named exports:
export { SomeClass } from 'some-lib';
// For namespace imports:
import * as someLibrary from 'some-lib';
export const someLib = someLibrary;
// For vendored/third-party code that can't be bundled:
const custom = require('./third_party/custom');
export const customThing = custom;
```
### 4. Register the bundle in `utils/build/build.js`
Add an entry to the `bundles` array (around line 246):
```javascript
bundles.push({
modulePath: 'packages/<package>/bundles/<name>',
entryPoints: ['src/<name>BundleImpl.ts'],
// Use outdir for a single .js file alongside other lib files:
outdir: 'packages/<package>/lib',
// OR use outfile for output in a subdirectory (needed if bundle has non-JS assets):
// outfile: 'packages/<package>/lib/<name>BundleImpl/index.js',
// Optional: deps that should NOT be bundled (must be installed at runtime):
// external: ['express'],
// Optional: redirect imports to custom implementations:
// alias: { 'some-module': 'custom-impl.ts' },
});
```
**`outdir` vs `outfile`:**
- `outdir` — output goes to `lib/<name>BundleImpl.js` (most bundles use this)
- `outfile` — output goes to `lib/<name>BundleImpl/index.js` (use when you need to copy companion files like binaries next to the bundle)
### 5. Create the typed wrapper `src/<name>Bundle.ts`
This file lives in the main package source (NOT in the bundle directory). It provides TypeScript types while loading the bundled code at runtime:
```typescript
// packages/<package>/src/<name>Bundle.ts
// (or src/subdir/<name>Bundle.ts if it belongs in a subdirectory)
export const someLib: typeof import('../bundles/<name>/node_modules/some-lib')
= require('./<name>BundleImpl').someLib;
export const SomeClass: typeof import('../bundles/<name>/node_modules/some-lib').SomeClass
= require('./<name>BundleImpl').SomeClass;
// Re-export types if needed:
export type { SomeType } from '../bundles/<name>/node_modules/some-lib';
```
The pattern is: `typeof import('../bundles/<name>/node_modules/...')` for the type, `require('./<name>BundleImpl').<export>` for the value.
If the wrapper lives in a subdirectory (e.g. `src/common/<name>Bundle.ts`), adjust the `outdir` accordingly so the BundleImpl ends up next to the compiled wrapper:
```javascript
// in build.js
outdir: 'packages/<package>/lib/common',
```
### 6. Build and verify
```bash
npm run build
```
Or if watch is running, it will pick up changes automatically.
### 7. Use the bundle in application code
Import from the wrapper file, never from the bundle directory or `node_modules` directly:
```typescript
import { someLib } from '../<name>Bundle';
```
## Existing Bundles Reference
### playwright-core bundles
| Bundle | Deps | Output |
|--------|------|--------|
| `utils` | colors, commander, debug, diff, dotenv, graceful-fs, https-proxy-agent, jpeg-js, mime, minimatch, open, pngjs, progress, proxy-from-env, socks-proxy-agent, ws, yaml | `lib/utilsBundleImpl/index.js` |
| `zip` | yauzl, yazl, get-stream, debug | `lib/zipBundleImpl.js` |
| `mcp` | @modelcontextprotocol/sdk, zod, zod-to-json-schema | `lib/mcpBundleImpl/index.js` |
### playwright bundles
| Bundle | Deps | Output |
|--------|------|--------|
| `utils` | chokidar, enquirer, json5, source-map-support, stoppable, unified, remark-parse | `lib/utilsBundleImpl.js` |
| `babel` | ~30 @babel/* packages | `lib/transform/babelBundleImpl.js` |
| `expect` | expect, jest-matcher-utils | `lib/common/expectBundleImpl.js` |
## Advanced Patterns
### Adding a dep to an existing bundle
If the dep logically belongs with an existing bundle (e.g. a new utility lib → `utils` bundle):
1. Add the dependency to the existing `bundles/<name>/package.json`
2. Run `npm install` in that bundle directory
3. Add the import/export to the existing `src/<name>BundleImpl.ts`
4. Add the typed re-export to the existing `src/<name>Bundle.ts`
### Vendored third-party code
If a package can't be bundled by esbuild (e.g. it uses dynamic requires or has runtime file dependencies), place a modified copy in `bundles/<name>/src/third_party/` and require it from the BundleImpl. See `bundles/zip/src/third_party/extract-zip.js` for an example.
### External dependencies
Use `external: ['pkg']` in the build.js config when a dependency should NOT be bundled — e.g. optional peer deps that users install themselves. These must be available at runtime in the consumer's `node_modules`.
### Module aliases
Use `alias: { 'module-name': 'local-file.ts' }` to replace a dependency with a custom local implementation. The alias path is relative to the bundle's `modulePath`. See the `mcp` bundle's `raw-body` alias for an example.
- To add a new vendored npm dep: root `package.json` → `utilsBundle.ts`
export → `utilsBundleMapping.js` entry → DEPS.list → `npm run flint`.
- To add a new in-tree third-party helper: drop the `.ts` file under
`server/utils/third_party/`, re-export from `server/utils/index.ts`,
and use `@utils/third_party/<name>` at call sites.
- To add a new bundle entry: add an `EsbuildStep` in
`utils/build/build.js`, pick output location so relative externals
line up with runtime layout, and list externals for every sibling
bundle the entry should not inline.
- To expose a bundle file as a package subpath: add it to the
`exports` field in `packages/<pkg>/package.json`.
- To check what's inside a bundle: read the `.js.txt` sidecar next to
the output.
+1051 -72
View File
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -58,7 +58,7 @@
"@eslint/compat": "^1.3.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.34.0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@modelcontextprotocol/sdk": "^1.28.0",
"@octokit/graphql-schema": "^15.26.0",
"@stylistic/eslint-plugin": "^5.2.3",
"@types/codemirror": "^5.60.7",
@@ -94,6 +94,8 @@
"commander": "^13.0.0",
"concurrently": "^6.2.1",
"cross-env": "^7.0.3",
"debug": "^4.3.4",
"diff": "^8.0.3",
"dotenv": "^16.4.5",
"electron": "^39.8.4",
"enquirer": "2.3.6",
@@ -105,7 +107,10 @@
"eslint-plugin-react-hooks": "^5.2.0",
"formidable": "^2.1.1",
"get-east-asian-width": "1.3.0",
"get-stream": "^5.1.0",
"graceful-fs": "4.2.10",
"https-proxy-agent": "7.0.6",
"ini": "^6.0.0",
"jpeg-js": "0.4.4",
"json5": "2.2.3",
"license-checker": "^25.0.1",
@@ -115,8 +120,11 @@
"open": "8.4.0",
"pngjs": "6.0.0",
"progress": "2.0.3",
"proxy-from-env": "2.0.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"retry": "0.12.0",
"signal-exit": "3.0.7",
"socks-proxy-agent": "8.0.5",
"source-map-support": "0.5.21",
"ssim.js": "^3.5.0",
+3 -1
View File
@@ -4,6 +4,8 @@
# Include sources from lib, but not map files.
!lib/**/*.js
# Include bundle license sidecars.
!lib/**/*.LICENSE
# Include web assets for recorder, trace viewer, etc.
!lib/**/*.css
!lib/**/*.html
@@ -12,7 +14,7 @@
!lib/**/*.ttf
!lib/**/*.json
!lib/**/*.md
!lib/utilsBundleImpl/xdg-open
!lib/xdg-open
!lib/**/manifest.webmanifest
# Exclude injected files. A preprocessed version of these is included via lib/generated.
# See packages/injected/src/README.md.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,40 +0,0 @@
{
"name": "utils-bundle",
"version": "0.0.1",
"private": true,
"dependencies": {
"@modelcontextprotocol/sdk": "^1.28.0",
"chokidar": "3.6.0",
"colors": "1.4.0",
"commander": "^13.0.0",
"debug": "^4.3.4",
"diff": "^8.0.3",
"dotenv": "^16.4.5",
"enquirer": "2.3.6",
"get-east-asian-width": "1.3.0",
"get-stream": "^5.1.0",
"graceful-fs": "4.2.10",
"https-proxy-agent": "7.0.6",
"ini": "^6.0.0",
"jpeg-js": "0.4.4",
"json5": "2.2.3",
"mime": "^3.0.0",
"minimatch": "^3.1.4",
"open": "8.4.0",
"pngjs": "6.0.0",
"progress": "2.0.3",
"proxy-from-env": "2.0.0",
"retry": "0.12.0",
"signal-exit": "3.0.7",
"socks-proxy-agent": "8.0.5",
"source-map-support": "0.5.21",
"stoppable": "1.1.0",
"ws": "8.17.1",
"yauzl": "3.2.1",
"yazl": "2.5.1",
"yaml": "^2.8.3",
"zod": "^4.3.6",
"zod-to-json-schema": "^3.25.1"
},
"devDependencies": {}
}
@@ -1,56 +0,0 @@
/**
* Copyright (c) 2014 Max Ogden and other contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Based on the type definitions for extract-zip 1.6
// Definitions by: Mizunashi Mana <https://github.com/mizunashi-mana>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e69b58e/types/extract-zip/index.d.ts
import { Entry, ZipFile } from 'yauzl';
declare namespace extract {
interface Options {
/** The path to the directory where the extracted files are written */
dir: string;
/** Directory Mode (permissions), defaults to `0o755` */
defaultDirMode?: number;
/** File Mode (permissions), defaults to `0o644` */
defaultFileMode?: number;
/**
* If present, will be called with (entry, zipfile),
* entry is every entry from the zip file forwarded
* from the entry event from yauzl. zipfile is the
* yauzl instance
*/
onEntry?: (entry: Entry, zipfile: ZipFile) => void;
}
}
declare function extract(
zipPath: string,
opts: extract.Options,
): Promise<void>;
export = extract;
@@ -1,199 +0,0 @@
/**
* Copyright (c) 2014 Max Ogden and other contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const debug = require('debug')('extract-zip')
// eslint-disable-next-line node/no-unsupported-features/node-builtins
const { createWriteStream, promises: fs } = require('fs')
const getStream = require('get-stream')
const path = require('path')
const { promisify } = require('util')
const stream = require('stream')
const yauzl = require('yauzl')
const openZip = promisify(yauzl.open)
const pipeline = promisify(stream.pipeline)
class Extractor {
constructor (zipPath, opts) {
this.zipPath = zipPath
this.opts = opts
}
async extract () {
debug('opening', this.zipPath, 'with opts', this.opts)
this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
this.canceled = false
return new Promise((resolve, reject) => {
this.zipfile.on('error', err => {
this.canceled = true
reject(err)
})
this.zipfile.readEntry()
this.zipfile.on('close', () => {
if (!this.canceled) {
debug('zip extraction complete')
resolve()
}
})
this.zipfile.on('entry', async entry => {
/* istanbul ignore if */
if (this.canceled) {
debug('skipping entry', entry.fileName, { cancelled: this.canceled })
return
}
debug('zipfile entry', entry.fileName)
if (entry.fileName.startsWith('__MACOSX/')) {
this.zipfile.readEntry()
return
}
const destDir = path.dirname(path.join(this.opts.dir, entry.fileName))
try {
await fs.mkdir(destDir, { recursive: true })
const canonicalDestDir = await fs.realpath(destDir)
const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir)
if (relativeDestDir.split(path.sep).includes('..')) {
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`)
}
await this.extractEntry(entry)
debug('finished processing', entry.fileName)
this.zipfile.readEntry()
} catch (err) {
this.canceled = true
this.zipfile.close()
reject(err)
}
})
})
}
async extractEntry (entry) {
/* istanbul ignore if */
if (this.canceled) {
debug('skipping entry extraction', entry.fileName, { cancelled: this.canceled })
return
}
if (this.opts.onEntry) {
this.opts.onEntry(entry, this.zipfile)
}
const dest = path.join(this.opts.dir, entry.fileName)
// convert external file attr int into a fs stat mode int
const mode = (entry.externalFileAttributes >> 16) & 0xFFFF
// check if it's a symlink or dir (using stat mode constants)
const IFMT = 61440
const IFDIR = 16384
const IFLNK = 40960
const symlink = (mode & IFMT) === IFLNK
let isDir = (mode & IFMT) === IFDIR
// Failsafe, borrowed from jsZip
if (!isDir && entry.fileName.endsWith('/')) {
isDir = true
}
// check for windows weird way of specifying a directory
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
const madeBy = entry.versionMadeBy >> 8
if (!isDir) isDir = (madeBy === 0 && entry.externalFileAttributes === 16)
debug('extracting entry', { filename: entry.fileName, isDir: isDir, isSymlink: symlink })
const procMode = this.getExtractedMode(mode, isDir) & 0o777
// always ensure folders are created
const destDir = isDir ? dest : path.dirname(dest)
const mkdirOptions = { recursive: true }
if (isDir) {
mkdirOptions.mode = procMode
}
debug('mkdir', { dir: destDir, ...mkdirOptions })
await fs.mkdir(destDir, mkdirOptions)
if (isDir) return
debug('opening read stream', dest)
const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry)
if (symlink) {
const link = await getStream(readStream)
debug('creating symlink', link, dest)
await fs.symlink(link, dest)
} else {
await pipeline(readStream, createWriteStream(dest, { mode: procMode }))
}
}
getExtractedMode (entryMode, isDir) {
let mode = entryMode
// Set defaults, if necessary
if (mode === 0) {
if (isDir) {
if (this.opts.defaultDirMode) {
mode = parseInt(this.opts.defaultDirMode, 10)
}
if (!mode) {
mode = 0o755
}
} else {
if (this.opts.defaultFileMode) {
mode = parseInt(this.opts.defaultFileMode, 10)
}
if (!mode) {
mode = 0o644
}
}
}
return mode
}
}
module.exports = async function (zipPath, opts) {
debug('creating target directory', opts.dir)
if (!path.isAbsolute(opts.dir)) {
throw new Error('Target directory is expected to be absolute')
}
await fs.mkdir(opts.dir, { recursive: true })
opts.dir = await fs.realpath(opts.dir)
return new Extractor(zipPath, opts).extract()
}
@@ -1,404 +0,0 @@
/**
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Made With MOXY Lda <[email protected]>
* Modifications copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
'use strict';
const path = require('path');
const fs = require('graceful-fs');
const retry = require('retry');
const onExit = require('signal-exit');
const locks = {};
const cacheSymbol = Symbol();
function probe(file, fs, callback) {
const cachedPrecision = fs[cacheSymbol];
if (cachedPrecision) {
return fs.stat(file, (err, stat) => {
/* istanbul ignore if */
if (err) {
return callback(err);
}
callback(null, stat.mtime, cachedPrecision);
});
}
// Set mtime by ceiling Date.now() to seconds + 5ms so that it's "not on the second"
const mtime = new Date((Math.ceil(Date.now() / 1000) * 1000) + 5);
fs.utimes(file, mtime, mtime, (err) => {
/* istanbul ignore if */
if (err) {
return callback(err);
}
fs.stat(file, (err, stat) => {
/* istanbul ignore if */
if (err) {
return callback(err);
}
const precision = stat.mtime.getTime() % 1000 === 0 ? 's' : 'ms';
// Cache the precision in a non-enumerable way
Object.defineProperty(fs, cacheSymbol, { value: precision });
callback(null, stat.mtime, precision);
});
});
}
function getMtime(precision) {
let now = Date.now();
if (precision === 's') {
now = Math.ceil(now / 1000) * 1000;
}
return new Date(now);
}
function getLockFile(file, options) {
return options.lockfilePath || `${file}.lock`;
}
function resolveCanonicalPath(file, options, callback) {
if (!options.realpath) {
return callback(null, path.resolve(file));
}
// Use realpath to resolve symlinks
// It also resolves relative paths
options.fs.realpath(file, callback);
}
function acquireLock(file, options, callback) {
const lockfilePath = getLockFile(file, options);
// Use mkdir to create the lockfile (atomic operation)
options.fs.mkdir(lockfilePath, (err) => {
if (!err) {
// At this point, we acquired the lock!
// Probe the mtime precision
return probe(lockfilePath, options.fs, (err, mtime, mtimePrecision) => {
// If it failed, try to remove the lock..
/* istanbul ignore if */
if (err) {
options.fs.rmdir(lockfilePath, () => {});
return callback(err);
}
callback(null, mtime, mtimePrecision);
});
}
// If error is not EEXIST then some other error occurred while locking
if (err.code !== 'EEXIST') {
return callback(err);
}
// Otherwise, check if lock is stale by analyzing the file mtime
if (options.stale <= 0) {
return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
}
options.fs.stat(lockfilePath, (err, stat) => {
if (err) {
// Retry if the lockfile has been removed (meanwhile)
// Skip stale check to avoid recursiveness
if (err.code === 'ENOENT') {
return acquireLock(file, { ...options, stale: 0 }, callback);
}
return callback(err);
}
if (!isLockStale(stat, options)) {
return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
}
// If it's stale, remove it and try again!
// Skip stale check to avoid recursiveness
removeLock(file, options, (err) => {
if (err) {
return callback(err);
}
acquireLock(file, { ...options, stale: 0 }, callback);
});
});
});
}
function isLockStale(stat, options) {
return stat.mtime.getTime() < Date.now() - options.stale;
}
function removeLock(file, options, callback) {
// Remove lockfile, ignoring ENOENT errors
options.fs.rmdir(getLockFile(file, options), (err) => {
if (err && err.code !== 'ENOENT') {
return callback(err);
}
callback();
});
}
function updateLock(file, options) {
const lock = locks[file];
// Just for safety, should never happen
/* istanbul ignore if */
if (lock.updateTimeout) {
return;
}
lock.updateDelay = lock.updateDelay || options.update;
lock.updateTimeout = setTimeout(() => {
lock.updateTimeout = null;
// Stat the file to check if mtime is still ours
// If it is, we can still recover from a system sleep or a busy event loop
options.fs.stat(lock.lockfilePath, (err, stat) => {
const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
// If it failed to update the lockfile, keep trying unless
// the lockfile was deleted or we are over the threshold
if (err) {
if (err.code === 'ENOENT' || isOverThreshold) {
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
}
lock.updateDelay = 1000;
return updateLock(file, options);
}
const isMtimeOurs = lock.mtime.getTime() === stat.mtime.getTime();
if (!isMtimeOurs) {
return setLockAsCompromised(
file,
lock,
Object.assign(
new Error('Unable to update lock within the stale threshold'),
{ code: 'ECOMPROMISED' }
));
}
const mtime = getMtime(lock.mtimePrecision);
options.fs.utimes(lock.lockfilePath, mtime, mtime, (err) => {
const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
// Ignore if the lock was released
if (lock.released) {
return;
}
// If it failed to update the lockfile, keep trying unless
// the lockfile was deleted or we are over the threshold
if (err) {
if (err.code === 'ENOENT' || isOverThreshold) {
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
}
lock.updateDelay = 1000;
return updateLock(file, options);
}
// All ok, keep updating..
lock.mtime = mtime;
lock.lastUpdate = Date.now();
lock.updateDelay = null;
updateLock(file, options);
});
});
}, lock.updateDelay);
// Unref the timer so that the nodejs process can exit freely
// This is safe because all acquired locks will be automatically released
// on process exit
// We first check that `lock.updateTimeout.unref` exists because some users
// may be using this module outside of NodeJS (e.g., in an electron app),
// and in those cases `setTimeout` return an integer.
/* istanbul ignore else */
if (lock.updateTimeout.unref) {
lock.updateTimeout.unref();
}
}
function setLockAsCompromised(file, lock, err) {
// Signal the lock has been released
lock.released = true;
// Cancel lock mtime update
// Just for safety, at this point updateTimeout should be null
/* istanbul ignore if */
if (lock.updateTimeout) {
clearTimeout(lock.updateTimeout);
}
if (locks[file] === lock) {
delete locks[file];
}
lock.options.onCompromised(err);
}
// ----------------------------------------------------------
function lock(file, options, callback) {
/* istanbul ignore next */
options = {
stale: 10000,
update: null,
realpath: true,
retries: 0,
fs,
onCompromised: (err) => { throw err; },
...options,
};
options.retries = options.retries || 0;
options.retries = typeof options.retries === 'number' ? { retries: options.retries } : options.retries;
options.stale = Math.max(options.stale || 0, 2000);
options.update = options.update == null ? options.stale / 2 : options.update || 0;
options.update = Math.max(Math.min(options.update, options.stale / 2), 1000);
// Resolve to a canonical file path
resolveCanonicalPath(file, options, (err, file) => {
if (err) {
return callback(err);
}
// Attempt to acquire the lock
const operation = retry.operation(options.retries);
operation.attempt(() => {
acquireLock(file, options, (err, mtime, mtimePrecision) => {
if (operation.retry(err)) {
return;
}
if (err) {
return callback(operation.mainError());
}
// We now own the lock
const lock = locks[file] = {
lockfilePath: getLockFile(file, options),
mtime,
mtimePrecision,
options,
lastUpdate: Date.now(),
};
// We must keep the lock fresh to avoid staleness
updateLock(file, options);
callback(null, (releasedCallback) => {
if (lock.released) {
return releasedCallback &&
releasedCallback(Object.assign(new Error('Lock is already released'), { code: 'ERELEASED' }));
}
// Not necessary to use realpath twice when unlocking
unlock(file, { ...options, realpath: false }, releasedCallback);
});
});
});
});
}
function unlock(file, options, callback) {
options = {
fs,
realpath: true,
...options,
};
// Resolve to a canonical file path
resolveCanonicalPath(file, options, (err, file) => {
if (err) {
return callback(err);
}
// Skip if the lock is not acquired
const lock = locks[file];
if (!lock) {
return callback(Object.assign(new Error('Lock is not acquired/owned by you'), { code: 'ENOTACQUIRED' }));
}
lock.updateTimeout && clearTimeout(lock.updateTimeout); // Cancel lock mtime update
lock.released = true; // Signal the lock has been released
delete locks[file]; // Delete from locks
removeLock(file, options, callback);
});
}
function toPromise(method) {
return (...args) => new Promise((resolve, reject) => {
args.push((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
method(...args);
});
}
// Remove acquired locks on exit
/* istanbul ignore next */
let cleanupInitialized = false;
function ensureCleanup() {
if (cleanupInitialized) {
return;
}
cleanupInitialized = true;
onExit(() => {
for (const file in locks) {
const options = locks[file].options;
try { options.fs.rmdirSync(getLockFile(file, options)); } catch (e) { /* Empty */ }
}
});
}
module.exports.lock = async (file, options) => {
ensureCleanup();
const release = await toPromise(lock)(file, options);
return toPromise(release);
}
@@ -1,107 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/order */
import colorsLibrary from 'colors/safe';
export const colors = colorsLibrary;
import debugLibrary from 'debug';
export const debug = debugLibrary;
import * as iniLibrary from 'ini';
export const ini = iniLibrary;
import * as diffLibrary from 'diff';
export const diff = diffLibrary;
import dotenvLibrary from 'dotenv';
export const dotenv = dotenvLibrary;
export { getProxyForUrl } from 'proxy-from-env';
export { HttpsProxyAgent } from 'https-proxy-agent';
import jpegLibrary from 'jpeg-js';
export const jpegjs = jpegLibrary;
const lockfileLibrary = require('./third_party/lockfile');
export const lockfile = lockfileLibrary;
import mimeLibrary from 'mime';
export const mime = mimeLibrary;
import minimatchLibrary from 'minimatch';
export const minimatch = minimatchLibrary;
import openLibrary from 'open';
export const open = openLibrary;
export { PNG } from 'pngjs';
export { program } from 'commander';
export { Option as ProgramOption } from 'commander';
import progressLibrary from 'progress';
export const progress = progressLibrary;
export { SocksProxyAgent } from 'socks-proxy-agent';
// @ts-ignore
import wsLibrary, { WebSocketServer, Receiver, Sender } from 'ws';
export const ws = wsLibrary;
export const wsServer = WebSocketServer;
export const wsReceiver = Receiver;
export const wsSender = Sender;
import yamlLibrary from 'yaml';
export const yaml = yamlLibrary;
import json5Library from 'json5';
export const json5 = json5Library;
import sourceMapSupportLibrary from 'source-map-support';
export const sourceMapSupport = sourceMapSupportLibrary;
import stoppableLibrary from 'stoppable';
export const stoppable = stoppableLibrary;
import enquirerLibrary from 'enquirer';
export const enquirer = enquirerLibrary;
import chokidarLibrary from 'chokidar';
export const chokidar = chokidarLibrary;
import * as getEastAsianWidthLibrary from 'get-east-asian-width';
export const getEastAsianWidth = getEastAsianWidthLibrary;
export * as yazl from 'yazl';
export * as yauzl from 'yauzl';
const extractZip = require('./third_party/extract-zip');
export const extract = extractZip;
export * as z from 'zod';
export { Client } from '@modelcontextprotocol/sdk/client/index.js';
export { Server } from '@modelcontextprotocol/sdk/server/index.js';
export { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
export { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
export { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
export { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
export { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
export { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
export { CallToolRequestSchema, ListRootsRequestSchema, ListToolsRequestSchema, PingRequestSchema, ProgressNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
export { zodToJsonSchema } from 'zod-to-json-schema';
+34
View File
@@ -25,6 +25,40 @@ protocol/
@isomorphic/**
@utils/**
[utilsBundle.ts]
node_modules/@modelcontextprotocol/sdk
node_modules/chokidar
node_modules/colors/safe
node_modules/commander
node_modules/debug
node_modules/diff
node_modules/dotenv
node_modules/enquirer
node_modules/get-east-asian-width
node_modules/get-stream
node_modules/graceful-fs
node_modules/https-proxy-agent
node_modules/ini
node_modules/jpeg-js
node_modules/json5
node_modules/mime
node_modules/minimatch
node_modules/open
node_modules/pngjs
node_modules/progress
node_modules/proxy-from-env
node_modules/retry
node_modules/signal-exit
node_modules/socks-proxy-agent
node_modules/source-map-support
node_modules/stoppable
node_modules/ws
node_modules/yaml
node_modules/yauzl
node_modules/yazl
node_modules/zod
node_modules/zod-to-json-schema
[package.ts]
"strict"
@@ -29,7 +29,7 @@ import { ProgressController } from './server/progress';
import type { BrowserServer, BrowserServerLauncher } from './client/browserType';
import type { LaunchServerOptions, Logger } from './client/types';
import type { ProtocolLogger } from './server/types';
import type { WebSocketEventEmitter } from './utilsBundle';
import type { EventEmitter as WebSocketEventEmitter } from 'events';
import type { Browser } from './server/browser';
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
+1 -1
View File
@@ -31,7 +31,7 @@ import { screenshot, pdf } from './browserActions';
import { program as cliProgram } from '../tools/cli-client/program';
import type { TraceViewerServerOptions } from '../server/trace/viewer/traceViewer';
import type { Command } from '../utilsBundle';
import type { Command } from 'commander';
export function decorateProgram(program: Command) {
@@ -19,7 +19,7 @@
import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher';
import { getPackageManager } from '@utils/env';
import type { Command } from '../utilsBundle';
import type { Command } from 'commander';
function printPlaywrightTestError(command: string) {
const packages: string[] = [];
@@ -16,7 +16,7 @@
import { EventEmitter } from 'events';
import type { WebSocket } from '../utilsBundle';
import type { WebSocket } from 'ws';
import type net from 'net';
export interface ServerTransport {
@@ -24,7 +24,7 @@ import { Artifact } from '../artifact';
import { HarTracer } from './harTracer';
import type { BrowserContext } from '../browserContext';
import type { HarTracerDelegate } from './harTracer';
import type { ZipFile } from '../../utilsBundle';
import type { ZipFile } from 'yazl';
import type { Page } from '../page';
import type * as channels from '@protocol/channels';
import type * as har from '@trace/har';
@@ -29,7 +29,7 @@ import { getEmbedderName } from '@utils/userAgent';
import { canAccessFile, existsAsync, removeFolders } from '@utils/fileUtils';
import { calculateSha1 } from '@utils/crypto';
import { getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand } from '@utils/env';
import { lockfile } from '../../utilsBundle';
import { lock } from '@utils/third_party/lockfile';
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from './dependencies';
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
@@ -1061,7 +1061,7 @@ export class Registry {
let releaseLock;
try {
releaseLock = await lockfile.lock(registryDirectory, {
releaseLock = await lock(registryDirectory, {
retries: {
// Retry 20 times during 10 minutes with
// exponential back-off.
@@ -20,7 +20,7 @@ import path from 'path';
import { ManualPromise } from '@isomorphic/manualPromise';
import { httpRequest } from '@utils/network';
import { removeFolders } from '@utils/fileUtils';
import { extract } from '../../utilsBundle';
import { extractZip } from '@utils/third_party/extractZip';
export type DownloadParams = {
title: string;
@@ -117,7 +117,7 @@ async function main(options: DownloadParams) {
log(`removing existing browser directory if any`);
await removeFolders([options.browserDirectory]);
log(`extracting archive`);
await extract(options.zipPath, { dir: options.browserDirectory });
await extractZip(options.zipPath, { dir: options.browserDirectory });
if (options.executablePath) {
log(`fixing permissions at ${options.executablePath}`);
await fs.promises.chmod(options.executablePath, 0o755);
@@ -18,7 +18,7 @@
import ws from 'ws';
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '@utils/happyEyeballs';
import { makeWaitForNextTask } from '@utils/task';
import type { WebSocket } from '../utilsBundle';
import type { WebSocket } from 'ws';
import type { Progress } from './progress';
import type { HeadersArray } from './types';
import type { ClientRequest, IncomingMessage } from 'http';
@@ -12,6 +12,10 @@ node_modules/mime
node_modules/pngjs
node_modules/proxy-from-env
node_modules/socks-proxy-agent
node_modules/get-stream
node_modules/graceful-fs
node_modules/retry
node_modules/signal-exit
node_modules/ws
node_modules/yauzl
node_modules/yazl
@@ -35,6 +35,8 @@ export * from './userAgent';
export * from './wsServer';
export * from './zipFile';
export * from './zones';
export * from './third_party/extractZip';
export * from './third_party/lockfile';
export * from './image_tools/colorUtils';
export * from './image_tools/compare';
export * from './image_tools/imageChannel';
@@ -0,0 +1,218 @@
/**
* Copyright (c) 2014 Max Ogden and other contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { createWriteStream, promises as fs } from 'fs';
import path from 'path';
import stream from 'stream';
import { promisify } from 'util';
import debugPkg from 'debug';
import getStream from 'get-stream';
import yauzl from 'yauzl';
import type { Entry, ZipFile } from 'yauzl';
const debug = debugPkg('extract-zip');
const openZip = promisify<string, yauzl.Options, ZipFile>(yauzl.open);
const pipeline = promisify(stream.pipeline);
export interface Options {
/** The path to the directory where the extracted files are written */
dir: string;
/** Directory Mode (permissions), defaults to `0o755` */
defaultDirMode?: number;
/** File Mode (permissions), defaults to `0o644` */
defaultFileMode?: number;
/**
* If present, will be called with (entry, zipfile),
* entry is every entry from the zip file forwarded
* from the entry event from yauzl. zipfile is the
* yauzl instance
*/
onEntry?: (entry: Entry, zipfile: ZipFile) => void;
}
class Extractor {
private zipPath: string;
private opts: Options;
private zipfile!: ZipFile;
private canceled = false;
constructor(zipPath: string, opts: Options) {
this.zipPath = zipPath;
this.opts = opts;
}
async extract(): Promise<void> {
debug('opening', this.zipPath, 'with opts', this.opts);
this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
this.canceled = false;
return new Promise<void>((resolve, reject) => {
this.zipfile.on('error', err => {
this.canceled = true;
reject(err);
});
this.zipfile.readEntry();
this.zipfile.on('close', () => {
if (!this.canceled) {
debug('zip extraction complete');
resolve();
}
});
this.zipfile.on('entry', async (entry: Entry) => {
/* istanbul ignore if */
if (this.canceled) {
debug('skipping entry', entry.fileName, { cancelled: this.canceled });
return;
}
debug('zipfile entry', entry.fileName);
if (entry.fileName.startsWith('__MACOSX/')) {
this.zipfile.readEntry();
return;
}
const destDir = path.dirname(path.join(this.opts.dir, entry.fileName));
try {
await fs.mkdir(destDir, { recursive: true });
const canonicalDestDir = await fs.realpath(destDir);
const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir);
if (relativeDestDir.split(path.sep).includes('..'))
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`);
await this.extractEntry(entry);
debug('finished processing', entry.fileName);
this.zipfile.readEntry();
} catch (err) {
this.canceled = true;
this.zipfile.close();
reject(err);
}
});
});
}
private async extractEntry(entry: Entry): Promise<void> {
/* istanbul ignore if */
if (this.canceled) {
debug('skipping entry extraction', entry.fileName, { cancelled: this.canceled });
return;
}
if (this.opts.onEntry)
this.opts.onEntry(entry, this.zipfile);
const dest = path.join(this.opts.dir, entry.fileName);
// convert external file attr int into a fs stat mode int
const mode = (entry.externalFileAttributes >> 16) & 0xFFFF;
// check if it's a symlink or dir (using stat mode constants)
const IFMT = 61440;
const IFDIR = 16384;
const IFLNK = 40960;
const symlink = (mode & IFMT) === IFLNK;
let isDir = (mode & IFMT) === IFDIR;
// Failsafe, borrowed from jsZip
if (!isDir && entry.fileName.endsWith('/'))
isDir = true;
// check for windows weird way of specifying a directory
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
const madeBy = entry.versionMadeBy >> 8;
if (!isDir)
isDir = (madeBy === 0 && entry.externalFileAttributes === 16);
debug('extracting entry', { filename: entry.fileName, isDir, isSymlink: symlink });
const procMode = this.getExtractedMode(mode, isDir) & 0o777;
// always ensure folders are created
const destDir = isDir ? dest : path.dirname(dest);
const mkdirOptions: { recursive: true, mode?: number } = { recursive: true };
if (isDir)
mkdirOptions.mode = procMode;
debug('mkdir', { dir: destDir, ...mkdirOptions });
await fs.mkdir(destDir, mkdirOptions);
if (isDir)
return;
debug('opening read stream', dest);
const readStream = await promisify<Entry, stream.Readable>(this.zipfile.openReadStream.bind(this.zipfile))(entry);
if (symlink) {
const link = await getStream(readStream);
debug('creating symlink', link, dest);
await fs.symlink(link, dest);
} else {
await pipeline(readStream, createWriteStream(dest, { mode: procMode }));
}
}
private getExtractedMode(entryMode: number, isDir: boolean): number {
let mode = entryMode;
// Set defaults, if necessary
if (mode === 0) {
if (isDir) {
if (this.opts.defaultDirMode)
mode = Number(this.opts.defaultDirMode);
if (!mode)
mode = 0o755;
} else {
if (this.opts.defaultFileMode)
mode = Number(this.opts.defaultFileMode);
if (!mode)
mode = 0o644;
}
}
return mode;
}
}
export async function extractZip(zipPath: string, opts: Options): Promise<void> {
debug('creating target directory', opts.dir);
if (!path.isAbsolute(opts.dir))
throw new Error('Target directory is expected to be absolute');
await fs.mkdir(opts.dir, { recursive: true });
opts.dir = await fs.realpath(opts.dir);
return new Extractor(zipPath, opts).extract();
}
export default extractZip;
@@ -0,0 +1,415 @@
/**
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Made With MOXY Lda <hello@moxy.studio>
* Modifications copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import path from 'path';
// @ts-expect-error untyped vendored module
import gracefulFs from 'graceful-fs';
import retry from 'retry';
// @ts-expect-error untyped vendored module
import onExit from 'signal-exit';
type LockRecord = {
lockfilePath: string;
mtime: Date;
mtimePrecision: 's' | 'ms';
options: LockOptions;
lastUpdate: number;
updateDelay?: number | null;
updateTimeout?: ReturnType<typeof setTimeout> | null;
released?: boolean;
};
type LockOptions = {
stale: number;
update: number | null;
realpath: boolean;
retries: number | retry.OperationOptions;
fs: any;
lockfilePath?: string;
onCompromised: (err: Error) => void;
};
type NodeCallback<T = void> = (err: Error | null, result?: T) => void;
const locks: Record<string, LockRecord> = {};
const cacheSymbol = Symbol();
function probe(file: string, fs: any, callback: (err: Error | null, mtime?: Date, precision?: 's' | 'ms') => void) {
const cachedPrecision = fs[cacheSymbol];
if (cachedPrecision) {
return fs.stat(file, (err: any, stat: any) => {
/* istanbul ignore if */
if (err)
return callback(err);
callback(null, stat.mtime, cachedPrecision);
});
}
// Set mtime by ceiling Date.now() to seconds + 5ms so that it's "not on the second"
const mtime = new Date((Math.ceil(Date.now() / 1000) * 1000) + 5);
fs.utimes(file, mtime, mtime, (err: any) => {
/* istanbul ignore if */
if (err)
return callback(err);
fs.stat(file, (err: any, stat: any) => {
/* istanbul ignore if */
if (err)
return callback(err);
const precision: 's' | 'ms' = stat.mtime.getTime() % 1000 === 0 ? 's' : 'ms';
// Cache the precision in a non-enumerable way
Object.defineProperty(fs, cacheSymbol, { value: precision });
callback(null, stat.mtime, precision);
});
});
}
function getMtime(precision: 's' | 'ms'): Date {
let now = Date.now();
if (precision === 's')
now = Math.ceil(now / 1000) * 1000;
return new Date(now);
}
function getLockFile(file: string, options: LockOptions): string {
return options.lockfilePath || `${file}.lock`;
}
function resolveCanonicalPath(file: string, options: LockOptions, callback: (err: Error | null, resolved?: string) => void) {
if (!options.realpath)
return callback(null, path.resolve(file));
// Use realpath to resolve symlinks
// It also resolves relative paths
options.fs.realpath(file, callback);
}
function acquireLock(file: string, options: LockOptions, callback: (err: Error | null, mtime?: Date, mtimePrecision?: 's' | 'ms') => void) {
const lockfilePath = getLockFile(file, options);
// Use mkdir to create the lockfile (atomic operation)
options.fs.mkdir(lockfilePath, (err: any) => {
if (!err) {
// At this point, we acquired the lock!
// Probe the mtime precision
return probe(lockfilePath, options.fs, (err, mtime, mtimePrecision) => {
// If it failed, try to remove the lock..
/* istanbul ignore if */
if (err) {
options.fs.rmdir(lockfilePath, () => {});
return callback(err);
}
callback(null, mtime, mtimePrecision);
});
}
// If error is not EEXIST then some other error occurred while locking
if (err.code !== 'EEXIST')
return callback(err);
// Otherwise, check if lock is stale by analyzing the file mtime
if (options.stale <= 0)
return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
options.fs.stat(lockfilePath, (err: any, stat: any) => {
if (err) {
// Retry if the lockfile has been removed (meanwhile)
// Skip stale check to avoid recursiveness
if (err.code === 'ENOENT')
return acquireLock(file, { ...options, stale: 0 }, callback);
return callback(err);
}
if (!isLockStale(stat, options))
return callback(Object.assign(new Error('Lock file is already being held'), { code: 'ELOCKED', file }));
// If it's stale, remove it and try again!
// Skip stale check to avoid recursiveness
removeLock(file, options, err => {
if (err)
return callback(err);
acquireLock(file, { ...options, stale: 0 }, callback);
});
});
});
}
function isLockStale(stat: { mtime: Date }, options: LockOptions): boolean {
return stat.mtime.getTime() < Date.now() - options.stale;
}
function removeLock(file: string, options: LockOptions, callback: NodeCallback) {
// Remove lockfile, ignoring ENOENT errors
options.fs.rmdir(getLockFile(file, options), (err: any) => {
if (err && err.code !== 'ENOENT')
return callback(err);
callback(null);
});
}
function updateLock(file: string, options: LockOptions) {
const lock = locks[file];
// Just for safety, should never happen
/* istanbul ignore if */
if (lock.updateTimeout)
return;
lock.updateDelay = lock.updateDelay || options.update!;
lock.updateTimeout = setTimeout(() => {
lock.updateTimeout = null;
// Stat the file to check if mtime is still ours
// If it is, we can still recover from a system sleep or a busy event loop
options.fs.stat(lock.lockfilePath, (err: any, stat: any) => {
const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
// If it failed to update the lockfile, keep trying unless
// the lockfile was deleted or we are over the threshold
if (err) {
if (err.code === 'ENOENT' || isOverThreshold)
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
lock.updateDelay = 1000;
return updateLock(file, options);
}
const isMtimeOurs = lock.mtime.getTime() === stat.mtime.getTime();
if (!isMtimeOurs) {
return setLockAsCompromised(
file,
lock,
Object.assign(
new Error('Unable to update lock within the stale threshold'),
{ code: 'ECOMPROMISED' }
));
}
const mtime = getMtime(lock.mtimePrecision);
options.fs.utimes(lock.lockfilePath, mtime, mtime, (err: any) => {
const isOverThreshold = lock.lastUpdate + options.stale < Date.now();
// Ignore if the lock was released
if (lock.released)
return;
// If it failed to update the lockfile, keep trying unless
// the lockfile was deleted or we are over the threshold
if (err) {
if (err.code === 'ENOENT' || isOverThreshold)
return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' }));
lock.updateDelay = 1000;
return updateLock(file, options);
}
// All ok, keep updating..
lock.mtime = mtime;
lock.lastUpdate = Date.now();
lock.updateDelay = null;
updateLock(file, options);
});
});
}, lock.updateDelay);
// Unref the timer so that the nodejs process can exit freely
// This is safe because all acquired locks will be automatically released
// on process exit
// We first check that `lock.updateTimeout.unref` exists because some users
// may be using this module outside of NodeJS (e.g., in an electron app),
// and in those cases `setTimeout` return an integer.
/* istanbul ignore else */
if (lock.updateTimeout && (lock.updateTimeout as any).unref)
(lock.updateTimeout as any).unref();
}
function setLockAsCompromised(file: string, lock: LockRecord, err: Error) {
// Signal the lock has been released
lock.released = true;
// Cancel lock mtime update
// Just for safety, at this point updateTimeout should be null
/* istanbul ignore if */
if (lock.updateTimeout)
clearTimeout(lock.updateTimeout);
if (locks[file] === lock)
delete locks[file];
lock.options.onCompromised(err);
}
// ----------------------------------------------------------
function lockImpl(
file: string,
options: Partial<LockOptions>,
callback: (err: Error | null, release?: (cb?: NodeCallback) => void) => void,
) {
/* istanbul ignore next */
const resolvedOptions: LockOptions = {
stale: 10000,
update: null,
realpath: true,
retries: 0,
fs: gracefulFs,
onCompromised: (err: Error) => { throw err; },
...options,
};
resolvedOptions.retries = resolvedOptions.retries || 0;
resolvedOptions.retries = typeof resolvedOptions.retries === 'number' ? { retries: resolvedOptions.retries } : resolvedOptions.retries;
resolvedOptions.stale = Math.max(resolvedOptions.stale || 0, 2000);
resolvedOptions.update = resolvedOptions.update === null || resolvedOptions.update === undefined ? resolvedOptions.stale / 2 : resolvedOptions.update || 0;
resolvedOptions.update = Math.max(Math.min(resolvedOptions.update, resolvedOptions.stale / 2), 1000);
// Resolve to a canonical file path
resolveCanonicalPath(file, resolvedOptions, (err, resolvedFile) => {
if (err)
return callback(err);
const canonicalFile = resolvedFile!;
// Attempt to acquire the lock
const operation = retry.operation(resolvedOptions.retries as retry.OperationOptions);
operation.attempt(() => {
acquireLock(canonicalFile, resolvedOptions, (err, mtime, mtimePrecision) => {
if (operation.retry(err || undefined))
return;
if (err)
return callback(operation.mainError());
// We now own the lock
const lock: LockRecord = locks[canonicalFile] = {
lockfilePath: getLockFile(canonicalFile, resolvedOptions),
mtime: mtime!,
mtimePrecision: mtimePrecision!,
options: resolvedOptions,
lastUpdate: Date.now(),
};
// We must keep the lock fresh to avoid staleness
updateLock(canonicalFile, resolvedOptions);
callback(null, (releasedCallback?: NodeCallback) => {
if (lock.released) {
return releasedCallback &&
releasedCallback(Object.assign(new Error('Lock is already released'), { code: 'ERELEASED' }));
}
// Not necessary to use realpath twice when unlocking
unlock(canonicalFile, { ...resolvedOptions, realpath: false }, releasedCallback || (() => {}));
});
});
});
});
}
function unlock(file: string, options: Partial<LockOptions>, callback: NodeCallback) {
const resolvedOptions: LockOptions = {
stale: 10000,
update: null,
realpath: true,
retries: 0,
fs: gracefulFs,
onCompromised: (err: Error) => { throw err; },
...options,
};
// Resolve to a canonical file path
resolveCanonicalPath(file, resolvedOptions, (err, resolvedFile) => {
if (err)
return callback(err);
const canonicalFile = resolvedFile!;
// Skip if the lock is not acquired
const lock = locks[canonicalFile];
if (!lock)
return callback(Object.assign(new Error('Lock is not acquired/owned by you'), { code: 'ENOTACQUIRED' }));
if (lock.updateTimeout)
clearTimeout(lock.updateTimeout); // Cancel lock mtime update
lock.released = true; // Signal the lock has been released
delete locks[canonicalFile]; // Delete from locks
removeLock(canonicalFile, resolvedOptions, callback);
});
}
function toPromise<T = void>(method: (...args: any[]) => void): (...args: any[]) => Promise<T> {
return (...args: any[]) => new Promise<T>((resolve, reject) => {
args.push((err: Error | null, result: T) => {
if (err)
reject(err);
else
resolve(result);
});
method(...args);
});
}
// Remove acquired locks on exit
/* istanbul ignore next */
let cleanupInitialized = false;
function ensureCleanup() {
if (cleanupInitialized)
return;
cleanupInitialized = true;
onExit(() => {
for (const file in locks) {
const options = locks[file].options;
try {
options.fs.rmdirSync(getLockFile(file, options));
} catch (e) { /* Empty */ }
}
});
}
export async function lock(file: string, options?: Partial<LockOptions>): Promise<() => Promise<void>> {
ensureCleanup();
const release = await toPromise<(cb?: NodeCallback) => void>(lockImpl)(file, options || {});
return toPromise<void>(release);
}
@@ -18,7 +18,7 @@ import { WebSocketServer as wsServer } from 'ws';
import { createHttpServer } from './network';
import { debugLogger } from './debugLogger';
import type { WebSocket, WebSocketServer } from '../../utilsBundle';
import type { WebSocket, WebSocketServer } from 'ws';
import type http from 'http';
import type stream from 'stream';
@@ -15,7 +15,7 @@
*/
import * as yauzl from 'yauzl';
import type { Entry, UnzipFile } from '../../utilsBundle';
import type { Entry, ZipFile as UnzipFile } from 'yauzl';
export class ZipFile {
private _fileName: string;
@@ -28,7 +28,7 @@ import { createBrowserWithInfo } from '../mcp/browserFactory';
import * as configUtils from '../mcp/config';
import { createClientInfo } from '../cli-client/registry';
import { registry as browserRegistry } from '../../server/registry/index';
import type { Command } from '../../utilsBundle';
import type { Command } from 'commander';
export function decorateProgram(program: Command) {
program.argument('[session-name]', 'name of the session to create or connect to', 'default')
@@ -43,7 +43,7 @@ import * as protocol from './protocol';
import type websocket from 'ws';
import type { ExtensionCommand, ExtensionEvents } from './protocol';
import type { WebSocket, WebSocketServer } from '../../utilsBundle';
import type { WebSocket, WebSocketServer } from 'ws';
const debugLogger = debug('pw:mcp:relay');
@@ -24,7 +24,7 @@ import { filteredTools } from '../backend/tools';
import { testDebug } from './log';
import { packageJSON } from '../../package';
import type { Command } from '../../utilsBundle';
import type { Command } from 'commander';
import type { ClientInfo } from '../utils/mcp/server';
import type * as playwright from '../../..';
@@ -28,7 +28,7 @@ import { traceAttachments } from './traceAttachments';
import { traceAttachment } from './traceAttachments';
import { installSkill } from './installSkill';
import type { Command } from '../../utilsBundle';
import type { Command } from 'commander';
export function addTraceCommands(program: Command, logErrorAndExit: (e: Error) => void) {
const traceCommand = program
+96 -54
View File
@@ -14,61 +14,103 @@
* limitations under the License.
*/
/* eslint-disable import/order */
import colorsLibrary from 'colors/safe';
export const colors = colorsLibrary;
import debugLibrary from 'debug';
export const debug = debugLibrary;
import * as iniLibrary from 'ini';
export const ini = iniLibrary;
import * as diffLibrary from 'diff';
export const diff = diffLibrary;
import dotenvLibrary from 'dotenv';
export const dotenv = dotenvLibrary;
export { getProxyForUrl } from 'proxy-from-env';
export { HttpsProxyAgent } from 'https-proxy-agent';
import jpegLibrary from 'jpeg-js';
export const jpegjs = jpegLibrary;
import mimeLibrary from 'mime';
export const mime = mimeLibrary;
import minimatchLibrary from 'minimatch';
export const minimatch = minimatchLibrary;
import openLibrary from 'open';
export const open = openLibrary;
export { PNG } from 'pngjs';
export { program } from 'commander';
export { Option as ProgramOption } from 'commander';
import progressLibrary from 'progress';
export const progress = progressLibrary;
export { SocksProxyAgent } from 'socks-proxy-agent';
// @ts-ignore
const _impl = require('./utilsBundleImpl');
import wsLibrary, { WebSocketServer, Receiver, Sender } from 'ws';
export const ws = wsLibrary;
export const wsServer = WebSocketServer;
export const wsReceiver = Receiver;
export const wsSender = Sender;
export const colors: typeof import('colors/safe') = _impl.colors;
export const debug: typeof import('debug') = _impl.debug;
export const diff: typeof import('diff') = _impl.diff;
export const dotenv: typeof import('dotenv') = _impl.dotenv;
export const ini: typeof import('ini') = _impl.ini;
export const getProxyForUrl: typeof import('proxy-from-env').getProxyForUrl = _impl.getProxyForUrl;
export const HttpsProxyAgent: typeof import('https-proxy-agent').HttpsProxyAgent = _impl.HttpsProxyAgent;
export const jpegjs: typeof import('jpeg-js') = _impl.jpegjs;
export const lockfile: typeof import('proper-lockfile') = _impl.lockfile;
export const mime: typeof import('mime') = _impl.mime;
export const minimatch: typeof import('minimatch') = _impl.minimatch;
export const open: typeof import('open') = _impl.open;
export const PNG: typeof import('pngjs').PNG = _impl.PNG;
export const program: typeof import('commander').program = _impl.program;
export const ProgramOption: typeof import('commander').Option = _impl.ProgramOption;
export const progress: typeof import('progress') = _impl.progress;
export const SocksProxyAgent: typeof import('socks-proxy-agent').SocksProxyAgent = _impl.SocksProxyAgent;
export const ws: typeof import('ws') = _impl.ws;
export const wsServer: typeof import('ws').WebSocketServer = _impl.wsServer;
export const wsReceiver = _impl.wsReceiver;
export const wsSender = _impl.wsSender;
export const yaml: typeof import('yaml') = _impl.yaml;
export type { Range as YAMLRange, Scalar as YAMLScalar, YAMLError, YAMLMap, YAMLSeq } from 'yaml';
export const json5: typeof import('json5') = _impl.json5;
export const sourceMapSupport: typeof import('source-map-support') = _impl.sourceMapSupport;
export const stoppable: typeof import('stoppable') = _impl.stoppable;
export const enquirer: typeof import('enquirer') = _impl.enquirer;
export const chokidar: typeof import('chokidar') = _impl.chokidar;
export const getEastAsianWidth: typeof import('get-east-asian-width') = _impl.getEastAsianWidth;
export type { RawSourceMap } from 'source-map';
export type { Command } from 'commander';
export type { RawData as WebSocketRawData, WebSocket, WebSocketServer } from 'ws';
export type { EventEmitter as WebSocketEventEmitter } from 'events';
import yamlLibrary from 'yaml';
export const yaml = yamlLibrary;
export const yazl: typeof import('yazl') = _impl.yazl;
export type { ZipFile } from 'yazl';
export const yauzl: typeof import('yauzl') = _impl.yauzl;
export type { Entry, ZipFile as UnzipFile } from 'yauzl';
export const extract: typeof import('../bundles/utils/src/third_party/extract-zip') = _impl.extract;
import json5Library from 'json5';
export const json5 = json5Library;
export const z: typeof import('zod') = _impl.z;
export const zodToJsonSchema: (schema: any, options?: any) => any = _impl.zodToJsonSchema;
export const Client: typeof import('@modelcontextprotocol/sdk/client/index.js').Client = _impl.Client;
export const Server: typeof import('@modelcontextprotocol/sdk/server/index.js').Server = _impl.Server;
export const SSEClientTransport: typeof import('@modelcontextprotocol/sdk/client/sse.js').SSEClientTransport = _impl.SSEClientTransport;
export const SSEServerTransport: typeof import('@modelcontextprotocol/sdk/server/sse.js').SSEServerTransport = _impl.SSEServerTransport;
export const StdioClientTransport: typeof import('@modelcontextprotocol/sdk/client/stdio.js').StdioClientTransport = _impl.StdioClientTransport;
export const StdioServerTransport: typeof import('@modelcontextprotocol/sdk/server/stdio.js').StdioServerTransport = _impl.StdioServerTransport;
export const StreamableHTTPServerTransport: typeof import('@modelcontextprotocol/sdk/server/streamableHttp.js').StreamableHTTPServerTransport = _impl.StreamableHTTPServerTransport;
export const StreamableHTTPClientTransport: typeof import('@modelcontextprotocol/sdk/client/streamableHttp.js').StreamableHTTPClientTransport = _impl.StreamableHTTPClientTransport;
export const CallToolRequestSchema: typeof import('@modelcontextprotocol/sdk/types.js').CallToolRequestSchema = _impl.CallToolRequestSchema;
export const ListRootsRequestSchema: typeof import('@modelcontextprotocol/sdk/types.js').ListRootsRequestSchema = _impl.ListRootsRequestSchema;
export const ProgressNotificationSchema: typeof import('@modelcontextprotocol/sdk/types.js').ProgressNotificationSchema = _impl.ProgressNotificationSchema;
export const ListToolsRequestSchema: typeof import('@modelcontextprotocol/sdk/types.js').ListToolsRequestSchema = _impl.ListToolsRequestSchema;
export const PingRequestSchema: typeof import('@modelcontextprotocol/sdk/types.js').PingRequestSchema = _impl.PingRequestSchema;
import sourceMapSupportLibrary from 'source-map-support';
export const sourceMapSupport = sourceMapSupportLibrary;
import stoppableLibrary from 'stoppable';
export const stoppable = stoppableLibrary;
import enquirerLibrary from 'enquirer';
export const enquirer = enquirerLibrary;
import chokidarLibrary from 'chokidar';
export const chokidar = chokidarLibrary;
import * as getEastAsianWidthLibrary from 'get-east-asian-width';
export const getEastAsianWidth = getEastAsianWidthLibrary;
export * as yazl from 'yazl';
export * as yauzl from 'yauzl';
// @ts-expect-error untyped vendored module
import * as gracefulFsLibrary from 'graceful-fs';
export const gracefulFs = gracefulFsLibrary;
import retryLibrary from 'retry';
export const retry = retryLibrary;
// @ts-expect-error untyped vendored module
import onExitLibrary from 'signal-exit';
export const onExit = onExitLibrary;
import getStreamLibrary from 'get-stream';
export const getStream = getStreamLibrary;
export * as z from 'zod';
export { Client } from '@modelcontextprotocol/sdk/client/index.js';
export { Server } from '@modelcontextprotocol/sdk/server/index.js';
export { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
export { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
export { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
export { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
export { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
export { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
export { CallToolRequestSchema, ListRootsRequestSchema, ListToolsRequestSchema, PingRequestSchema, ProgressNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
export { zodToJsonSchema } from 'zod-to-json-schema';
@@ -16,7 +16,7 @@
import path from 'path';
import { babel } from 'playwright/lib/common';
import * as babel from 'playwright/lib/transform/babelBundle';
const t: typeof babel.T = babel.types;
-1
View File
@@ -1,2 +1 @@
src
bundles
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,36 +0,0 @@
{
"name": "babel-bundle",
"version": "0.0.1",
"private": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/core": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/parser": "^7.28.0",
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-import-attributes": "^7.27.1",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-numeric-separator": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-typescript": "^7.27.1"
},
"devDependencies": {
"@types/babel__code-frame": "^7.0.6",
"@types/babel__core": "^7.20.5",
"@types/babel__helper-plugin-utils": "^7.10.3",
"@types/babel__traverse": "^7.20.6"
}
}
-23
View File
@@ -1,23 +0,0 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
const packageJSON = require('./package.json');
for (const dep of Object.keys(packageJSON.dependencies)) {
await page.waitForTimeout(3000);
console.log('Processing ', dep);
await page.goto(`https://www.npmjs.com/package/${dep}`);
const title = await page.getByText('Public').locator('..').textContent();
if (!title.startsWith(dep))
throw new Error('Malformed title: ', title);
const i = title.indexOf(' • Public');
if (i === -1)
throw new Error('Malformed title: ' + title);
const version = title.slice(dep.length, i);
console.log(version);
packageJSON.dependencies[dep] = '^' + version;
}
await browser.close();
console.log(JSON.stringify(packageJSON, null, 2));
})();
@@ -1,140 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import * as babel from '@babel/core';
import traverseFunction from '@babel/traverse';
import type { BabelFileResult, NodePath, PluginObj, TransformOptions } from '@babel/core';
import type { TemplateBuilder } from '@babel/template';
import type { ImportDeclaration, TSExportAssignment } from '@babel/types';
export { codeFrameColumns } from '@babel/code-frame';
export { declare } from '@babel/helper-plugin-utils';
export { types } from '@babel/core';
export const traverse = traverseFunction;
function babelTransformOptions(isTypeScript: boolean, isModule: boolean, pluginsPrologue: [string, any?][], pluginsEpilogue: [string, any?][]): TransformOptions {
const plugins = [
[require('@babel/plugin-syntax-import-attributes'), { deprecatedAssertSyntax: true }],
];
if (isTypeScript) {
plugins.push(
[require('@babel/plugin-proposal-decorators'), { version: '2023-05' }],
[require('@babel/plugin-transform-explicit-resource-management')],
[require('@babel/plugin-transform-class-properties')],
[require('@babel/plugin-transform-class-static-block')],
[require('@babel/plugin-transform-numeric-separator')],
[require('@babel/plugin-transform-logical-assignment-operators')],
[require('@babel/plugin-transform-nullish-coalescing-operator')],
[require('@babel/plugin-transform-optional-chaining')],
[require('@babel/plugin-transform-private-methods')],
[require('@babel/plugin-syntax-json-strings')],
[require('@babel/plugin-syntax-optional-catch-binding')],
[require('@babel/plugin-syntax-async-generators')],
[require('@babel/plugin-syntax-object-rest-spread')],
[require('@babel/plugin-transform-export-namespace-from')],
[
// From https://github.com/G-Rath/babel-plugin-replace-ts-export-assignment/blob/8dfdca32c8aa428574b0cae341444fc5822f2dc6/src/index.ts
(
{ template }: { template: TemplateBuilder<TSExportAssignment> }
): PluginObj => ({
name: 'replace-ts-export-assignment',
visitor: {
TSExportAssignment(path: NodePath<TSExportAssignment>) {
path.replaceWith(template('module.exports = ASSIGNMENT;')({
ASSIGNMENT: path.node.expression
}));
}
}
})
]
);
}
// Support JSX/TSX at all times, regardless of the file extension.
plugins.push([require('@babel/plugin-transform-react-jsx'), {
throwIfNamespace: false,
runtime: 'automatic',
importSource: path.dirname(require.resolve('playwright')),
}]);
if (!isModule) {
plugins.push([require('@babel/plugin-transform-modules-commonjs')]);
// Note: we used to include '@babel/plugin-transform-dynamic-import' to convert async imports
// into require(), so that pirates can intercept them. With the ESM loader enabled by default,
// there is no need for this.
plugins.push([
(): PluginObj => ({
name: 'css-to-identity-obj-proxy',
visitor: {
ImportDeclaration(path: NodePath<ImportDeclaration>) {
if (path.node.source.value.match(/\.(css|less|scss)$/))
path.remove();
}
}
})
]);
}
return {
browserslistConfigFile: false,
babelrc: false,
configFile: false,
assumptions: {
// Without this, babel defines a top level function that
// breaks playwright evaluates.
setPublicClassFields: true,
},
presets: isTypeScript ? [
[require('@babel/preset-typescript'), { onlyRemoveTypeImports: false }],
] : [],
plugins: [
...pluginsPrologue.map(([name, options]) => [require(name), options]),
...plugins,
...pluginsEpilogue.map(([name, options]) => [require(name), options]),
],
compact: false,
sourceMaps: 'both',
};
}
let isTransforming = false;
function isTypeScript(filename: string) {
return filename.endsWith('.ts') || filename.endsWith('.tsx') || filename.endsWith('.mts') || filename.endsWith('.cts');
}
export function babelTransform(code: string, filename: string, isModule: boolean, pluginsPrologue: [string, any?][], pluginsEpilogue: [string, any?][]): BabelFileResult | null {
if (isTransforming)
return null;
// Prevent reentry while requiring plugins lazily.
isTransforming = true;
try {
const options = babelTransformOptions(isTypeScript(filename), isModule, pluginsPrologue, pluginsEpilogue);
return babel.transform(code, { filename, ...options });
} finally {
isTransforming = false;
}
}
export function babelParse(code: string, filename: string, isModule: boolean): babel.ParseResult {
const options = babelTransformOptions(isTypeScript(filename), isModule, [], []);
return babel.parse(code, { filename, ...options })!;
}
-871
View File
@@ -1,871 +0,0 @@
{
"name": "expect-bundle",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "expect-bundle",
"version": "0.0.1",
"dependencies": {
"expect": "30.2.0",
"jest-matcher-utils": "30.2.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@jest/diff-sequences": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
"integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/expect-utils": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
"integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/get-type": {
"version": "30.1.0",
"resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
"integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/pattern": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
"integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"jest-regex-util": "30.0.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/schemas": {
"version": "30.0.5",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
"integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/types": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
"integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
"license": "MIT",
"dependencies": {
"@jest/pattern": "30.0.1",
"@jest/schemas": "30.0.5",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
"@types/yargs": "^17.0.33",
"chalk": "^4.1.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.34.41",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
"integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/node": {
"version": "24.9.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"node_modules/@types/yargs": {
"version": "17.0.34",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
"integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==",
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"license": "MIT"
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ci-info": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
"integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"engines": {
"node": ">=8"
}
},
"node_modules/expect": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
"integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
"license": "MIT",
"dependencies": {
"@jest/expect-utils": "30.2.0",
"@jest/get-type": "30.1.0",
"jest-matcher-utils": "30.2.0",
"jest-message-util": "30.2.0",
"jest-mock": "30.2.0",
"jest-util": "30.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/jest-diff": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
"integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
"license": "MIT",
"dependencies": {
"@jest/diff-sequences": "30.0.1",
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
"pretty-format": "30.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-matcher-utils": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
"integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
"jest-diff": "30.2.0",
"pretty-format": "30.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-message-util": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
"integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@jest/types": "30.2.0",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"micromatch": "^4.0.8",
"pretty-format": "30.2.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-mock": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
"integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
"license": "MIT",
"dependencies": {
"@jest/types": "30.2.0",
"@types/node": "*",
"jest-util": "30.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-regex-util": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
"integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-util": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
"integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
"license": "MIT",
"dependencies": {
"@jest/types": "30.2.0",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"graceful-fs": "^4.2.11",
"picomatch": "^4.0.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-util/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pretty-format": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
"integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
"license": "MIT",
"dependencies": {
"@jest/schemas": "30.0.5",
"ansi-styles": "^5.2.0",
"react-is": "^18.3.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dependencies": {
"escape-string-regexp": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
}
},
"dependencies": {
"@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"requires": {
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
}
},
"@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
},
"@jest/diff-sequences": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
"integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw=="
},
"@jest/expect-utils": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
"integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
"requires": {
"@jest/get-type": "30.1.0"
}
},
"@jest/get-type": {
"version": "30.1.0",
"resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
"integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA=="
},
"@jest/pattern": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
"integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
"requires": {
"@types/node": "*",
"jest-regex-util": "30.0.1"
}
},
"@jest/schemas": {
"version": "30.0.5",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
"integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
"requires": {
"@sinclair/typebox": "^0.34.0"
}
},
"@jest/types": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
"integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
"requires": {
"@jest/pattern": "30.0.1",
"@jest/schemas": "30.0.5",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
"@types/yargs": "^17.0.33",
"chalk": "^4.1.2"
}
},
"@sinclair/typebox": {
"version": "0.34.41",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
"integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
},
"@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"requires": {
"@types/istanbul-lib-coverage": "*"
}
},
"@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"requires": {
"@types/istanbul-lib-report": "*"
}
},
"@types/node": {
"version": "24.9.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
"requires": {
"undici-types": "~7.16.0"
}
},
"@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"@types/yargs": {
"version": "17.0.34",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
"integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==",
"requires": {
"@types/yargs-parser": "*"
}
},
"@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"requires": {
"fill-range": "^7.1.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"ci-info": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
"integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
},
"expect": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
"integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
"requires": {
"@jest/expect-utils": "30.2.0",
"@jest/get-type": "30.1.0",
"jest-matcher-utils": "30.2.0",
"jest-message-util": "30.2.0",
"jest-mock": "30.2.0",
"jest-util": "30.2.0"
}
},
"fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"requires": {
"to-regex-range": "^5.0.1"
}
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"jest-diff": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
"integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
"requires": {
"@jest/diff-sequences": "30.0.1",
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
"pretty-format": "30.2.0"
}
},
"jest-matcher-utils": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
"integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
"requires": {
"@jest/get-type": "30.1.0",
"chalk": "^4.1.2",
"jest-diff": "30.2.0",
"pretty-format": "30.2.0"
}
},
"jest-message-util": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
"integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
"requires": {
"@babel/code-frame": "^7.27.1",
"@jest/types": "30.2.0",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"micromatch": "^4.0.8",
"pretty-format": "30.2.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
}
},
"jest-mock": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
"integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
"requires": {
"@jest/types": "30.2.0",
"@types/node": "*",
"jest-util": "30.2.0"
}
},
"jest-regex-util": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
"integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA=="
},
"jest-util": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
"integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
"requires": {
"@jest/types": "30.2.0",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"graceful-fs": "^4.2.11",
"picomatch": "^4.0.2"
},
"dependencies": {
"picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
}
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"requires": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
}
},
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="
},
"pretty-format": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
"integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
"requires": {
"@jest/schemas": "30.0.5",
"ansi-styles": "^5.2.0",
"react-is": "^18.3.1"
},
"dependencies": {
"ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
}
}
},
"react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
"stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"requires": {
"escape-string-regexp": "^2.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"requires": {
"is-number": "^7.0.0"
}
},
"undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
}
}
}
@@ -1,9 +0,0 @@
{
"name": "expect-bundle",
"version": "0.0.1",
"private": true,
"dependencies": {
"expect": "30.2.0",
"jest-matcher-utils": "30.2.0"
}
}
@@ -1,18 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import expectLibrary from 'expect';
export const expect = expectLibrary;
+32
View File
@@ -24,6 +24,7 @@
"./lib/mcp/index": "./lib/mcp/index.js",
"./lib/program": "./lib/program.js",
"./lib/runner": "./lib/runner/index.js",
"./lib/transform/babelBundle": "./lib/transform/babelBundle.js",
"./jsx-runtime": {
"import": "./jsx-runtime.mjs",
"require": "./jsx-runtime.js",
@@ -53,6 +54,37 @@
"dependencies": {
"playwright-core": "1.60.0-next"
},
"devDependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/core": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/parser": "^7.28.0",
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-import-attributes": "^7.27.1",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-numeric-separator": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/preset-typescript": "^7.27.1",
"@types/babel__code-frame": "^7.0.6",
"@types/babel__core": "^7.20.5",
"@types/babel__helper-plugin-utils": "^7.10.3",
"@types/babel__traverse": "^7.20.6",
"expect": "30.2.0",
"jest-matcher-utils": "30.2.0"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
@@ -41,7 +41,7 @@ export function registerESMLoader() {
const { port1, port2 } = new MessageChannel();
// register will wait until the loader is initialized.
register(url.pathToFileURL(require.resolve('../esmLoaderBundle.js')), {
register(url.pathToFileURL(require.resolve('../transform/esmLoader.js')), {
data: { port: port2 },
transferList: [port2],
});
-1
View File
@@ -14,7 +14,6 @@
* limitations under the License.
*/
export * as babel from '../transform/babelBundle';
export * as cc from '../transform/compilationCache';
export * as config from './config';
export * as configLoader from './configLoader';
@@ -3,3 +3,4 @@
@utils/**
../package.ts
node_modules/colors/safe
node_modules/expect
+3 -3
View File
@@ -16,6 +16,9 @@
import path from 'path';
import {
expect as expectLibrary,
} from 'expect';
import { parseStackFrame, captureRawStack } from '@isomorphic/stackTrace';
import { escapeWithQuotes, isString } from '@isomorphic/stringUtils';
import { pollAgainstDeadline } from '@isomorphic/timeoutRunner';
@@ -57,9 +60,6 @@ import {
} from './matchers';
import { toMatchAriaSnapshot } from './toMatchAriaSnapshot';
import { toHaveScreenshot, toMatchSnapshot } from './toMatchSnapshot';
import {
expect as expectLibrary,
} from './expectBundle';
import type { ExpectMatcherStateInternal } from './matchers';
import type { Expect } from '../../types/test';
@@ -1,18 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { libPath } from '../package';
export const expect: typeof import('../../bundles/expect/node_modules/expect/build').expect = require(libPath('matchers', 'expectBundleImpl')).expect;
+1 -1
View File
@@ -31,7 +31,7 @@ import { packageJSON } from './package';
export { program };
import type { TraceMode } from '../types/test';
import type { Command } from 'playwright-core/lib/utilsBundle';
import type { Command } from 'commander';
libCli.decorateProgram(program);
+3 -3
View File
@@ -31,7 +31,7 @@ import { HttpServer } from '@utils/httpServer';
import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher';
import { CommonReporterOptions, formatError, formatResultFailure, internalScreen } from './base';
import { babel } from '../common';
import * as babel from '../transform/babelBundle';
import { resolveReporterOutputPath, stripAnsiEscapes } from '../util';
import type { ReportConfigureParams, ReportEndParams, ReporterV2 } from './reporterV2';
@@ -39,7 +39,7 @@ import type { HtmlReporterOptions as HtmlReporterConfigOptions, Metadata, TestAn
import type * as api from '../../types/testReporter';
import type { HTMLReport, HTMLReportOptions, Location, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types';
import type { TransformCallback } from 'stream';
import type { ZipFile } from 'playwright-core/lib/utilsBundle';
import type { ZipFile } from 'yazl';
type TestEntry = {
testCase: TestCase;
@@ -266,7 +266,7 @@ class HtmlBuilder {
private _options: HTMLReportOptions;
private _doNotInlineAssets: boolean;
constructor(yazl: typeof import('playwright-core/lib/utilsBundle').yazl, config: api.FullConfig, outputDir: string, attachmentsBaseURL: string, doNotInlineAssets: boolean, options: HTMLReportOptions) {
constructor(yazl: typeof import('yazl'), config: api.FullConfig, outputDir: string, attachmentsBaseURL: string, doNotInlineAssets: boolean, options: HTMLReportOptions) {
this._dataZipFile = new yazl.ZipFile();
this._config = config;
this._reportFolder = outputDir;
@@ -20,7 +20,8 @@ import { monotonicTime } from '@isomorphic/time';
import { internalScreen, prepareErrorStack, relativeFilePath } from './base';
import { Multiplexer } from './multiplexer';
import { babel, test as testNs } from '../common';
import { test as testNs } from '../common';
import * as babel from '../transform/babelBundle';
import { wrapReporterAsV2 } from './reporterV2';
import type { AnyReporter, ReporterV2 } from './reporterV2';
+1 -1
View File
@@ -26,7 +26,7 @@ import { buildProjectsClosure, collectFilesForProject } from './projectUtils';
import { createTestGroups, filterForShard } from './testGroups';
import { cc, config as commonConfig, FullConfigInternal, suiteUtils, test as testNs, transform } from '../common';
import type { RawSourceMap } from 'playwright-core/lib/utilsBundle';
import type { RawSourceMap } from 'source-map';
import type { TestRun } from './tasks';
import type { TestGroup } from './testGroups';
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
+2 -1
View File
@@ -21,7 +21,8 @@ import colors from 'colors/safe';
import * as diff from 'diff';
import { MultiMap } from '@isomorphic/multimap';
import { babel, config as commonConfig, FullConfigInternal } from '../common';
import { config as commonConfig, FullConfigInternal } from '../common';
import * as babel from '../transform/babelBundle';
import type { InternalReporter } from '../reporters/internalReporter';
import type { T } from '../transform/babelBundle';
@@ -3,5 +3,27 @@
../globals.ts
../package.ts
@utils/**
node_modules/json5
node_modules/source-map-support
[babelBundle.ts]
node_modules/@babel/code-frame
node_modules/@babel/core
node_modules/@babel/helper-plugin-utils
node_modules/@babel/plugin-proposal-decorators
node_modules/@babel/plugin-syntax-import-attributes
node_modules/@babel/plugin-transform-class-properties
node_modules/@babel/plugin-transform-class-static-block
node_modules/@babel/plugin-transform-export-namespace-from
node_modules/@babel/plugin-transform-logical-assignment-operators
node_modules/@babel/plugin-transform-modules-commonjs
node_modules/@babel/plugin-transform-nullish-coalescing-operator
node_modules/@babel/plugin-transform-numeric-separator
node_modules/@babel/plugin-transform-optional-chaining
node_modules/@babel/plugin-transform-private-methods
node_modules/@babel/plugin-transform-private-property-in-object
node_modules/@babel/plugin-transform-react-jsx
node_modules/@babel/preset-typescript
node_modules/@babel/traverse
node_modules/@babel/types
+127 -12
View File
@@ -14,18 +14,133 @@
* limitations under the License.
*/
import { libPath } from '../package';
import path from 'path';
import * as babel from '@babel/core';
import traverseFunction from '@babel/traverse';
import type { BabelFileResult, NodePath, PluginObj, TransformOptions } from '@babel/core';
import type { TemplateBuilder } from '@babel/template';
import type { ImportDeclaration, TSExportAssignment } from '@babel/types';
export { codeFrameColumns } from '@babel/code-frame';
export { declare } from '@babel/helper-plugin-utils';
export { types } from '@babel/core';
export const traverse = traverseFunction;
export type { NodePath, PluginObj, types as T } from '@babel/core';
export type { BabelAPI } from '@babel/helper-plugin-utils';
import type { BabelFileResult, ParseResult } from '../../bundles/babel/node_modules/@types/babel__core';
const babelBundleImpl = require(libPath('transform', 'babelBundleImpl'));
export const codeFrameColumns: typeof import('../../bundles/babel/node_modules/@types/babel__code-frame').codeFrameColumns = babelBundleImpl.codeFrameColumns;
export const declare: typeof import('../../bundles/babel/node_modules/@types/babel__helper-plugin-utils').declare = babelBundleImpl.declare;
export const types: typeof import('../../bundles/babel/node_modules/@types/babel__core').types = babelBundleImpl.types;
export const traverse: typeof import('../../bundles/babel/node_modules/@types/babel__traverse').default = babelBundleImpl.traverse;
export type BabelPlugin = [string, any?];
export type BabelTransformFunction = (code: string, filename: string, isModule: boolean, pluginsPrefix: BabelPlugin[], pluginsSuffix: BabelPlugin[]) => BabelFileResult | null;
export const babelTransform: BabelTransformFunction = babelBundleImpl.babelTransform;
export type BabelParseFunction = (code: string, filename: string, isModule: boolean) => ParseResult;
export const babelParse: BabelParseFunction = babelBundleImpl.babelParse;
export type { NodePath, PluginObj, types as T } from '../../bundles/babel/node_modules/@types/babel__core';
export type { BabelAPI } from '../../bundles/babel/node_modules/@types/babel__helper-plugin-utils';
function babelTransformOptions(isTypeScript: boolean, isModule: boolean, pluginsPrologue: [string, any?][], pluginsEpilogue: [string, any?][]): TransformOptions {
const plugins = [
[require('@babel/plugin-syntax-import-attributes'), { deprecatedAssertSyntax: true }],
];
if (isTypeScript) {
plugins.push(
[require('@babel/plugin-proposal-decorators'), { version: '2023-05' }],
[require('@babel/plugin-transform-explicit-resource-management')],
[require('@babel/plugin-transform-class-properties')],
[require('@babel/plugin-transform-class-static-block')],
[require('@babel/plugin-transform-numeric-separator')],
[require('@babel/plugin-transform-logical-assignment-operators')],
[require('@babel/plugin-transform-nullish-coalescing-operator')],
[require('@babel/plugin-transform-optional-chaining')],
[require('@babel/plugin-transform-private-methods')],
[require('@babel/plugin-syntax-json-strings')],
[require('@babel/plugin-syntax-optional-catch-binding')],
[require('@babel/plugin-syntax-async-generators')],
[require('@babel/plugin-syntax-object-rest-spread')],
[require('@babel/plugin-transform-export-namespace-from')],
[
// From https://github.com/G-Rath/babel-plugin-replace-ts-export-assignment/blob/8dfdca32c8aa428574b0cae341444fc5822f2dc6/src/index.ts
(
{ template }: { template: TemplateBuilder<TSExportAssignment> }
): PluginObj => ({
name: 'replace-ts-export-assignment',
visitor: {
TSExportAssignment(path: NodePath<TSExportAssignment>) {
path.replaceWith(template('module.exports = ASSIGNMENT;')({
ASSIGNMENT: path.node.expression
}));
}
}
})
]
);
}
// Support JSX/TSX at all times, regardless of the file extension.
plugins.push([require('@babel/plugin-transform-react-jsx'), {
throwIfNamespace: false,
runtime: 'automatic',
importSource: path.dirname(require.resolve('playwright')),
}]);
if (!isModule) {
plugins.push([require('@babel/plugin-transform-modules-commonjs')]);
// Note: we used to include '@babel/plugin-transform-dynamic-import' to convert async imports
// into require(), so that pirates can intercept them. With the ESM loader enabled by default,
// there is no need for this.
plugins.push([
(): PluginObj => ({
name: 'css-to-identity-obj-proxy',
visitor: {
ImportDeclaration(path: NodePath<ImportDeclaration>) {
if (path.node.source.value.match(/\.(css|less|scss)$/))
path.remove();
}
}
})
]);
}
return {
browserslistConfigFile: false,
babelrc: false,
configFile: false,
assumptions: {
// Without this, babel defines a top level function that
// breaks playwright evaluates.
setPublicClassFields: true,
},
presets: isTypeScript ? [
[require('@babel/preset-typescript'), { onlyRemoveTypeImports: false }],
] : [],
plugins: [
...pluginsPrologue.map(([name, options]) => [require(name), options]),
...plugins,
...pluginsEpilogue.map(([name, options]) => [require(name), options]),
],
compact: false,
sourceMaps: 'both',
};
}
let isTransforming = false;
function isTypeScript(filename: string) {
return filename.endsWith('.ts') || filename.endsWith('.tsx') || filename.endsWith('.mts') || filename.endsWith('.cts');
}
export function babelTransform(code: string, filename: string, isModule: boolean, pluginsPrologue: [string, any?][], pluginsEpilogue: [string, any?][]): BabelFileResult | null {
if (isTransforming)
return null;
// Prevent reentry while requiring plugins lazily.
isTransforming = true;
try {
const options = babelTransformOptions(isTypeScript(filename), isModule, pluginsPrologue, pluginsEpilogue);
return babel.transform(code, { filename, ...options });
} finally {
isTransforming = false;
}
}
export function babelParse(code: string, filename: string, isModule: boolean): babel.ParseResult {
const options = babelTransformOptions(isTypeScript(filename), isModule, [], []);
return babel.parse(code, { filename, ...options })!;
}
@@ -23,7 +23,7 @@ import crypto from 'crypto';
import sourceMapSupport from 'source-map-support';
import { loadTsConfig } from './tsconfig-loader';
import { packageJSON } from '../package';
import { libPath, packageJSON } from '../package';
import { createFileMatcher, debugTest, fileIsModule, resolveImportSpecifierAfterMapping } from '../util';
import { belongsToNodeModules, currentFileDepsCollector, getFromCompilationCache, installSourceMapSupport } from './compilationCache';
import { addHook } from './pirates';
@@ -236,7 +236,7 @@ export function transformHook(originalCode: string, filename: string, moduleUrl?
// Silence the annoying warning.
process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true';
const { babelTransform }: { babelTransform: BabelTransformFunction } = require('./babelBundle');
const { babelTransform }: { babelTransform: BabelTransformFunction } = require(libPath('transform', 'babelBundle'));
transformData = new Map<string, any>();
// Pass `setTransformData` to plugins via plugin options instead of having
// them import it. The bundled esmLoader inlines its own copy of this file,
+1 -1
View File
@@ -17,7 +17,7 @@
import { browserTest as it, expect } from '../config/browserTest';
import fs from 'fs';
import path from 'path';
import extractZip from '../../packages/playwright-core/bundles/utils/src/third_party/extract-zip';
import { extractZip } from '../../packages/playwright-core/src/server/utils/third_party/extractZip';
it('should context.routeFromHAR, matching the method and following redirects', async ({ context, asset }) => {
const path = asset('har-fulfill.har');
+1 -1
View File
@@ -19,7 +19,7 @@
import type { TraceViewerFixtures } from '../config/traceViewerFixtures';
import { traceViewerFixtures } from '../config/traceViewerFixtures';
import extractZip from '../../packages/playwright-core/bundles/utils/src/third_party/extract-zip';
import { extractZip } from '../../packages/playwright-core/src/server/utils/third_party/extractZip';
import fs from 'fs';
import path from 'path';
import type http from 'http';
+2 -2
View File
@@ -20,8 +20,8 @@ import path from 'path';
import { html } from '../../packages/playwright/lib/runner';
const { startHtmlReportServer } = html;
import { expect as baseExpect, test as baseTest, stripAnsi } from './playwright-test-fixtures';
import extractZip from '../../packages/playwright-core/bundles/utils/src/third_party/extract-zip';
import * as yazl from '../../packages/playwright-core/bundles/utils/node_modules/yazl';
import { extractZip } from '../../packages/playwright-core/src/server/utils/third_party/extractZip';
import * as yazl from 'yazl';
import { utils } from '../../packages/playwright-core/lib/coreBundle';
import { Readable } from 'stream';
import type { FullResult, JSONReportTestResult } from '../../packages/playwright-test/reporter';
+92 -179
View File
@@ -69,8 +69,6 @@ const copyFiles = [];
const watchMode = process.argv.slice(2).includes('--watch');
const withSourceMaps = watchMode;
const disableInstall = process.argv.slice(2).includes('--disable-install');
const bundleFilterIndex = process.argv.indexOf('--bundle');
const bundleFilter = bundleFilterIndex !== -1 ? process.argv[bundleFilterIndex + 1] : undefined;
const ROOT = path.join(__dirname, '..', '..');
/**
@@ -202,19 +200,6 @@ async function runWatch() {
runOnChange(onChange);
}
/**
* @param {string} filter
*/
async function runBundleOnly(filter) {
const matching = bundleSteps.filter((_, i) => bundles[i].modulePath.includes(filter));
if (!matching.length) {
console.error(`No bundles matching "${filter}". Available: ${bundles.map(b => b.modulePath).join(', ')}`);
process.exit(1);
}
for (const step of matching)
await step.run();
}
async function runBuild() {
for (const { files, from, to, ignored } of copyFiles) {
const watcher = chokidar.watch([filePath(files)], {
@@ -243,52 +228,6 @@ function copyFile(file, from, to) {
fs.copyFileSync(file, destination);
}
/**
* @typedef {{
* modulePath: string,
* entryPoints: string[],
* external?: string[],
* outdir?: string,
* outfile?: string,
* minify?: boolean,
* alias?: Record<string, string>,
* }} BundleOptions
*/
/** @type {BundleOptions[]} */
const bundles = [];
bundles.push({
modulePath: 'packages/playwright/bundles/babel',
outdir: 'packages/playwright/lib/transform',
entryPoints: ['src/babelBundleImpl.ts'],
external: ['playwright'],
});
bundles.push({
modulePath: 'packages/playwright/bundles/expect',
outdir: 'packages/playwright/lib/matchers',
entryPoints: ['src/expectBundleImpl.ts'],
});
bundles.push({
modulePath: 'packages/playwright-core/bundles/utils',
outfile: 'packages/playwright-core/lib/utilsBundleImpl/index.js',
entryPoints: ['src/utilsBundleImpl.ts'],
external: ['fsevents', 'express', '@anthropic-ai/sdk'],
alias: {
'raw-body': 'raw-body.ts',
},
});
// @playwright/client
bundles.push({
modulePath: 'packages/playwright-client',
outdir: 'packages/playwright-client/lib',
entryPoints: ['src/index.ts'],
minify: false,
});
class GroupStep extends Step {
/** @param {Step[]} steps */
constructor(steps) {
@@ -317,33 +256,9 @@ updateSteps.push(new ProgramStep({
concurrent: true,
}));
// Update bundles.
for (const bundle of bundles) {
// Do not update @playwright/client, it has not its own deps.
if (bundle.modulePath === 'packages/playwright-client')
continue;
const packageJson = path.join(filePath(bundle.modulePath), 'package.json');
if (!fs.existsSync(packageJson))
throw new Error(`${packageJson} does not exist`);
updateSteps.push(new ProgramStep({
command: 'npm',
args: ['ci', '--save=false', '--fund=false', '--audit=false', '--omit=optional'],
shell: true,
cwd: filePath(bundle.modulePath),
concurrent: true,
}));
}
steps.push(new GroupStep(updateSteps));
// Generate third party licenses for bundles.
steps.push(new ProgramStep({
command: 'node',
args: [path.resolve(__dirname, '../generate_third_party_notice.js')],
shell: true,
}));
// Build injected icons.
steps.push(new ProgramStep({
command: 'node',
@@ -379,7 +294,7 @@ class EsbuildStep extends Step {
console.log('==== Running esbuild:', this._relativeEntryPoints().join(', '));
const start = Date.now();
const result = await build(this._options);
this._writeBundleReport(result);
await this._writeBundleReport(result);
console.log('==== Done in', Date.now() - start, 'ms');
}
}
@@ -410,7 +325,7 @@ class EsbuildStep extends Step {
try {
const result = await this._context?.rebuild();
if (result)
this._writeBundleReport(result);
await this._writeBundleReport(result);
} catch (e) {
// Ignore. Esbuild inherits stderr and already logs nicely formatted errors
// before throwing.
@@ -423,51 +338,10 @@ class EsbuildStep extends Step {
/**
* @param {import('esbuild').BuildResult} result
*/
_writeBundleReport(result) {
async _writeBundleReport(result) {
if (!this._options.bundle || !result.metafile)
return;
const { outputs } = result.metafile;
for (const [outFile, outInfo] of Object.entries(outputs)) {
if (outFile.endsWith('.map'))
continue;
const inputs = Object.keys(outInfo.inputs)
.filter(p => !p.startsWith('(disabled):'))
.sort();
const externals = new Set();
for (const [, meta] of Object.entries(outInfo.inputs)) {
// imports field is per-input via metafile.inputs, not outputs.
}
for (const inFile of inputs) {
const meta = result.metafile.inputs[inFile];
if (!meta) continue;
for (const imp of meta.imports || []) {
if (!imp.external)
continue;
if (imp.path.startsWith('node:'))
continue;
if (require('module').isBuiltin?.(imp.path) || require('module').builtinModules.includes(imp.path))
continue;
externals.add(imp.path);
}
}
const sortedExternals = [...externals].sort();
const lines = [];
lines.push(`# ${path.relative(ROOT, outFile)}`);
lines.push(`# size: ${(outInfo.bytes / 1024).toFixed(1)} KB`);
lines.push('');
lines.push(`## Inlined (${inputs.length})`);
for (const f of inputs)
lines.push(` ${f}`);
lines.push('');
lines.push(`## External (${sortedExternals.length})`);
for (const e of sortedExternals)
lines.push(` ${e}`);
lines.push('');
const reportPath = outFile + '.txt';
fs.writeFileSync(reportPath, lines.join('\n'));
const rel = path.relative(ROOT, outFile);
console.log(` bundle: ${rel} (${inputs.length} files, ${sortedExternals.length} external, ${(outInfo.bytes / 1024).toFixed(1)} KB)`);
}
await require('./bundle_report').writeReports(result);
}
_relativeEntryPoints() {
@@ -567,10 +441,21 @@ function _parseNamedList(braced) {
const inner = braced.replace(/^\s*\{|\}\s*$/g, '').trim();
if (!inner)
return [];
return inner.split(',').map(s => s.trim()).filter(Boolean).map(spec => {
const m = spec.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
return { src: m[1], alias: m[2] || m[1] };
});
return inner
.split(',')
.map(s => s.trim())
.filter(Boolean)
// Strip `type` modifier on individual specifiers — type-only imports
// don't need runtime rewriting, but they're often mixed with value
// imports inside a single `{ ... }` clause.
.map(s => s.replace(/^type\s+/, ''))
.map(spec => {
const m = spec.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
if (!m)
return null;
return { src: m[1], alias: m[2] || m[1] };
})
.filter(Boolean);
}
function _rewriteVendoredImports(filePath, contents) {
@@ -613,6 +498,11 @@ const dynamicImportToRequirePlugin = {
}
if (!hasAlias && !hasVendored)
return undefined;
// Run vendored rewrites FIRST so that mappings for specific
// `@utils/third_party/*` paths win over the generic `@utils/*`
// alias rewrite below.
if (hasVendored)
contents = _rewriteVendoredImports(args.path, contents);
if (hasAlias) {
contents = contents.replace(
/import\s*\{([^}]*)\}\s*from\s*'@isomorphic\/[^']+';?/g,
@@ -623,8 +513,6 @@ const dynamicImportToRequirePlugin = {
(_, names) => `const {${names}} = require('playwright-core/lib/coreBundle').utils;`
);
}
if (hasVendored)
contents = _rewriteVendoredImports(args.path, contents);
return { contents, loader: 'ts' };
});
}
@@ -667,9 +555,6 @@ steps.push(new EsbuildStep({
filePath('packages/playwright-core/src/package.ts'),
filePath('packages/playwright-core/src/serverRegistry.ts'),
filePath('packages/playwright-core/src/tools/utils/socketConnection.ts'),
// Bundle entry points otherwise inlined in coreBundle, figure this out.
filePath('packages/playwright-core/src/utilsBundle.ts'),
],
outdir: filePath('packages/playwright-core/lib'),
sourcemap: withSourceMaps ? 'linked' : false,
@@ -680,6 +565,45 @@ steps.push(new EsbuildStep({
const playwrightCoreSrc = filePath('packages/playwright-core/src');
// playwright-core/lib/utilsBundle.js — bundled npm utilities barrel.
steps.push(new EsbuildStep({
bundle: true,
entryPoints: [filePath('packages/playwright-core/src/utilsBundle.ts')],
outfile: filePath('packages/playwright-core/lib/utilsBundle.js'),
sourcemap: withSourceMaps ? 'linked' : false,
platform: 'node',
format: 'cjs',
external: ['fsevents', 'express', '@anthropic-ai/sdk'],
alias: {
'raw-body': filePath('utils/build/raw-body.ts'),
},
}, [filePath('packages/playwright-core/src/utilsBundle.ts'), filePath('utils/build/raw-body.ts')]));
// Any import whose specifier ends with `/utilsBundle` (relative, any
// depth — the dynamic-import-to-require plugin emits depth-based paths
// like `./utilsBundle`, `../utilsBundle`, `../../utilsBundle` depending
// on the source file) must be kept external and rewritten to the sibling
// `./utilsBundle` at bundle output level, because the bundle file lives
// at lib/coreBundle.js right next to lib/utilsBundle.js regardless of
// where the original source lived.
const externalizeUtilsBundlePlugin = {
name: 'externalize-utilsBundle',
setup(build) {
build.onResolve({ filter: /utilsBundle/ }, args => {
if (!args.path.startsWith('.'))
return;
// Match `<depth>/utilsBundle` or `<depth>/utilsBundle.js` so
// depth-based relative paths like ./utilsBundle, ../utilsBundle,
// ../../utilsBundle all normalize to a single external at the
// bundle output level. The `.js` suffix form is used by direct
// require() call sites (e.g. tools/cli-client/program.ts).
if (!/(^|\/)utilsBundle(\.js)?$/.test(args.path))
return;
return { path: './utilsBundle', external: true };
});
},
};
// Build playwright-core as a single bundle.
steps.push(new EsbuildStep({
bundle: true,
@@ -689,8 +613,6 @@ steps.push(new EsbuildStep({
platform: 'node',
format: 'cjs',
external: [
'./utilsBundleImpl',
'./utilsBundleImpl/*',
'../../api.json',
'./help.json',
// TODO: await import plugin is incompatible with esbuild, remove it
@@ -700,7 +622,7 @@ steps.push(new EsbuildStep({
'chromium-bidi/*',
'mitt',
],
plugins: [dynamicImportToRequirePlugin],
plugins: [externalizeUtilsBundlePlugin, dynamicImportToRequirePlugin],
}, [playwrightCoreSrc]));
function assertCoreBundleHasNoNodeModules() {
@@ -727,20 +649,23 @@ function assertCoreBundleHasNoNodeModules() {
steps.push(new CustomCallbackStep(assertCoreBundleHasNoNodeModules));
// playwright/lib/transform/esmLoader.js — bundled ESM loader registered by
// common/esmLoaderHost.ts via node:module register. Same externalization
// rules as the worker bundle.
// common/esmLoaderHost.ts via node:module register. Output sits next to
// babelBundle.js so source-relative `./babelBundle` matches the runtime
// sibling external.
{
const playwrightSrc = filePath('packages/playwright/src');
steps.push(new EsbuildStep({
bundle: true,
entryPoints: [filePath('packages/playwright/src/transform/esmLoader.ts')],
outfile: filePath('packages/playwright/lib/esmLoaderBundle.js'),
outfile: filePath('packages/playwright/lib/transform/esmLoader.js'),
sourcemap: withSourceMaps ? 'linked' : false,
platform: 'node',
format: 'cjs',
external: [
'playwright-core',
'playwright-core/*',
'../package',
'../globals',
],
plugins: [],
}, [playwrightSrc]));
@@ -762,6 +687,24 @@ steps.push(new EsbuildStep({
plugins: [dynamicImportToRequirePlugin],
}));
// playwright/lib/transform/babelBundle.js — bundled babel facade.
// Shared by esmLoaderBundle and commonBundle as an external sibling to
// avoid inlining the full babel package graph into each consumer bundle.
steps.push(new EsbuildStep({
bundle: true,
entryPoints: [filePath('packages/playwright/src/transform/babelBundle.ts')],
outfile: filePath('packages/playwright/lib/transform/babelBundle.js'),
sourcemap: withSourceMaps ? 'linked' : false,
platform: 'node',
format: 'cjs',
external: [
'playwright-core',
'playwright-core/*',
'../package',
],
plugins: [dynamicImportToRequirePlugin],
}, [filePath('packages/playwright/src')]));
// playwright/lib/matchers/expect.js — bundled jest expect facade.
steps.push(new EsbuildStep({
bundle: true,
@@ -816,6 +759,7 @@ steps.push(new EsbuildStep({
'../matchers/expect',
'../loader/loaderProcessEntry.js',
'../worker/workerProcessEntry.js',
'../transform/babelBundle',
],
plugins: [dynamicImportToRequirePlugin],
}, [filePath('packages/playwright/src')]));
@@ -877,12 +821,12 @@ steps.push(new EsbuildStep({
}, [playwrightCoreSrc]));
function copyXdgOpen() {
const outdir = filePath('packages/playwright-core/lib/utilsBundleImpl');
const outdir = filePath('packages/playwright-core/lib');
if (!fs.existsSync(outdir))
fs.mkdirSync(outdir, { recursive: true });
// 'open' package requires 'xdg-open' binary to be present, which does not get bundled by esbuild.
fs.copyFileSync(filePath('packages/playwright-core/bundles/utils/node_modules/open/xdg-open'), path.join(outdir, 'xdg-open'));
fs.copyFileSync(filePath('node_modules/open/xdg-open'), path.join(outdir, 'xdg-open'));
console.log('==== Copied xdg-open to', path.join(outdir, 'xdg-open'));
}
@@ -922,36 +866,6 @@ const pkgSizePlugin = {
},
};
// Build/watch bundles.
/**
* @param {BundleOptions} bundle
* @returns {import('esbuild').BuildOptions}
*/
function bundleToEsbuildOptions(bundle) {
return {
bundle: true,
format: 'cjs',
platform: 'node',
target: 'ES2019',
sourcemap: watchMode,
minify: !watchMode,
entryPoints: bundle.entryPoints.map(e => path.join(filePath(bundle.modulePath), e)),
...(bundle.outdir ? { outdir: filePath(bundle.outdir) } : {}),
...(bundle.outfile ? { outfile: filePath(bundle.outfile) } : {}),
...(bundle.external ? { external: bundle.external } : {}),
...(bundle.minify !== undefined ? { minify: bundle.minify } : {}),
alias: bundle.alias ? Object.fromEntries(Object.entries(bundle.alias).map(([k, v]) => [k, path.join(filePath(bundle.modulePath), v)])) : undefined,
metafile: true,
plugins: [pkgSizePlugin],
};
}
/** @type {EsbuildStep[]} */
const bundleSteps = bundles.map(b => new EsbuildStep(bundleToEsbuildOptions(b)));
for (const step of bundleSteps)
steps.push(step);
// Build/watch trace viewer service worker.
steps.push(new ProgramStep({
command: 'npx',
@@ -1128,5 +1042,4 @@ process.on('SIGINT', () => {
process.exit(0);
});
bundleFilter ? runBundleOnly(bundleFilter) : watchMode ? runWatch() : runBuild();
watchMode ? runWatch() : runBuild();
+193
View File
@@ -0,0 +1,193 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-check
const fs = require('fs');
const path = require('path');
const checker = require('license-checker');
const ROOT = path.join(__dirname, '..', '..');
const BUILTIN_SET = new Set(require('module').builtinModules);
/** @type {Record<string, { licenseText?: string, repository?: string, licenses?: string }> | null} */
let _licenseMap = null;
/** Memoized license map keyed by "name@version". Lazily populated on
* first call; shared across all bundles in a single build run. */
async function getLicenseMap() {
if (_licenseMap)
return _licenseMap;
_licenseMap = await new Promise((resolve, reject) => {
checker.init({
start: ROOT,
production: false,
customPath: { licenseText: '' },
}, (err, packages) => err ? reject(err) : resolve(packages));
});
return _licenseMap;
}
/** Extract the owning npm package directory from an input path like
* "node_modules/foo/src/index.js" or
* "packages/x/node_modules/@scope/foo/lib/x.js". */
function packageDirForInput(inputPath) {
const segments = inputPath.split('/');
const nmIdx = segments.lastIndexOf('node_modules');
if (nmIdx === -1)
return null;
const next = segments[nmIdx + 1];
if (!next)
return null;
const pkgSegs = next.startsWith('@')
? [next, segments[nmIdx + 2]]
: [next];
if (pkgSegs.some(s => !s))
return null;
return path.join(ROOT, ...segments.slice(0, nmIdx), 'node_modules', ...pkgSegs);
}
/** Read "name@version" from a package directory, or null. */
function packageKeyForDir(pkgDir) {
try {
const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
if (!pkg.name || !pkg.version)
return null;
return `${pkg.name}@${pkg.version}`;
} catch {
return null;
}
}
/** Given one bundle's metafile output, return the deduped sorted list of
* "name@version" strings for every inlined npm package. */
function inlinedPackages(outInfo) {
const keys = new Set();
for (const inputPath of Object.keys(outInfo.inputs)) {
if (inputPath.startsWith('(disabled):'))
continue;
const dir = packageDirForInput(inputPath);
if (!dir)
continue;
const key = packageKeyForDir(dir);
if (key)
keys.add(key);
}
return [...keys].sort();
}
function fmtKB(bytes) {
return `${(bytes / 1024).toFixed(1)} KB`;
}
/** Write the .js.txt report (inlined files + externals + sizes). */
function writeBundleReport(result, outFile, outInfo) {
const inputEntries = Object.entries(outInfo.inputs)
.filter(([p]) => !p.startsWith('(disabled):'))
.map(([p, info]) => ({ path: p, bytes: info.bytesInOutput || 0 }))
.sort((a, b) => a.path.localeCompare(b.path));
const externals = new Set();
for (const { path: inFile } of inputEntries) {
const meta = result.metafile.inputs[inFile];
if (!meta)
continue;
for (const imp of meta.imports || []) {
if (!imp.external)
continue;
if (imp.path.startsWith('node:'))
continue;
if (BUILTIN_SET.has(imp.path))
continue;
externals.add(imp.path);
}
}
const sortedExternals = [...externals].sort();
const maxBytes = Math.max(0, ...inputEntries.map(e => e.bytes));
const bytesColWidth = fmtKB(maxBytes).length;
const lines = [];
lines.push(`# ${path.relative(ROOT, outFile)}`);
lines.push(`# total: ${fmtKB(outInfo.bytes)}`);
lines.push('');
lines.push(`## Inlined (${inputEntries.length})`);
for (const { path: f, bytes } of inputEntries)
lines.push(` ${fmtKB(bytes).padStart(bytesColWidth)} ${f}`);
lines.push('');
lines.push(`## External (${sortedExternals.length})`);
for (const e of sortedExternals)
lines.push(` ${e}`);
lines.push('');
fs.writeFileSync(outFile + '.txt', lines.join('\n'));
return { inputCount: inputEntries.length, externalCount: sortedExternals.length };
}
/** Write the .js.LICENSE sidecar. No-op for bundles with no inlined
* third-party packages. */
async function writeBundleLicenses(outFile, outInfo) {
const keys = inlinedPackages(outInfo);
if (keys.length === 0)
return 0;
const licenseMap = await getLicenseMap();
const lines = [];
lines.push(`${path.relative(ROOT, outFile)}`);
lines.push('');
lines.push('THIRD-PARTY SOFTWARE NOTICES AND INFORMATION');
lines.push('');
lines.push('The following npm packages are inlined into this bundle.');
lines.push('');
for (const key of keys) {
const info = licenseMap[key];
const repo = info && info.repository ? info.repository : '';
lines.push(`- ${key}${repo ? ` (${repo})` : ''}`);
}
for (const key of keys) {
const info = licenseMap[key];
lines.push('');
lines.push(`%% ${key} NOTICES AND INFORMATION BEGIN HERE`);
lines.push('=========================================');
lines.push((info && info.licenseText) || `(no license text found; declared licenses: ${(info && info.licenses) || 'unknown'})`);
lines.push('=========================================');
lines.push(`END OF ${key} NOTICES AND INFORMATION`);
}
lines.push('');
lines.push('SUMMARY');
lines.push('=========================================');
lines.push(`Total Packages: ${keys.length}`);
lines.push('=========================================');
fs.writeFileSync(outFile + '.LICENSE', lines.join('\n'));
return keys.length;
}
/** Top-level entry called by EsbuildStep after each bundled build. */
async function writeReports(result) {
if (!result.metafile)
return;
for (const [outFile, outInfo] of Object.entries(result.metafile.outputs)) {
if (outFile.endsWith('.map'))
continue;
const { inputCount, externalCount } = writeBundleReport(result, outFile, outInfo);
const licCount = await writeBundleLicenses(outFile, outInfo);
const rel = path.relative(ROOT, outFile);
const licFragment = licCount ? `, ${licCount} licenses` : '';
console.log(` bundle: ${rel} (${inputCount} files, ${externalCount} external${licFragment}, ${fmtKB(outInfo.bytes)})`);
}
}
module.exports = { writeReports };
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-expect-error untyped module
import bytes from 'bytes';
import type { IncomingMessage } from 'node:http';
export default function getRawBody(req: IncomingMessage, { limit, encoding }: { limit: string, encoding: BufferEncoding }) {
const limitNumber = bytes.parse(limit);
const limitNumber = bytes.parse(limit) as number;
return new Promise<string>((resolve, reject) => {
let received = 0;
+9 -1
View File
@@ -34,7 +34,7 @@ const MAPPING = {
'chokidar': { default: 'chokidar' },
'get-east-asian-width': { namespace: 'getEastAsianWidth' },
'yazl': { namespace: 'yazl' },
'yauzl': { namespace: 'yauzl' },
'yauzl': { default: 'yauzl', namespace: 'yauzl' },
'zod': { namespace: 'z' },
'zod-to-json-schema': { named: { zodToJsonSchema: 'zodToJsonSchema' } },
'@modelcontextprotocol/sdk/client/index.js': { named: { Client: 'Client' } },
@@ -54,6 +54,14 @@ const MAPPING = {
ProgressNotificationSchema: 'ProgressNotificationSchema',
},
},
// Transitive deps of in-tree third_party extractZip.ts / lockfile.ts.
// Callers use `@utils/third_party/*` which routes through coreBundle.utils;
// their transitive imports of these npm packages need to stay external and
// come from utilsBundle just like any other vendored package.
'graceful-fs': { default: 'gracefulFs', namespace: 'gracefulFs' },
'retry': { default: 'retry' },
'signal-exit': { default: 'onExit' },
'get-stream': { default: 'getStream' },
};
const VENDORED_PACKAGES = new Set(Object.keys(MAPPING));
+17 -9
View File
@@ -62,7 +62,8 @@ async function checkDeps() {
async function innerCheckDeps(root) {
console.log('Checking DEPS for ' + path.relative(packagesDir, root));
const deps = new Set();
/** @type {Map<string, Set<string>>} */
const deps = new Map();
const src = path.join(root, 'src');
let packageJSON;
@@ -104,7 +105,7 @@ async function innerCheckDeps(root) {
const top = pkg.startsWith('@') ? pkg.split('/').slice(0, 2).join('/') : pkg.split('/')[0];
deps.delete(top);
}
for (const dep of deps) {
for (const dep of [...deps.keys()]) {
let resolved;
try {
resolved = require.resolve(dep, { paths: [root] });
@@ -119,8 +120,11 @@ async function innerCheckDeps(root) {
if (deps.size) {
console.log('Dependencies are not declared in package.json:');
for (const dep of deps)
for (const [dep, files] of deps) {
console.log(` ${dep}`);
for (const file of files)
console.log(` ${path.relative(root, file)}`);
}
process.exit(1);
}
}
@@ -177,16 +181,20 @@ async function innerCheckDeps(root) {
return;
}
if (importName.startsWith('@'))
deps.add(importName.split('/').slice(0, 2).join('/'));
else
deps.add(importName.split('/')[0]);
// Per-folder explicit allow-list: `node_modules/<importName>` in DEPS.list
// declares the file may import that exact package specifier.
// declares the file may import that exact package specifier. When a
// DEPS.list authorizes the dep, do NOT add it to the package.json
// dependency check either — the per-file allowlist is the contract.
if (mergedDeps.includes('node_modules/' + importName))
return;
const topLevel = importName.startsWith('@')
? importName.split('/').slice(0, 2).join('/')
: importName.split('/')[0];
if (!deps.has(topLevel))
deps.set(topLevel, new Set());
deps.get(topLevel).add(fileName);
if (!allowExternalImport(importName, packageJSON))
errors.push(`Disallowed external dependency ${importName} from ${path.relative(root, fileName)}`);
}
-91
View File
@@ -1,91 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const checker = require('license-checker');
const fs = require('fs');
const path = require('path');
async function checkDir(dir) {
return await new Promise((f, r) => {
checker.init({
start: dir,
production: true,
customPath: {
licenseText: '',
}
}, function(err, packages) {
if (err && err.message.startsWith('No packages found'))
r(new Error(`No packages found in ${dir}`));
else if (err)
r(err);
else
f(packages);
});
});
}
(async () => {
for (const project of ['playwright-core', 'playwright']) {
const lines = [];
lines.push(`microsoft/${project}
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise.
`);
const allPackages = {};
const projectDir = path.join(__dirname, '..', 'packages', project);
const bundlesDir = path.join(projectDir, 'bundles');
for (const bundle of fs.readdirSync(bundlesDir, { withFileTypes: true })) {
if (!bundle.isDirectory())
continue;
const dir = path.join(bundlesDir, bundle.name);
const packages = await checkDir(dir);
for (const [key, value] of Object.entries(packages)) {
if (value.licenseText)
allPackages[key] = value;
}
}
const packages = await checkDir('node_modules/codemirror');
for (const [key, value] of Object.entries(packages)) {
if (value.licenseText)
allPackages[key] = value;
}
// fsevents is a darwin-only dependency that we do not bundle.
const keys = Object.keys(allPackages).sort().filter(key => !key.startsWith('fsevents@'));
for (const key of keys)
lines.push(`-\t${key} (${allPackages[key].repository})`);
for (const key of keys) {
lines.push(`\n%% ${key} NOTICES AND INFORMATION BEGIN HERE`);
lines.push(`=========================================`);
lines.push(allPackages[key].licenseText);
lines.push(`=========================================`);
lines.push(`END OF ${key} AND INFORMATION`);
}
lines.push(`\nSUMMARY BEGIN HERE`);
lines.push(`=========================================`);
lines.push(`Total Packages: ${keys.length}`);
lines.push(`=========================================`);
lines.push(`END OF SUMMARY`);
fs.writeFileSync(path.join(projectDir, 'ThirdPartyNotices.txt'), lines.join('\n').replace(/\r\n/g, '\n'));
}
})();