Skip to content

Commit 456a525

Browse files
committed
feat(npm): use pnpm publish for npm_package
1 parent 5a47903 commit 456a525

6 files changed

Lines changed: 114 additions & 18 deletions

File tree

npm/private/npm_package.bzl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,19 @@ def npm_package(
140140
include_runfiles = False,
141141
hardlink = "auto",
142142
publishable = False,
143+
pnpm_binary = "@pnpm//:pnpm",
143144
verbose = False,
144145
**kwargs):
145146
"""A macro that packages sources into a directory (a tree artifact) and provides an `NpmPackageInfo`.
146147
147148
This target can be used as the `src` attribute to `npm_link_package`.
148149
149150
With `publishable = True` the macro also produces a target `[name].publish`, that can be run to publish to an npm registry.
150-
Under the hood, this target runs `npm publish`. You can pass arguments to npm by escaping them from Bazel using a double-hyphen,
151-
for example: `bazel run //path/to:my_package.publish -- --tag=next`
151+
Under the hood, this target runs `pnpm publish`. You can pass arguments to pnpm by escaping them from Bazel using a double-hyphen,
152+
for example: `bazel run //path/to:my_package.publish -- --tag=next`.
153+
154+
pnpm publishing uses the configured `pnpm_binary` and can resolve pnpm workspace settings such as `catalog` entries from
155+
`pnpm-workspace.yaml`. Workspace protocol dependencies follow pnpm's own resolution requirements.
152156
153157
Files and directories can be arranged as needed in the output directory using
154158
the `root_paths`, `include_srcs_patterns`, `exclude_srcs_patterns` and `replace_prefixes` attributes.
@@ -210,7 +214,7 @@ def npm_package(
210214
211215
srcs: Files and/or directories or targets that provide `DirectoryPathInfo` to copy into the output directory.
212216
213-
args: Arguments that are passed down to `<name>.publish` target and `npm publish` command.
217+
args: Arguments that are passed down to `<name>.publish` target and the publish command.
214218
215219
data: Runtime / linktime npm dependencies of this npm package.
216220
@@ -409,6 +413,9 @@ def npm_package(
409413
410414
publishable: When True, enable generation of `{name}.publish` target
411415
416+
pnpm_binary: Label of the pnpm binary used for publishing. This is typically `@pnpm//:pnpm`
417+
from the `pnpm` extension in `@aspect_rules_js//npm:extensions.bzl`.
418+
412419
verbose: If true, prints out verbose logs to stdout
413420
414421
**kwargs: Additional attributes such as `tags` and `visibility`
@@ -437,10 +444,11 @@ def npm_package(
437444
name = "{}.publish".format(name),
438445
entry_point = Label("@aspect_rules_js//npm/private:npm_publish_mjs"),
439446
fixed_args = [
447+
"$(rootpath {})".format(pnpm_binary),
440448
"./$(rootpath :{})".format(name),
441449
],
442-
data = [name],
443-
# required to make npm to be available in PATH
450+
data = [name, pnpm_binary],
451+
# pnpm <11 may delegate registry operations to npm. This can be removed when rules_js drops pnpm <=10.
444452
include_npm = True,
445453
args = args,
446454
tags = kwargs.get("tags", []) + ["manual"],

npm/private/npm_publish.mjs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
import { spawnSync } from 'node:child_process'
2+
import path from 'node:path'
23

3-
const restArgs = process.argv.slice(2)
4+
const [toolPath, packageDir, ...restArgs] = process.argv.slice(2)
45

5-
const spawn = spawnSync('npm', ['publish', ...restArgs], {
6-
stdio: 'inherit',
7-
})
6+
if (!toolPath || !packageDir) {
7+
console.error(
8+
'Expected publish tool path and package directory arguments.',
9+
)
10+
process.exit(1)
11+
}
12+
13+
const publishCwd = process.env.BUILD_WORKSPACE_DIRECTORY || process.cwd()
14+
15+
const spawn = spawnSync(
16+
path.resolve(toolPath),
17+
['publish', path.resolve(packageDir), '--no-git-checks', ...restArgs],
18+
{
19+
cwd: publishCwd,
20+
env: {
21+
...process.env,
22+
BAZEL_BINDIR: process.env.BAZEL_BINDIR || '.',
23+
},
24+
stdio: 'inherit',
25+
},
26+
)
27+
28+
if (spawn.error) {
29+
console.error(spawn.error.message)
30+
process.exit(1)
31+
}
32+
33+
if (spawn.signal) {
34+
console.error(`pnpm publish exited with signal ${spawn.signal}`)
35+
process.exit(1)
36+
}
837

938
process.exit(spawn.status)

npm/private/test/npm_package_publish/BUILD.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,28 @@ npm_package(
2020
publishable = True,
2121
)
2222

23+
npm_package(
24+
name = "pkg_pnpm",
25+
srcs = [
26+
"pnpm_catalog/index.js",
27+
"pnpm_catalog/package.json",
28+
],
29+
publishable = True,
30+
root_paths = [package_name() + "/pnpm_catalog"],
31+
)
32+
2333
sh_test(
2434
name = "test",
2535
srcs = ["test.sh"],
2636
args = [
2737
"$(locations :pkg_a.publish)",
2838
"$(locations :pkg_b.publish)",
39+
"$(locations :pkg_pnpm.publish)",
2940
],
3041
data = [
3142
":pkg_a.publish",
3243
":pkg_b.publish",
44+
":pkg_pnpm.publish",
3345
],
3446
tags = [
3547
"no-remote-exec",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const value = 1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "@mycorp/pkg-to-publish-pnpm",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"typescript": "catalog:"
6+
}
7+
}

npm/private/test/npm_package_publish/test.sh

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,66 @@
22

33
readonly PUBLISH_A="$1"
44
readonly PUBLISH_B="$2"
5+
readonly PUBLISH_PNPM="$3"
56

6-
# assert that it prints package name from package.json to stderr,
7-
# to ensure package directory is properly passed and npm can read it.
8-
$PUBLISH_A 2>pub_a.log
7+
# Assert that pnpm reads package.json from the package directory.
8+
$PUBLISH_A >pub_a.log 2>&1
99

10-
cat pub_a.log | grep 'npm notice package: @mycorp/pkg-to-publish@'
10+
cat pub_a.log | grep 'ERR_PNPM_PACKAGE_VERSION_NOT_FOUND'
1111

1212
# shellcheck disable=SC2181
1313
if [ $? != 0 ]; then
14-
echo "FAIL: expected 'npm notice package: @mycorp/pkg-to-publish@' error, GOT: $(cat pub_a.log)"
14+
echo "FAIL: expected 'ERR_PNPM_PACKAGE_VERSION_NOT_FOUND' error, GOT: $(cat pub_a.log)"
1515
exit 1
1616
fi
1717

1818
# asserting that npm_package has no package.json in it's srcs and we fail correctly.
19-
# npm publish requires a package.json in the root of the package directory.
20-
$PUBLISH_B 2>pub_b.log
19+
# pnpm publish requires a package.json in the root of the package directory.
20+
$PUBLISH_B >pub_b.log 2>&1
2121

22-
cat pub_b.log | grep 'npm error enoent Could not read package.json:'
22+
cat pub_b.log | grep 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND'
2323

2424
# shellcheck disable=SC2181
2525
if [ $? != 0 ]; then
26-
echo "FAIL: expected 'npm error enoent Could not read package.json:' error, GOT: $(cat pub_b.log)"
26+
echo "FAIL: expected 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND' error, GOT: $(cat pub_b.log)"
27+
exit 1
28+
fi
29+
30+
readonly TMP_WORKSPACE="$(mktemp -d)"
31+
trap 'rm -rf "${TMP_WORKSPACE}"' EXIT
32+
33+
cat >"${TMP_WORKSPACE}/pnpm-workspace.yaml" <<'EOF'
34+
packages:
35+
- packages/*
36+
catalog:
37+
typescript: 5.9.3
38+
EOF
39+
40+
# Ensure pnpm publish can read workspace-level catalog settings when publishing
41+
# a generated package directory.
42+
BUILD_WORKSPACE_DIRECTORY="${TMP_WORKSPACE}" \
43+
"$PUBLISH_PNPM" --dry-run --json >pub_pnpm.log 2>pub_pnpm.err
44+
45+
# shellcheck disable=SC2181
46+
if [ $? != 0 ]; then
47+
echo "FAIL: expected pnpm publish dry-run to pass, GOT stdout: $(cat pub_pnpm.log), stderr: $(cat pub_pnpm.err)"
48+
exit 1
49+
fi
50+
51+
cat pub_pnpm.log | grep '"name": "@mycorp/pkg-to-publish-pnpm"'
52+
53+
# shellcheck disable=SC2181
54+
if [ $? != 0 ]; then
55+
echo "FAIL: expected pnpm publish dry-run output to include package name, GOT: $(cat pub_pnpm.log)"
56+
exit 1
57+
fi
58+
59+
# The source package.json is 132 bytes. pnpm rewrites catalog: to the concrete
60+
# version before packing, producing the smaller manifest below.
61+
cat pub_pnpm.log | grep '"size": 116'
62+
63+
# shellcheck disable=SC2181
64+
if [ $? != 0 ]; then
65+
echo "FAIL: expected pnpm publish to resolve catalog: in package.json, GOT: $(cat pub_pnpm.log)"
2766
exit 1
2867
fi

0 commit comments

Comments
 (0)