@pnpm/napi 0.0.0-0 → 12.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +63 -0
- package/index.d.ts +289 -0
- package/index.js +169 -0
- package/package.json +39 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors
|
|
4
|
+
Copyright (c) 2016-2026 Zoltan Kochan and other contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @pnpm/napi
|
|
2
|
+
|
|
3
|
+
Node.js bindings for pnpm v12's Rust engine (pacquet), exposing pnpm's
|
|
4
|
+
programmatic API — install, rebuild, dependency resolution, and pack — to a
|
|
5
|
+
JavaScript host. The reference consumer is [Bit](https://bit.dev), which drives
|
|
6
|
+
pnpm entirely through its programmatic API.
|
|
7
|
+
|
|
8
|
+
This package binds only pnpm's **engine**. Pure data utilities that operate on
|
|
9
|
+
in-memory objects or the (byte-stable) on-disk lockfile/store formats stay as
|
|
10
|
+
regular `@pnpm/*` JS packages — both stacks share the same lockfile v9 shape,
|
|
11
|
+
`.modules.yaml` format, and store layout.
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
See [`index.d.ts`](./index.d.ts) for the full typed contract.
|
|
16
|
+
|
|
17
|
+
| Export | Purpose |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `install(options, onLog?, readPackageHook?)` | Install in-memory importers (single or workspace); `readPackageHook` transforms each resolved dependency manifest (must be synchronous). Returns `{ stats, depsRequiringBuild?, storeDir }`. |
|
|
20
|
+
| `rebuild(options, onLog?, selectedNames?)` | Re-run dependency build scripts against a materialized install (frozen path). |
|
|
21
|
+
| `resolveDependency(wanted, options)` | Resolve an npm-registry specifier to `{ id, manifest, resolvedVia, … }`. |
|
|
22
|
+
| `pack(options, onLog?)` | Build a publishable `.tgz` from a project directory. |
|
|
23
|
+
| `parseBareSpecifier(spec, alias?)` | Split/validate a dependency specifier; `null` when unparsable. |
|
|
24
|
+
| `engineVersion()` | Version string of the underlying Rust engine (pacquet). |
|
|
25
|
+
| `getPeerDependencyIssues(options)` | **Not yet implemented** — throws `ERR_PNPM_NAPI_UNIMPLEMENTED`. Peer-issue reporting is not ported in pacquet's CLI either; consumers should degrade gracefully. |
|
|
26
|
+
|
|
27
|
+
Errors are plain `Error` objects carrying pnpm's `code` (`ERR_PNPM_*`) and,
|
|
28
|
+
where applicable, `hint` — lifted onto the error by the loader from the engine's
|
|
29
|
+
structured envelope.
|
|
30
|
+
|
|
31
|
+
Auth: pass `authHeaderByUri` — a map of nerf-darted registry URI → `Authorization`
|
|
32
|
+
header value (with `""` for the default registry). The host resolves these from
|
|
33
|
+
its `.npmrc` credentials; the engine applies them as-is.
|
|
34
|
+
|
|
35
|
+
## Distribution
|
|
36
|
+
|
|
37
|
+
The addon ships as prebuilt per-platform packages, the same model as the
|
|
38
|
+
`@pnpm/exe.*` CLI packages:
|
|
39
|
+
|
|
40
|
+
- `index.js` resolves the addon at load time in this order: a
|
|
41
|
+
`PNPM_NAPI_BINARY` env override, the matching
|
|
42
|
+
`@pnpm/napi.<platform>` optional dependency, then a local build.
|
|
43
|
+
- CI cross-compiles the addon per target (`napi build --release --target
|
|
44
|
+
<rust-triple>`), uploads each as `pnpm-napi.<codeTarget>.node` at the repo
|
|
45
|
+
root, then runs `scripts/generate-packages.mjs` to produce the eight
|
|
46
|
+
`@pnpm/napi.<platform>` packages and wire them as this wrapper's
|
|
47
|
+
`optionalDependencies`.
|
|
48
|
+
|
|
49
|
+
Supported targets: `win32-x64`, `win32-arm64`, `darwin-x64`, `darwin-arm64`,
|
|
50
|
+
`linux-x64`, `linux-arm64`, `linux-x64-musl`, `linux-arm64-musl`.
|
|
51
|
+
|
|
52
|
+
## Local development
|
|
53
|
+
|
|
54
|
+
Build the Rust crate and point the loader at the artifact:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
cargo build -p pacquet-napi --profile napi-release
|
|
58
|
+
cp ../../../target/napi-release/libpacquet_napi.dylib \
|
|
59
|
+
./pnpm-napi.darwin-arm64.node # .so on Linux, .dll on Windows
|
|
60
|
+
node -e "console.log(require('.').engineVersion())"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or set `PNPM_NAPI_BINARY=/path/to/addon.node`.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node API bindings for the pnpm v12 Rust engine (pacquet).
|
|
3
|
+
*
|
|
4
|
+
* Shapes intentionally mirror the pnpm v11 TypeScript programmatic API
|
|
5
|
+
* (`@pnpm/installing.deps-installer`, `@pnpm/installing.client`) so that
|
|
6
|
+
* consumers migrating from the TS engine keep their call sites stable.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface PackageManifest {
|
|
10
|
+
name?: string
|
|
11
|
+
version?: string
|
|
12
|
+
dependencies?: Record<string, string>
|
|
13
|
+
devDependencies?: Record<string, string>
|
|
14
|
+
optionalDependencies?: Record<string, string>
|
|
15
|
+
peerDependencies?: Record<string, string>
|
|
16
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>
|
|
17
|
+
dependenciesMeta?: Record<string, { injected?: boolean }>
|
|
18
|
+
bundledDependencies?: string[] | boolean
|
|
19
|
+
scripts?: Record<string, string>
|
|
20
|
+
bin?: string | Record<string, string>
|
|
21
|
+
engines?: Record<string, string>
|
|
22
|
+
os?: string[]
|
|
23
|
+
cpu?: string[]
|
|
24
|
+
libc?: string[]
|
|
25
|
+
[key: string]: unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NodeApiProject {
|
|
29
|
+
/** Absolute path of the importer directory. */
|
|
30
|
+
rootDir: string
|
|
31
|
+
/** In-memory manifest; the engine never reads package.json from disk for listed projects. */
|
|
32
|
+
manifest: PackageManifest
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ProxyConfig {
|
|
36
|
+
httpProxy?: string
|
|
37
|
+
httpsProxy?: string
|
|
38
|
+
noProxy?: string | boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface NetworkConfig {
|
|
42
|
+
ca?: string | string[]
|
|
43
|
+
cert?: string | string[]
|
|
44
|
+
key?: string
|
|
45
|
+
localAddress?: string
|
|
46
|
+
strictSsl?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of concurrent connections (sockets) to a single registry
|
|
49
|
+
* origin — pnpm's `maxSockets`. Bounds each `scheme://host[:port]` origin
|
|
50
|
+
* independently; the global `networkConcurrency` remains the outer cap.
|
|
51
|
+
*/
|
|
52
|
+
maxSockets?: number
|
|
53
|
+
networkConcurrency?: number
|
|
54
|
+
fetchRetries?: number
|
|
55
|
+
fetchRetryFactor?: number
|
|
56
|
+
fetchRetryMintimeout?: number
|
|
57
|
+
fetchRetryMaxtimeout?: number
|
|
58
|
+
fetchTimeout?: number
|
|
59
|
+
userAgent?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A synchronous `readPackage` hook applied to resolved dependency manifests. */
|
|
63
|
+
export type ReadPackageHook = (manifest: PackageManifest) => PackageManifest
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Receives engine log events. The event stream is wire-compatible with
|
|
67
|
+
* `@pnpm/core-loggers` / the bunyan-shaped objects consumed by
|
|
68
|
+
* `@pnpm/logger`'s streamParser and `@pnpm/cli.default-reporter`.
|
|
69
|
+
*/
|
|
70
|
+
export type LogListener = (event: Record<string, unknown>) => void
|
|
71
|
+
|
|
72
|
+
export interface SharedEngineOptions {
|
|
73
|
+
/** Registry routes: `{ default: url, '@scope': url, ... }` */
|
|
74
|
+
registries?: Record<string, string>
|
|
75
|
+
/**
|
|
76
|
+
* Pre-computed `Authorization` header values keyed by nerf-darted registry
|
|
77
|
+
* URI (`//host/path/`), plus `''` for the default registry — e.g.
|
|
78
|
+
* `{ '': 'Bearer abc', '//npm.example.com/': 'Basic <base64(user:pass)>' }`. The
|
|
79
|
+
* host resolves these from its `authConfig`; the engine applies them as-is.
|
|
80
|
+
*/
|
|
81
|
+
authHeaderByUri?: Record<string, string>
|
|
82
|
+
proxyConfig?: ProxyConfig
|
|
83
|
+
networkConfig?: NetworkConfig
|
|
84
|
+
cacheDir?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface InstallOptions extends SharedEngineOptions {
|
|
88
|
+
/** Lockfile / workspace root directory. */
|
|
89
|
+
dir: string
|
|
90
|
+
projects: NodeApiProject[]
|
|
91
|
+
storeDir?: string
|
|
92
|
+
nodeLinker?: 'hoisted' | 'isolated'
|
|
93
|
+
hoistPattern?: string[]
|
|
94
|
+
publicHoistPattern?: string[]
|
|
95
|
+
/** Packages linked from outside the workspace; excluded from hoisting/pruning. */
|
|
96
|
+
externalDependencies?: string[]
|
|
97
|
+
overrides?: Record<string, string>
|
|
98
|
+
allowBuilds?: Record<string, boolean>
|
|
99
|
+
dangerouslyAllowAllBuilds?: boolean
|
|
100
|
+
autoInstallPeers?: boolean
|
|
101
|
+
excludeLinksFromLockfile?: boolean
|
|
102
|
+
lockfileOnly?: boolean
|
|
103
|
+
frozenLockfile?: boolean
|
|
104
|
+
preferFrozenLockfile?: boolean
|
|
105
|
+
packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone'
|
|
106
|
+
preferOffline?: boolean
|
|
107
|
+
offline?: boolean
|
|
108
|
+
virtualStoreDirMaxLength?: number
|
|
109
|
+
peersSuffixMaxLength?: number
|
|
110
|
+
dedupePeerDependents?: boolean
|
|
111
|
+
dedupeDirectDeps?: boolean
|
|
112
|
+
dedupeInjectedDeps?: boolean
|
|
113
|
+
resolvePeersFromWorkspaceRoot?: boolean
|
|
114
|
+
injectWorkspacePackages?: boolean
|
|
115
|
+
hoistWorkspacePackages?: boolean
|
|
116
|
+
minimumReleaseAge?: number
|
|
117
|
+
minimumReleaseAgeExclude?: string[]
|
|
118
|
+
includeOptionalDeps?: boolean
|
|
119
|
+
ignoreScripts?: boolean
|
|
120
|
+
/**
|
|
121
|
+
* Re-resolve the whole dependency graph to the highest in-range version
|
|
122
|
+
* (pnpm's `update: true` / `depth: Infinity`). The binding takes no package
|
|
123
|
+
* selectors, so an update always targets every dependency.
|
|
124
|
+
*/
|
|
125
|
+
update?: boolean
|
|
126
|
+
/**
|
|
127
|
+
* pnpm's `depth`. Accepted for API compatibility; it only toggles pnpm's
|
|
128
|
+
* direct-vs-any-depth selector matching, which has no effect without package
|
|
129
|
+
* selectors, so it does not change the whole-graph `update` behavior.
|
|
130
|
+
*/
|
|
131
|
+
depth?: number
|
|
132
|
+
/**
|
|
133
|
+
* Fail the install with `ERR_PNPM_UNSUPPORTED_ENGINE` when a dependency's
|
|
134
|
+
* `engines` / platform constraint the host does not satisfy is required
|
|
135
|
+
* (rather than warning). Defaults to `false`.
|
|
136
|
+
*/
|
|
137
|
+
engineStrict?: boolean
|
|
138
|
+
/**
|
|
139
|
+
* Node.js version used as the `engines.node` target for the engine check.
|
|
140
|
+
* Defaults to the version auto-detected from the `node` binary.
|
|
141
|
+
*/
|
|
142
|
+
nodeVersion?: string
|
|
143
|
+
/**
|
|
144
|
+
* `false` installs without creating a `node_modules` directory: the graph
|
|
145
|
+
* resolves and the lockfile is written, but nothing is materialized.
|
|
146
|
+
*/
|
|
147
|
+
enableModulesDir?: boolean
|
|
148
|
+
/**
|
|
149
|
+
* Install from the lockfile without gating on the `package.json` ↔
|
|
150
|
+
* `pnpm-lock.yaml` freshness check, so an in-memory manifest that disagrees
|
|
151
|
+
* with the lockfile does not block the install.
|
|
152
|
+
*/
|
|
153
|
+
ignorePackageManifest?: boolean
|
|
154
|
+
/** pnpm home directory. Accepted for compatibility; unused for project installs. */
|
|
155
|
+
pnpmHomeDir?: string
|
|
156
|
+
/**
|
|
157
|
+
* Fail with `ERR_PNPM_IGNORED_BUILDS` when a dependency build script is
|
|
158
|
+
* blocked. Defaults to `false`: the blocked packages are reported in
|
|
159
|
+
* `InstallResult.depsRequiringBuild` instead.
|
|
160
|
+
*/
|
|
161
|
+
strictDepBuilds?: boolean
|
|
162
|
+
/** Customizations for how peer-dependency mismatches are treated. */
|
|
163
|
+
peerDependencyRules?: PeerDependencyRules
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** pnpm's `peerDependencyRules`. */
|
|
167
|
+
export interface PeerDependencyRules {
|
|
168
|
+
ignoreMissing?: string[]
|
|
169
|
+
allowAny?: string[]
|
|
170
|
+
allowedVersions?: Record<string, string>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface InstallResult {
|
|
174
|
+
stats: {
|
|
175
|
+
added: number
|
|
176
|
+
removed: number
|
|
177
|
+
linkedToRoot: number
|
|
178
|
+
}
|
|
179
|
+
/** Dep paths whose build scripts were skipped and require approval to run. */
|
|
180
|
+
depsRequiringBuild?: string[]
|
|
181
|
+
/** The resolved content-addressable store directory used by this install. */
|
|
182
|
+
storeDir: string
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param onLog receives wire-compatible pnpm log events.
|
|
187
|
+
* @param readPackageHook a **synchronous** `(manifest) => manifest` transform
|
|
188
|
+
* applied to every resolved dependency manifest during resolution (the
|
|
189
|
+
* `readPackage` hook). Must return the manifest object, not a promise.
|
|
190
|
+
*/
|
|
191
|
+
export function install(
|
|
192
|
+
options: InstallOptions,
|
|
193
|
+
onLog?: LogListener,
|
|
194
|
+
readPackageHook?: ReadPackageHook,
|
|
195
|
+
): Promise<InstallResult>
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Rebuild dependency build scripts against the already-materialized
|
|
199
|
+
* `node_modules` (frozen path). Takes the same options shape as `install`.
|
|
200
|
+
* @param selectedNames restrict the rebuild to these package names / build
|
|
201
|
+
* keys; omit (or pass an empty array) to rebuild every build-needing package.
|
|
202
|
+
*/
|
|
203
|
+
export function rebuild(
|
|
204
|
+
options: InstallOptions,
|
|
205
|
+
onLog?: LogListener,
|
|
206
|
+
selectedNames?: string[],
|
|
207
|
+
): Promise<void>
|
|
208
|
+
|
|
209
|
+
export interface PeerIssuesOptions extends SharedEngineOptions {
|
|
210
|
+
dir: string
|
|
211
|
+
projects: NodeApiProject[]
|
|
212
|
+
storeDir?: string
|
|
213
|
+
overrides?: Record<string, string>
|
|
214
|
+
peersSuffixMaxLength?: number
|
|
215
|
+
virtualStoreDirMaxLength?: number
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface PeerDependencyIssues {
|
|
219
|
+
missing: Record<string, Array<{ parents: Array<{ name: string; version: string }>; optional: boolean; wantedRange: string }>>
|
|
220
|
+
bad: Record<string, Array<{ parents: Array<{ name: string; version: string }>; foundVersion: string; resolvedFrom: Array<{ name: string; version: string }>; optional: boolean; wantedRange: string }>>
|
|
221
|
+
conflicts: string[]
|
|
222
|
+
intersections: Record<string, string>
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type PeerDependencyIssuesByProjects = Record<string, PeerDependencyIssues>
|
|
226
|
+
|
|
227
|
+
export function getPeerDependencyIssues(options: PeerIssuesOptions): Promise<PeerDependencyIssuesByProjects>
|
|
228
|
+
|
|
229
|
+
export interface WantedDependency {
|
|
230
|
+
alias?: string
|
|
231
|
+
bareSpecifier?: string
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface ResolveOptions extends SharedEngineOptions {
|
|
235
|
+
/** Project/lockfile dir used to resolve `link:`/`file:` and workspace specs. */
|
|
236
|
+
dir: string
|
|
237
|
+
/** Return the full packument-derived manifest instead of the abbreviated one. */
|
|
238
|
+
fullMetadata?: boolean
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface ResolveResult {
|
|
242
|
+
id: string
|
|
243
|
+
manifest?: PackageManifest
|
|
244
|
+
resolvedVia: string
|
|
245
|
+
normalizedBareSpecifier?: string
|
|
246
|
+
latest?: string
|
|
247
|
+
resolution?: Record<string, unknown>
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function resolveDependency(wanted: WantedDependency, options: ResolveOptions): Promise<ResolveResult>
|
|
251
|
+
|
|
252
|
+
export interface PackOptions {
|
|
253
|
+
dir: string
|
|
254
|
+
workspaceDir?: string
|
|
255
|
+
/** Destination directory for the tarball (defaults to `dir`). */
|
|
256
|
+
packDestination?: string
|
|
257
|
+
/** Exact output path/filename for the tarball. */
|
|
258
|
+
out?: string
|
|
259
|
+
ignoreScripts?: boolean
|
|
260
|
+
packGzipLevel?: number
|
|
261
|
+
embedReadme?: boolean
|
|
262
|
+
dryRun?: boolean
|
|
263
|
+
extraBinPaths?: string[]
|
|
264
|
+
extraEnv?: Record<string, string>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface PackResult {
|
|
268
|
+
publishedManifest: PackageManifest
|
|
269
|
+
contents: string[]
|
|
270
|
+
tarballPath: string
|
|
271
|
+
unpackedSize: number
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function pack(options: PackOptions, onLog?: LogListener): Promise<PackResult>
|
|
275
|
+
|
|
276
|
+
export interface ParsedBareSpecifier {
|
|
277
|
+
alias?: string
|
|
278
|
+
bareSpecifier?: string
|
|
279
|
+
name?: string
|
|
280
|
+
fetchSpec?: string
|
|
281
|
+
normalizedBareSpecifier?: string
|
|
282
|
+
type?: string
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Parses/validates a dependency specifier. Returns null for unparsable input. */
|
|
286
|
+
export function parseBareSpecifier(spec: string, alias?: string): ParsedBareSpecifier | null
|
|
287
|
+
|
|
288
|
+
/** Version of the underlying Rust engine (pacquet). */
|
|
289
|
+
export function engineVersion(): string
|
package/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Loader for the pacquet-napi native addon.
|
|
4
|
+
//
|
|
5
|
+
// Resolution order:
|
|
6
|
+
// 1. PNPM_NAPI_BINARY env var — explicit path to a .node file (local dev,
|
|
7
|
+
// custom builds).
|
|
8
|
+
// 2. A platform package (`@pnpm/napi.<platform>`) installed as an
|
|
9
|
+
// optionalDependency (release-time; injected by the package generator like
|
|
10
|
+
// the `@pnpm/exe.*` packages).
|
|
11
|
+
// 3. A locally built artifact next to this file or under the Cargo target dir
|
|
12
|
+
// (development checkouts).
|
|
13
|
+
|
|
14
|
+
const path = require('node:path')
|
|
15
|
+
const fs = require('node:fs')
|
|
16
|
+
|
|
17
|
+
// `'glibc' | 'musl' | null` — `null` when the host isn't Linux or the libc
|
|
18
|
+
// can't be probed (`process.report` may be unavailable/disabled). glibc builds
|
|
19
|
+
// set `glibcVersionRuntime`; musl leaves it unset.
|
|
20
|
+
function detectLinuxLibc() {
|
|
21
|
+
if (process.platform !== 'linux') return null
|
|
22
|
+
try {
|
|
23
|
+
return process.report?.getReport()?.header?.glibcVersionRuntime ? 'glibc' : 'musl'
|
|
24
|
+
} catch {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Ordered platform triples to try. On Linux both libc variants are attempted
|
|
30
|
+
// (ordered by detection) so a musl host whose libc can't be probed still
|
|
31
|
+
// resolves the `-musl` addon instead of failing on the glibc one; elsewhere
|
|
32
|
+
// there is a single triple.
|
|
33
|
+
function platformTriples() {
|
|
34
|
+
const { platform, arch } = process
|
|
35
|
+
if (platform === 'linux') {
|
|
36
|
+
const order = detectLinuxLibc() === 'musl' ? ['-musl', ''] : ['', '-musl']
|
|
37
|
+
return order.map((suffix) => `linux-${arch}${suffix}`)
|
|
38
|
+
}
|
|
39
|
+
return [`${platform}-${arch}`]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function tryLoad(candidate, loadErrors) {
|
|
43
|
+
if (!candidate) return null
|
|
44
|
+
try {
|
|
45
|
+
if (candidate.endsWith('.node') && !fs.existsSync(candidate)) return null
|
|
46
|
+
return require(candidate)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (isRetryableLoadError(err, candidate)) {
|
|
49
|
+
loadErrors.push(err)
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
throw err
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isRetryableLoadError(err, candidate) {
|
|
57
|
+
if (!err) return false
|
|
58
|
+
// The candidate isn't installed (platform package absent) — try the next one.
|
|
59
|
+
if (isMissingCandidate(err, candidate)) return true
|
|
60
|
+
// The candidate exists but is the wrong libc / ABI: `require()` throws
|
|
61
|
+
// `ERR_DLOPEN_FAILED`. Fall through to the other Linux libc variant rather
|
|
62
|
+
// than aborting the whole load on a wrong first guess.
|
|
63
|
+
return err.code === 'ERR_DLOPEN_FAILED'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isMissingCandidate(err, candidate) {
|
|
67
|
+
return (
|
|
68
|
+
err &&
|
|
69
|
+
err.code === 'MODULE_NOT_FOUND' &&
|
|
70
|
+
typeof err.message === 'string' &&
|
|
71
|
+
err.message.includes(`'${candidate}'`)
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function loadFailure(triple, loadErrors) {
|
|
76
|
+
const error = new Error(
|
|
77
|
+
`Failed to load the pnpm Rust engine for ${triple}. ` +
|
|
78
|
+
'Install the matching @pnpm/napi platform package, or point ' +
|
|
79
|
+
'PNPM_NAPI_BINARY at a locally built .node file.'
|
|
80
|
+
)
|
|
81
|
+
if (loadErrors.length > 0) {
|
|
82
|
+
error.cause = loadErrors[0]
|
|
83
|
+
}
|
|
84
|
+
return error
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadBinding() {
|
|
88
|
+
const triples = platformTriples()
|
|
89
|
+
const loadErrors = []
|
|
90
|
+
// An explicit PNPM_NAPI_BINARY is strict: it must be a `.node` file (so a
|
|
91
|
+
// non-.node value can't be require()'d as an arbitrary module), and if it
|
|
92
|
+
// exists but fails to load we surface that error directly rather than
|
|
93
|
+
// silently falling back to a platform package.
|
|
94
|
+
const explicit = process.env.PNPM_NAPI_BINARY
|
|
95
|
+
if (explicit) {
|
|
96
|
+
if (!explicit.endsWith('.node')) {
|
|
97
|
+
throw new Error(`PNPM_NAPI_BINARY must point to a .node addon file, got: ${explicit}`)
|
|
98
|
+
}
|
|
99
|
+
if (fs.existsSync(explicit)) {
|
|
100
|
+
return require(explicit)
|
|
101
|
+
}
|
|
102
|
+
loadErrors.push(new Error(`PNPM_NAPI_BINARY was set but not found: ${explicit}`))
|
|
103
|
+
}
|
|
104
|
+
// Platform packages / local artifacts. On Linux both libc variants are tried,
|
|
105
|
+
// so a wrong-ABI first candidate (an `ERR_DLOPEN_FAILED`) falls through to the
|
|
106
|
+
// other rather than aborting.
|
|
107
|
+
const candidates = [
|
|
108
|
+
...triples.flatMap((triple) => [
|
|
109
|
+
`@pnpm/napi.${triple}`,
|
|
110
|
+
path.join(__dirname, `pnpm-napi.${triple}.node`),
|
|
111
|
+
]),
|
|
112
|
+
path.join(__dirname, 'pnpm-napi.node'),
|
|
113
|
+
]
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
const binding = tryLoad(candidate, loadErrors)
|
|
116
|
+
if (binding) return binding
|
|
117
|
+
}
|
|
118
|
+
throw loadFailure(triples[0], loadErrors)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The Rust side encodes structured error fields (code/hint) as a JSON envelope
|
|
122
|
+
// in the thrown error's message, prefixed with PNPM_ERR_JSON:. Lift them back
|
|
123
|
+
// onto the Error object so consumers keep reading `err.code` / `err.hint`.
|
|
124
|
+
const ENVELOPE_PREFIX = 'PNPM_ERR_JSON:'
|
|
125
|
+
function decorateError(err) {
|
|
126
|
+
if (err && typeof err.message === 'string' && err.message.startsWith(ENVELOPE_PREFIX)) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(err.message.slice(ENVELOPE_PREFIX.length))
|
|
129
|
+
if (parsed.code != null) err.code = parsed.code
|
|
130
|
+
if (parsed.hint != null) err.hint = parsed.hint
|
|
131
|
+
if (typeof parsed.message === 'string') err.message = parsed.message
|
|
132
|
+
} catch {
|
|
133
|
+
// Leave the error untouched if the envelope doesn't parse.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return err
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Wrap every exported function so both sync throws and rejected promises get
|
|
140
|
+
// the envelope lifted. Non-function exports (if any) pass through unchanged.
|
|
141
|
+
function wrapExports(binding) {
|
|
142
|
+
const wrapped = {}
|
|
143
|
+
for (const key of Object.keys(binding)) {
|
|
144
|
+
const value = binding[key]
|
|
145
|
+
if (typeof value !== 'function') {
|
|
146
|
+
wrapped[key] = value
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
wrapped[key] = function (...args) {
|
|
150
|
+
try {
|
|
151
|
+
const result = value.apply(this, args)
|
|
152
|
+
if (result && typeof result.then === 'function') {
|
|
153
|
+
return result.then(
|
|
154
|
+
(resolved) => resolved,
|
|
155
|
+
(err) => {
|
|
156
|
+
throw decorateError(err)
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return result
|
|
161
|
+
} catch (err) {
|
|
162
|
+
throw decorateError(err)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return wrapped
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = wrapExports(loadBinding())
|
package/package.json
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/napi",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "12.0.0-alpha.3",
|
|
4
|
+
"description": "Node.js addon bindings for the pnpm v12 Rust engine (pacquet)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pnpm",
|
|
7
|
+
"package manager",
|
|
8
|
+
"rust",
|
|
9
|
+
"napi"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/pacquet",
|
|
13
|
+
"bugs": "https://github.com/pnpm/pnpm/issues",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/pnpm/pnpm",
|
|
17
|
+
"directory": "pacquet/npm/napi"
|
|
18
|
+
},
|
|
19
|
+
"main": "index.js",
|
|
20
|
+
"types": "index.d.ts",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.*"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"index.js",
|
|
26
|
+
"index.d.ts",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"@pnpm/napi.win32-x64": "12.0.0-alpha.3",
|
|
31
|
+
"@pnpm/napi.win32-arm64": "12.0.0-alpha.3",
|
|
32
|
+
"@pnpm/napi.darwin-x64": "12.0.0-alpha.3",
|
|
33
|
+
"@pnpm/napi.darwin-arm64": "12.0.0-alpha.3",
|
|
34
|
+
"@pnpm/napi.linux-x64": "12.0.0-alpha.3",
|
|
35
|
+
"@pnpm/napi.linux-arm64": "12.0.0-alpha.3",
|
|
36
|
+
"@pnpm/napi.linux-x64-musl": "12.0.0-alpha.3",
|
|
37
|
+
"@pnpm/napi.linux-arm64-musl": "12.0.0-alpha.3"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"generate-packages": "node scripts/generate-packages.mjs"
|
|
41
|
+
}
|
|
5
42
|
}
|