@napi-rs/cli 3.0.0-alpha.8 → 3.0.0-alpha.80

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 (161) hide show
  1. package/cli.mjs +1 -1
  2. package/dist/api/artifacts.js +28 -7
  3. package/dist/api/artifacts.js.map +1 -1
  4. package/dist/api/build.js +309 -64
  5. package/dist/api/build.js.map +1 -1
  6. package/dist/api/create-npm-dirs.js +46 -9
  7. package/dist/api/create-npm-dirs.js.map +1 -1
  8. package/dist/api/new.js +57 -20
  9. package/dist/api/new.js.map +1 -1
  10. package/dist/api/pre-publish.js +9 -7
  11. package/dist/api/pre-publish.js.map +1 -1
  12. package/dist/api/rename.js +9 -1
  13. package/dist/api/rename.js.map +1 -1
  14. package/dist/api/templates/cargo.toml.js +1 -0
  15. package/dist/api/templates/cargo.toml.js.map +1 -1
  16. package/dist/api/templates/ci-template.d.ts +3 -1
  17. package/dist/api/templates/ci-template.js +140 -125
  18. package/dist/api/templates/ci-template.js.map +1 -1
  19. package/dist/api/templates/ci.yml.d.ts +3 -1
  20. package/dist/api/templates/ci.yml.js +21 -5
  21. package/dist/api/templates/ci.yml.js.map +1 -1
  22. package/dist/api/templates/js-binding.d.ts +1 -0
  23. package/dist/api/templates/js-binding.js +141 -222
  24. package/dist/api/templates/js-binding.js.map +1 -1
  25. package/dist/api/templates/lib.rs.js +1 -4
  26. package/dist/api/templates/lib.rs.js.map +1 -1
  27. package/dist/api/templates/load-wasi-template.d.ts +2 -0
  28. package/dist/api/templates/load-wasi-template.js +168 -0
  29. package/dist/api/templates/load-wasi-template.js.map +1 -0
  30. package/dist/api/templates/package.json.d.ts +9 -2
  31. package/dist/api/templates/package.json.js +26 -4
  32. package/dist/api/templates/package.json.js.map +1 -1
  33. package/dist/api/templates/wasi-worker-template.d.ts +2 -0
  34. package/dist/api/templates/wasi-worker-template.js +122 -0
  35. package/dist/api/templates/wasi-worker-template.js.map +1 -0
  36. package/dist/api/universalize.js +5 -5
  37. package/dist/api/universalize.js.map +1 -1
  38. package/dist/api/version.js +2 -2
  39. package/dist/api/version.js.map +1 -1
  40. package/dist/commands/artifacts.js +2 -2
  41. package/dist/commands/artifacts.js.map +1 -1
  42. package/dist/commands/build.js +1 -1
  43. package/dist/commands/build.js.map +1 -1
  44. package/dist/commands/new.js +34 -34
  45. package/dist/commands/new.js.map +1 -1
  46. package/dist/def/artifacts.d.ts +21 -1
  47. package/dist/def/artifacts.js +10 -2
  48. package/dist/def/artifacts.js.map +1 -1
  49. package/dist/def/build.d.ts +155 -4
  50. package/dist/def/build.js +30 -4
  51. package/dist/def/build.js.map +1 -1
  52. package/dist/def/create-npm-dirs.d.ts +10 -0
  53. package/dist/def/create-npm-dirs.js +4 -0
  54. package/dist/def/create-npm-dirs.js.map +1 -1
  55. package/dist/def/new.d.ts +25 -7
  56. package/dist/def/new.js +11 -1
  57. package/dist/def/new.js.map +1 -1
  58. package/dist/def/pre-publish.d.ts +12 -2
  59. package/dist/def/pre-publish.js +7 -3
  60. package/dist/def/pre-publish.js.map +1 -1
  61. package/dist/def/rename.d.ts +16 -6
  62. package/dist/def/rename.js +5 -1
  63. package/dist/def/rename.js.map +1 -1
  64. package/dist/def/universalize.d.ts +10 -0
  65. package/dist/def/universalize.js +4 -0
  66. package/dist/def/universalize.js.map +1 -1
  67. package/dist/def/version.d.ts +10 -0
  68. package/dist/def/version.js +4 -0
  69. package/dist/def/version.js.map +1 -1
  70. package/dist/index.cjs +8706 -14649
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.js +1 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/utils/__tests__/config.spec.d.ts +1 -0
  75. package/dist/utils/__tests__/config.spec.js +63 -0
  76. package/dist/utils/__tests__/config.spec.js.map +1 -0
  77. package/dist/utils/__tests__/typegen.spec.js +8 -4
  78. package/dist/utils/__tests__/typegen.spec.js.map +1 -1
  79. package/dist/utils/cargo.js +2 -2
  80. package/dist/utils/cargo.js.map +1 -1
  81. package/dist/utils/config.d.ts +52 -4
  82. package/dist/utils/config.js +37 -5
  83. package/dist/utils/config.js.map +1 -1
  84. package/dist/utils/index.d.ts +0 -1
  85. package/dist/utils/index.js +0 -1
  86. package/dist/utils/index.js.map +1 -1
  87. package/dist/utils/metadata.d.ts +12 -0
  88. package/dist/utils/metadata.js +21 -10
  89. package/dist/utils/metadata.js.map +1 -1
  90. package/dist/utils/misc.d.ts +1 -2
  91. package/dist/utils/misc.js +3 -3
  92. package/dist/utils/misc.js.map +1 -1
  93. package/dist/utils/target.d.ts +7 -5
  94. package/dist/utils/target.js +17 -3
  95. package/dist/utils/target.js.map +1 -1
  96. package/dist/utils/typegen.d.ts +1 -1
  97. package/dist/utils/typegen.js +55 -20
  98. package/dist/utils/typegen.js.map +1 -1
  99. package/dist/utils/version.d.ts +2 -1
  100. package/dist/utils/version.js +10 -8
  101. package/dist/utils/version.js.map +1 -1
  102. package/package.json +27 -26
  103. package/src/api/artifacts.ts +81 -9
  104. package/src/api/build.ts +467 -87
  105. package/src/api/create-npm-dirs.ts +57 -9
  106. package/src/api/new.ts +89 -31
  107. package/src/api/pre-publish.ts +13 -7
  108. package/src/api/rename.ts +10 -1
  109. package/src/api/templates/cargo.toml.ts +1 -0
  110. package/src/api/templates/ci-template.ts +150 -125
  111. package/src/api/templates/ci.yml.ts +27 -6
  112. package/src/api/templates/js-binding.ts +147 -220
  113. package/src/api/templates/lib.rs.ts +1 -4
  114. package/src/api/templates/load-wasi-template.ts +182 -0
  115. package/src/api/templates/package.json.ts +47 -5
  116. package/src/api/templates/wasi-worker-template.ts +122 -0
  117. package/src/api/universalize.ts +13 -8
  118. package/src/api/version.ts +5 -2
  119. package/src/commands/artifacts.ts +2 -2
  120. package/src/commands/build.ts +1 -1
  121. package/src/commands/new.ts +46 -45
  122. package/src/def/artifacts.ts +22 -3
  123. package/src/def/build.ts +64 -8
  124. package/src/def/create-npm-dirs.ts +9 -0
  125. package/src/def/new.ts +28 -3
  126. package/src/def/pre-publish.ts +12 -3
  127. package/src/def/rename.ts +11 -2
  128. package/src/def/universalize.ts +9 -0
  129. package/src/def/version.ts +9 -0
  130. package/src/index.ts +2 -0
  131. package/src/utils/__tests__/__fixtures__/napi_type_def +130 -130
  132. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  133. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  134. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +28 -0
  135. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  136. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +711 -158
  137. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  138. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +8 -7
  139. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  140. package/src/utils/__tests__/config.spec.ts +81 -0
  141. package/src/utils/__tests__/typegen.spec.ts +18 -3
  142. package/src/utils/cargo.ts +2 -2
  143. package/src/utils/config.ts +116 -10
  144. package/src/utils/index.ts +0 -1
  145. package/src/utils/metadata.ts +38 -13
  146. package/src/utils/misc.ts +11 -3
  147. package/src/utils/target.ts +22 -4
  148. package/src/utils/typegen.ts +98 -49
  149. package/src/utils/version.ts +10 -8
  150. package/dist/api/load-wasi-template.d.ts +0 -1
  151. package/dist/api/load-wasi-template.js +0 -64
  152. package/dist/api/load-wasi-template.js.map +0 -1
  153. package/dist/api/wasi-worker-template.d.ts +0 -1
  154. package/dist/api/wasi-worker-template.js +0 -53
  155. package/dist/api/wasi-worker-template.js.map +0 -1
  156. package/dist/utils/ci.d.ts +0 -11
  157. package/dist/utils/ci.js +0 -96
  158. package/dist/utils/ci.js.map +0 -1
  159. package/src/api/load-wasi-template.ts +0 -66
  160. package/src/api/wasi-worker-template.ts +0 -52
  161. package/src/utils/ci.ts +0 -111
package/src/api/build.ts CHANGED
@@ -1,10 +1,19 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { createHash } from 'node:crypto'
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ unlinkSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from 'node:fs'
3
10
  import { createRequire } from 'node:module'
4
- import { tmpdir } from 'node:os'
11
+ import { tmpdir, homedir } from 'node:os'
5
12
  import { parse, join, resolve } from 'node:path'
6
13
 
7
14
  import * as colors from 'colorette'
15
+ import { groupBy, split } from 'lodash-es'
16
+ import { include as setjmpInclude, lib as setjmpLib } from 'wasm-sjlj'
8
17
 
9
18
  import { BuildOptions as RawBuildOptions } from '../def/build.js'
10
19
  import {
@@ -30,9 +39,15 @@ import {
30
39
  writeFileAsync,
31
40
  } from '../utils/index.js'
32
41
 
33
- import { createWasiBinding } from './load-wasi-template.js'
34
- import { createCjsBinding } from './templates/index.js'
35
- import { WASI_WORKER_TEMPLATE } from './wasi-worker-template.js'
42
+ import { createCjsBinding, createEsmBinding } from './templates/index.js'
43
+ import {
44
+ createWasiBinding,
45
+ createWasiBrowserBinding,
46
+ } from './templates/load-wasi-template.js'
47
+ import {
48
+ createWasiBrowserWorkerBinding,
49
+ WASI_WORKER_TEMPLATE,
50
+ } from './templates/wasi-worker-template.js'
36
51
 
37
52
  const debug = debugFactory('build')
38
53
  const require = createRequire(import.meta.url)
@@ -50,6 +65,11 @@ type BuildOptions = RawBuildOptions & {
50
65
  export async function buildProject(options: BuildOptions) {
51
66
  debug('napi build command receive options: %O', options)
52
67
 
68
+ options = {
69
+ dtsCache: true,
70
+ ...options,
71
+ }
72
+
53
73
  const cwd = options.cwd ?? process.cwd()
54
74
 
55
75
  const resolvePath = (...paths: string[]) => resolve(cwd, ...paths)
@@ -72,6 +92,13 @@ export async function buildProject(options: BuildOptions) {
72
92
  )
73
93
  }
74
94
 
95
+ const depsNapiPackages = pkg.dependencies
96
+ .map((p) => metadata.packages.find((c) => c.name === p.name))
97
+ .filter(
98
+ (c): c is Crate =>
99
+ c != null && c.dependencies.some((d) => d.name == 'napi-derive'),
100
+ )
101
+
75
102
  const crateDir = parse(pkg.manifest_path).dir
76
103
 
77
104
  const builder = new Builder(
@@ -81,16 +108,20 @@ export async function buildProject(options: BuildOptions) {
81
108
  options.target
82
109
  ? parseTriple(options.target)
83
110
  : process.env.CARGO_BUILD_TARGET
84
- ? parseTriple(process.env.CARGO_BUILD_TARGET)
85
- : getSystemDefaultTarget(),
111
+ ? parseTriple(process.env.CARGO_BUILD_TARGET)
112
+ : getSystemDefaultTarget(),
86
113
  crateDir,
87
114
  resolvePath(options.outputDir ?? crateDir),
88
115
  options.targetDir ??
89
116
  process.env.CARGO_BUILD_TARGET_DIR ??
90
117
  metadata.target_directory,
91
118
  await readNapiConfig(
92
- resolvePath(options.packageJsonPath ?? 'package.json'),
119
+ resolvePath(
120
+ options.configPath ?? options.packageJsonPath ?? 'package.json',
121
+ ),
122
+ options.configPath ? resolvePath(options.configPath) : undefined,
93
123
  ),
124
+ depsNapiPackages,
94
125
  )
95
126
 
96
127
  return builder.build()
@@ -110,6 +141,7 @@ class Builder {
110
141
  private readonly outputDir: string,
111
142
  private readonly targetDir: string,
112
143
  private readonly config: NapiConfig,
144
+ private readonly depsNapiPackages: Crate[],
113
145
  ) {}
114
146
 
115
147
  get cdyLibName() {
@@ -143,11 +175,138 @@ class Builder {
143
175
  .setPackage()
144
176
  .setFeatures()
145
177
  .setTarget()
178
+ .pickCrossToolchain()
146
179
  .setEnvs()
147
180
  .setBypassArgs()
148
181
  .exec()
149
182
  }
150
183
 
184
+ private pickCrossToolchain() {
185
+ if (!this.options.useNapiCross) {
186
+ return this
187
+ }
188
+ if (this.options.useCross) {
189
+ debug.warn(
190
+ 'You are trying to use both `--cross` and `--use-napi-cross` options, `--use-cross` will be ignored.',
191
+ )
192
+ }
193
+
194
+ if (this.options.crossCompile) {
195
+ debug.warn(
196
+ 'You are trying to use both `--cross-compile` and `--use-napi-cross` options, `--cross-compile` will be ignored.',
197
+ )
198
+ }
199
+
200
+ try {
201
+ const { version, download } = require('@napi-rs/cross-toolchain')
202
+
203
+ const alias: Record<string, string> = {
204
+ 's390x-unknown-linux-gnu': 's390x-ibm-linux-gnu',
205
+ }
206
+
207
+ const toolchainPath = join(
208
+ homedir(),
209
+ '.napi-rs',
210
+ 'cross-toolchain',
211
+ version,
212
+ this.target.triple,
213
+ )
214
+ mkdirSync(toolchainPath, { recursive: true })
215
+ if (existsSync(join(toolchainPath, 'package.json'))) {
216
+ debug(`Toolchain ${toolchainPath} exists, skip extracting`)
217
+ } else {
218
+ const tarArchive = download(process.arch, this.target.triple)
219
+ tarArchive.unpack(toolchainPath)
220
+ }
221
+ const upperCaseTarget = targetToEnvVar(this.target.triple)
222
+ const crossTargetName = alias[this.target.triple] ?? this.target.triple
223
+ const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`
224
+ this.envs[linkerEnv] = join(
225
+ toolchainPath,
226
+ 'bin',
227
+ `${crossTargetName}-gcc`,
228
+ )
229
+ if (!process.env.TARGET_SYSROOT) {
230
+ this.envs[`TARGET_SYSROOT`] = join(
231
+ toolchainPath,
232
+ crossTargetName,
233
+ 'sysroot',
234
+ )
235
+ }
236
+ if (!process.env.TARGET_AR) {
237
+ this.envs[`TARGET_AR`] = join(
238
+ toolchainPath,
239
+ 'bin',
240
+ `${crossTargetName}-ar`,
241
+ )
242
+ }
243
+ if (!process.env.TARGET_RANLIB) {
244
+ this.envs[`TARGET_RANLIB`] = join(
245
+ toolchainPath,
246
+ 'bin',
247
+ `${crossTargetName}-ranlib`,
248
+ )
249
+ }
250
+ if (!process.env.TARGET_READELF) {
251
+ this.envs[`TARGET_READELF`] = join(
252
+ toolchainPath,
253
+ 'bin',
254
+ `${crossTargetName}-readelf`,
255
+ )
256
+ }
257
+ if (!process.env.TARGET_C_INCLUDE_PATH) {
258
+ this.envs[`TARGET_C_INCLUDE_PATH`] = join(
259
+ toolchainPath,
260
+ crossTargetName,
261
+ 'sysroot',
262
+ 'usr',
263
+ 'include/',
264
+ )
265
+ }
266
+ if (!process.env.CC && !process.env.TARGET_CC) {
267
+ this.envs[`CC`] = join(toolchainPath, 'bin', `${crossTargetName}-gcc`)
268
+ this.envs[`TARGET_CC`] = join(
269
+ toolchainPath,
270
+ 'bin',
271
+ `${crossTargetName}-gcc`,
272
+ )
273
+ }
274
+ if (!process.env.CXX && !process.env.TARGET_CXX) {
275
+ this.envs[`CXX`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`)
276
+ this.envs[`TARGET_CXX`] = join(
277
+ toolchainPath,
278
+ 'bin',
279
+ `${crossTargetName}-g++`,
280
+ )
281
+ }
282
+ if (
283
+ (process.env.CC?.startsWith('clang') &&
284
+ (process.env.TARGET_CC?.startsWith('clang') ||
285
+ !process.env.TARGET_CC)) ||
286
+ process.env.TARGET_CC?.startsWith('clang')
287
+ ) {
288
+ const CFLAGS = process.env.CFLAGS ?? ''
289
+ this.envs.CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${CFLAGS}`
290
+ }
291
+ if (
292
+ (process.env.CXX?.startsWith('clang++') &&
293
+ (process.env.TARGET_CXX?.startsWith('clang++') ||
294
+ !process.env.TARGET_CXX)) ||
295
+ process.env.TARGET_CXX?.startsWith('clang++')
296
+ ) {
297
+ const CXXFLAGS = process.env.CXXFLAGS ?? ''
298
+ this.envs.CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${CXXFLAGS} ${CXXFLAGS}`
299
+ }
300
+ this.envs.PATH = this.envs.PATH
301
+ ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}`
302
+ : `${toolchainPath}/bin:${process.env.PATH}`
303
+ } catch (e) {
304
+ debug.warn('Pick cross toolchain failed', e as Error)
305
+ // ignore, do nothing
306
+ }
307
+ return this
308
+ }
309
+
151
310
  private exec() {
152
311
  debug(`Start building crate: ${this.crate.name}`)
153
312
  debug(' %i', `cargo ${this.args.join(' ')}`)
@@ -156,6 +315,11 @@ class Builder {
156
315
 
157
316
  const watch = this.options.watch
158
317
  const buildTask = new Promise<void>((resolve, reject) => {
318
+ if (this.options.useCross && this.options.crossCompile) {
319
+ throw new Error(
320
+ '`--use-cross` and `--cross-compile` can not be used together',
321
+ )
322
+ }
159
323
  const command =
160
324
  process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo')
161
325
  const buildProcess = spawn(command, this.args, {
@@ -189,7 +353,7 @@ class Builder {
189
353
  buildProcess.stderr?.on('data', (data) => {
190
354
  const output = data.toString()
191
355
  console.error(output)
192
- if (/Finished\s(dev|release)/.test(output)) {
356
+ if (/Finished\s(`dev`|`release`)/.test(output)) {
193
357
  this.postBuild().catch(() => {})
194
358
  }
195
359
  })
@@ -315,11 +479,74 @@ class Builder {
315
479
  private setEnvs() {
316
480
  // type definition intermediate file
317
481
  this.envs.TYPE_DEF_TMP_PATH = this.getIntermediateTypeFile()
318
- // WASI register intermediate file
319
- this.envs.WASI_REGISTER_TMP_PATH = this.getIntermediateWasiRegisterFile()
320
- // TODO:
321
- // remove after napi-derive@v3 release
322
- this.envs.CARGO_CFG_NAPI_RS_CLI_VERSION = CLI_VERSION
482
+ // Validate the packages and dependencies
483
+ if (existsSync(this.envs.TYPE_DEF_TMP_PATH)) {
484
+ const { depsNapiPackages, crate } = this
485
+ let shouldInvalidateSelf = false
486
+ const content = readFileSync(this.envs.TYPE_DEF_TMP_PATH, 'utf-8')
487
+ const typedefRaw = content
488
+ .split('\n')
489
+ .filter((line) => line.trim().length)
490
+ .map((line) => {
491
+ const [pkgName, ...jsonContents] = split(line, ':')
492
+ return {
493
+ pkgName,
494
+ jsonContent: jsonContents.join(':'),
495
+ }
496
+ })
497
+ const groupedTypedefRaw = groupBy(typedefRaw, ({ pkgName }) => pkgName)
498
+ const invalidPackages = new Set<string>()
499
+ for (const [pkgName, value] of Object.entries(groupedTypedefRaw)) {
500
+ const packageInvalidKey = `NAPI_PACKAGE_${pkgName.toUpperCase().replaceAll('-', '_')}_INVALID`
501
+ let done = false
502
+ for (const { jsonContent } of value) {
503
+ try {
504
+ // package_name: { "done": true } was written by napi-build in project build.rs
505
+ // if it exists, the package was successfully built and typedef was written
506
+ const json = JSON.parse(jsonContent)
507
+ if (json.done === true) {
508
+ done = true
509
+ break
510
+ }
511
+ } catch {
512
+ done = false
513
+ break
514
+ }
515
+ }
516
+ if (!done) {
517
+ process.env[packageInvalidKey] = `${Date.now()}`
518
+ shouldInvalidateSelf = true
519
+ invalidPackages.add(pkgName)
520
+ }
521
+ }
522
+ const typedefPackages = new Set(Object.keys(groupedTypedefRaw))
523
+ for (const crate of depsNapiPackages) {
524
+ if (!typedefPackages.has(crate.name)) {
525
+ debug('Set invalid package typedef: %i', crate.name)
526
+ shouldInvalidateSelf = true
527
+ process.env[
528
+ `NAPI_PACKAGE_${crate.name.toUpperCase().replaceAll('-', '_')}_INVALID`
529
+ ] = `${Date.now()}`
530
+ }
531
+ }
532
+ if (shouldInvalidateSelf) {
533
+ debug('Set invalid package typedef: %i', crate.name)
534
+ process.env[
535
+ `NAPI_PACKAGE_${crate.name.toUpperCase().replaceAll('-', '_')}_INVALID`
536
+ ] = `${Date.now()}`
537
+ }
538
+ // clean invalid packages typedef
539
+ if (invalidPackages.size) {
540
+ debug('Clean invalid packages typedef: %O', invalidPackages)
541
+ writeFileSync(
542
+ this.envs.TYPE_DEF_TMP_PATH,
543
+ typedefRaw
544
+ .filter(({ pkgName }) => !invalidPackages.has(pkgName))
545
+ .map(({ pkgName, jsonContent }) => `${pkgName}:${jsonContent}`)
546
+ .join('\n'),
547
+ )
548
+ }
549
+ }
323
550
 
324
551
  // RUSTFLAGS
325
552
  let rustflags =
@@ -352,7 +579,7 @@ class Builder {
352
579
  const linkerEnv = `CARGO_TARGET_${targetToEnvVar(
353
580
  this.target.triple,
354
581
  )}_LINKER`
355
- if (linker && !process.env[linkerEnv]) {
582
+ if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) {
356
583
  this.envs[linkerEnv] = linker
357
584
  }
358
585
 
@@ -373,15 +600,15 @@ class Builder {
373
600
  process.platform === 'darwin'
374
601
  ? 'darwin'
375
602
  : process.platform === 'win32'
376
- ? 'windows'
377
- : 'linux'
603
+ ? 'windows'
604
+ : 'linux'
378
605
  Object.assign(this.envs, {
379
606
  CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
380
607
  CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
381
- CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
382
- CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
383
- AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
384
- RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
608
+ TARGET_CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
609
+ TARGET_CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
610
+ TARGET_AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
611
+ TARGET_RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
385
612
  ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
386
613
  PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin:${process.env.PATH}`,
387
614
  })
@@ -396,6 +623,47 @@ class Builder {
396
623
  'wasm32-wasi-threads',
397
624
  )
398
625
  this.envs.EMNAPI_LINK_DIR = emnapi
626
+ this.envs.SETJMP_LINK_DIR = setjmpLib
627
+ const { WASI_SDK_PATH } = process.env
628
+
629
+ if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
630
+ this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(
631
+ WASI_SDK_PATH,
632
+ 'bin',
633
+ 'wasm-ld',
634
+ )
635
+ this.envs.CARGO_TARGET_WASM32_WASIP1_LINKER = join(
636
+ WASI_SDK_PATH,
637
+ 'bin',
638
+ 'wasm-ld',
639
+ )
640
+ this.envs.CARGO_TARGET_WASM32_WASIP1_THREADS_LINKER = join(
641
+ WASI_SDK_PATH,
642
+ 'bin',
643
+ 'wasm-ld',
644
+ )
645
+ this.envs.CARGO_TARGET_WASM32_WASIP2_LINKER = join(
646
+ WASI_SDK_PATH,
647
+ 'bin',
648
+ 'wasm-ld',
649
+ )
650
+ this.setEnvIfNotExists('CC', join(WASI_SDK_PATH, 'bin', 'clang'))
651
+ this.setEnvIfNotExists('CXX', join(WASI_SDK_PATH, 'bin', 'clang++'))
652
+ this.setEnvIfNotExists('AR', join(WASI_SDK_PATH, 'bin', 'ar'))
653
+ this.setEnvIfNotExists('RANLIB', join(WASI_SDK_PATH, 'bin', 'ranlib'))
654
+ this.setEnvIfNotExists(
655
+ 'CFLAGS',
656
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
657
+ )
658
+ this.setEnvIfNotExists(
659
+ 'CXXFLAGS',
660
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
661
+ )
662
+ this.setEnvIfNotExists(
663
+ `LDFLAGS`,
664
+ `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`,
665
+ )
666
+ }
399
667
  }
400
668
 
401
669
  debug('Set envs: ')
@@ -408,11 +676,17 @@ class Builder {
408
676
 
409
677
  private setFeatures() {
410
678
  const args = []
679
+ if (this.options.allFeatures && this.options.noDefaultFeatures) {
680
+ throw new Error(
681
+ 'Cannot specify --all-features and --no-default-features together',
682
+ )
683
+ }
411
684
  if (this.options.allFeatures) {
412
685
  args.push('--all-features')
413
686
  } else if (this.options.noDefaultFeatures) {
414
687
  args.push('--no-default-features')
415
- } else if (this.options.features) {
688
+ }
689
+ if (this.options.features) {
416
690
  args.push('--features', ...this.options.features)
417
691
  }
418
692
 
@@ -440,6 +714,10 @@ class Builder {
440
714
  this.args.push('--profile', this.options.profile)
441
715
  }
442
716
 
717
+ if (this.options.manifestPath) {
718
+ this.args.push('--manifest-path', this.options.manifestPath)
719
+ }
720
+
443
721
  if (this.options.cargoOptions?.length) {
444
722
  this.args.push(...this.options.cargoOptions)
445
723
  }
@@ -448,25 +726,21 @@ class Builder {
448
726
  }
449
727
 
450
728
  private getIntermediateTypeFile() {
451
- return join(
452
- tmpdir(),
453
- `${this.crate.name}-${createHash('sha256')
454
- .update(this.crate.manifest_path)
455
- .update(CLI_VERSION)
456
- .digest('hex')
457
- .substring(0, 8)}.napi_type_def.tmp`,
458
- )
459
- }
460
-
461
- private getIntermediateWasiRegisterFile() {
462
- return join(
729
+ const dtsPath = join(
463
730
  tmpdir(),
464
731
  `${this.crate.name}-${createHash('sha256')
465
732
  .update(this.crate.manifest_path)
466
733
  .update(CLI_VERSION)
467
734
  .digest('hex')
468
- .substring(0, 8)}.napi_wasi_register.tmp`,
735
+ .substring(0, 8)}.napi_type_def`,
469
736
  )
737
+ if (!this.options.dtsCache) {
738
+ try {
739
+ unlinkSync(dtsPath)
740
+ } catch {}
741
+ return `${dtsPath}_${Date.now()}.tmp`
742
+ }
743
+ return `${dtsPath}.tmp`
470
744
  }
471
745
 
472
746
  private async postBuild() {
@@ -481,38 +755,21 @@ class Builder {
481
755
  })
482
756
  }
483
757
 
484
- const dest = await this.copyArtifact()
758
+ const wasmBinaryName = await this.copyArtifact()
485
759
 
486
760
  // only for cdylib
487
761
  if (this.cdyLibName) {
488
762
  const idents = await this.generateTypeDef()
489
- const intermediateWasiRegisterFile = this.envs.WASI_REGISTER_TMP_PATH
490
- const wasiRegisterFunctions =
491
- this.target.arch === 'wasm32'
492
- ? JSON.parse(
493
- await readFileAsync(intermediateWasiRegisterFile, 'utf8').catch(
494
- (err) => {
495
- console.warn(
496
- `Read ${colors.yellowBright(
497
- intermediateWasiRegisterFile,
498
- )} failed, reason: ${err.message}`,
499
- )
500
- return `[]`
501
- },
502
- ),
503
- )
504
- : []
505
763
  const jsOutput = await this.writeJsBinding(idents)
506
- const wasmOutput = await this.writeWasiBinding(
507
- wasiRegisterFunctions,
508
- dest ?? 'index.wasm',
764
+ const wasmBindingsOutput = await this.writeWasiBinding(
765
+ wasmBinaryName,
509
766
  idents,
510
767
  )
511
768
  if (jsOutput) {
512
769
  this.outputs.push(jsOutput)
513
770
  }
514
- if (wasmOutput) {
515
- this.outputs.push(wasmOutput)
771
+ if (wasmBindingsOutput) {
772
+ this.outputs.push(...wasmBindingsOutput)
516
773
  }
517
774
  }
518
775
 
@@ -520,7 +777,7 @@ class Builder {
520
777
  }
521
778
 
522
779
  private async copyArtifact() {
523
- const [srcName, destName] = this.getArtifactNames()
780
+ const [srcName, destName, wasmBinaryName] = this.getArtifactNames()
524
781
  if (!srcName || !destName) {
525
782
  return
526
783
  }
@@ -530,6 +787,7 @@ class Builder {
530
787
  const src = join(this.targetDir, this.target.triple, profile, srcName)
531
788
  debug(`Copy artifact from: [${src}]`)
532
789
  const dest = join(this.outputDir, destName)
790
+ const isWasm = dest.endsWith('.wasm')
533
791
 
534
792
  try {
535
793
  if (await fileExists(dest)) {
@@ -538,12 +796,47 @@ class Builder {
538
796
  }
539
797
  debug('Copy artifact to:')
540
798
  debug(' %i', dest)
541
- await copyFileAsync(src, dest)
799
+ if (isWasm) {
800
+ const { ModuleConfig } = await import('@napi-rs/wasm-tools')
801
+ debug('Generate debug wasm module')
802
+ try {
803
+ const debugWasmModule = new ModuleConfig()
804
+ .generateDwarf(true)
805
+ .generateNameSection(true)
806
+ .generateProducersSection(true)
807
+ .preserveCodeTransform(true)
808
+ .strictValidate(false)
809
+ .parse(await readFileAsync(src))
810
+ const debugWasmBinary = debugWasmModule.emitWasm(true)
811
+ await writeFileAsync(
812
+ dest.replace(/\.wasm$/, '.debug.wasm'),
813
+ debugWasmBinary,
814
+ )
815
+ debug('Generate release wasm module')
816
+ const releaseWasmModule = new ModuleConfig()
817
+ .generateDwarf(false)
818
+ .generateNameSection(false)
819
+ .generateProducersSection(false)
820
+ .preserveCodeTransform(false)
821
+ .strictValidate(false)
822
+ .onlyStableFeatures(false)
823
+ .parse(debugWasmBinary)
824
+ const releaseWasmBinary = releaseWasmModule.emitWasm(false)
825
+ await writeFileAsync(dest, releaseWasmBinary)
826
+ } catch (e) {
827
+ debug.warn(
828
+ `Failed to generate debug wasm module: ${(e as any).message ?? e}`,
829
+ )
830
+ await copyFileAsync(src, dest)
831
+ }
832
+ } else {
833
+ await copyFileAsync(src, dest)
834
+ }
542
835
  this.outputs.push({
543
- kind: dest.endsWith('.node') ? 'node' : 'exe',
836
+ kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
544
837
  path: dest,
545
838
  })
546
- return dest
839
+ return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
547
840
  } catch (e) {
548
841
  throw new Error('Failed to copy artifact', {
549
842
  cause: e,
@@ -554,15 +847,16 @@ class Builder {
554
847
  private getArtifactNames() {
555
848
  if (this.cdyLibName) {
556
849
  const cdyLib = this.cdyLibName.replace(/-/g, '_')
850
+ const wasiTarget = this.config.targets.find((t) => t.platform === 'wasi')
557
851
 
558
852
  const srcName =
559
853
  this.target.platform === 'darwin'
560
854
  ? `lib${cdyLib}.dylib`
561
855
  : this.target.platform === 'win32'
562
- ? `${cdyLib}.dll`
563
- : this.target.platform === 'wasi' || this.target.platform === 'wasm'
564
- ? `${cdyLib}.wasm`
565
- : `lib${cdyLib}.so`
856
+ ? `${cdyLib}.dll`
857
+ : this.target.platform === 'wasi' || this.target.platform === 'wasm'
858
+ ? `${cdyLib}.wasm`
859
+ : `lib${cdyLib}.so`
566
860
 
567
861
  let destName = this.config.binaryName
568
862
  // add platform suffix to binary name
@@ -572,12 +866,18 @@ class Builder {
572
866
  destName += `.${this.target.platformArchABI}`
573
867
  }
574
868
  if (srcName.endsWith('.wasm')) {
575
- destName += '.wasi-wasm32.wasm'
869
+ destName += '.wasm'
576
870
  } else {
577
871
  destName += '.node'
578
872
  }
579
873
 
580
- return [srcName, destName]
874
+ return [
875
+ srcName,
876
+ destName,
877
+ wasiTarget
878
+ ? `${this.config.binaryName}.${wasiTarget.platformArchABI}.wasm`
879
+ : null,
880
+ ]
581
881
  } else if (this.binName) {
582
882
  const srcName =
583
883
  this.target.platform === 'win32' ? `${this.binName}.exe` : this.binName
@@ -597,8 +897,22 @@ class Builder {
597
897
 
598
898
  const { dts, exports } = await processTypeDef(
599
899
  this.envs.TYPE_DEF_TMP_PATH,
900
+ this.options.constEnum ?? this.config.constEnum ?? true,
600
901
  !this.options.noDtsHeader
601
- ? this.options.dtsHeader ?? DEFAULT_TYPE_DEF_HEADER
902
+ ? (this.options.dtsHeader ??
903
+ (this.config.dtsHeaderFile
904
+ ? await readFileAsync(
905
+ join(this.cwd, this.config.dtsHeaderFile),
906
+ 'utf-8',
907
+ ).catch(() => {
908
+ debug.warn(
909
+ `Failed to read dts header file ${this.config.dtsHeaderFile}`,
910
+ )
911
+ return null
912
+ })
913
+ : null) ??
914
+ this.config.dtsHeader ??
915
+ DEFAULT_TYPE_DEF_HEADER)
602
916
  : '',
603
917
  )
604
918
 
@@ -628,19 +942,20 @@ class Builder {
628
942
  return
629
943
  }
630
944
 
631
- const name = parse(this.options.jsBinding ?? 'index.js').name
945
+ const name = this.options.jsBinding ?? 'index.js'
632
946
 
633
- const cjs = createCjsBinding(
947
+ const createBinding = this.options.esm ? createEsmBinding : createCjsBinding
948
+ const binding = createBinding(
634
949
  this.config.binaryName,
635
950
  this.config.packageName,
636
951
  idents,
637
952
  )
638
953
 
639
954
  try {
640
- const dest = join(this.outputDir, `${name}.js`)
955
+ const dest = join(this.outputDir, name)
641
956
  debug('Writing js binding to:')
642
957
  debug(' %i', dest)
643
- await writeFileAsync(dest, cjs, 'utf-8')
958
+ await writeFileAsync(dest, binding, 'utf-8')
644
959
  return {
645
960
  kind: 'js',
646
961
  path: dest,
@@ -651,32 +966,97 @@ class Builder {
651
966
  }
652
967
 
653
968
  private async writeWasiBinding(
654
- wasiRegisterFunctions: string[],
655
- distFileName: string | undefined,
969
+ distFileName: string | undefined | null,
656
970
  idents: string[],
657
971
  ) {
658
- if (distFileName && wasiRegisterFunctions.length) {
972
+ if (distFileName) {
659
973
  const { name, dir } = parse(distFileName)
660
- const newPath = join(dir, `${this.config.binaryName}.wasi.mjs`)
974
+ const bindingPath = join(dir, `${this.config.binaryName}.wasi.cjs`)
975
+ const browserBindingPath = join(
976
+ dir,
977
+ `${this.config.binaryName}.wasi-browser.js`,
978
+ )
661
979
  const workerPath = join(dir, 'wasi-worker.mjs')
662
- const declareCodes = `const { ${idents.join(', ')} } = binding\n`
663
- const exportsCode = `export {\n${idents
664
- .map((ident) => ` ${ident}`)
665
- .join(',\n')}\n}`
980
+ const browserWorkerPath = join(dir, 'wasi-worker-browser.mjs')
981
+ const browserEntryPath = join(dir, 'browser.js')
982
+ const exportsCode =
983
+ `module.exports = __napiModule.exports\n` +
984
+ idents
985
+ .map(
986
+ (ident) =>
987
+ `module.exports.${ident} = __napiModule.exports.${ident}`,
988
+ )
989
+ .join('\n')
666
990
  await writeFileAsync(
667
- newPath,
668
- createWasiBinding(name, wasiRegisterFunctions) +
669
- declareCodes +
991
+ bindingPath,
992
+ createWasiBinding(
993
+ name,
994
+ this.config.packageName,
995
+ this.config.wasm?.initialMemory,
996
+ this.config.wasm?.maximumMemory,
997
+ ) +
670
998
  exportsCode +
671
999
  '\n',
672
1000
  'utf8',
673
1001
  )
1002
+ await writeFileAsync(
1003
+ browserBindingPath,
1004
+ createWasiBrowserBinding(
1005
+ name,
1006
+ this.config.wasm?.initialMemory,
1007
+ this.config.wasm?.maximumMemory,
1008
+ this.config.wasm?.browser?.fs,
1009
+ this.config.wasm?.browser?.asyncInit,
1010
+ ) +
1011
+ `export default __napiModule.exports\n` +
1012
+ idents
1013
+ .map(
1014
+ (ident) =>
1015
+ `export const ${ident} = __napiModule.exports.${ident}`,
1016
+ )
1017
+ .join('\n') +
1018
+ '\n',
1019
+ 'utf8',
1020
+ )
674
1021
  await writeFileAsync(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
675
- return {
676
- kind: 'wasm',
677
- path: newPath,
678
- } satisfies Output
1022
+ await writeFileAsync(
1023
+ browserWorkerPath,
1024
+ createWasiBrowserWorkerBinding(this.config.wasm?.browser?.fs ?? false),
1025
+ 'utf8',
1026
+ )
1027
+ await writeFileAsync(
1028
+ browserEntryPath,
1029
+ `export * from '${this.config.packageName}-wasm32-wasi'\n`,
1030
+ )
1031
+ return [
1032
+ {
1033
+ kind: 'js',
1034
+ path: bindingPath,
1035
+ },
1036
+ {
1037
+ kind: 'js',
1038
+ path: browserBindingPath,
1039
+ },
1040
+ {
1041
+ kind: 'js',
1042
+ path: workerPath,
1043
+ },
1044
+ {
1045
+ kind: 'js',
1046
+ path: browserWorkerPath,
1047
+ },
1048
+ {
1049
+ kind: 'js',
1050
+ path: browserEntryPath,
1051
+ },
1052
+ ] satisfies Output[]
1053
+ }
1054
+ return []
1055
+ }
1056
+
1057
+ private setEnvIfNotExists(env: string, value: string) {
1058
+ if (!process.env[env]) {
1059
+ this.envs[env] = value
679
1060
  }
680
- return null
681
1061
  }
682
1062
  }