@napi-rs/cli 3.8.4 → 3.8.5
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/dist/cli.js +112 -58
- package/dist/index.cjs +113 -59
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +112 -58
- package/docs/pre-publish.md +14 -13
- package/package.json +1 -1
- package/src/api/__tests__/pre-publish.spec.ts +419 -0
- package/src/api/pre-publish.ts +135 -11
- package/src/def/pre-publish.ts +13 -0
- package/src/utils/config.ts +34 -1
package/docs/pre-publish.md
CHANGED
|
@@ -22,16 +22,17 @@ new NapiCli().prePublish({
|
|
|
22
22
|
|
|
23
23
|
## Options
|
|
24
24
|
|
|
25
|
-
| Options | CLI Options | type
|
|
26
|
-
| ------------------- | ------------------------- |
|
|
27
|
-
| | --help,-h |
|
|
28
|
-
| cwd | --cwd | string
|
|
29
|
-
| configPath | --config-path,-c | string
|
|
30
|
-
| packageJsonPath | --package-json-path | string
|
|
31
|
-
| npmDir | --npm-dir,-p | string
|
|
32
|
-
| tagStyle | --tag-style,--tagstyle,-t | 'npm' \| 'lerna'
|
|
33
|
-
| ghRelease | --gh-release | boolean
|
|
34
|
-
| ghReleaseName | --gh-release-name | string
|
|
35
|
-
| ghReleaseId | --gh-release-id | string
|
|
36
|
-
| skipOptionalPublish | --skip-optional-publish | boolean
|
|
37
|
-
|
|
|
25
|
+
| Options | CLI Options | type | required | default | description |
|
|
26
|
+
| ------------------- | ------------------------- | ------------------------------------------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
27
|
+
| | --help,-h | | | | get help |
|
|
28
|
+
| cwd | --cwd | string | false | process.cwd() | The working directory of where napi command will be executed in, all other paths options are relative to this path |
|
|
29
|
+
| configPath | --config-path,-c | string | false | | Path to `napi` config json file |
|
|
30
|
+
| packageJsonPath | --package-json-path | string | false | 'package.json' | Path to `package.json` |
|
|
31
|
+
| npmDir | --npm-dir,-p | string | false | 'npm' | Path to the folder where the npm packages put |
|
|
32
|
+
| tagStyle | --tag-style,--tagstyle,-t | 'npm' \| 'lerna' | false | 'lerna' | git tag style, `npm` or `lerna` |
|
|
33
|
+
| ghRelease | --gh-release | boolean | false | true | Whether create GitHub release |
|
|
34
|
+
| ghReleaseName | --gh-release-name | string | false | | GitHub release name |
|
|
35
|
+
| ghReleaseId | --gh-release-id | string | false | | Existing GitHub release id |
|
|
36
|
+
| skipOptionalPublish | --skip-optional-publish | boolean | false | false | Whether skip optionalDependencies packages publish |
|
|
37
|
+
| rootPublisher | --root-publisher | 'npm' \| 'pnpm' \| 'yarn' \| 'yarn-classic' | false | | The package manager that publishes the root package. pnpm and yarn berry replace `exports` with `publishConfig.exports` while packing, so for them the root package validation targets the publish-effective export map. Defaults to the `napi.rootPublisher` config field, and validates both export maps when neither is set |
|
|
38
|
+
| dryRun | --dry-run | boolean | false | false | Dry run without touching file system |
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { mkdtempSync } from 'node:fs'
|
|
2
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
|
|
1
6
|
import test from 'ava'
|
|
2
7
|
|
|
3
8
|
import {
|
|
9
|
+
collectRootPackagePathReferences,
|
|
4
10
|
parseNpmPackFiles,
|
|
5
11
|
resolveRootOptionalDependencies,
|
|
12
|
+
resolveRootPublisher,
|
|
13
|
+
rootPublisherRewritesRootExports,
|
|
14
|
+
sniffRewritingPublisherFromUserAgent,
|
|
15
|
+
validateRootFacadePacklist,
|
|
6
16
|
} from '../pre-publish.js'
|
|
7
17
|
import { parseTriple } from '../../utils/index.js'
|
|
8
18
|
|
|
@@ -139,3 +149,412 @@ test('rejects an unexpected npm pack JSON result', (t) => {
|
|
|
139
149
|
message: 'npm pack returned an unexpected JSON result',
|
|
140
150
|
})
|
|
141
151
|
})
|
|
152
|
+
|
|
153
|
+
// pnpm and yarn berry wholesale-replace `exports` with `publishConfig.exports`
|
|
154
|
+
// in the published manifest, so under those publishers the root facade
|
|
155
|
+
// validation must target the effective map: local-only conditions like
|
|
156
|
+
// `dev: ./src/*.ts` never reach the registry and must not fail the packlist
|
|
157
|
+
// check. npm and Yarn Classic keep the raw `exports` untouched (publishConfig
|
|
158
|
+
// is publish-time configuration only), so both maps must stay valid there.
|
|
159
|
+
//
|
|
160
|
+
// `prePublish` does not publish the root package, so the publisher is declared
|
|
161
|
+
// explicitly (`--root-publisher` / `napi.rootPublisher`). `npm_config_user_agent`
|
|
162
|
+
// names the process that invoked this command, which may differ from the root
|
|
163
|
+
// publisher, so it never selects a branch — it only enriches the failure.
|
|
164
|
+
const PNPM_USER_AGENT = 'pnpm/11.9.0 npm/? node/v24.19.0 darwin arm64'
|
|
165
|
+
const YARN_USER_AGENT = 'yarn/4.6.0 npm/? node/v24.19.0 darwin arm64'
|
|
166
|
+
const YARN_CLASSIC_USER_AGENT = 'yarn/1.22.22 npm/? node/v24.19.0 darwin arm64'
|
|
167
|
+
const NPM_USER_AGENT = 'npm/11.6.2 node/v24.19.0 darwin arm64 workspaces/false'
|
|
168
|
+
// A truncated version token must not be read as a major version: an agent we
|
|
169
|
+
// cannot parse names no package manager at all.
|
|
170
|
+
const MALFORMED_YARN_USER_AGENT = 'yarn/2garbage npm/? node/v24.19.0'
|
|
171
|
+
const MALFORMED_PNPM_USER_AGENT = 'pnpm/2garbage npm/? node/v24.19.0'
|
|
172
|
+
const PNPM_HINT =
|
|
173
|
+
' This ran under pnpm, which replaces "exports" with "publishConfig.exports" when it packs: if pnpm also publishes the root package, set napi.rootPublisher to "pnpm" (or pass --root-publisher pnpm) to validate the publish-effective export map instead.'
|
|
174
|
+
|
|
175
|
+
function withNpmUserAgent<T>(agent: string | undefined, fn: () => T): T {
|
|
176
|
+
const previous = process.env.npm_config_user_agent
|
|
177
|
+
if (agent === undefined) {
|
|
178
|
+
delete process.env.npm_config_user_agent
|
|
179
|
+
} else {
|
|
180
|
+
process.env.npm_config_user_agent = agent
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
return fn()
|
|
184
|
+
} finally {
|
|
185
|
+
if (previous === undefined) {
|
|
186
|
+
delete process.env.npm_config_user_agent
|
|
187
|
+
} else {
|
|
188
|
+
process.env.npm_config_user_agent = previous
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const EFFECTIVE_EXPORTS_MANIFEST = {
|
|
194
|
+
name: PACKAGE_NAME,
|
|
195
|
+
version: VERSION,
|
|
196
|
+
main: './dist/index.cjs',
|
|
197
|
+
types: './dist/index.d.ts',
|
|
198
|
+
files: ['dist'],
|
|
199
|
+
exports: {
|
|
200
|
+
'.': {
|
|
201
|
+
dev: './src/index.ts',
|
|
202
|
+
types: './dist/index.d.ts',
|
|
203
|
+
default: './dist/index.cjs',
|
|
204
|
+
},
|
|
205
|
+
'./helper': {
|
|
206
|
+
dev: './src/helper.ts',
|
|
207
|
+
default: './dist/helper.cjs',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
publishConfig: {
|
|
211
|
+
exports: {
|
|
212
|
+
'.': {
|
|
213
|
+
types: './dist/index.d.ts',
|
|
214
|
+
default: './dist/index.cjs',
|
|
215
|
+
},
|
|
216
|
+
'./helper': './dist/helper.cjs',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function createStagedRoot(
|
|
222
|
+
manifest: Record<string, unknown>,
|
|
223
|
+
stagedFiles: string[],
|
|
224
|
+
) {
|
|
225
|
+
const rootDir = mkdtempSync(join(tmpdir(), 'napi-pre-publish-spec-'))
|
|
226
|
+
await writeFile(join(rootDir, 'package.json'), JSON.stringify(manifest))
|
|
227
|
+
for (const file of stagedFiles) {
|
|
228
|
+
await mkdir(join(rootDir, dirname(file)), { recursive: true })
|
|
229
|
+
await writeFile(join(rootDir, file), '// content\n')
|
|
230
|
+
}
|
|
231
|
+
return rootDir
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
test('only declared pnpm and yarn publishers rewrite the root exports', (t) => {
|
|
235
|
+
t.true(rootPublisherRewritesRootExports('pnpm'))
|
|
236
|
+
t.true(rootPublisherRewritesRootExports('yarn'))
|
|
237
|
+
t.false(rootPublisherRewritesRootExports('npm'))
|
|
238
|
+
t.false(rootPublisherRewritesRootExports('yarn-classic'))
|
|
239
|
+
t.false(rootPublisherRewritesRootExports(undefined))
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('resolves the root publisher from the CLI option over the config field', (t) => {
|
|
243
|
+
t.is(resolveRootPublisher(undefined, undefined), undefined)
|
|
244
|
+
t.is(resolveRootPublisher(undefined, 'pnpm'), 'pnpm')
|
|
245
|
+
t.is(resolveRootPublisher('npm', 'pnpm'), 'npm')
|
|
246
|
+
t.is(resolveRootPublisher('yarn-classic', undefined), 'yarn-classic')
|
|
247
|
+
t.throws(() => resolveRootPublisher('pnpm@10', undefined), {
|
|
248
|
+
message:
|
|
249
|
+
'Unknown --root-publisher value "pnpm@10". Expected one of: npm, pnpm, yarn, yarn-classic.',
|
|
250
|
+
})
|
|
251
|
+
t.throws(() => resolveRootPublisher(undefined, 'berry'), {
|
|
252
|
+
message:
|
|
253
|
+
'Unknown napi.rootPublisher value "berry". Expected one of: npm, pnpm, yarn, yarn-classic.',
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('sniffs rewriting package managers from the user agent', (t) => {
|
|
258
|
+
t.is(sniffRewritingPublisherFromUserAgent(PNPM_USER_AGENT), 'pnpm')
|
|
259
|
+
t.is(sniffRewritingPublisherFromUserAgent(YARN_USER_AGENT), 'yarn')
|
|
260
|
+
t.is(
|
|
261
|
+
sniffRewritingPublisherFromUserAgent('yarn/2.4.3 npm/? node/v24.19.0'),
|
|
262
|
+
'yarn',
|
|
263
|
+
)
|
|
264
|
+
t.is(sniffRewritingPublisherFromUserAgent(YARN_CLASSIC_USER_AGENT), undefined)
|
|
265
|
+
t.is(
|
|
266
|
+
sniffRewritingPublisherFromUserAgent('yarn/ npm/? node/v24.19.0'),
|
|
267
|
+
undefined,
|
|
268
|
+
)
|
|
269
|
+
t.is(sniffRewritingPublisherFromUserAgent(NPM_USER_AGENT), undefined)
|
|
270
|
+
t.is(
|
|
271
|
+
sniffRewritingPublisherFromUserAgent('bun/1.2.0 node/v24.19.0'),
|
|
272
|
+
undefined,
|
|
273
|
+
)
|
|
274
|
+
// An explicit `undefined` argument falls back to the ambient environment,
|
|
275
|
+
// so pin the environment to "absent" for the no-agent case.
|
|
276
|
+
withNpmUserAgent(undefined, () => {
|
|
277
|
+
t.is(sniffRewritingPublisherFromUserAgent(), undefined)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('requires a complete version token to sniff a rewriting publisher', (t) => {
|
|
282
|
+
// `yarn/2garbage` is not yarn 2 and `pnpm/x` is not pnpm: matching only the
|
|
283
|
+
// numeric prefix (or the bare name) would name the wrong manager in the hint.
|
|
284
|
+
t.is(
|
|
285
|
+
sniffRewritingPublisherFromUserAgent(MALFORMED_YARN_USER_AGENT),
|
|
286
|
+
undefined,
|
|
287
|
+
)
|
|
288
|
+
t.is(sniffRewritingPublisherFromUserAgent('yarn/2garbage'), undefined)
|
|
289
|
+
t.is(sniffRewritingPublisherFromUserAgent('yarn/4.6.0.1 npm/?'), undefined)
|
|
290
|
+
t.is(
|
|
291
|
+
sniffRewritingPublisherFromUserAgent(MALFORMED_PNPM_USER_AGENT),
|
|
292
|
+
undefined,
|
|
293
|
+
)
|
|
294
|
+
t.is(
|
|
295
|
+
sniffRewritingPublisherFromUserAgent('pnpm/x npm/? node/v24.19.0'),
|
|
296
|
+
undefined,
|
|
297
|
+
)
|
|
298
|
+
t.is(sniffRewritingPublisherFromUserAgent('pnpm/'), undefined)
|
|
299
|
+
// Well-formed prerelease and build metadata tails still parse.
|
|
300
|
+
t.is(
|
|
301
|
+
sniffRewritingPublisherFromUserAgent('yarn/4.10.0-git.20250101 npm/?'),
|
|
302
|
+
'yarn',
|
|
303
|
+
)
|
|
304
|
+
t.is(
|
|
305
|
+
sniffRewritingPublisherFromUserAgent('pnpm/10.15.1+sha512.ab npm/?'),
|
|
306
|
+
'pnpm',
|
|
307
|
+
)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
test('collects publishConfig.exports only for a declared rewriting publisher', (t) => {
|
|
311
|
+
const effectiveOnly = ['dist/helper.cjs', 'dist/index.cjs', 'dist/index.d.ts']
|
|
312
|
+
for (const rootPublisher of ['pnpm', 'yarn'] as const) {
|
|
313
|
+
t.deepEqual(
|
|
314
|
+
collectRootPackagePathReferences(
|
|
315
|
+
EFFECTIVE_EXPORTS_MANIFEST,
|
|
316
|
+
rootPublisher,
|
|
317
|
+
).sort(),
|
|
318
|
+
effectiveOnly,
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('collects the union of exports and publishConfig.exports for every other publisher', (t) => {
|
|
324
|
+
const union = [
|
|
325
|
+
'dist/helper.cjs',
|
|
326
|
+
'dist/index.cjs',
|
|
327
|
+
'dist/index.d.ts',
|
|
328
|
+
'src/helper.ts',
|
|
329
|
+
'src/index.ts',
|
|
330
|
+
]
|
|
331
|
+
for (const rootPublisher of ['npm', 'yarn-classic', undefined] as const) {
|
|
332
|
+
t.deepEqual(
|
|
333
|
+
collectRootPackagePathReferences(
|
|
334
|
+
EFFECTIVE_EXPORTS_MANIFEST,
|
|
335
|
+
rootPublisher,
|
|
336
|
+
).sort(),
|
|
337
|
+
union,
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test('never lets the ambient user agent widen or narrow the validated map', (t) => {
|
|
343
|
+
const union = [
|
|
344
|
+
'dist/helper.cjs',
|
|
345
|
+
'dist/index.cjs',
|
|
346
|
+
'dist/index.d.ts',
|
|
347
|
+
'src/helper.ts',
|
|
348
|
+
'src/index.ts',
|
|
349
|
+
]
|
|
350
|
+
const effectiveOnly = ['dist/helper.cjs', 'dist/index.cjs', 'dist/index.d.ts']
|
|
351
|
+
for (const agent of [
|
|
352
|
+
PNPM_USER_AGENT,
|
|
353
|
+
YARN_USER_AGENT,
|
|
354
|
+
NPM_USER_AGENT,
|
|
355
|
+
YARN_CLASSIC_USER_AGENT,
|
|
356
|
+
MALFORMED_YARN_USER_AGENT,
|
|
357
|
+
MALFORMED_PNPM_USER_AGENT,
|
|
358
|
+
undefined,
|
|
359
|
+
]) {
|
|
360
|
+
withNpmUserAgent(agent, () => {
|
|
361
|
+
// No declared publisher: always the conservative union, even under pnpm.
|
|
362
|
+
t.deepEqual(
|
|
363
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST).sort(),
|
|
364
|
+
union,
|
|
365
|
+
)
|
|
366
|
+
// A declared publisher wins over whatever the agent claims.
|
|
367
|
+
t.deepEqual(
|
|
368
|
+
collectRootPackagePathReferences(
|
|
369
|
+
EFFECTIVE_EXPORTS_MANIFEST,
|
|
370
|
+
'npm',
|
|
371
|
+
).sort(),
|
|
372
|
+
union,
|
|
373
|
+
)
|
|
374
|
+
t.deepEqual(
|
|
375
|
+
collectRootPackagePathReferences(
|
|
376
|
+
EFFECTIVE_EXPORTS_MANIFEST,
|
|
377
|
+
'pnpm',
|
|
378
|
+
).sort(),
|
|
379
|
+
effectiveOnly,
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
test('falls back to raw exports without publishConfig.exports', (t) => {
|
|
386
|
+
const manifest = {
|
|
387
|
+
...EFFECTIVE_EXPORTS_MANIFEST,
|
|
388
|
+
publishConfig: { access: 'public' },
|
|
389
|
+
}
|
|
390
|
+
const rawRefs = [
|
|
391
|
+
'dist/helper.cjs',
|
|
392
|
+
'dist/index.cjs',
|
|
393
|
+
'dist/index.d.ts',
|
|
394
|
+
'src/helper.ts',
|
|
395
|
+
'src/index.ts',
|
|
396
|
+
]
|
|
397
|
+
for (const rootPublisher of ['pnpm', 'npm', undefined] as const) {
|
|
398
|
+
t.deepEqual(
|
|
399
|
+
collectRootPackagePathReferences(manifest, rootPublisher).sort(),
|
|
400
|
+
rawRefs,
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
test('accepts dev-only export conditions replaced by publishConfig.exports under a declared pnpm publisher', async (t) => {
|
|
406
|
+
// The raw `exports` references src/*.ts files that are neither on disk nor
|
|
407
|
+
// in the packlist; under a rewriting publisher only the publish-effective
|
|
408
|
+
// references must survive pack.
|
|
409
|
+
const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
|
|
410
|
+
'dist/index.cjs',
|
|
411
|
+
'dist/index.d.ts',
|
|
412
|
+
'dist/helper.cjs',
|
|
413
|
+
])
|
|
414
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
415
|
+
|
|
416
|
+
t.notThrows(() =>
|
|
417
|
+
validateRootFacadePacklist(
|
|
418
|
+
rootDir,
|
|
419
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST, 'pnpm'),
|
|
420
|
+
'pnpm',
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('rejects dev-only raw export references when no root publisher is declared', async (t) => {
|
|
426
|
+
// Nobody declared a rewriting publisher, so the raw `exports` may ship: the
|
|
427
|
+
// dev sources exist on disk but `files: ['dist']` keeps them out of the
|
|
428
|
+
// packlist. Running under npm adds no hint.
|
|
429
|
+
const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
|
|
430
|
+
'dist/index.cjs',
|
|
431
|
+
'dist/index.d.ts',
|
|
432
|
+
'dist/helper.cjs',
|
|
433
|
+
'src/index.ts',
|
|
434
|
+
'src/helper.ts',
|
|
435
|
+
])
|
|
436
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
437
|
+
|
|
438
|
+
for (const agent of [NPM_USER_AGENT, YARN_CLASSIC_USER_AGENT, undefined]) {
|
|
439
|
+
t.throws(
|
|
440
|
+
() =>
|
|
441
|
+
withNpmUserAgent(agent, () =>
|
|
442
|
+
validateRootFacadePacklist(
|
|
443
|
+
rootDir,
|
|
444
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
|
|
445
|
+
),
|
|
446
|
+
),
|
|
447
|
+
{
|
|
448
|
+
message:
|
|
449
|
+
'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.',
|
|
450
|
+
},
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
// Declaring npm explicitly keeps the same failure, still without a hint.
|
|
454
|
+
t.throws(
|
|
455
|
+
() =>
|
|
456
|
+
withNpmUserAgent(PNPM_USER_AGENT, () =>
|
|
457
|
+
validateRootFacadePacklist(
|
|
458
|
+
rootDir,
|
|
459
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST, 'npm'),
|
|
460
|
+
'npm',
|
|
461
|
+
),
|
|
462
|
+
),
|
|
463
|
+
{
|
|
464
|
+
message:
|
|
465
|
+
'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.',
|
|
466
|
+
},
|
|
467
|
+
)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
test('points an undeclared publisher at the option when a rewriting manager is running', async (t) => {
|
|
471
|
+
// The union branch failed while pnpm invoked us: pnpm may well be the root
|
|
472
|
+
// publisher, so name the option instead of leaving the user to guess.
|
|
473
|
+
const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
|
|
474
|
+
'dist/index.cjs',
|
|
475
|
+
'dist/index.d.ts',
|
|
476
|
+
'dist/helper.cjs',
|
|
477
|
+
'src/index.ts',
|
|
478
|
+
'src/helper.ts',
|
|
479
|
+
])
|
|
480
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
481
|
+
|
|
482
|
+
t.throws(
|
|
483
|
+
() =>
|
|
484
|
+
withNpmUserAgent(PNPM_USER_AGENT, () =>
|
|
485
|
+
validateRootFacadePacklist(
|
|
486
|
+
rootDir,
|
|
487
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
|
|
488
|
+
),
|
|
489
|
+
),
|
|
490
|
+
{
|
|
491
|
+
message: `The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.${PNPM_HINT}`,
|
|
492
|
+
},
|
|
493
|
+
)
|
|
494
|
+
// Yarn berry gets its own name; a malformed agent gets no hint at all.
|
|
495
|
+
const yarnError = t.throws(() =>
|
|
496
|
+
withNpmUserAgent(YARN_USER_AGENT, () =>
|
|
497
|
+
validateRootFacadePacklist(
|
|
498
|
+
rootDir,
|
|
499
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
|
|
500
|
+
),
|
|
501
|
+
),
|
|
502
|
+
)
|
|
503
|
+
t.true(yarnError!.message.includes('set napi.rootPublisher to "yarn"'))
|
|
504
|
+
const malformedError = t.throws(() =>
|
|
505
|
+
withNpmUserAgent(MALFORMED_PNPM_USER_AGENT, () =>
|
|
506
|
+
validateRootFacadePacklist(
|
|
507
|
+
rootDir,
|
|
508
|
+
collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
|
|
509
|
+
),
|
|
510
|
+
),
|
|
511
|
+
)
|
|
512
|
+
t.false(malformedError!.message.includes('napi.rootPublisher'))
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
test('still rejects publishConfig.exports references omitted by npm pack', async (t) => {
|
|
516
|
+
const manifest = {
|
|
517
|
+
...EFFECTIVE_EXPORTS_MANIFEST,
|
|
518
|
+
// Restrict the packlist so dist/helper.cjs exists on disk but is not
|
|
519
|
+
// packed: the effective exports validation must keep failing under both
|
|
520
|
+
// publisher kinds.
|
|
521
|
+
files: ['dist/index.cjs', 'dist/index.d.ts'],
|
|
522
|
+
}
|
|
523
|
+
const rootDir = await createStagedRoot(manifest, [
|
|
524
|
+
'dist/index.cjs',
|
|
525
|
+
'dist/index.d.ts',
|
|
526
|
+
'dist/helper.cjs',
|
|
527
|
+
'src/index.ts',
|
|
528
|
+
'src/helper.ts',
|
|
529
|
+
])
|
|
530
|
+
t.teardown(() => rm(rootDir, { recursive: true, force: true }))
|
|
531
|
+
|
|
532
|
+
t.throws(
|
|
533
|
+
() =>
|
|
534
|
+
withNpmUserAgent(PNPM_USER_AGENT, () =>
|
|
535
|
+
validateRootFacadePacklist(
|
|
536
|
+
rootDir,
|
|
537
|
+
collectRootPackagePathReferences(manifest, 'pnpm'),
|
|
538
|
+
'pnpm',
|
|
539
|
+
),
|
|
540
|
+
),
|
|
541
|
+
{
|
|
542
|
+
message:
|
|
543
|
+
'The threadless WASI root package references paths omitted by npm pack: dist/helper.cjs. Add them to package.json "files" or remove the matching .npmignore rules.',
|
|
544
|
+
},
|
|
545
|
+
)
|
|
546
|
+
for (const rootPublisher of ['npm', 'yarn-classic'] as const) {
|
|
547
|
+
t.throws(
|
|
548
|
+
() =>
|
|
549
|
+
validateRootFacadePacklist(
|
|
550
|
+
rootDir,
|
|
551
|
+
collectRootPackagePathReferences(manifest, rootPublisher),
|
|
552
|
+
rootPublisher,
|
|
553
|
+
),
|
|
554
|
+
{
|
|
555
|
+
message:
|
|
556
|
+
'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts, dist/helper.cjs. Add them to package.json "files" or remove the matching .npmignore rules.',
|
|
557
|
+
},
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
})
|