@napi-rs/cli 3.0.0-alpha.1 → 3.0.0-alpha.100

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 (176) hide show
  1. package/cli.mjs +1 -1
  2. package/dist/api/__tests__/create-npm-dirs.spec.d.ts +1 -0
  3. package/dist/api/__tests__/new.spec.d.ts +1 -0
  4. package/dist/api/build.d.ts +2 -2
  5. package/dist/api/create-npm-dirs.d.ts +5 -0
  6. package/dist/api/templates/index.d.ts +0 -7
  7. package/dist/api/templates/js-binding.d.ts +2 -1
  8. package/dist/api/templates/load-wasi-template.d.ts +2 -0
  9. package/dist/api/templates/wasi-worker-template.d.ts +2 -0
  10. package/dist/cli.js +3961 -14
  11. package/dist/def/artifacts.d.ts +21 -1
  12. package/dist/def/build.d.ts +167 -4
  13. package/dist/def/create-npm-dirs.d.ts +10 -0
  14. package/dist/def/new.d.ts +25 -7
  15. package/dist/def/pre-publish.d.ts +21 -2
  16. package/dist/def/rename.d.ts +16 -6
  17. package/dist/def/universalize.d.ts +10 -0
  18. package/dist/def/version.d.ts +10 -0
  19. package/dist/index.cjs +10142 -20524
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +3711 -29
  22. package/dist/utils/__tests__/config.spec.d.ts +1 -0
  23. package/dist/utils/config.d.ts +70 -6
  24. package/dist/utils/index.d.ts +1 -1
  25. package/dist/utils/metadata.d.ts +13 -1
  26. package/dist/utils/misc.d.ts +3 -3
  27. package/dist/utils/read-config.d.ts +7 -0
  28. package/dist/utils/target.d.ts +9 -6
  29. package/dist/utils/typegen.d.ts +4 -1
  30. package/dist/utils/version.d.ts +2 -1
  31. package/package.json +60 -24
  32. package/src/api/__tests__/create-npm-dirs.spec.ts +210 -0
  33. package/src/api/__tests__/new.spec.ts +429 -0
  34. package/src/api/artifacts.ts +83 -8
  35. package/src/api/build.ts +682 -134
  36. package/src/api/create-npm-dirs.ts +64 -13
  37. package/src/api/new.ts +389 -125
  38. package/src/api/pre-publish.ts +27 -16
  39. package/src/api/rename.ts +103 -17
  40. package/src/api/templates/index.ts +0 -7
  41. package/src/api/templates/js-binding.ts +212 -107
  42. package/src/api/templates/load-wasi-template.ts +216 -0
  43. package/src/api/templates/wasi-worker-template.ts +122 -0
  44. package/src/api/universalize.ts +13 -8
  45. package/src/api/version.ts +5 -2
  46. package/src/commands/artifacts.ts +2 -2
  47. package/src/commands/build.ts +1 -1
  48. package/src/commands/new.ts +46 -49
  49. package/src/def/artifacts.ts +22 -3
  50. package/src/def/build.ts +83 -8
  51. package/src/def/create-npm-dirs.ts +9 -0
  52. package/src/def/new.ts +28 -3
  53. package/src/def/pre-publish.ts +24 -3
  54. package/src/def/rename.ts +11 -2
  55. package/src/def/universalize.ts +9 -0
  56. package/src/def/version.ts +9 -0
  57. package/src/index.ts +2 -0
  58. package/src/utils/__tests__/__fixtures__/napi_type_def +130 -130
  59. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  60. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  61. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +56 -0
  62. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  63. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +703 -164
  64. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  65. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +9 -8
  66. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  67. package/src/utils/__tests__/config.spec.ts +81 -0
  68. package/src/utils/__tests__/typegen.spec.ts +19 -4
  69. package/src/utils/cargo.ts +2 -2
  70. package/src/utils/config.ts +147 -13
  71. package/src/utils/index.ts +1 -1
  72. package/src/utils/log.ts +1 -1
  73. package/src/utils/metadata.ts +52 -14
  74. package/src/utils/misc.ts +21 -7
  75. package/src/utils/read-config.ts +20 -0
  76. package/src/utils/target.ts +53 -9
  77. package/src/utils/typegen.ts +100 -40
  78. package/src/utils/version.ts +11 -9
  79. package/dist/api/artifacts.js +0 -50
  80. package/dist/api/build.js +0 -404
  81. package/dist/api/create-npm-dirs.js +0 -63
  82. package/dist/api/new.js +0 -154
  83. package/dist/api/pre-publish.js +0 -157
  84. package/dist/api/rename.js +0 -28
  85. package/dist/api/templates/.gitignore.d.ts +0 -1
  86. package/dist/api/templates/.gitignore.js +0 -198
  87. package/dist/api/templates/.npmignore.d.ts +0 -1
  88. package/dist/api/templates/.npmignore.js +0 -14
  89. package/dist/api/templates/build.rs.d.ts +0 -1
  90. package/dist/api/templates/build.rs.js +0 -4
  91. package/dist/api/templates/cargo.toml.d.ts +0 -6
  92. package/dist/api/templates/cargo.toml.js +0 -25
  93. package/dist/api/templates/ci-template.d.ts +0 -1
  94. package/dist/api/templates/ci-template.js +0 -566
  95. package/dist/api/templates/ci.yml.d.ts +0 -1
  96. package/dist/api/templates/ci.yml.js +0 -92
  97. package/dist/api/templates/index.js +0 -8
  98. package/dist/api/templates/js-binding.js +0 -124
  99. package/dist/api/templates/lib.rs.d.ts +0 -1
  100. package/dist/api/templates/lib.rs.js +0 -10
  101. package/dist/api/templates/package.json.d.ts +0 -8
  102. package/dist/api/templates/package.json.js +0 -30
  103. package/dist/api/universalize.js +0 -38
  104. package/dist/api/version.js +0 -16
  105. package/dist/commands/artifacts.js +0 -19
  106. package/dist/commands/build.js +0 -34
  107. package/dist/commands/create-npm-dirs.js +0 -7
  108. package/dist/commands/help.js +0 -12
  109. package/dist/commands/new.js +0 -112
  110. package/dist/commands/pre-publish.js +0 -8
  111. package/dist/commands/rename.js +0 -7
  112. package/dist/commands/universalize.js +0 -7
  113. package/dist/commands/version.js +0 -7
  114. package/dist/def/artifacts.js +0 -38
  115. package/dist/def/build.js +0 -105
  116. package/dist/def/commands/artifacts.d.ts +0 -15
  117. package/dist/def/commands/artifacts.js +0 -29
  118. package/dist/def/commands/build.d.ts +0 -53
  119. package/dist/def/commands/build.js +0 -105
  120. package/dist/def/commands/create-npm-dirs.d.ts +0 -15
  121. package/dist/def/commands/create-npm-dirs.js +0 -29
  122. package/dist/def/commands/new.d.ts +0 -27
  123. package/dist/def/commands/new.js +0 -53
  124. package/dist/def/commands/pre-publish.d.ts +0 -23
  125. package/dist/def/commands/pre-publish.js +0 -45
  126. package/dist/def/commands/rename.d.ts +0 -25
  127. package/dist/def/commands/rename.js +0 -49
  128. package/dist/def/commands/universalize.d.ts +0 -13
  129. package/dist/def/commands/universalize.js +0 -25
  130. package/dist/def/commands/version.d.ts +0 -13
  131. package/dist/def/commands/version.js +0 -25
  132. package/dist/def/create-npm-dirs.js +0 -38
  133. package/dist/def/new.js +0 -66
  134. package/dist/def/options/artifacts.d.ts +0 -35
  135. package/dist/def/options/artifacts.js +0 -11
  136. package/dist/def/options/build.d.ts +0 -97
  137. package/dist/def/options/build.js +0 -3
  138. package/dist/def/options/create-npm-dirs.d.ts +0 -35
  139. package/dist/def/options/create-npm-dirs.js +0 -11
  140. package/dist/def/options/new.d.ts +0 -79
  141. package/dist/def/options/new.js +0 -15
  142. package/dist/def/options/pre-publish.d.ts +0 -65
  143. package/dist/def/options/pre-publish.js +0 -13
  144. package/dist/def/options/rename.d.ts +0 -75
  145. package/dist/def/options/rename.js +0 -11
  146. package/dist/def/options/universalize.d.ts +0 -28
  147. package/dist/def/options/universalize.js +0 -10
  148. package/dist/def/options/version.d.ts +0 -28
  149. package/dist/def/options/version.js +0 -10
  150. package/dist/def/pre-publish.js +0 -56
  151. package/dist/def/rename.js +0 -58
  152. package/dist/def/universalize.js +0 -33
  153. package/dist/def/version.js +0 -33
  154. package/dist/utils/__tests__/target.spec.js +0 -10
  155. package/dist/utils/__tests__/typegen.spec.js +0 -37
  156. package/dist/utils/__tests__/version.spec.js +0 -5
  157. package/dist/utils/cargo.js +0 -33
  158. package/dist/utils/ci.d.ts +0 -11
  159. package/dist/utils/ci.js +0 -86
  160. package/dist/utils/config.js +0 -42
  161. package/dist/utils/index.js +0 -9
  162. package/dist/utils/log.js +0 -14
  163. package/dist/utils/metadata.js +0 -19
  164. package/dist/utils/misc.js +0 -35
  165. package/dist/utils/target.js +0 -109
  166. package/dist/utils/typegen.js +0 -158
  167. package/dist/utils/version.js +0 -59
  168. package/src/api/templates/.gitignore.ts +0 -198
  169. package/src/api/templates/.npmignore.ts +0 -14
  170. package/src/api/templates/build.rs.ts +0 -4
  171. package/src/api/templates/cargo.toml.ts +0 -35
  172. package/src/api/templates/ci-template.ts +0 -566
  173. package/src/api/templates/ci.yml.ts +0 -116
  174. package/src/api/templates/lib.rs.ts +0 -10
  175. package/src/api/templates/package.json.ts +0 -44
  176. package/src/utils/ci.ts +0 -102
package/src/api/build.ts CHANGED
@@ -1,9 +1,12 @@
1
- import { spawn } from 'child_process'
2
- import { createHash } from 'crypto'
3
- import { tmpdir } from 'os'
4
- import { parse, join, resolve } from 'path'
1
+ import { spawn } from 'node:child_process'
2
+ import { createHash } from 'node:crypto'
3
+ import { existsSync, mkdirSync, rmSync } from 'node:fs'
4
+ import { createRequire } from 'node:module'
5
+ import { homedir } from 'node:os'
6
+ import { parse, join, resolve } from 'node:path'
5
7
 
6
8
  import * as colors from 'colorette'
9
+ import { include as setjmpInclude, lib as setjmpLib } from 'wasm-sjlj'
7
10
 
8
11
  import { BuildOptions as RawBuildOptions } from '../def/build.js'
9
12
  import {
@@ -20,39 +23,52 @@ import {
20
23
  parseMetadata,
21
24
  parseTriple,
22
25
  processTypeDef,
26
+ readFileAsync,
23
27
  readNapiConfig,
24
28
  Target,
25
29
  targetToEnvVar,
26
30
  tryInstallCargoBinary,
27
31
  unlinkAsync,
28
32
  writeFileAsync,
33
+ dirExistsAsync,
34
+ readdirAsync,
35
+ CargoWorkspaceMetadata,
29
36
  } from '../utils/index.js'
30
37
 
31
- import { createJsBinding } from './templates/index.js'
38
+ import { createCjsBinding, createEsmBinding } from './templates/index.js'
39
+ import {
40
+ createWasiBinding,
41
+ createWasiBrowserBinding,
42
+ } from './templates/load-wasi-template.js'
43
+ import {
44
+ createWasiBrowserWorkerBinding,
45
+ WASI_WORKER_TEMPLATE,
46
+ } from './templates/wasi-worker-template.js'
32
47
 
33
48
  const debug = debugFactory('build')
49
+ const require = createRequire(import.meta.url)
34
50
 
35
- type OutputKind = 'js' | 'dts' | 'node' | 'exe'
36
- type Output = {
37
- kind: OutputKind
38
- path: string
39
- }
51
+ type OutputKind = 'js' | 'dts' | 'node' | 'exe' | 'wasm'
52
+ type Output = { kind: OutputKind; path: string }
40
53
 
41
- type BuildOptions = RawBuildOptions & {
42
- cargoOptions?: string[]
43
- }
54
+ type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
55
+ type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
44
56
 
45
- export async function buildProject(options: BuildOptions) {
46
- debug('napi build command receive options: %O', options)
57
+ export async function buildProject(rawOptions: BuildOptions) {
58
+ debug('napi build command receive options: %O', rawOptions)
47
59
 
48
- const cwd = options.cwd ?? process.cwd()
60
+ const options: ParsedBuildOptions = {
61
+ dtsCache: true,
62
+ ...rawOptions,
63
+ cwd: rawOptions.cwd ?? process.cwd(),
64
+ }
49
65
 
50
- const resolvePath = (...paths: string[]) => resolve(cwd, ...paths)
66
+ const resolvePath = (...paths: string[]) => resolve(options.cwd, ...paths)
51
67
 
52
68
  const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
53
- const metadata = parseMetadata(manifestPath)
69
+ const metadata = await parseMetadata(manifestPath)
54
70
 
55
- const pkg = metadata.packages.find((p) => {
71
+ const crate = metadata.packages.find((p) => {
56
72
  // package with given name
57
73
  if (options.package) {
58
74
  return p.name === options.package
@@ -61,33 +77,20 @@ export async function buildProject(options: BuildOptions) {
61
77
  }
62
78
  })
63
79
 
64
- if (!pkg) {
80
+ if (!crate) {
65
81
  throw new Error(
66
82
  'Unable to find crate to build. It seems you are trying to build a crate in a workspace, try using `--package` option to specify the package to build.',
67
83
  )
68
84
  }
69
-
70
- const crateDir = parse(pkg.manifest_path).dir
71
-
72
- const builder = new Builder(
73
- options,
74
- pkg,
75
- cwd,
76
- options.target
77
- ? parseTriple(options.target)
78
- : process.env.CARGO_BUILD_TARGET
79
- ? parseTriple(process.env.CARGO_BUILD_TARGET)
80
- : getSystemDefaultTarget(),
81
- crateDir,
82
- resolvePath(options.outputDir ?? crateDir),
83
- options.targetDir ??
84
- process.env.CARGO_BUILD_TARGET_DIR ??
85
- metadata.target_directory,
86
- await readNapiConfig(
87
- resolvePath(options.packageJsonPath ?? 'package.json'),
85
+ const config = await readNapiConfig(
86
+ resolvePath(
87
+ options.configPath ?? options.packageJsonPath ?? 'package.json',
88
88
  ),
89
+ options.configPath ? resolvePath(options.configPath) : undefined,
89
90
  )
90
91
 
92
+ const builder = new Builder(metadata, crate, config, options)
93
+
91
94
  return builder.build()
92
95
  }
93
96
 
@@ -96,16 +99,57 @@ class Builder {
96
99
  private readonly envs: Record<string, string> = {}
97
100
  private readonly outputs: Output[] = []
98
101
 
102
+ private readonly target: Target
103
+ private readonly crateDir: string
104
+ private readonly outputDir: string
105
+ private readonly targetDir: string
106
+ private readonly enableTypeDef: boolean = false
107
+
99
108
  constructor(
100
- private readonly options: BuildOptions,
109
+ private readonly metadata: CargoWorkspaceMetadata,
101
110
  private readonly crate: Crate,
102
- private readonly cwd: string,
103
- private readonly target: Target,
104
- private readonly crateDir: string,
105
- private readonly outputDir: string,
106
- private readonly targetDir: string,
107
111
  private readonly config: NapiConfig,
108
- ) {}
112
+ private readonly options: ParsedBuildOptions,
113
+ ) {
114
+ this.target = options.target
115
+ ? parseTriple(options.target)
116
+ : process.env.CARGO_BUILD_TARGET
117
+ ? parseTriple(process.env.CARGO_BUILD_TARGET)
118
+ : getSystemDefaultTarget()
119
+ this.crateDir = parse(crate.manifest_path).dir
120
+ this.outputDir = resolve(
121
+ this.options.cwd,
122
+ options.outputDir ?? this.crateDir,
123
+ )
124
+ this.targetDir =
125
+ options.targetDir ??
126
+ process.env.CARGO_BUILD_TARGET_DIR ??
127
+ metadata.target_directory
128
+ this.enableTypeDef = this.crate.dependencies.some(
129
+ (dep) =>
130
+ dep.name === 'napi-derive' &&
131
+ (dep.uses_default_features || dep.features.includes('type-def')),
132
+ )
133
+
134
+ if (!this.enableTypeDef) {
135
+ const requirementWarning =
136
+ '`napi-derive` crate is not used or `type-def` feature is not enabled for `napi-derive` crate'
137
+ debug.warn(
138
+ `${requirementWarning}. Will skip binding generation for \`.node\`, \`.wasi\` and \`.d.ts\` files.`,
139
+ )
140
+
141
+ if (
142
+ this.options.dts ||
143
+ this.options.dtsHeader ||
144
+ this.config.dtsHeader ||
145
+ this.config.dtsHeaderFile
146
+ ) {
147
+ debug.warn(
148
+ `${requirementWarning}. \`dts\` related options are enabled but will be ignored.`,
149
+ )
150
+ }
151
+ }
152
+ }
109
153
 
110
154
  get cdyLibName() {
111
155
  return this.crate.targets.find((t) => t.crate_types.includes('cdylib'))
@@ -138,25 +182,151 @@ class Builder {
138
182
  .setPackage()
139
183
  .setFeatures()
140
184
  .setTarget()
185
+ .pickCrossToolchain()
141
186
  .setEnvs()
142
187
  .setBypassArgs()
143
188
  .exec()
144
189
  }
145
190
 
191
+ private pickCrossToolchain() {
192
+ if (!this.options.useNapiCross) {
193
+ return this
194
+ }
195
+ if (this.options.useCross) {
196
+ debug.warn(
197
+ 'You are trying to use both `--cross` and `--use-napi-cross` options, `--use-cross` will be ignored.',
198
+ )
199
+ }
200
+
201
+ if (this.options.crossCompile) {
202
+ debug.warn(
203
+ 'You are trying to use both `--cross-compile` and `--use-napi-cross` options, `--cross-compile` will be ignored.',
204
+ )
205
+ }
206
+
207
+ try {
208
+ const { version, download } = require('@napi-rs/cross-toolchain')
209
+
210
+ const alias: Record<string, string> = {
211
+ 's390x-unknown-linux-gnu': 's390x-ibm-linux-gnu',
212
+ }
213
+
214
+ const toolchainPath = join(
215
+ homedir(),
216
+ '.napi-rs',
217
+ 'cross-toolchain',
218
+ version,
219
+ this.target.triple,
220
+ )
221
+ mkdirSync(toolchainPath, { recursive: true })
222
+ if (existsSync(join(toolchainPath, 'package.json'))) {
223
+ debug(`Toolchain ${toolchainPath} exists, skip extracting`)
224
+ } else {
225
+ const tarArchive = download(process.arch, this.target.triple)
226
+ tarArchive.unpack(toolchainPath)
227
+ }
228
+ const upperCaseTarget = targetToEnvVar(this.target.triple)
229
+ const crossTargetName = alias[this.target.triple] ?? this.target.triple
230
+ const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`
231
+ this.envs[linkerEnv] = join(
232
+ toolchainPath,
233
+ 'bin',
234
+ `${crossTargetName}-gcc`,
235
+ )
236
+ if (!process.env.TARGET_SYSROOT) {
237
+ this.envs[`TARGET_SYSROOT`] = join(
238
+ toolchainPath,
239
+ crossTargetName,
240
+ 'sysroot',
241
+ )
242
+ }
243
+ if (!process.env.TARGET_AR) {
244
+ this.envs[`TARGET_AR`] = join(
245
+ toolchainPath,
246
+ 'bin',
247
+ `${crossTargetName}-ar`,
248
+ )
249
+ }
250
+ if (!process.env.TARGET_RANLIB) {
251
+ this.envs[`TARGET_RANLIB`] = join(
252
+ toolchainPath,
253
+ 'bin',
254
+ `${crossTargetName}-ranlib`,
255
+ )
256
+ }
257
+ if (!process.env.TARGET_READELF) {
258
+ this.envs[`TARGET_READELF`] = join(
259
+ toolchainPath,
260
+ 'bin',
261
+ `${crossTargetName}-readelf`,
262
+ )
263
+ }
264
+ if (!process.env.TARGET_C_INCLUDE_PATH) {
265
+ this.envs[`TARGET_C_INCLUDE_PATH`] = join(
266
+ toolchainPath,
267
+ crossTargetName,
268
+ 'sysroot',
269
+ 'usr',
270
+ 'include/',
271
+ )
272
+ }
273
+ if (!process.env.TARGET_CC) {
274
+ this.envs[`TARGET_CC`] = join(
275
+ toolchainPath,
276
+ 'bin',
277
+ `${crossTargetName}-gcc`,
278
+ )
279
+ }
280
+ if (!process.env.TARGET_CXX) {
281
+ this.envs[`TARGET_CXX`] = join(
282
+ toolchainPath,
283
+ 'bin',
284
+ `${crossTargetName}-g++`,
285
+ )
286
+ }
287
+ if (
288
+ process.env.TARGET_CC?.startsWith('clang') ||
289
+ (process.env.CC?.startsWith('clang') && !process.env.TARGET_CC)
290
+ ) {
291
+ const TARGET_CFLAGS = process.env.TARGET_CFLAGS ?? ''
292
+ this.envs.TARGET_CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CFLAGS}`
293
+ }
294
+ if (
295
+ (process.env.CXX?.startsWith('clang++') && !process.env.TARGET_CXX) ||
296
+ process.env.TARGET_CXX?.startsWith('clang++')
297
+ ) {
298
+ const TARGET_CXXFLAGS = process.env.TARGET_CXXFLAGS ?? ''
299
+ this.envs.TARGET_CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CXXFLAGS}`
300
+ }
301
+ this.envs.PATH = this.envs.PATH
302
+ ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}`
303
+ : `${toolchainPath}/bin:${process.env.PATH}`
304
+ } catch (e) {
305
+ debug.warn('Pick cross toolchain failed', e as Error)
306
+ // ignore, do nothing
307
+ }
308
+ return this
309
+ }
310
+
146
311
  private exec() {
147
312
  debug(`Start building crate: ${this.crate.name}`)
148
313
  debug(' %i', `cargo ${this.args.join(' ')}`)
149
314
 
150
315
  const controller = new AbortController()
151
316
 
317
+ const watch = this.options.watch
152
318
  const buildTask = new Promise<void>((resolve, reject) => {
153
- const buildProcess = spawn('cargo', this.args, {
154
- env: {
155
- ...process.env,
156
- ...this.envs,
157
- },
158
- stdio: 'inherit',
159
- cwd: this.cwd,
319
+ if (this.options.useCross && this.options.crossCompile) {
320
+ throw new Error(
321
+ '`--use-cross` and `--cross-compile` can not be used together',
322
+ )
323
+ }
324
+ const command =
325
+ process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo')
326
+ const buildProcess = spawn(command, this.args, {
327
+ env: { ...process.env, ...this.envs },
328
+ stdio: watch ? ['inherit', 'inherit', 'pipe'] : 'inherit',
329
+ cwd: this.options.cwd,
160
330
  signal: controller.signal,
161
331
  })
162
332
 
@@ -170,11 +340,16 @@ class Builder {
170
340
  })
171
341
 
172
342
  buildProcess.once('error', (e) => {
173
- reject(
174
- new Error(`Build failed with error: ${e.message}`, {
175
- cause: e,
176
- }),
177
- )
343
+ reject(new Error(`Build failed with error: ${e.message}`, { cause: e }))
344
+ })
345
+
346
+ // watch mode only, they are piped through stderr
347
+ buildProcess.stderr?.on('data', (data) => {
348
+ const output = data.toString()
349
+ console.error(output)
350
+ if (/Finished\s(`dev`|`release`)/.test(output)) {
351
+ this.postBuild().catch(() => {})
352
+ }
178
353
  })
179
354
  })
180
355
 
@@ -205,6 +380,7 @@ class Builder {
205
380
  this.crateDir,
206
381
  '--',
207
382
  'cargo',
383
+ 'build',
208
384
  )
209
385
  set = true
210
386
  }
@@ -295,9 +471,12 @@ class Builder {
295
471
  }
296
472
 
297
473
  private setEnvs() {
298
- // type definition intermediate file
299
- this.envs.TYPE_DEF_TMP_PATH = this.getIntermediateTypeFile()
300
- this.envs.CARGO_CFG_NAPI_RS_CLI_VERSION = CLI_VERSION
474
+ // TYPE DEF
475
+ if (this.enableTypeDef) {
476
+ this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
477
+ this.generateIntermediateTypeDefFolder()
478
+ this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
479
+ }
301
480
 
302
481
  // RUSTFLAGS
303
482
  let rustflags =
@@ -320,39 +499,31 @@ class Builder {
320
499
  // END RUSTFLAGS
321
500
 
322
501
  // LINKER
323
- const linker = getTargetLinker(this.target.triple)
324
- if (
325
- linker &&
326
- !process.env.RUSTC_LINKER &&
327
- !process.env[`CARGET_TARGET_${targetToEnvVar(this.target.triple)}_LINKER`]
328
- ) {
329
- this.envs.RUSTC_LINKER = linker
502
+ const linker = this.options.crossCompile
503
+ ? void 0
504
+ : getTargetLinker(this.target.triple)
505
+ // TODO:
506
+ // directly set CARGO_TARGET_<target>_LINKER will cover .cargo/config.toml
507
+ // will detect by cargo config when it becomes stable
508
+ // see: https://github.com/rust-lang/cargo/issues/9301
509
+ const linkerEnv = `CARGO_TARGET_${targetToEnvVar(
510
+ this.target.triple,
511
+ )}_LINKER`
512
+ if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) {
513
+ this.envs[linkerEnv] = linker
330
514
  }
331
515
 
332
516
  if (this.target.platform === 'android') {
333
- const { ANDROID_NDK_LATEST_HOME } = process.env
334
- if (!ANDROID_NDK_LATEST_HOME) {
335
- debug.warn(
336
- `${colors.red(
337
- 'ANDROID_NDK_LATEST_HOME',
338
- )} environment variable is missing`,
339
- )
340
- }
517
+ this.setAndroidEnv()
518
+ }
341
519
 
342
- const targetArch = this.target.arch === 'arm' ? 'armv7a' : 'aarch64'
343
- const targetPlatform =
344
- this.target.arch === 'arm' ? 'androideabi24' : 'android24'
345
- Object.assign(this.envs, {
346
- CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-android24-clang`,
347
- CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
348
- CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
349
- CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
350
- AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`,
351
- ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
352
- PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${process.env.PATH}`,
353
- })
520
+ if (this.target.platform === 'wasi') {
521
+ this.setWasiEnv()
522
+ }
523
+
524
+ if (this.target.platform === 'openharmony') {
525
+ this.setOpenHarmonyEnv()
354
526
  }
355
- // END LINKER
356
527
 
357
528
  debug('Set envs: ')
358
529
  Object.entries(this.envs).forEach(([k, v]) => {
@@ -362,13 +533,168 @@ class Builder {
362
533
  return this
363
534
  }
364
535
 
536
+ private setForceBuildEnvs(typeDefTmpFolder: string) {
537
+ // dynamically check all napi-rs deps and set `NAPI_FORCE_BUILD_{uppercase(snake_case(name))} = timestamp`
538
+ this.metadata.packages.forEach((crate) => {
539
+ if (
540
+ crate.dependencies.some((d) => d.name === 'napi-derive') &&
541
+ !existsSync(join(typeDefTmpFolder, crate.name))
542
+ ) {
543
+ this.envs[
544
+ `NAPI_FORCE_BUILD_${crate.name.replace(/-/g, '_').toUpperCase()}`
545
+ ] = Date.now().toString()
546
+ }
547
+ })
548
+ }
549
+
550
+ private setAndroidEnv() {
551
+ const { ANDROID_NDK_LATEST_HOME } = process.env
552
+ if (!ANDROID_NDK_LATEST_HOME) {
553
+ debug.warn(
554
+ `${colors.red(
555
+ 'ANDROID_NDK_LATEST_HOME',
556
+ )} environment variable is missing`,
557
+ )
558
+ }
559
+
560
+ // skip cross compile setup if host is android
561
+ if (process.platform === 'android') {
562
+ return
563
+ }
564
+
565
+ const targetArch = this.target.arch === 'arm' ? 'armv7a' : 'aarch64'
566
+ const targetPlatform =
567
+ this.target.arch === 'arm' ? 'androideabi24' : 'android24'
568
+ const hostPlatform =
569
+ process.platform === 'darwin'
570
+ ? 'darwin'
571
+ : process.platform === 'win32'
572
+ ? 'windows'
573
+ : 'linux'
574
+ Object.assign(this.envs, {
575
+ CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
576
+ CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
577
+ TARGET_CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
578
+ TARGET_CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
579
+ TARGET_AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
580
+ TARGET_RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
581
+ ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
582
+ PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin${process.platform === 'win32' ? ';' : ':'}${process.env.PATH}`,
583
+ })
584
+ }
585
+
586
+ private setWasiEnv() {
587
+ const emnapi = join(
588
+ require.resolve('emnapi'),
589
+ '..',
590
+ 'lib',
591
+ 'wasm32-wasi-threads',
592
+ )
593
+ this.envs.EMNAPI_LINK_DIR = emnapi
594
+ this.envs.SETJMP_LINK_DIR = setjmpLib
595
+ const { WASI_SDK_PATH } = process.env
596
+
597
+ if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
598
+ this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(
599
+ WASI_SDK_PATH,
600
+ 'bin',
601
+ 'wasm-ld',
602
+ )
603
+ this.envs.CARGO_TARGET_WASM32_WASIP1_LINKER = join(
604
+ WASI_SDK_PATH,
605
+ 'bin',
606
+ 'wasm-ld',
607
+ )
608
+ this.envs.CARGO_TARGET_WASM32_WASIP1_THREADS_LINKER = join(
609
+ WASI_SDK_PATH,
610
+ 'bin',
611
+ 'wasm-ld',
612
+ )
613
+ this.envs.CARGO_TARGET_WASM32_WASIP2_LINKER = join(
614
+ WASI_SDK_PATH,
615
+ 'bin',
616
+ 'wasm-ld',
617
+ )
618
+ this.setEnvIfNotExists('TARGET_CC', join(WASI_SDK_PATH, 'bin', 'clang'))
619
+ this.setEnvIfNotExists(
620
+ 'TARGET_CXX',
621
+ join(WASI_SDK_PATH, 'bin', 'clang++'),
622
+ )
623
+ this.setEnvIfNotExists('TARGET_AR', join(WASI_SDK_PATH, 'bin', 'ar'))
624
+ this.setEnvIfNotExists(
625
+ 'TARGET_RANLIB',
626
+ join(WASI_SDK_PATH, 'bin', 'ranlib'),
627
+ )
628
+ this.setEnvIfNotExists(
629
+ 'TARGET_CFLAGS',
630
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
631
+ )
632
+ this.setEnvIfNotExists(
633
+ 'TARGET_CXXFLAGS',
634
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
635
+ )
636
+ this.setEnvIfNotExists(
637
+ `TARGET_LDFLAGS`,
638
+ `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`,
639
+ )
640
+ }
641
+ }
642
+
643
+ private setOpenHarmonyEnv() {
644
+ const { OHOS_SDK_PATH, OHOS_SDK_NATIVE } = process.env
645
+ const ndkPath = OHOS_SDK_PATH
646
+ ? `${OHOS_SDK_NATIVE}/native`
647
+ : OHOS_SDK_NATIVE
648
+ // @ts-expect-error
649
+ if (!ndkPath && process.platform !== 'openharmony') {
650
+ debug.warn(
651
+ `${colors.red('OHOS_SDK_PATH')} or ${colors.red('OHOS_SDK_NATIVE')} environment variable is missing`,
652
+ )
653
+ return
654
+ }
655
+ const linkerName = `CARGO_TARGET_${this.target.triple.toUpperCase().replace(/-/g, '_')}_LINKER`
656
+ const ranPath = `${ndkPath}/llvm/bin/llvm-ranlib`
657
+ const arPath = `${ndkPath}/llvm/bin/llvm-ar`
658
+ const ccPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang`
659
+ const cxxPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang++`
660
+ const asPath = `${ndkPath}/llvm/bin/llvm-as`
661
+ const ldPath = `${ndkPath}/llvm/bin/ld.lld`
662
+ const stripPath = `${ndkPath}/llvm/bin/llvm-strip`
663
+ const objDumpPath = `${ndkPath}/llvm/bin/llvm-objdump`
664
+ const objCopyPath = `${ndkPath}/llvm/bin/llvm-objcopy`
665
+ const nmPath = `${ndkPath}/llvm/bin/llvm-nm`
666
+ const binPath = `${ndkPath}/llvm/bin`
667
+ const libPath = `${ndkPath}/llvm/lib`
668
+
669
+ this.setEnvIfNotExists('LIBCLANG_PATH', libPath)
670
+ this.setEnvIfNotExists('DEP_ATOMIC', 'clang_rt.builtins')
671
+ this.setEnvIfNotExists(linkerName, ccPath)
672
+ this.setEnvIfNotExists('TARGET_CC', ccPath)
673
+ this.setEnvIfNotExists('TARGET_CXX', cxxPath)
674
+ this.setEnvIfNotExists('TARGET_AR', arPath)
675
+ this.setEnvIfNotExists('TARGET_RANLIB', ranPath)
676
+ this.setEnvIfNotExists('TARGET_AS', asPath)
677
+ this.setEnvIfNotExists('TARGET_LD', ldPath)
678
+ this.setEnvIfNotExists('TARGET_STRIP', stripPath)
679
+ this.setEnvIfNotExists('TARGET_OBJDUMP', objDumpPath)
680
+ this.setEnvIfNotExists('TARGET_OBJCOPY', objCopyPath)
681
+ this.setEnvIfNotExists('TARGET_NM', nmPath)
682
+ this.envs.PATH = `${binPath}${process.platform === 'win32' ? ';' : ':'}${process.env.PATH}`
683
+ }
684
+
365
685
  private setFeatures() {
366
686
  const args = []
687
+ if (this.options.allFeatures && this.options.noDefaultFeatures) {
688
+ throw new Error(
689
+ 'Cannot specify --all-features and --no-default-features together',
690
+ )
691
+ }
367
692
  if (this.options.allFeatures) {
368
693
  args.push('--all-features')
369
694
  } else if (this.options.noDefaultFeatures) {
370
695
  args.push('--no-default-features')
371
- } else if (this.options.features) {
696
+ }
697
+ if (this.options.features) {
372
698
  args.push('--features', ...this.options.features)
373
699
  }
374
700
 
@@ -392,6 +718,14 @@ class Builder {
392
718
  this.args.push('--target-dir', this.options.targetDir)
393
719
  }
394
720
 
721
+ if (this.options.profile) {
722
+ this.args.push('--profile', this.options.profile)
723
+ }
724
+
725
+ if (this.options.manifestPath) {
726
+ this.args.push('--manifest-path', this.options.manifestPath)
727
+ }
728
+
395
729
  if (this.options.cargoOptions?.length) {
396
730
  this.args.push(...this.options.cargoOptions)
397
731
  }
@@ -399,15 +733,25 @@ class Builder {
399
733
  return this
400
734
  }
401
735
 
402
- private getIntermediateTypeFile() {
403
- return join(
404
- tmpdir(),
736
+ private generateIntermediateTypeDefFolder() {
737
+ let folder = join(
738
+ this.targetDir,
739
+ 'napi-rs',
405
740
  `${this.crate.name}-${createHash('sha256')
406
741
  .update(this.crate.manifest_path)
407
742
  .update(CLI_VERSION)
408
743
  .digest('hex')
409
- .substring(0, 8)}.napi_type_def.tmp`,
744
+ .substring(0, 8)}`,
410
745
  )
746
+
747
+ if (!this.options.dtsCache) {
748
+ rmSync(folder, { recursive: true, force: true })
749
+ folder += `_${Date.now()}`
750
+ }
751
+
752
+ mkdirAsync(folder, { recursive: true })
753
+
754
+ return folder
411
755
  }
412
756
 
413
757
  private async postBuild() {
@@ -422,30 +766,39 @@ class Builder {
422
766
  })
423
767
  }
424
768
 
425
- await this.copyArtifact()
769
+ const wasmBinaryName = await this.copyArtifact()
426
770
 
427
771
  // only for cdylib
428
772
  if (this.cdyLibName) {
429
- await this.generateTypeDef()
430
- await this.writeJsBinding()
773
+ const idents = await this.generateTypeDef()
774
+ const jsOutput = await this.writeJsBinding(idents)
775
+ const wasmBindingsOutput = await this.writeWasiBinding(
776
+ wasmBinaryName,
777
+ idents,
778
+ )
779
+ if (jsOutput) {
780
+ this.outputs.push(jsOutput)
781
+ }
782
+ if (wasmBindingsOutput) {
783
+ this.outputs.push(...wasmBindingsOutput)
784
+ }
431
785
  }
432
786
 
433
787
  return this.outputs
434
788
  }
435
789
 
436
790
  private async copyArtifact() {
437
- const [srcName, destName] = this.getArtifactNames()
791
+ const [srcName, destName, wasmBinaryName] = this.getArtifactNames()
438
792
  if (!srcName || !destName) {
439
793
  return
440
794
  }
441
795
 
442
- const src = join(
443
- this.targetDir,
444
- this.target.triple,
445
- this.options.release ? 'release' : 'debug',
446
- srcName,
447
- )
796
+ const profile =
797
+ this.options.profile ?? (this.options.release ? 'release' : 'debug')
798
+ const src = join(this.targetDir, this.target.triple, profile, srcName)
799
+ debug(`Copy artifact from: [${src}]`)
448
800
  const dest = join(this.outputDir, destName)
801
+ const isWasm = dest.endsWith('.wasm')
449
802
 
450
803
  try {
451
804
  if (await fileExists(dest)) {
@@ -454,28 +807,65 @@ class Builder {
454
807
  }
455
808
  debug('Copy artifact to:')
456
809
  debug(' %i', dest)
457
- await copyFileAsync(src, dest)
810
+ if (isWasm) {
811
+ const { ModuleConfig } = await import('@napi-rs/wasm-tools')
812
+ debug('Generate debug wasm module')
813
+ try {
814
+ const debugWasmModule = new ModuleConfig()
815
+ .generateDwarf(true)
816
+ .generateNameSection(true)
817
+ .generateProducersSection(true)
818
+ .preserveCodeTransform(true)
819
+ .strictValidate(false)
820
+ .parse(await readFileAsync(src))
821
+ const debugWasmBinary = debugWasmModule.emitWasm(true)
822
+ await writeFileAsync(
823
+ dest.replace(/\.wasm$/, '.debug.wasm'),
824
+ debugWasmBinary,
825
+ )
826
+ debug('Generate release wasm module')
827
+ const releaseWasmModule = new ModuleConfig()
828
+ .generateDwarf(false)
829
+ .generateNameSection(false)
830
+ .generateProducersSection(false)
831
+ .preserveCodeTransform(false)
832
+ .strictValidate(false)
833
+ .onlyStableFeatures(false)
834
+ .parse(debugWasmBinary)
835
+ const releaseWasmBinary = releaseWasmModule.emitWasm(false)
836
+ await writeFileAsync(dest, releaseWasmBinary)
837
+ } catch (e) {
838
+ debug.warn(
839
+ `Failed to generate debug wasm module: ${(e as any).message ?? e}`,
840
+ )
841
+ await copyFileAsync(src, dest)
842
+ }
843
+ } else {
844
+ await copyFileAsync(src, dest)
845
+ }
458
846
  this.outputs.push({
459
- kind: dest.endsWith('.node') ? 'node' : 'exe',
847
+ kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
460
848
  path: dest,
461
849
  })
850
+ return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
462
851
  } catch (e) {
463
- throw new Error('Failed to copy artifact', {
464
- cause: e,
465
- })
852
+ throw new Error('Failed to copy artifact', { cause: e })
466
853
  }
467
854
  }
468
855
 
469
856
  private getArtifactNames() {
470
857
  if (this.cdyLibName) {
471
858
  const cdyLib = this.cdyLibName.replace(/-/g, '_')
859
+ const wasiTarget = this.config.targets.find((t) => t.platform === 'wasi')
472
860
 
473
861
  const srcName =
474
862
  this.target.platform === 'darwin'
475
863
  ? `lib${cdyLib}.dylib`
476
864
  : this.target.platform === 'win32'
477
- ? `${cdyLib}.dll`
478
- : `lib${cdyLib}.so`
865
+ ? `${cdyLib}.dll`
866
+ : this.target.platform === 'wasi' || this.target.platform === 'wasm'
867
+ ? `${cdyLib}.wasm`
868
+ : `lib${cdyLib}.so`
479
869
 
480
870
  let destName = this.config.binaryName
481
871
  // add platform suffix to binary name
@@ -484,9 +874,19 @@ class Builder {
484
874
  if (this.options.platform) {
485
875
  destName += `.${this.target.platformArchABI}`
486
876
  }
487
- destName += '.node'
877
+ if (srcName.endsWith('.wasm')) {
878
+ destName += '.wasm'
879
+ } else {
880
+ destName += '.node'
881
+ }
488
882
 
489
- return [srcName, destName]
883
+ return [
884
+ srcName,
885
+ destName,
886
+ wasiTarget
887
+ ? `${this.config.binaryName}.${wasiTarget.platformArchABI}.wasm`
888
+ : null,
889
+ ]
490
890
  } else if (this.binName) {
491
891
  const srcName =
492
892
  this.target.platform === 'win32' ? `${this.binName}.exe` : this.binName
@@ -498,52 +898,200 @@ class Builder {
498
898
  }
499
899
 
500
900
  private async generateTypeDef() {
501
- if (!(await fileExists(this.envs.TYPE_DEF_TMP_PATH))) {
502
- return
901
+ const typeDefDir = this.envs.NAPI_TYPE_DEF_TMP_FOLDER
902
+ if (!this.enableTypeDef || !(await dirExistsAsync(typeDefDir))) {
903
+ return []
503
904
  }
504
905
 
505
906
  const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
506
907
 
507
- const dts = await processTypeDef(
508
- this.envs.TYPE_DEF_TMP_PATH,
509
- !this.options.noDtsHeader
510
- ? this.options.dtsHeader ?? DEFAULT_TYPE_DEF_HEADER
511
- : '',
512
- )
908
+ let header = ''
909
+ let dts = ''
910
+ let exports: string[] = []
911
+
912
+ if (!this.options.noDtsHeader) {
913
+ const dtsHeader = this.options.dtsHeader ?? this.config.dtsHeader
914
+ // `dtsHeaderFile` in config > `dtsHeader` in cli flag > `dtsHeader` in config
915
+ if (this.config.dtsHeaderFile) {
916
+ try {
917
+ header = await readFileAsync(
918
+ join(this.options.cwd, this.config.dtsHeaderFile),
919
+ 'utf-8',
920
+ )
921
+ } catch (e) {
922
+ debug.warn(
923
+ `Failed to read dts header file ${this.config.dtsHeaderFile}`,
924
+ e,
925
+ )
926
+ }
927
+ } else if (dtsHeader) {
928
+ header = dtsHeader
929
+ } else {
930
+ header = DEFAULT_TYPE_DEF_HEADER
931
+ }
932
+ }
933
+
934
+ const files = await readdirAsync(typeDefDir, { withFileTypes: true })
935
+
936
+ if (!files.length) {
937
+ debug('No type def files found. Skip generating dts file.')
938
+ return []
939
+ }
940
+
941
+ for (const file of files) {
942
+ if (!file.isFile()) {
943
+ continue
944
+ }
945
+
946
+ const { dts: fileDts, exports: fileExports } = await processTypeDef(
947
+ join(typeDefDir, file.name),
948
+ this.options.constEnum ?? this.config.constEnum ?? true,
949
+ )
950
+
951
+ dts += fileDts
952
+ exports.push(...fileExports)
953
+ }
954
+
955
+ if (dts.indexOf('ExternalObject<') > -1) {
956
+ header += `
957
+ export declare class ExternalObject<T> {
958
+ readonly '': {
959
+ readonly '': unique symbol
960
+ [K: symbol]: T
961
+ }
962
+ }
963
+ `
964
+ }
965
+
966
+ if (dts.indexOf('TypedArray') > -1) {
967
+ header += `
968
+ export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array
969
+ `
970
+ }
971
+
972
+ dts = header + dts
513
973
 
514
974
  try {
515
975
  debug('Writing type def to:')
516
976
  debug(' %i', dest)
517
977
  await writeFileAsync(dest, dts, 'utf-8')
518
- this.outputs.push({
519
- kind: 'dts',
520
- path: dest,
521
- })
978
+ this.outputs.push({ kind: 'dts', path: dest })
522
979
  } catch (e) {
523
980
  debug.error('Failed to write type def file')
524
981
  debug.error(e as Error)
525
982
  }
983
+
984
+ return exports
526
985
  }
527
986
 
528
- private async writeJsBinding() {
529
- if (!this.options.platform || this.options.noJsBinding) {
987
+ private async writeJsBinding(idents: string[]) {
988
+ if (
989
+ !this.options.platform ||
990
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
991
+ this.options.noJsBinding ||
992
+ idents.length === 0
993
+ ) {
530
994
  return
531
995
  }
532
996
 
533
- const dest = join(this.outputDir, this.options.jsBinding ?? 'index.js')
997
+ const name = this.options.jsBinding ?? 'index.js'
534
998
 
535
- const js = createJsBinding(this.config.binaryName, this.config.packageName)
999
+ const createBinding = this.options.esm ? createEsmBinding : createCjsBinding
1000
+ const binding = createBinding(
1001
+ this.config.binaryName,
1002
+ this.config.packageName,
1003
+ idents,
1004
+ )
536
1005
 
537
1006
  try {
1007
+ const dest = join(this.outputDir, name)
538
1008
  debug('Writing js binding to:')
539
1009
  debug(' %i', dest)
540
- await writeFileAsync(dest, js, 'utf-8')
541
- this.outputs.push({
542
- kind: 'js',
543
- path: dest,
544
- })
1010
+ await writeFileAsync(dest, binding, 'utf-8')
1011
+ return { kind: 'js', path: dest } satisfies Output
545
1012
  } catch (e) {
546
1013
  throw new Error('Failed to write js binding file', { cause: e })
547
1014
  }
548
1015
  }
1016
+
1017
+ private async writeWasiBinding(
1018
+ distFileName: string | undefined | null,
1019
+ idents: string[],
1020
+ ) {
1021
+ if (distFileName) {
1022
+ const { name, dir } = parse(distFileName)
1023
+ const bindingPath = join(dir, `${this.config.binaryName}.wasi.cjs`)
1024
+ const browserBindingPath = join(
1025
+ dir,
1026
+ `${this.config.binaryName}.wasi-browser.js`,
1027
+ )
1028
+ const workerPath = join(dir, 'wasi-worker.mjs')
1029
+ const browserWorkerPath = join(dir, 'wasi-worker-browser.mjs')
1030
+ const browserEntryPath = join(dir, 'browser.js')
1031
+ const exportsCode =
1032
+ `module.exports = __napiModule.exports\n` +
1033
+ idents
1034
+ .map(
1035
+ (ident) =>
1036
+ `module.exports.${ident} = __napiModule.exports.${ident}`,
1037
+ )
1038
+ .join('\n')
1039
+ await writeFileAsync(
1040
+ bindingPath,
1041
+ createWasiBinding(
1042
+ name,
1043
+ this.config.packageName,
1044
+ this.config.wasm?.initialMemory,
1045
+ this.config.wasm?.maximumMemory,
1046
+ ) +
1047
+ exportsCode +
1048
+ '\n',
1049
+ 'utf8',
1050
+ )
1051
+ await writeFileAsync(
1052
+ browserBindingPath,
1053
+ createWasiBrowserBinding(
1054
+ name,
1055
+ this.config.wasm?.initialMemory,
1056
+ this.config.wasm?.maximumMemory,
1057
+ this.config.wasm?.browser?.fs,
1058
+ this.config.wasm?.browser?.asyncInit,
1059
+ this.config.wasm?.browser?.buffer,
1060
+ ) +
1061
+ `export default __napiModule.exports\n` +
1062
+ idents
1063
+ .map(
1064
+ (ident) =>
1065
+ `export const ${ident} = __napiModule.exports.${ident}`,
1066
+ )
1067
+ .join('\n') +
1068
+ '\n',
1069
+ 'utf8',
1070
+ )
1071
+ await writeFileAsync(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
1072
+ await writeFileAsync(
1073
+ browserWorkerPath,
1074
+ createWasiBrowserWorkerBinding(this.config.wasm?.browser?.fs ?? false),
1075
+ 'utf8',
1076
+ )
1077
+ await writeFileAsync(
1078
+ browserEntryPath,
1079
+ `export * from '${this.config.packageName}-wasm32-wasi'\n`,
1080
+ )
1081
+ return [
1082
+ { kind: 'js', path: bindingPath },
1083
+ { kind: 'js', path: browserBindingPath },
1084
+ { kind: 'js', path: workerPath },
1085
+ { kind: 'js', path: browserWorkerPath },
1086
+ { kind: 'js', path: browserEntryPath },
1087
+ ] satisfies Output[]
1088
+ }
1089
+ return []
1090
+ }
1091
+
1092
+ private setEnvIfNotExists(env: string, value: string) {
1093
+ if (!process.env[env]) {
1094
+ this.envs[env] = value
1095
+ }
1096
+ }
549
1097
  }