@napi-rs/cli 3.8.6 → 3.9.1

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.
Files changed (34) hide show
  1. package/dist/cli.js +9085 -8737
  2. package/dist/index.cjs +9092 -8744
  3. package/dist/index.d.cts +37 -17
  4. package/dist/index.d.ts +37 -17
  5. package/dist/index.js +9085 -8737
  6. package/docs/build.md +36 -34
  7. package/package.json +4 -5
  8. package/src/api/__tests__/__snapshots__/templates.spec.ts.md +9 -13
  9. package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
  10. package/src/api/__tests__/build.spec.ts +284 -0
  11. package/src/api/__tests__/create-npm-dirs.spec.ts +4 -3
  12. package/src/api/__tests__/templates.spec.ts +84 -1
  13. package/src/api/build.ts +181 -7
  14. package/src/api/create-npm-dirs.ts +2 -1
  15. package/src/api/new.ts +13 -18
  16. package/src/api/pre-publish.ts +5 -10
  17. package/src/api/rename.ts +10 -21
  18. package/src/api/templates/js-binding.ts +7 -9
  19. package/src/api/templates/load-wasi-template.ts +20 -17
  20. package/src/api/templates/wasi-worker-template.ts +36 -31
  21. package/src/commands/__tests__/build.spec.ts +24 -0
  22. package/src/commands/build.ts +1 -1
  23. package/src/def/build.ts +30 -4
  24. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +18 -28
  25. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  26. package/src/utils/__tests__/misc.spec.ts +4 -0
  27. package/src/utils/__tests__/serialize.spec.ts +55 -0
  28. package/src/utils/__tests__/target.spec.ts +221 -0
  29. package/src/utils/__tests__/typegen.spec.ts +115 -0
  30. package/src/utils/index.ts +1 -0
  31. package/src/utils/misc.ts +4 -5
  32. package/src/utils/serialize.ts +47 -0
  33. package/src/utils/target.ts +150 -1
  34. package/src/utils/typegen.ts +241 -30
package/docs/build.md CHANGED
@@ -22,37 +22,39 @@ new NapiCli().build({
22
22
 
23
23
  ## Options
24
24
 
25
- | Options | CLI Options | type | required | default | description |
26
- | ----------------- | --------------------- | -------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | | --help,-h | | | | get help |
28
- | target | --target,-t | string | false | | Build for the target triple, bypassed to `cargo build --target` |
29
- | cwd | --cwd | string | false | | The working directory of where napi command will be executed in, all other paths options are relative to this path |
30
- | manifestPath | --manifest-path | string | false | | Path to `Cargo.toml` |
31
- | configPath | --config-path,-c | string | false | | Path to `napi` config json file |
32
- | packageJsonPath | --package-json-path | string | false | | Path to `package.json` |
33
- | targetDir | --target-dir | string | false | | Directory for all crate generated artifacts, see `cargo build --target-dir` |
34
- | outputDir | --output-dir,-o | string | false | | Path to where all the built files would be put. Default to the crate folder |
35
- | platform | --platform | boolean | false | | Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node` |
36
- | jsPackageName | --js-package-name | string | false | | Package name in generated js binding file. Only works with `--platform` flag |
37
- | constEnum | --const-enum | boolean | false | | Whether generate const enum for typescript bindings |
38
- | runtimeStringEnum | --runtime-string-enum | boolean | false | | Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union. |
39
- | jsBinding | --js | string | false | | Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`. |
40
- | noJsBinding | --no-js | boolean | false | | Whether to disable the generation JS binding file. Only works with `--platform` flag. |
41
- | dts | --dts | string | false | | Path and filename of generated type def file. Relative to `--output-dir` |
42
- | dtsHeader | --dts-header | string | false | | Custom file header for generated type def file. Only works when `typedef` feature enabled. |
43
- | noDtsHeader | --no-dts-header | boolean | false | | Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled. |
44
- | dtsCache | --dts-cache | boolean | false | true | Whether to enable the dts cache, default to true |
45
- | esm | --esm | boolean | false | | Whether to emit an ESM JS binding file instead of CJS format. Only works with `--platform` flag. |
46
- | strip | --strip,-s | boolean | false | | Whether strip the library to achieve the minimum file size |
47
- | release | --release,-r | boolean | false | | Build in release mode |
48
- | verbose | --verbose,-v | boolean | false | | Verbosely log build command trace |
49
- | bin | --bin | string | false | | Build only the specified binary |
50
- | package | --package,-p | string | false | | Build the specified library or the one at cwd |
51
- | profile | --profile | string | false | | Build artifacts with the specified profile |
52
- | crossCompile | --cross-compile,-x | boolean | false | | [experimental] cross compile by replacing the cargo subcommand: Windows MSVC targets from a non-Windows host build with `cargo-xwin` (`windows-gnu` targets are rejected, `cargo-xwin` cannot handle them), non-Windows targets build with `cargo-zigbuild` (requires `zig` on PATH). The selected subcommand is auto-installed on first use. Cannot be combined with `--use-cross`, `--use-napi-cross` or `--watch` |
53
- | useCross | --use-cross | boolean | false | | [experimental] not recommended, prefer `--cross-compile` or `--use-napi-cross`: build in a Docker or Podman container with [cross](https://github.com/cross-rs/cross), which must be installed manually and needs a running container engine. Cannot be combined with `--cross-compile`, `--use-napi-cross` or `--watch` |
54
- | useNapiCross | --use-napi-cross | boolean | false | | [experimental] download a prebuilt gcc cross toolchain from `@napi-rs/cross-toolchain` (glibc 2.17) and set linker and C compiler environment variables. Linux glibc targets only (x64, arm64, armv7, ppc64le, s390x) on a Linux x64 or arm64 host, any other target or host errors. Cannot be combined with `--cross-compile` or `--use-cross` |
55
- | watch | --watch,-w | boolean | false | | watch the crate changes and build continuously with `cargo-watch` crates |
56
- | features | --features,-F | string[] | false | | Space-separated list of features to activate |
57
- | allFeatures | --all-features | boolean | false | | Activate all available features |
58
- | noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
25
+ | Options | CLI Options | type | required | default | description |
26
+ | ----------------- | --------------------- | ------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | | --help,-h | | | | get help |
28
+ | target | --target,-t | string | false | | Build for the target triple, bypassed to `cargo build --target` |
29
+ | cwd | --cwd | string | false | | The working directory of where napi command will be executed in, all other paths options are relative to this path |
30
+ | manifestPath | --manifest-path | string | false | | Path to `Cargo.toml` |
31
+ | configPath | --config-path,-c | string | false | | Path to `napi` config json file |
32
+ | packageJsonPath | --package-json-path | string | false | | Path to `package.json` |
33
+ | targetDir | --target-dir | string | false | | Directory for all crate generated artifacts, see `cargo build --target-dir` |
34
+ | outputDir | --output-dir,-o | string | false | | Path to where all the built files would be put. Default to the crate folder |
35
+ | platform | --platform | boolean | false | | Add platform triple to the generated nodejs binding file, eg: `[name].linux-x64-gnu.node` |
36
+ | jsPackageName | --js-package-name | string | false | | Package name in generated js binding file. Only works with `--platform` flag |
37
+ | constEnum | --const-enum | boolean | false | | Whether generate const enum for typescript bindings |
38
+ | runtimeStringEnum | --runtime-string-enum | boolean | false | | Emit `#[napi(string_enum)]` enums as runtime enums (`export declare enum`) under `--no-const-enum`. Default: type-only union. |
39
+ | jsBinding | --js,--js-binding | string | false | | Path and filename of generated JS binding file. Only works with `--platform` flag. Relative to `--output-dir`. |
40
+ | noJsBinding | --no-js | boolean | false | | Whether to disable the generation JS binding file. Only works with `--platform` flag. |
41
+ | dts | --dts | string | false | | Path and filename of generated type def file. Relative to `--output-dir` |
42
+ | dtsHeader | --dts-header | string | false | | Custom file header for generated type def file. Only works when `typedef` feature enabled. |
43
+ | noDtsHeader | --no-dts-header | boolean | false | | Whether to disable the default file header for generated type def file. Only works when `typedef` feature enabled. |
44
+ | dtsCache | --dts-cache | boolean | false | true | Whether to enable the dts cache, default to true |
45
+ | format | --format | 'esm' \| 'commonjs' | false | | The module format of the generated JS binding file. Only works with `--platform` flag. Defaults to `commonjs`. |
46
+ | esm | --esm | boolean | false | | Alias for `--format esm`. |
47
+ | commonjs | --commonjs | boolean | false | | Alias for `--format commonjs`. |
48
+ | strip | --strip,-s | boolean | false | | Whether strip the library to achieve the minimum file size |
49
+ | release | --release,-r | boolean | false | | Build in release mode |
50
+ | verbose | --verbose,-v | boolean | false | | Verbosely log build command trace |
51
+ | bin | --bin | string | false | | Build only the specified binary |
52
+ | package | --package,-p | string | false | | Build the specified library or the one at cwd |
53
+ | profile | --profile | string | false | | Build artifacts with the specified profile |
54
+ | crossCompile | --cross-compile,-x | boolean | false | | [experimental] cross compile by replacing the cargo subcommand: Windows MSVC targets from a non-Windows host build with `cargo-xwin` (`windows-gnu` targets are rejected, `cargo-xwin` cannot handle them), non-Windows targets build with `cargo-zigbuild` (requires `zig` on PATH). The selected subcommand is auto-installed on first use. Cannot be combined with `--use-cross`, `--use-napi-cross` or `--watch` |
55
+ | useCross | --use-cross | boolean | false | | [experimental] not recommended, prefer `--cross-compile` or `--use-napi-cross`: build in a Docker or Podman container with [cross](https://github.com/cross-rs/cross), which must be installed manually and needs a running container engine. Cannot be combined with `--cross-compile`, `--use-napi-cross` or `--watch` |
56
+ | useNapiCross | --use-napi-cross | boolean | false | | [experimental] download a prebuilt gcc cross toolchain from `@napi-rs/cross-toolchain` (glibc 2.17) and set linker and C compiler environment variables. Linux glibc targets only (x64, arm64, armv7, ppc64le, s390x) on a Linux x64 or arm64 host, any other target or host errors. Cannot be combined with `--cross-compile` or `--use-cross` |
57
+ | watch | --watch,-w | boolean | false | | watch the crate changes and build continuously with `cargo-watch` crates |
58
+ | features | --features,-F | string[] | false | | Space-separated list of features to activate |
59
+ | allFeatures | --all-features | boolean | false | | Activate all available features |
60
+ | noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napi-rs/cli",
3
- "version": "3.8.6",
3
+ "version": "3.9.1",
4
4
  "description": "Cli tools for napi-rs",
5
5
  "author": "LongYinan <lynweklm@gmail.com>",
6
6
  "homepage": "https://napi.rs/",
@@ -68,7 +68,7 @@
68
68
  "clipanion": "^4.0.0-rc.4",
69
69
  "colorette": "^2.0.20",
70
70
  "es-toolkit": "^1.47.0",
71
- "js-yaml": "^4.2.0",
71
+ "js-yaml": "^5.0.0",
72
72
  "obug": "^2.1.2",
73
73
  "semver": "^7.8.2",
74
74
  "typanion": "^3.14.0",
@@ -87,9 +87,8 @@
87
87
  "emnapi": "^2.0.0-alpha.4",
88
88
  "empathic": "^2.0.1",
89
89
  "env-paths": "^4.0.0",
90
- "oxc-parser": "^0.144.0",
91
- "prettier": "^3.8.3",
92
- "tsdown": "^0.22.2",
90
+ "oxc-parser": "^0.149.0",
91
+ "tsdown": "^0.23.0",
93
92
  "tslib": "^2.8.1"
94
93
  },
95
94
  "peerDependencies": {
@@ -61,11 +61,11 @@ Generated by [AVA](https://avajs.dev).
61
61
  let __emnapiWasmEnvCleanupDrainPromise␊
62
62
  let __wasiDisposed = false␊
63
63
  let __wasiDisposePromise␊
64
- let __completeWasiDisposal = function() {}␊
64
+ let __completeWasiDisposal = function () {}␊
65
65
  // Overridden by loader flavors that have a last-resort reclaim for a rollback␊
66
66
  // that stopped short of destroying the context. See␊
67
67
  // \`__rollbackWasiInitialization\`.␊
68
- let __retainWasiRollbackForRetry = function() {}␊
68
+ let __retainWasiRollbackForRetry = function () {}␊
69
69
  ␊
70
70
  function __isThenable(value) {␊
71
71
  return (␊
@@ -669,11 +669,11 @@ Generated by [AVA](https://avajs.dev).
669
669
  let __emnapiWasmEnvCleanupDrainPromise␊
670
670
  let __wasiDisposed = false␊
671
671
  let __wasiDisposePromise␊
672
- let __completeWasiDisposal = function() {}␊
672
+ let __completeWasiDisposal = function () {}␊
673
673
  // Overridden by loader flavors that have a last-resort reclaim for a rollback␊
674
674
  // that stopped short of destroying the context. See␊
675
675
  // \`__rollbackWasiInitialization\`.␊
676
- let __retainWasiRollbackForRetry = function() {}␊
676
+ let __retainWasiRollbackForRetry = function () {}␊
677
677
  ␊
678
678
  function __isThenable(value) {␊
679
679
  return (␊
@@ -1296,11 +1296,11 @@ Generated by [AVA](https://avajs.dev).
1296
1296
  let __emnapiWasmEnvCleanupDrainPromise␊
1297
1297
  let __wasiDisposed = false␊
1298
1298
  let __wasiDisposePromise␊
1299
- let __completeWasiDisposal = function() {}␊
1299
+ let __completeWasiDisposal = function () {}␊
1300
1300
  // Overridden by loader flavors that have a last-resort reclaim for a rollback␊
1301
1301
  // that stopped short of destroying the context. See␊
1302
1302
  // \`__rollbackWasiInitialization\`.␊
1303
- let __retainWasiRollbackForRetry = function() {}␊
1303
+ let __retainWasiRollbackForRetry = function () {}␊
1304
1304
  ␊
1305
1305
  function __isThenable(value) {␊
1306
1306
  return (␊
@@ -1880,10 +1880,9 @@ Generated by [AVA](https://avajs.dev).
1880
1880
  // eslint-disable-next-line no-console␊
1881
1881
  console.log.apply(console, arguments)␊
1882
1882
  },␊
1883
- printErr: function() {␊
1883
+ printErr: function () {␊
1884
1884
  // eslint-disable-next-line no-console␊
1885
1885
  console.error.apply(console, arguments)␊
1886
- ␊
1887
1886
  },␊
1888
1887
  })␊
1889
1888
  return instantiateNapiModuleSync(wasmModule, {␊
@@ -1904,7 +1903,6 @@ Generated by [AVA](https://avajs.dev).
1904
1903
  },␊
1905
1904
  })␊
1906
1905
  },␊
1907
- ␊
1908
1906
  })␊
1909
1907
  ␊
1910
1908
  globalThis.onmessage = function (e) {␊
@@ -1933,10 +1931,9 @@ Generated by [AVA](https://avajs.dev).
1933
1931
  // eslint-disable-next-line no-console␊
1934
1932
  console.log.apply(console, arguments)␊
1935
1933
  },␊
1936
- printErr: function() {␊
1934
+ printErr: function () {␊
1937
1935
  // eslint-disable-next-line no-console␊
1938
1936
  console.error.apply(console, arguments)␊
1939
- ␊
1940
1937
  errorOutputs.push([...arguments])␊
1941
1938
  },␊
1942
1939
  })␊
@@ -1998,10 +1995,9 @@ Generated by [AVA](https://avajs.dev).
1998
1995
  // eslint-disable-next-line no-console␊
1999
1996
  console.log.apply(console, arguments)␊
2000
1997
  },␊
2001
- printErr: function() {␊
1998
+ printErr: function () {␊
2002
1999
  // eslint-disable-next-line no-console␊
2003
2000
  console.error.apply(console, arguments)␊
2004
- ␊
2005
2001
  errorOutputs.push([...arguments])␊
2006
2002
  },␊
2007
2003
  })␊
@@ -25,8 +25,11 @@ import ava, { type ExecutionContext, type TestFn } from 'ava'
25
25
 
26
26
  import {
27
27
  buildProject,
28
+ EMNAPI_WASI_SDK_34_LINK_DIR,
28
29
  generateTypeDef,
29
30
  napiCrossToolchainEnvs,
31
+ resolveBuildFormat,
32
+ selectEmnapiLinkDir,
30
33
  validateCrossCompileFlags,
31
34
  validateNapiCrossSupport,
32
35
  writeJsBinding,
@@ -197,6 +200,95 @@ napi-build = { path = "${napiBuildPath}" }
197
200
  t.regex(jsContent, /module\.exports\.sum = nativeBinding\.sum/)
198
201
  })
199
202
 
203
+ test('writeJsBinding uses the explicit format independently of the filename', async (t) => {
204
+ const { projectDir } = t.context
205
+ const commonjsPath = join(projectDir, 'binding.cjs')
206
+ const esmPath = join(projectDir, 'binding.js')
207
+ const legacyEsmPath = join(projectDir, 'legacy.js')
208
+
209
+ await writeJsBinding({
210
+ platform: true,
211
+ idents: ['sum'],
212
+ binaryName: 'build-integration',
213
+ packageName: 'build-integration',
214
+ version: '0.1.0',
215
+ outputDir: projectDir,
216
+ jsBinding: 'binding.cjs',
217
+ format: 'commonjs',
218
+ })
219
+ await writeJsBinding({
220
+ platform: true,
221
+ idents: ['sum'],
222
+ binaryName: 'build-integration',
223
+ packageName: 'build-integration',
224
+ version: '0.1.0',
225
+ outputDir: projectDir,
226
+ jsBinding: 'binding.js',
227
+ format: 'esm',
228
+ })
229
+ await writeJsBinding({
230
+ platform: true,
231
+ idents: ['sum'],
232
+ binaryName: 'build-integration',
233
+ packageName: 'build-integration',
234
+ version: '0.1.0',
235
+ outputDir: projectDir,
236
+ jsBinding: 'legacy.js',
237
+ esm: true,
238
+ })
239
+
240
+ const [commonjs, esm, legacyEsm] = await Promise.all([
241
+ readFile(commonjsPath, 'utf8'),
242
+ readFile(esmPath, 'utf8'),
243
+ readFile(legacyEsmPath, 'utf8'),
244
+ ])
245
+
246
+ t.regex(commonjs, /module\.exports\.sum = nativeBinding\.sum/)
247
+ t.regex(esm, /export \{ sum \}/)
248
+ t.regex(legacyEsm, /export \{ sum \}/)
249
+ })
250
+
251
+ test('resolveBuildFormat handles defaults, aliases, and conflicts', (t) => {
252
+ const validCases = [
253
+ { options: {}, expected: 'commonjs' },
254
+ { options: { format: 'esm' }, expected: 'esm' },
255
+ { options: { format: 'commonjs' }, expected: 'commonjs' },
256
+ { options: { esm: true }, expected: 'esm' },
257
+ { options: { commonjs: true }, expected: 'commonjs' },
258
+ ] as const
259
+
260
+ for (const { options, expected } of validCases) {
261
+ t.is(resolveBuildFormat(options), expected)
262
+ }
263
+
264
+ const invalidCases = [
265
+ {
266
+ options: { esm: true, commonjs: true },
267
+ message: /`--esm` and `--commonjs` cannot be used together/,
268
+ },
269
+ {
270
+ options: { format: 'esm', commonjs: true },
271
+ message: /`--format esm` cannot be used with `--commonjs`/,
272
+ },
273
+ {
274
+ options: { format: 'commonjs', esm: true },
275
+ message: /`--format commonjs` cannot be used with `--esm`/,
276
+ },
277
+ {
278
+ options: { format: 'invalid' },
279
+ message: /Invalid build format "invalid"/,
280
+ },
281
+ {
282
+ options: { format: '' },
283
+ message: /Invalid build format ""/,
284
+ },
285
+ ] as const
286
+
287
+ for (const { options, message } of invalidCases) {
288
+ t.throws(() => resolveBuildFormat(options), { message })
289
+ }
290
+ })
291
+
200
292
  test('generateTypeDef preserves deterministic file order', async (t) => {
201
293
  const { projectDir, typeDefDir } = t.context
202
294
 
@@ -1018,3 +1110,195 @@ const isNapiCrossUnsupportedHost =
1018
1110
  )
1019
1111
  },
1020
1112
  )
1113
+
1114
+ async function createEmnapiLibDir(root: string, dirNames: string[]) {
1115
+ const emnapiLibDir = join(root, 'emnapi', 'lib')
1116
+ for (const dirName of dirNames) {
1117
+ await mkdir(join(emnapiLibDir, dirName), { recursive: true })
1118
+ }
1119
+ return emnapiLibDir
1120
+ }
1121
+
1122
+ async function createWasiSdkDir(root: string, version: string) {
1123
+ const wasiSdkPath = join(root, `wasi-sdk-${version}`)
1124
+ await mkdir(wasiSdkPath, { recursive: true })
1125
+ await writeFile(join(wasiSdkPath, 'VERSION'), `${version}\n`)
1126
+ return wasiSdkPath
1127
+ }
1128
+
1129
+ async function createWasiLibcArchive(
1130
+ projectDir: string,
1131
+ abi: 'legacy' | 'new',
1132
+ ) {
1133
+ const libcPath = join(projectDir, `libc-${abi}.a`)
1134
+ // Only the archive member names matter: wasi-libc moved the futex helpers
1135
+ // into `futex.c` when it dropped the `int op` parameter, and kept `__wait.c`
1136
+ // for other symbols.
1137
+ const members =
1138
+ abi === 'new'
1139
+ ? ['__wait.c.obj', 'futex.c.obj']
1140
+ : ['__wait.c.obj', '__wasilibc_busywait.c.obj']
1141
+ await writeFile(libcPath, `!<arch>\n${members.join('\n')}\n`)
1142
+ return libcPath
1143
+ }
1144
+
1145
+ test('selects the wasi-sdk 34 emnapi archives for wasi-sdk >= 34', async (t) => {
1146
+ const { projectDir } = t.context
1147
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1148
+ 'wasm32-wasip1',
1149
+ 'wasm32-wasip1-threads',
1150
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1151
+ ])
1152
+ const wasiSdkPath = await createWasiSdkDir(projectDir, '34.0')
1153
+
1154
+ t.deepEqual(
1155
+ selectEmnapiLinkDir(
1156
+ emnapiLibDir,
1157
+ 'wasm32-wasip1-threads',
1158
+ true,
1159
+ wasiSdkPath,
1160
+ ),
1161
+ {
1162
+ linkDirName: EMNAPI_WASI_SDK_34_LINK_DIR,
1163
+ wasiSdkMajor: 34,
1164
+ needsWasiSdk34: true,
1165
+ },
1166
+ )
1167
+ })
1168
+
1169
+ test('keeps the legacy emnapi archives for wasi-sdk <= 33', async (t) => {
1170
+ const { projectDir } = t.context
1171
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1172
+ 'wasm32-wasip1-threads',
1173
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1174
+ ])
1175
+ const wasiSdkPath = await createWasiSdkDir(projectDir, '33.0')
1176
+
1177
+ t.deepEqual(
1178
+ selectEmnapiLinkDir(
1179
+ emnapiLibDir,
1180
+ 'wasm32-wasip1-threads',
1181
+ true,
1182
+ wasiSdkPath,
1183
+ ),
1184
+ {
1185
+ linkDirName: 'wasm32-wasip1-threads',
1186
+ wasiSdkMajor: 33,
1187
+ needsWasiSdk34: false,
1188
+ },
1189
+ )
1190
+ })
1191
+
1192
+ test('keeps the legacy emnapi archives when the wasi-libc ABI is unknown', async (t) => {
1193
+ const { projectDir } = t.context
1194
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1195
+ 'wasm32-wasip1-threads',
1196
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1197
+ ])
1198
+
1199
+ t.deepEqual(
1200
+ // `null` stands in for a Rust sysroot that cannot be probed.
1201
+ selectEmnapiLinkDir(
1202
+ emnapiLibDir,
1203
+ 'wasm32-wasip1-threads',
1204
+ true,
1205
+ undefined,
1206
+ { rustWasiLibc: null },
1207
+ ),
1208
+ {
1209
+ linkDirName: 'wasm32-wasip1-threads',
1210
+ wasiSdkMajor: null,
1211
+ needsWasiSdk34: false,
1212
+ },
1213
+ )
1214
+ })
1215
+
1216
+ test('keeps the legacy emnapi archives for a pre wasi-sdk 34 Rust toolchain', async (t) => {
1217
+ const { projectDir } = t.context
1218
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1219
+ 'wasm32-wasip1-threads',
1220
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1221
+ ])
1222
+ const rustWasiLibc = await createWasiLibcArchive(projectDir, 'legacy')
1223
+
1224
+ t.deepEqual(
1225
+ selectEmnapiLinkDir(
1226
+ emnapiLibDir,
1227
+ 'wasm32-wasip1-threads',
1228
+ true,
1229
+ undefined,
1230
+ { rustWasiLibc },
1231
+ ),
1232
+ {
1233
+ linkDirName: 'wasm32-wasip1-threads',
1234
+ wasiSdkMajor: null,
1235
+ needsWasiSdk34: false,
1236
+ },
1237
+ )
1238
+ })
1239
+
1240
+ test('selects the wasi-sdk 34 archives for a Rust toolchain that bundles the new wasi-libc', async (t) => {
1241
+ const { projectDir } = t.context
1242
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1243
+ 'wasm32-wasip1-threads',
1244
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1245
+ ])
1246
+ const rustWasiLibc = await createWasiLibcArchive(projectDir, 'new')
1247
+
1248
+ t.deepEqual(
1249
+ // No wasi-sdk is configured: cargo links Rust's bundled wasi-libc, and
1250
+ // Rust picked up wasi-sdk 34 in rust-lang/rust#161773.
1251
+ selectEmnapiLinkDir(
1252
+ emnapiLibDir,
1253
+ 'wasm32-wasip1-threads',
1254
+ true,
1255
+ undefined,
1256
+ { rustWasiLibc },
1257
+ ),
1258
+ {
1259
+ linkDirName: EMNAPI_WASI_SDK_34_LINK_DIR,
1260
+ wasiSdkMajor: null,
1261
+ needsWasiSdk34: true,
1262
+ },
1263
+ )
1264
+ })
1265
+
1266
+ test('never selects the wasi-sdk 34 archives for the threadless target', async (t) => {
1267
+ const { projectDir } = t.context
1268
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1269
+ 'wasm32-wasip1',
1270
+ EMNAPI_WASI_SDK_34_LINK_DIR,
1271
+ ])
1272
+ const wasiSdkPath = await createWasiSdkDir(projectDir, '34.0')
1273
+
1274
+ t.deepEqual(
1275
+ selectEmnapiLinkDir(emnapiLibDir, 'wasm32-wasip1', false, wasiSdkPath),
1276
+ {
1277
+ linkDirName: 'wasm32-wasip1',
1278
+ wasiSdkMajor: 34,
1279
+ needsWasiSdk34: false,
1280
+ },
1281
+ )
1282
+ })
1283
+
1284
+ test('falls back to the legacy archives when emnapi has no wasi-sdk 34 directory', async (t) => {
1285
+ const { projectDir } = t.context
1286
+ const emnapiLibDir = await createEmnapiLibDir(projectDir, [
1287
+ 'wasm32-wasip1-threads',
1288
+ ])
1289
+ const wasiSdkPath = await createWasiSdkDir(projectDir, '34.0')
1290
+
1291
+ t.deepEqual(
1292
+ selectEmnapiLinkDir(
1293
+ emnapiLibDir,
1294
+ 'wasm32-wasip1-threads',
1295
+ true,
1296
+ wasiSdkPath,
1297
+ ),
1298
+ {
1299
+ linkDirName: 'wasm32-wasip1-threads',
1300
+ wasiSdkMajor: 34,
1301
+ needsWasiSdk34: true,
1302
+ },
1303
+ )
1304
+ })
@@ -131,9 +131,10 @@ test('should omit exports fields from publishConfig in scoped packages', async (
131
131
  const scopedPackageJsonPath = join(scopedDir, 'package.json')
132
132
  t.true(existsSync(scopedPackageJsonPath))
133
133
 
134
- const scopedPackageJson = JSON.parse(
135
- await readFile(scopedPackageJsonPath, 'utf-8'),
136
- )
134
+ const scopedPackageJsonSource = await readFile(scopedPackageJsonPath, 'utf-8')
135
+ t.true(scopedPackageJsonSource.endsWith('\n'))
136
+ t.false(scopedPackageJsonSource.endsWith('\n\n'))
137
+ const scopedPackageJson = JSON.parse(scopedPackageJsonSource)
137
138
 
138
139
  // Verify that publishConfig only contains registry and access, not exports
139
140
  t.truthy(scopedPackageJson.publishConfig)