Skip to content

Commit 0e66a09

Browse files
authored
devops(pipeline): resolve pip and npm packages from DevDiv_PublicPackages feed (#3194)
1 parent 010a9cc commit 0e66a09

6 files changed

Lines changed: 70 additions & 23 deletions

File tree

.azure-pipelines/publish.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,55 @@ extends:
3636
path: $(Build.ArtifactStagingDirectory)/esrp-build
3737
artifact: esrp-build
3838
steps:
39+
- bash: |
40+
if [[ ! "$CURRENT_BRANCH" =~ ^v1\..* ]]; then
41+
echo "Can only publish from a release tag (v1.*)."
42+
echo "Unexpected ref name: $CURRENT_BRANCH"
43+
exit 1
44+
fi
45+
env:
46+
CURRENT_BRANCH: ${{ variables['Build.SourceBranchName'] }}
47+
displayName: 'Check the ref is a release tag'
48+
# Allow manual runs on any branch to exercise the build without publishing.
49+
condition: ne(variables['Build.Reason'], 'Manual')
3950
- task: UsePythonVersion@0
4051
inputs:
4152
versionSpec: '3.10'
4253
displayName: 'Use Python'
43-
- task: NodeTool@0
54+
# Resolve every pip install (including the isolated build environments
55+
# that `python -m build` and `pip install -e .` create) through the
56+
# DevDiv_PublicPackages Azure Artifacts feed instead of pypi.org, as
57+
# required by SFI-ES4.2.4. The task exports an authenticated PIP_INDEX_URL.
58+
- task: PipAuthenticate@1
4459
inputs:
45-
versionSpec: '24.x'
46-
displayName: 'Use Node.js'
60+
artifactFeeds: DevDiv/DevDiv_PublicPackages
61+
displayName: 'Authenticate pip to DevDiv_PublicPackages feed'
62+
- task: UseNode@1
63+
inputs:
64+
version: '24.x'
65+
displayName: 'Install Node.js'
66+
# scripts/build_driver.py fetches playwright-core with `npm pack`, which
67+
# picks up the registry and credentials from this .npmrc.
68+
- script: echo "registry=https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/DevDiv_PublicPackages/npm/registry/" >> .npmrc
69+
displayName: 'Point npm registry at DevDiv_PublicPackages feed'
70+
- task: npmAuthenticate@0
71+
inputs:
72+
workingFile: .npmrc
73+
displayName: 'Authenticate npm to DevDiv_PublicPackages feed'
4774
- script: |
48-
python -m pip install --upgrade pip
49-
pip install -r local-requirements.txt
50-
pip install -r requirements.txt
51-
pip install -e .
75+
python -m pip install --upgrade pip --disable-pip-version-check
76+
pip install -r local-requirements.txt --disable-pip-version-check
77+
pip install -r requirements.txt --disable-pip-version-check
78+
pip install -e . --disable-pip-version-check
5279
for wheel in $(python setup.py --list-wheels); do
5380
PLAYWRIGHT_TARGET_WHEEL=$wheel python -m build --wheel --outdir $(Build.ArtifactStagingDirectory)/esrp-build
5481
done
5582
displayName: 'Install & Build'
5683
- job: Publish
5784
dependsOn: Build
85+
# Only publish from release tags; manual runs on a branch stop after Build,
86+
# which lets the build be exercised without publishing.
87+
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v1.'))
5888
templateContext:
5989
type: releaseJob
6090
isProduction: true

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ utils/docker/dist/
2121
Pipfile
2222
Pipfile.lock
2323
.venv/
24+
25+
# Written by the release pipeline (npmAuthenticate@0); holds feed credentials.
26+
.npmrc

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ Python bindings for [Playwright](https://playwright.dev). The Python client talk
1515
- `tests/async/`, `tests/sync/` — pytest suites. Most new tests are added to the async file with a sync mirror.
1616
- `DRIVER_VERSION` — the single source of truth for which Playwright release the driver is assembled from (one line, the `playwright-core` npm version, e.g. `1.61.0`, no `v` prefix). Read by `setup.py`, `scripts/build_driver.py`, and CI. The wheel build downloads `playwright-core` at this version from npm plus the matching Node.js binary and assembles the per-platform bundles — no source build. The version is baked into the staged bundle filenames (`driver/playwright-<version>-<suffix>.zip`), so it doubles as the build cache key.
1717
- `NODE_VERSION` — the Node.js version bundled with the driver (one line, e.g. `24.16.0`). Maintained at roll time by `scripts/update_node_version.py` (latest LTS, mirroring upstream's `utils/build/update-playwright-node.mjs`).
18-
- `scripts/build_driver.py` — assembles the per-platform driver bundles into `driver/` by downloading the `playwright-core` npm package (`DRIVER_VERSION`) and the official Node.js binaries (`NODE_VERSION`). Pure Python stdlib (no Node/npm/git); invoked from `setup.py`'s `bdist_wheel` with the target platform's suffix (no arg builds all six).
18+
- `scripts/build_driver.py` — assembles the per-platform driver bundles into `driver/` by downloading the `playwright-core` npm package (`DRIVER_VERSION`) and the official Node.js binaries (`NODE_VERSION`). Fetches `playwright-core` with `npm pack` (needs Node.js/npm on PATH; honours a root `.npmrc`) and the Node.js binaries over plain HTTP; invoked from `setup.py`'s `bdist_wheel` with the target platform's suffix (no arg builds all six).
1919
- `api.json` is **not** shipped in the bundle and is never written into the driver — `scripts/update_api.sh` generates it from a nearby `microsoft/playwright` checkout (`$PW_SRC_DIR`) into a temp file and passes it to codegen via `PW_API_JSON` (read by `scripts/documentation_provider.py`). Needed only when regenerating the API, never at runtime.
2020
- `ROLLING.md`, `CONTRIBUTING.md` — human-facing setup and roll docs.
2121

2222
## Setup
2323

24-
`CONTRIBUTING.md` has the full sequence. The short version (needs Node.js, npm, git and bash for the driver build):
24+
`CONTRIBUTING.md` has the full sequence. The short version (needs Node.js and npm for the driver build):
2525

2626
```sh
2727
python3 -m venv env && source env/bin/activate

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Build and install drivers:
2323

2424
The driver is assembled from published artifacts — the `playwright-core` npm
2525
package (version pinned in `DRIVER_VERSION`) and the official Node.js binary
26-
(pinned in `NODE_VERSION`). Building a wheel just downloads them; no Node/npm/git
27-
toolchain is required.
26+
(pinned in `NODE_VERSION`). Building a wheel downloads them with `npm pack` and
27+
plain HTTP, so Node.js/npm must be installed; no git or source build is needed.
2828

2929
```sh
3030
pip install -e .

ROLLING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pre-commit install
1212
pip install -e .
1313
```
1414
* change the driver pin in `DRIVER_VERSION` (the `playwright-core` npm version, e.g. `1.61.0`) and refresh `NODE_VERSION`: `python scripts/update_node_version.py`
15-
* download the new driver: `python -m build --wheel` (fetches `playwright-core` from npm + the matching Node.js binary and assembles the bundle; no source build). Set `npm_config_registry` to use a different npm registry.
15+
* download the new driver: `python -m build --wheel` (fetches `playwright-core` via `npm pack` + the matching Node.js binary and assembles the bundle; no source build).
1616
* generate API (needs a nearby `microsoft/playwright` checkout at `v<new>`): `PW_SRC_DIR=../playwright ./scripts/update_api.sh`
1717
* commit changes & send PR
1818
* wait for bots to pass & merge the PR

scripts/build_driver.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,24 @@
2727
LICENSE - the Node.js license
2828
package/** - the playwright-core npm package
2929
30-
Unlike the old source build this needs no Node.js, npm, git or bash — only the
31-
Python standard library.
30+
``playwright-core`` is fetched with ``npm pack`` (run from the repository root, so
31+
a root-level ``.npmrc`` -- e.g. the one the release pipeline writes to point at
32+
the internal Azure Artifacts feed -- and its credentials are honoured). The
33+
Node.js binaries are downloaded directly from nodejs.org. Apart from Node.js/npm
34+
this needs only the Python standard library.
3235
3336
Usage::
3437
3538
scripts/build_driver.py # assemble every platform bundle
3639
scripts/build_driver.py <suffix> # assemble a single bundle, e.g. mac-arm64
3740
38-
Set ``npm_config_registry`` to download ``playwright-core`` from an
39-
alternative npm registry.
40-
4141
``setup.py`` invokes the single-suffix form so a wheel build only downloads the
4242
one Node.js binary it needs.
4343
"""
4444

4545
import os
4646
import shutil
47+
import subprocess
4748
import sys
4849
import tarfile
4950
import tempfile
@@ -56,9 +57,6 @@
5657
REPO_ROOT = Path(__file__).resolve().parent.parent
5758
DRIVER_DIR = REPO_ROOT / "driver"
5859

59-
NPM_REGISTRY = os.environ.get(
60-
"npm_config_registry", "https://registry.npmjs.org"
61-
).rstrip("/")
6260
NODEJS_DIST = "https://nodejs.org/dist"
6361

6462

@@ -136,9 +134,25 @@ def _extract_zip_file(archive: zipfile.ZipFile, name: str, destination: Path) ->
136134

137135
def fetch_playwright_core(version: str, work_dir: Path) -> Path:
138136
"""Download playwright-core@<version> and extract its package/ tree once."""
139-
url = f"{NPM_REGISTRY}/playwright-core/-/playwright-core-{version}.tgz"
137+
npm = "npm.cmd" if sys.platform == "win32" else "npm"
138+
spec = f"playwright-core@{version}"
139+
# npm is run from the repository root so that a root-level .npmrc (registry
140+
# and credentials) is honoured. `npm pack` writes <name>-<version>.tgz.
141+
print(f"Downloading {spec} with npm pack", flush=True)
142+
try:
143+
subprocess.check_call(
144+
[npm, "pack", spec, "--pack-destination", str(work_dir)],
145+
cwd=REPO_ROOT,
146+
)
147+
except FileNotFoundError:
148+
raise SystemExit(
149+
"npm was not found on PATH; Node.js/npm are required to assemble the driver."
150+
)
151+
except subprocess.CalledProcessError as error:
152+
raise SystemExit(f"npm pack {spec} failed with exit code {error.returncode}")
140153
tgz = work_dir / f"playwright-core-{version}.tgz"
141-
download(url, tgz)
154+
if not tgz.is_file():
155+
raise SystemExit(f"npm pack did not produce {tgz}")
142156
with tarfile.open(tgz, "r:gz") as tar:
143157
# npm tarballs nest every file under a top-level "package/" directory,
144158
# which is exactly the bundle layout we want.
@@ -148,7 +162,7 @@ def fetch_playwright_core(version: str, work_dir: Path) -> Path:
148162
if m.name == "package" or m.name.startswith("package/")
149163
]
150164
if not members:
151-
raise SystemExit(f"No package/ entries found in {url}")
165+
raise SystemExit(f"No package/ entries found in {tgz.name}")
152166
_extract_members(tar, work_dir, members)
153167
tgz.unlink()
154168
return work_dir / "package"

0 commit comments

Comments
 (0)