@napi-rs/cli 3.8.3 → 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/README.md +7 -4
- package/dist/cli.js +611 -71
- package/dist/index.cjs +612 -72
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +611 -71
- package/docs/pre-publish.md +14 -13
- package/package.json +1 -1
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +768 -9
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/pre-publish.spec.ts +419 -0
- package/src/api/__tests__/templates.spec.ts +85 -1
- package/src/api/pre-publish.ts +135 -11
- package/src/api/templates/load-wasi-template.ts +499 -13
- package/src/def/pre-publish.ts +13 -0
- package/src/utils/config.ts +34 -1
package/src/api/pre-publish.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
mkdirAsync,
|
|
52
52
|
restrictWasiNodeEngine,
|
|
53
53
|
resolvePackageReconciliationPaths,
|
|
54
|
+
RootPublishers,
|
|
54
55
|
wasiLoaderSuffix,
|
|
55
56
|
wasiTargetHasThreads,
|
|
56
57
|
writeFileAtomic,
|
|
@@ -59,6 +60,7 @@ import {
|
|
|
59
60
|
parseTriple,
|
|
60
61
|
type CommonPackageJsonFields,
|
|
61
62
|
type FileSystemTransactionWrite,
|
|
63
|
+
type RootPublisher,
|
|
62
64
|
type Target,
|
|
63
65
|
type UserNapiConfig,
|
|
64
66
|
} from '../utils/index.js'
|
|
@@ -312,6 +314,7 @@ export async function prePublish(userOptions: PrePublishOptions) {
|
|
|
312
314
|
packageName,
|
|
313
315
|
binaryName,
|
|
314
316
|
npmClient,
|
|
317
|
+
rootPublisher: configuredRootPublisher,
|
|
315
318
|
wasm,
|
|
316
319
|
} = await readNapiConfig(
|
|
317
320
|
packageJsonPath,
|
|
@@ -319,6 +322,10 @@ export async function prePublish(userOptions: PrePublishOptions) {
|
|
|
319
322
|
? resolve(options.cwd, options.configPath)
|
|
320
323
|
: undefined,
|
|
321
324
|
)
|
|
325
|
+
const rootPublisher = resolveRootPublisher(
|
|
326
|
+
options.rootPublisher,
|
|
327
|
+
configuredRootPublisher,
|
|
328
|
+
)
|
|
322
329
|
const threadlessWasiTarget = targets.find(
|
|
323
330
|
(target) =>
|
|
324
331
|
target.platform === 'wasi' && !wasiTargetHasThreads(target),
|
|
@@ -425,6 +432,7 @@ export async function prePublish(userOptions: PrePublishOptions) {
|
|
|
425
432
|
nodeEngine,
|
|
426
433
|
facade: rootFacade,
|
|
427
434
|
staleGeneratedFiles: rootFacadeReconciliation.staleGeneratedFiles,
|
|
435
|
+
rootPublisher,
|
|
428
436
|
}
|
|
429
437
|
if (preparedWorkspaceRoot) {
|
|
430
438
|
const stagedTransactionRoot = join(
|
|
@@ -1573,8 +1581,101 @@ function createLegacyDeepImportExports(rootDir: string) {
|
|
|
1573
1581
|
return exportsMap
|
|
1574
1582
|
}
|
|
1575
1583
|
|
|
1576
|
-
|
|
1584
|
+
// pnpm and yarn berry (>= 2) rewrite the manifest they pack:
|
|
1585
|
+
// `publishConfig.exports` wholesale-replaces `exports` in the published
|
|
1586
|
+
// package.json (pnpm docs: "It is possible to override some fields in the
|
|
1587
|
+
// manifest before the package is packed"; verified empirically with pnpm 11
|
|
1588
|
+
// and yarn 4 `pack`). npm does NOT: its `publishConfig` only carries
|
|
1589
|
+
// publish-time configuration such as tag, registry and access
|
|
1590
|
+
// (https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig),
|
|
1591
|
+
// and npm 11 packs the raw `exports` untouched. Yarn Classic (1.x) behaves
|
|
1592
|
+
// like npm here — verified empirically with yarn 1.22.22 `pack`, which keeps
|
|
1593
|
+
// the raw `exports` in the tarball manifest.
|
|
1594
|
+
const REWRITING_ROOT_PUBLISHERS = new Set<RootPublisher>(['pnpm', 'yarn'])
|
|
1595
|
+
|
|
1596
|
+
export function rootPublisherRewritesRootExports(
|
|
1597
|
+
rootPublisher: RootPublisher | undefined,
|
|
1598
|
+
) {
|
|
1599
|
+
return (
|
|
1600
|
+
rootPublisher !== undefined && REWRITING_ROOT_PUBLISHERS.has(rootPublisher)
|
|
1601
|
+
)
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// `prePublish` publishes only the generated per-platform packages; the root
|
|
1605
|
+
// package is packed and published by whoever called us. Which manifest ships
|
|
1606
|
+
// therefore cannot be observed here, so it must be declared: `--root-publisher`
|
|
1607
|
+
// wins over the `napi.rootPublisher` config field, and when neither is set the
|
|
1608
|
+
// conservative union of both export maps is validated.
|
|
1609
|
+
export function resolveRootPublisher(
|
|
1610
|
+
optionValue: string | undefined,
|
|
1611
|
+
configValue: string | undefined,
|
|
1612
|
+
): RootPublisher | undefined {
|
|
1613
|
+
const [value, source] =
|
|
1614
|
+
optionValue === undefined
|
|
1615
|
+
? ([configValue, 'napi.rootPublisher'] as const)
|
|
1616
|
+
: ([optionValue, '--root-publisher'] as const)
|
|
1617
|
+
if (value === undefined) {
|
|
1618
|
+
return undefined
|
|
1619
|
+
}
|
|
1620
|
+
if (!(RootPublishers as readonly string[]).includes(value)) {
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Unknown ${source} value "${value}". Expected one of: ${RootPublishers.join(', ')}.`,
|
|
1623
|
+
)
|
|
1624
|
+
}
|
|
1625
|
+
return value as RootPublisher
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Package managers identify themselves to lifecycle scripts (e.g.
|
|
1629
|
+
// `prepublishOnly`) via `npm_config_user_agent`. That names the process that
|
|
1630
|
+
// invoked this command, which is not necessarily the root publisher, so it
|
|
1631
|
+
// never selects a validation branch — it only makes the failure actionable by
|
|
1632
|
+
// naming the option to set.
|
|
1633
|
+
//
|
|
1634
|
+
// The patterns require a complete `<name>/<version>` token — a semver-ish
|
|
1635
|
+
// version followed by a token boundary — so that a malformed agent such as
|
|
1636
|
+
// `yarn/2garbage` is not read as "yarn 2" (and `pnpm/x` not as pnpm) by
|
|
1637
|
+
// matching a numeric prefix. The prerelease/build tails are kept as two
|
|
1638
|
+
// separate optional groups on purpose: collapsing them into
|
|
1639
|
+
// `(?:[-+][0-9A-Za-z.-]+)*` makes the `-` ambiguous with the character class
|
|
1640
|
+
// and backtracks exponentially on a non-matching tail.
|
|
1641
|
+
const VERSION_TOKEN_TAIL = String.raw`(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?(?=\s|$)`
|
|
1642
|
+
const PNPM_PUBLISHER_PATTERN = new RegExp(
|
|
1643
|
+
String.raw`(?:^|\s)pnpm\/\d+` + VERSION_TOKEN_TAIL,
|
|
1644
|
+
)
|
|
1645
|
+
const YARN_PUBLISHER_PATTERN = new RegExp(
|
|
1646
|
+
String.raw`(?:^|\s)yarn\/(\d+)` + VERSION_TOKEN_TAIL,
|
|
1647
|
+
)
|
|
1648
|
+
|
|
1649
|
+
export function sniffRewritingPublisherFromUserAgent(
|
|
1650
|
+
userAgent: string | undefined = process.env.npm_config_user_agent,
|
|
1651
|
+
): 'pnpm' | 'yarn' | undefined {
|
|
1652
|
+
if (typeof userAgent !== 'string') {
|
|
1653
|
+
return undefined
|
|
1654
|
+
}
|
|
1655
|
+
if (PNPM_PUBLISHER_PATTERN.test(userAgent)) {
|
|
1656
|
+
return 'pnpm'
|
|
1657
|
+
}
|
|
1658
|
+
const yarnMajor = YARN_PUBLISHER_PATTERN.exec(userAgent)
|
|
1659
|
+
return yarnMajor !== null && Number(yarnMajor[1]) >= 2 ? 'yarn' : undefined
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
export function rootPublisherHint(
|
|
1663
|
+
rootPublisher: RootPublisher | undefined,
|
|
1664
|
+
userAgent?: string,
|
|
1665
|
+
) {
|
|
1666
|
+
if (rootPublisher !== undefined) {
|
|
1667
|
+
return ''
|
|
1668
|
+
}
|
|
1669
|
+
const sniffed = sniffRewritingPublisherFromUserAgent(userAgent)
|
|
1670
|
+
if (sniffed === undefined) {
|
|
1671
|
+
return ''
|
|
1672
|
+
}
|
|
1673
|
+
return ` This ran under ${sniffed}, which replaces "exports" with "publishConfig.exports" when it packs: if ${sniffed} also publishes the root package, set napi.rootPublisher to "${sniffed}" (or pass --root-publisher ${sniffed}) to validate the publish-effective export map instead.`
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
export function collectRootPackagePathReferences(
|
|
1577
1677
|
packageJson: CommonPackageJsonFields,
|
|
1678
|
+
rootPublisher?: RootPublisher,
|
|
1578
1679
|
) {
|
|
1579
1680
|
const files = new Set<string>()
|
|
1580
1681
|
addLegacyPackageFileReference(files, packageJson.main)
|
|
@@ -1588,13 +1689,24 @@ function collectRootPackagePathReferences(
|
|
|
1588
1689
|
(packageJson as CommonPackageJsonFields & { typings?: unknown }).typings,
|
|
1589
1690
|
)
|
|
1590
1691
|
addLegacyPackageFileReference(files, packageJson.browser)
|
|
1591
|
-
collectRootExportFileReferences(files, packageJson.exports)
|
|
1592
1692
|
|
|
1693
|
+
// Validate the export map(s) that will actually ship. Under a declared
|
|
1694
|
+
// manifest-rewriting publisher (pnpm/yarn), `publishConfig.exports` replaces
|
|
1695
|
+
// the local `exports` in the published manifest, so only the
|
|
1696
|
+
// publish-effective map matters: the local map may reference
|
|
1697
|
+
// development-only sources (e.g. `dev: ./src/*.ts` conditions) that are
|
|
1698
|
+
// never packed. Under npm and Yarn Classic — or when no publisher is
|
|
1699
|
+
// declared — the raw `exports` ships as-is, so validate both maps.
|
|
1593
1700
|
const publishConfig = asRecord(packageJson.publishConfig)
|
|
1594
|
-
|
|
1701
|
+
const hasPublishConfigExports =
|
|
1595
1702
|
publishConfig &&
|
|
1596
1703
|
Object.prototype.hasOwnProperty.call(publishConfig, 'exports')
|
|
1597
|
-
|
|
1704
|
+
const rawExportsReplacedOnPublish =
|
|
1705
|
+
hasPublishConfigExports && rootPublisherRewritesRootExports(rootPublisher)
|
|
1706
|
+
if (!rawExportsReplacedOnPublish) {
|
|
1707
|
+
collectRootExportFileReferences(files, packageJson.exports)
|
|
1708
|
+
}
|
|
1709
|
+
if (hasPublishConfigExports) {
|
|
1598
1710
|
collectRootExportFileReferences(files, publishConfig.exports)
|
|
1599
1711
|
}
|
|
1600
1712
|
return [...files]
|
|
@@ -1717,9 +1829,10 @@ export function parseNpmPackFiles(output: string) {
|
|
|
1717
1829
|
)
|
|
1718
1830
|
}
|
|
1719
1831
|
|
|
1720
|
-
function validateRootFacadePacklist(
|
|
1832
|
+
export function validateRootFacadePacklist(
|
|
1721
1833
|
rootDir: string,
|
|
1722
1834
|
rootPackageFiles: string[],
|
|
1835
|
+
rootPublisher?: RootPublisher,
|
|
1723
1836
|
) {
|
|
1724
1837
|
const paths = validateRootPackagePaths(rootDir, rootPackageFiles)
|
|
1725
1838
|
const packedFiles = readNpmPackFiles(rootDir, 'threadless WASI root package')
|
|
@@ -1736,7 +1849,7 @@ function validateRootFacadePacklist(
|
|
|
1736
1849
|
.map(({ path }) => path)
|
|
1737
1850
|
if (missingPaths.length > 0) {
|
|
1738
1851
|
throw new Error(
|
|
1739
|
-
`The threadless WASI root package references paths omitted by npm pack: ${missingPaths.join(', ')}. Add them to package.json "files" or remove the matching .npmignore rules
|
|
1852
|
+
`The threadless WASI root package references paths omitted by npm pack: ${missingPaths.join(', ')}. Add them to package.json "files" or remove the matching .npmignore rules.${rootPublisherHint(rootPublisher)}`,
|
|
1740
1853
|
)
|
|
1741
1854
|
}
|
|
1742
1855
|
}
|
|
@@ -1747,6 +1860,7 @@ interface RootReleaseMaterializationPlan {
|
|
|
1747
1860
|
nodeEngine?: string
|
|
1748
1861
|
facade?: ThreadlessWasiRootFacade
|
|
1749
1862
|
staleGeneratedFiles: string[]
|
|
1863
|
+
rootPublisher?: RootPublisher
|
|
1750
1864
|
}
|
|
1751
1865
|
|
|
1752
1866
|
function pathIsWithin(root: string, path: string) {
|
|
@@ -2218,7 +2332,10 @@ async function stageRootReleasePlan(
|
|
|
2218
2332
|
await cp(packageJsonPath, join(stagedRootDir, 'package.json'))
|
|
2219
2333
|
if (plan.facade) {
|
|
2220
2334
|
const packedFiles = readNpmPackFiles(rootDir, 'root package')
|
|
2221
|
-
const referencedFiles = collectRootPackagePathReferences(
|
|
2335
|
+
const referencedFiles = collectRootPackagePathReferences(
|
|
2336
|
+
plan.packageJson,
|
|
2337
|
+
plan.rootPublisher,
|
|
2338
|
+
)
|
|
2222
2339
|
for (const file of new Set([
|
|
2223
2340
|
...packedFiles,
|
|
2224
2341
|
...referencedFiles,
|
|
@@ -2240,10 +2357,17 @@ async function stageRootReleasePlan(
|
|
|
2240
2357
|
}
|
|
2241
2358
|
await materializeRootReleasePlan(stagedRootDir, plan)
|
|
2242
2359
|
if (plan.facade) {
|
|
2243
|
-
validateRootFacadePacklist(
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2360
|
+
validateRootFacadePacklist(
|
|
2361
|
+
stagedRootDir,
|
|
2362
|
+
[
|
|
2363
|
+
...plan.facade.generatedFiles,
|
|
2364
|
+
...collectRootPackagePathReferences(
|
|
2365
|
+
plan.packageJson,
|
|
2366
|
+
plan.rootPublisher,
|
|
2367
|
+
),
|
|
2368
|
+
],
|
|
2369
|
+
plan.rootPublisher,
|
|
2370
|
+
)
|
|
2247
2371
|
}
|
|
2248
2372
|
}
|
|
2249
2373
|
|