@napi-rs/cli 3.0.0-alpha.10 → 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 (199) 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 +1 -1
  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 +1 -0
  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 -15
  11. package/dist/def/artifacts.d.ts +21 -1
  12. package/dist/def/build.d.ts +149 -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 +10052 -15662
  20. package/dist/index.js +3712 -32
  21. package/dist/utils/__tests__/config.spec.d.ts +1 -0
  22. package/dist/utils/config.d.ts +56 -4
  23. package/dist/utils/index.d.ts +1 -1
  24. package/dist/utils/metadata.d.ts +13 -1
  25. package/dist/utils/misc.d.ts +3 -3
  26. package/dist/utils/read-config.d.ts +7 -0
  27. package/dist/utils/target.d.ts +8 -6
  28. package/dist/utils/typegen.d.ts +1 -1
  29. package/dist/utils/version.d.ts +2 -1
  30. package/package.json +38 -34
  31. package/src/api/__tests__/create-npm-dirs.spec.ts +210 -0
  32. package/src/api/__tests__/new.spec.ts +429 -0
  33. package/src/api/artifacts.ts +81 -9
  34. package/src/api/build.ts +595 -183
  35. package/src/api/create-npm-dirs.ts +64 -13
  36. package/src/api/new.ts +389 -122
  37. package/src/api/pre-publish.ts +19 -11
  38. package/src/api/rename.ts +103 -17
  39. package/src/api/templates/index.ts +0 -7
  40. package/src/api/templates/js-binding.ts +159 -220
  41. package/src/api/templates/load-wasi-template.ts +216 -0
  42. package/src/api/templates/wasi-worker-template.ts +122 -0
  43. package/src/api/universalize.ts +13 -8
  44. package/src/api/version.ts +5 -2
  45. package/src/commands/artifacts.ts +2 -2
  46. package/src/commands/build.ts +1 -1
  47. package/src/commands/new.ts +46 -45
  48. package/src/def/artifacts.ts +22 -3
  49. package/src/def/build.ts +55 -8
  50. package/src/def/create-npm-dirs.ts +9 -0
  51. package/src/def/new.ts +28 -3
  52. package/src/def/pre-publish.ts +24 -3
  53. package/src/def/rename.ts +11 -2
  54. package/src/def/universalize.ts +9 -0
  55. package/src/def/version.ts +9 -0
  56. package/src/utils/__tests__/__fixtures__/napi_type_def +130 -130
  57. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  58. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  59. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +49 -0
  60. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  61. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +316 -332
  62. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  63. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +8 -7
  64. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  65. package/src/utils/__tests__/config.spec.ts +81 -0
  66. package/src/utils/cargo.ts +2 -2
  67. package/src/utils/config.ts +127 -9
  68. package/src/utils/index.ts +1 -1
  69. package/src/utils/log.ts +1 -1
  70. package/src/utils/metadata.ts +52 -14
  71. package/src/utils/misc.ts +21 -12
  72. package/src/utils/read-config.ts +20 -0
  73. package/src/utils/target.ts +37 -5
  74. package/src/utils/typegen.ts +87 -52
  75. package/src/utils/version.ts +10 -8
  76. package/dist/api/artifacts.js +0 -53
  77. package/dist/api/artifacts.js.map +0 -1
  78. package/dist/api/build.js +0 -498
  79. package/dist/api/build.js.map +0 -1
  80. package/dist/api/create-npm-dirs.js +0 -64
  81. package/dist/api/create-npm-dirs.js.map +0 -1
  82. package/dist/api/load-wasi-template.d.ts +0 -1
  83. package/dist/api/load-wasi-template.js +0 -60
  84. package/dist/api/load-wasi-template.js.map +0 -1
  85. package/dist/api/new.js +0 -155
  86. package/dist/api/new.js.map +0 -1
  87. package/dist/api/pre-publish.js +0 -158
  88. package/dist/api/pre-publish.js.map +0 -1
  89. package/dist/api/rename.js +0 -29
  90. package/dist/api/rename.js.map +0 -1
  91. package/dist/api/templates/.gitignore.d.ts +0 -1
  92. package/dist/api/templates/.gitignore.js +0 -200
  93. package/dist/api/templates/.gitignore.js.map +0 -1
  94. package/dist/api/templates/.npmignore.d.ts +0 -1
  95. package/dist/api/templates/.npmignore.js +0 -15
  96. package/dist/api/templates/.npmignore.js.map +0 -1
  97. package/dist/api/templates/build.rs.d.ts +0 -1
  98. package/dist/api/templates/build.rs.js +0 -5
  99. package/dist/api/templates/build.rs.js.map +0 -1
  100. package/dist/api/templates/cargo.toml.d.ts +0 -6
  101. package/dist/api/templates/cargo.toml.js +0 -26
  102. package/dist/api/templates/cargo.toml.js.map +0 -1
  103. package/dist/api/templates/ci-template.d.ts +0 -1
  104. package/dist/api/templates/ci-template.js +0 -578
  105. package/dist/api/templates/ci-template.js.map +0 -1
  106. package/dist/api/templates/ci.yml.d.ts +0 -1
  107. package/dist/api/templates/ci.yml.js +0 -99
  108. package/dist/api/templates/ci.yml.js.map +0 -1
  109. package/dist/api/templates/index.js +0 -9
  110. package/dist/api/templates/index.js.map +0 -1
  111. package/dist/api/templates/js-binding.js +0 -293
  112. package/dist/api/templates/js-binding.js.map +0 -1
  113. package/dist/api/templates/lib.rs.d.ts +0 -1
  114. package/dist/api/templates/lib.rs.js +0 -8
  115. package/dist/api/templates/lib.rs.js.map +0 -1
  116. package/dist/api/templates/package.json.d.ts +0 -8
  117. package/dist/api/templates/package.json.js +0 -32
  118. package/dist/api/templates/package.json.js.map +0 -1
  119. package/dist/api/universalize.js +0 -39
  120. package/dist/api/universalize.js.map +0 -1
  121. package/dist/api/version.js +0 -17
  122. package/dist/api/version.js.map +0 -1
  123. package/dist/api/wasi-worker-template.d.ts +0 -1
  124. package/dist/api/wasi-worker-template.js +0 -53
  125. package/dist/api/wasi-worker-template.js.map +0 -1
  126. package/dist/cli.js.map +0 -1
  127. package/dist/commands/artifacts.js +0 -20
  128. package/dist/commands/artifacts.js.map +0 -1
  129. package/dist/commands/build.js +0 -35
  130. package/dist/commands/build.js.map +0 -1
  131. package/dist/commands/create-npm-dirs.js +0 -8
  132. package/dist/commands/create-npm-dirs.js.map +0 -1
  133. package/dist/commands/help.js +0 -13
  134. package/dist/commands/help.js.map +0 -1
  135. package/dist/commands/new.js +0 -110
  136. package/dist/commands/new.js.map +0 -1
  137. package/dist/commands/pre-publish.js +0 -9
  138. package/dist/commands/pre-publish.js.map +0 -1
  139. package/dist/commands/rename.js +0 -8
  140. package/dist/commands/rename.js.map +0 -1
  141. package/dist/commands/universalize.js +0 -8
  142. package/dist/commands/universalize.js.map +0 -1
  143. package/dist/commands/version.js +0 -8
  144. package/dist/commands/version.js.map +0 -1
  145. package/dist/def/artifacts.js +0 -39
  146. package/dist/def/artifacts.js.map +0 -1
  147. package/dist/def/build.js +0 -118
  148. package/dist/def/build.js.map +0 -1
  149. package/dist/def/create-npm-dirs.js +0 -39
  150. package/dist/def/create-npm-dirs.js.map +0 -1
  151. package/dist/def/new.js +0 -67
  152. package/dist/def/new.js.map +0 -1
  153. package/dist/def/pre-publish.js +0 -57
  154. package/dist/def/pre-publish.js.map +0 -1
  155. package/dist/def/rename.js +0 -59
  156. package/dist/def/rename.js.map +0 -1
  157. package/dist/def/universalize.js +0 -34
  158. package/dist/def/universalize.js.map +0 -1
  159. package/dist/def/version.js +0 -34
  160. package/dist/def/version.js.map +0 -1
  161. package/dist/index.js.map +0 -1
  162. package/dist/utils/__tests__/target.spec.js +0 -11
  163. package/dist/utils/__tests__/target.spec.js.map +0 -1
  164. package/dist/utils/__tests__/typegen.spec.js +0 -42
  165. package/dist/utils/__tests__/typegen.spec.js.map +0 -1
  166. package/dist/utils/__tests__/version.spec.js +0 -6
  167. package/dist/utils/__tests__/version.spec.js.map +0 -1
  168. package/dist/utils/cargo.js +0 -34
  169. package/dist/utils/cargo.js.map +0 -1
  170. package/dist/utils/ci.d.ts +0 -11
  171. package/dist/utils/ci.js +0 -96
  172. package/dist/utils/ci.js.map +0 -1
  173. package/dist/utils/config.js +0 -43
  174. package/dist/utils/config.js.map +0 -1
  175. package/dist/utils/index.js +0 -10
  176. package/dist/utils/index.js.map +0 -1
  177. package/dist/utils/log.js +0 -15
  178. package/dist/utils/log.js.map +0 -1
  179. package/dist/utils/metadata.js +0 -20
  180. package/dist/utils/metadata.js.map +0 -1
  181. package/dist/utils/misc.js +0 -41
  182. package/dist/utils/misc.js.map +0 -1
  183. package/dist/utils/target.js +0 -121
  184. package/dist/utils/target.js.map +0 -1
  185. package/dist/utils/typegen.js +0 -179
  186. package/dist/utils/typegen.js.map +0 -1
  187. package/dist/utils/version.js +0 -60
  188. package/dist/utils/version.js.map +0 -1
  189. package/src/api/load-wasi-template.ts +0 -62
  190. package/src/api/templates/.gitignore.ts +0 -199
  191. package/src/api/templates/.npmignore.ts +0 -14
  192. package/src/api/templates/build.rs.ts +0 -4
  193. package/src/api/templates/cargo.toml.ts +0 -35
  194. package/src/api/templates/ci-template.ts +0 -577
  195. package/src/api/templates/ci.yml.ts +0 -119
  196. package/src/api/templates/lib.rs.ts +0 -7
  197. package/src/api/templates/package.json.ts +0 -48
  198. package/src/api/wasi-worker-template.ts +0 -52
  199. package/src/utils/ci.ts +0 -111
package/src/api/build.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { createHash } from 'node:crypto'
3
+ import { existsSync, mkdirSync, rmSync } from 'node:fs'
3
4
  import { createRequire } from 'node:module'
4
- import { tmpdir } from 'node:os'
5
+ import { homedir } from 'node:os'
5
6
  import { parse, join, resolve } from 'node:path'
6
7
 
7
8
  import * as colors from 'colorette'
9
+ import { include as setjmpInclude, lib as setjmpLib } from 'wasm-sjlj'
8
10
 
9
11
  import { BuildOptions as RawBuildOptions } from '../def/build.js'
10
12
  import {
@@ -28,36 +30,45 @@ import {
28
30
  tryInstallCargoBinary,
29
31
  unlinkAsync,
30
32
  writeFileAsync,
33
+ dirExistsAsync,
34
+ readdirAsync,
35
+ CargoWorkspaceMetadata,
31
36
  } from '../utils/index.js'
32
37
 
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'
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'
36
47
 
37
48
  const debug = debugFactory('build')
38
49
  const require = createRequire(import.meta.url)
39
50
 
40
51
  type OutputKind = 'js' | 'dts' | 'node' | 'exe' | 'wasm'
41
- type Output = {
42
- kind: OutputKind
43
- path: string
44
- }
52
+ type Output = { kind: OutputKind; path: string }
45
53
 
46
- type BuildOptions = RawBuildOptions & {
47
- cargoOptions?: string[]
48
- }
54
+ type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
55
+ type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
49
56
 
50
- export async function buildProject(options: BuildOptions) {
51
- debug('napi build command receive options: %O', options)
57
+ export async function buildProject(rawOptions: BuildOptions) {
58
+ debug('napi build command receive options: %O', rawOptions)
52
59
 
53
- const cwd = options.cwd ?? process.cwd()
60
+ const options: ParsedBuildOptions = {
61
+ dtsCache: true,
62
+ ...rawOptions,
63
+ cwd: rawOptions.cwd ?? process.cwd(),
64
+ }
54
65
 
55
- const resolvePath = (...paths: string[]) => resolve(cwd, ...paths)
66
+ const resolvePath = (...paths: string[]) => resolve(options.cwd, ...paths)
56
67
 
57
68
  const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
58
- const metadata = parseMetadata(manifestPath)
69
+ const metadata = await parseMetadata(manifestPath)
59
70
 
60
- const pkg = metadata.packages.find((p) => {
71
+ const crate = metadata.packages.find((p) => {
61
72
  // package with given name
62
73
  if (options.package) {
63
74
  return p.name === options.package
@@ -66,33 +77,20 @@ export async function buildProject(options: BuildOptions) {
66
77
  }
67
78
  })
68
79
 
69
- if (!pkg) {
80
+ if (!crate) {
70
81
  throw new Error(
71
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.',
72
83
  )
73
84
  }
74
-
75
- const crateDir = parse(pkg.manifest_path).dir
76
-
77
- const builder = new Builder(
78
- options,
79
- pkg,
80
- cwd,
81
- options.target
82
- ? parseTriple(options.target)
83
- : process.env.CARGO_BUILD_TARGET
84
- ? parseTriple(process.env.CARGO_BUILD_TARGET)
85
- : getSystemDefaultTarget(),
86
- crateDir,
87
- resolvePath(options.outputDir ?? crateDir),
88
- options.targetDir ??
89
- process.env.CARGO_BUILD_TARGET_DIR ??
90
- metadata.target_directory,
91
- await readNapiConfig(
92
- resolvePath(options.packageJsonPath ?? 'package.json'),
85
+ const config = await readNapiConfig(
86
+ resolvePath(
87
+ options.configPath ?? options.packageJsonPath ?? 'package.json',
93
88
  ),
89
+ options.configPath ? resolvePath(options.configPath) : undefined,
94
90
  )
95
91
 
92
+ const builder = new Builder(metadata, crate, config, options)
93
+
96
94
  return builder.build()
97
95
  }
98
96
 
@@ -101,16 +99,57 @@ class Builder {
101
99
  private readonly envs: Record<string, string> = {}
102
100
  private readonly outputs: Output[] = []
103
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
+
104
108
  constructor(
105
- private readonly options: BuildOptions,
109
+ private readonly metadata: CargoWorkspaceMetadata,
106
110
  private readonly crate: Crate,
107
- private readonly cwd: string,
108
- private readonly target: Target,
109
- private readonly crateDir: string,
110
- private readonly outputDir: string,
111
- private readonly targetDir: string,
112
111
  private readonly config: NapiConfig,
113
- ) {}
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
+ }
114
153
 
115
154
  get cdyLibName() {
116
155
  return this.crate.targets.find((t) => t.crate_types.includes('cdylib'))
@@ -143,11 +182,132 @@ class Builder {
143
182
  .setPackage()
144
183
  .setFeatures()
145
184
  .setTarget()
185
+ .pickCrossToolchain()
146
186
  .setEnvs()
147
187
  .setBypassArgs()
148
188
  .exec()
149
189
  }
150
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
+
151
311
  private exec() {
152
312
  debug(`Start building crate: ${this.crate.name}`)
153
313
  debug(' %i', `cargo ${this.args.join(' ')}`)
@@ -156,15 +316,17 @@ class Builder {
156
316
 
157
317
  const watch = this.options.watch
158
318
  const buildTask = new Promise<void>((resolve, reject) => {
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
+ }
159
324
  const command =
160
325
  process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo')
161
326
  const buildProcess = spawn(command, this.args, {
162
- env: {
163
- ...process.env,
164
- ...this.envs,
165
- },
327
+ env: { ...process.env, ...this.envs },
166
328
  stdio: watch ? ['inherit', 'inherit', 'pipe'] : 'inherit',
167
- cwd: this.cwd,
329
+ cwd: this.options.cwd,
168
330
  signal: controller.signal,
169
331
  })
170
332
 
@@ -178,18 +340,14 @@ class Builder {
178
340
  })
179
341
 
180
342
  buildProcess.once('error', (e) => {
181
- reject(
182
- new Error(`Build failed with error: ${e.message}`, {
183
- cause: e,
184
- }),
185
- )
343
+ reject(new Error(`Build failed with error: ${e.message}`, { cause: e }))
186
344
  })
187
345
 
188
346
  // watch mode only, they are piped through stderr
189
347
  buildProcess.stderr?.on('data', (data) => {
190
348
  const output = data.toString()
191
349
  console.error(output)
192
- if (/Finished\s(dev|release)/.test(output)) {
350
+ if (/Finished\s(`dev`|`release`)/.test(output)) {
193
351
  this.postBuild().catch(() => {})
194
352
  }
195
353
  })
@@ -313,13 +471,12 @@ class Builder {
313
471
  }
314
472
 
315
473
  private setEnvs() {
316
- // type definition intermediate file
317
- 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
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
+ }
323
480
 
324
481
  // RUSTFLAGS
325
482
  let rustflags =
@@ -352,50 +509,20 @@ class Builder {
352
509
  const linkerEnv = `CARGO_TARGET_${targetToEnvVar(
353
510
  this.target.triple,
354
511
  )}_LINKER`
355
- if (linker && !process.env[linkerEnv]) {
512
+ if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) {
356
513
  this.envs[linkerEnv] = linker
357
514
  }
358
515
 
359
516
  if (this.target.platform === 'android') {
360
- const { ANDROID_NDK_LATEST_HOME } = process.env
361
- if (!ANDROID_NDK_LATEST_HOME) {
362
- debug.warn(
363
- `${colors.red(
364
- 'ANDROID_NDK_LATEST_HOME',
365
- )} environment variable is missing`,
366
- )
367
- }
368
-
369
- const targetArch = this.target.arch === 'arm' ? 'armv7a' : 'aarch64'
370
- const targetPlatform =
371
- this.target.arch === 'arm' ? 'androideabi24' : 'android24'
372
- const hostPlatform =
373
- process.platform === 'darwin'
374
- ? 'darwin'
375
- : process.platform === 'win32'
376
- ? 'windows'
377
- : 'linux'
378
- Object.assign(this.envs, {
379
- CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
380
- 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`,
385
- ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
386
- PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin:${process.env.PATH}`,
387
- })
517
+ this.setAndroidEnv()
388
518
  }
389
- // END LINKER
390
519
 
391
520
  if (this.target.platform === 'wasi') {
392
- const emnapi = join(
393
- require.resolve('emnapi'),
394
- '..',
395
- 'lib',
396
- 'wasm32-wasi-threads',
397
- )
398
- this.envs.EMNAPI_LINK_DIR = emnapi
521
+ this.setWasiEnv()
522
+ }
523
+
524
+ if (this.target.platform === 'openharmony') {
525
+ this.setOpenHarmonyEnv()
399
526
  }
400
527
 
401
528
  debug('Set envs: ')
@@ -406,13 +533,168 @@ class Builder {
406
533
  return this
407
534
  }
408
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
+
409
685
  private setFeatures() {
410
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
+ }
411
692
  if (this.options.allFeatures) {
412
693
  args.push('--all-features')
413
694
  } else if (this.options.noDefaultFeatures) {
414
695
  args.push('--no-default-features')
415
- } else if (this.options.features) {
696
+ }
697
+ if (this.options.features) {
416
698
  args.push('--features', ...this.options.features)
417
699
  }
418
700
 
@@ -440,6 +722,10 @@ class Builder {
440
722
  this.args.push('--profile', this.options.profile)
441
723
  }
442
724
 
725
+ if (this.options.manifestPath) {
726
+ this.args.push('--manifest-path', this.options.manifestPath)
727
+ }
728
+
443
729
  if (this.options.cargoOptions?.length) {
444
730
  this.args.push(...this.options.cargoOptions)
445
731
  }
@@ -447,26 +733,25 @@ class Builder {
447
733
  return this
448
734
  }
449
735
 
450
- private getIntermediateTypeFile() {
451
- return join(
452
- tmpdir(),
736
+ private generateIntermediateTypeDefFolder() {
737
+ let folder = join(
738
+ this.targetDir,
739
+ 'napi-rs',
453
740
  `${this.crate.name}-${createHash('sha256')
454
741
  .update(this.crate.manifest_path)
455
742
  .update(CLI_VERSION)
456
743
  .digest('hex')
457
- .substring(0, 8)}.napi_type_def.tmp`,
744
+ .substring(0, 8)}`,
458
745
  )
459
- }
460
746
 
461
- private getIntermediateWasiRegisterFile() {
462
- return join(
463
- tmpdir(),
464
- `${this.crate.name}-${createHash('sha256')
465
- .update(this.crate.manifest_path)
466
- .update(CLI_VERSION)
467
- .digest('hex')
468
- .substring(0, 8)}.napi_wasi_register.tmp`,
469
- )
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
470
755
  }
471
756
 
472
757
  private async postBuild() {
@@ -481,38 +766,21 @@ class Builder {
481
766
  })
482
767
  }
483
768
 
484
- const dest = await this.copyArtifact()
769
+ const wasmBinaryName = await this.copyArtifact()
485
770
 
486
771
  // only for cdylib
487
772
  if (this.cdyLibName) {
488
773
  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
774
  const jsOutput = await this.writeJsBinding(idents)
506
- const wasmOutput = await this.writeWasiBinding(
507
- wasiRegisterFunctions,
508
- dest ?? 'index.wasm',
775
+ const wasmBindingsOutput = await this.writeWasiBinding(
776
+ wasmBinaryName,
509
777
  idents,
510
778
  )
511
779
  if (jsOutput) {
512
780
  this.outputs.push(jsOutput)
513
781
  }
514
- if (wasmOutput) {
515
- this.outputs.push(wasmOutput)
782
+ if (wasmBindingsOutput) {
783
+ this.outputs.push(...wasmBindingsOutput)
516
784
  }
517
785
  }
518
786
 
@@ -520,7 +788,7 @@ class Builder {
520
788
  }
521
789
 
522
790
  private async copyArtifact() {
523
- const [srcName, destName] = this.getArtifactNames()
791
+ const [srcName, destName, wasmBinaryName] = this.getArtifactNames()
524
792
  if (!srcName || !destName) {
525
793
  return
526
794
  }
@@ -530,6 +798,7 @@ class Builder {
530
798
  const src = join(this.targetDir, this.target.triple, profile, srcName)
531
799
  debug(`Copy artifact from: [${src}]`)
532
800
  const dest = join(this.outputDir, destName)
801
+ const isWasm = dest.endsWith('.wasm')
533
802
 
534
803
  try {
535
804
  if (await fileExists(dest)) {
@@ -538,22 +807,56 @@ class Builder {
538
807
  }
539
808
  debug('Copy artifact to:')
540
809
  debug(' %i', dest)
541
- 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
+ }
542
846
  this.outputs.push({
543
- kind: dest.endsWith('.node') ? 'node' : 'exe',
847
+ kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
544
848
  path: dest,
545
849
  })
546
- return dest
850
+ return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
547
851
  } catch (e) {
548
- throw new Error('Failed to copy artifact', {
549
- cause: e,
550
- })
852
+ throw new Error('Failed to copy artifact', { cause: e })
551
853
  }
552
854
  }
553
855
 
554
856
  private getArtifactNames() {
555
857
  if (this.cdyLibName) {
556
858
  const cdyLib = this.cdyLibName.replace(/-/g, '_')
859
+ const wasiTarget = this.config.targets.find((t) => t.platform === 'wasi')
557
860
 
558
861
  const srcName =
559
862
  this.target.platform === 'darwin'
@@ -572,12 +875,18 @@ class Builder {
572
875
  destName += `.${this.target.platformArchABI}`
573
876
  }
574
877
  if (srcName.endsWith('.wasm')) {
575
- destName += '.wasi-wasm32.wasm'
878
+ destName += '.wasm'
576
879
  } else {
577
880
  destName += '.node'
578
881
  }
579
882
 
580
- return [srcName, destName]
883
+ return [
884
+ srcName,
885
+ destName,
886
+ wasiTarget
887
+ ? `${this.config.binaryName}.${wasiTarget.platformArchABI}.wasm`
888
+ : null,
889
+ ]
581
890
  } else if (this.binName) {
582
891
  const srcName =
583
892
  this.target.platform === 'win32' ? `${this.binName}.exe` : this.binName
@@ -589,28 +898,84 @@ class Builder {
589
898
  }
590
899
 
591
900
  private async generateTypeDef() {
592
- if (!(await fileExists(this.envs.TYPE_DEF_TMP_PATH))) {
901
+ const typeDefDir = this.envs.NAPI_TYPE_DEF_TMP_FOLDER
902
+ if (!this.enableTypeDef || !(await dirExistsAsync(typeDefDir))) {
593
903
  return []
594
904
  }
595
905
 
596
906
  const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
597
907
 
598
- const { dts, exports } = await processTypeDef(
599
- this.envs.TYPE_DEF_TMP_PATH,
600
- this.options.constEnum ?? true,
601
- !this.options.noDtsHeader
602
- ? this.options.dtsHeader ?? DEFAULT_TYPE_DEF_HEADER
603
- : '',
604
- )
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
605
973
 
606
974
  try {
607
975
  debug('Writing type def to:')
608
976
  debug(' %i', dest)
609
977
  await writeFileAsync(dest, dts, 'utf-8')
610
- this.outputs.push({
611
- kind: 'dts',
612
- path: dest,
613
- })
978
+ this.outputs.push({ kind: 'dts', path: dest })
614
979
  } catch (e) {
615
980
  debug.error('Failed to write type def file')
616
981
  debug.error(e as Error)
@@ -629,57 +994,104 @@ class Builder {
629
994
  return
630
995
  }
631
996
 
632
- const name = parse(this.options.jsBinding ?? 'index.js').name
997
+ const name = this.options.jsBinding ?? 'index.js'
633
998
 
634
- const cjs = createCjsBinding(
999
+ const createBinding = this.options.esm ? createEsmBinding : createCjsBinding
1000
+ const binding = createBinding(
635
1001
  this.config.binaryName,
636
1002
  this.config.packageName,
637
1003
  idents,
638
1004
  )
639
1005
 
640
1006
  try {
641
- const dest = join(this.outputDir, `${name}.js`)
1007
+ const dest = join(this.outputDir, name)
642
1008
  debug('Writing js binding to:')
643
1009
  debug(' %i', dest)
644
- await writeFileAsync(dest, cjs, 'utf-8')
645
- return {
646
- kind: 'js',
647
- path: dest,
648
- } satisfies Output
1010
+ await writeFileAsync(dest, binding, 'utf-8')
1011
+ return { kind: 'js', path: dest } satisfies Output
649
1012
  } catch (e) {
650
1013
  throw new Error('Failed to write js binding file', { cause: e })
651
1014
  }
652
1015
  }
653
1016
 
654
1017
  private async writeWasiBinding(
655
- wasiRegisterFunctions: string[],
656
- distFileName: string | undefined,
1018
+ distFileName: string | undefined | null,
657
1019
  idents: string[],
658
1020
  ) {
659
- if (distFileName && wasiRegisterFunctions.length) {
1021
+ if (distFileName) {
660
1022
  const { name, dir } = parse(distFileName)
661
- const newPath = join(dir, `${this.config.binaryName}.wasi.cjs`)
1023
+ const bindingPath = join(dir, `${this.config.binaryName}.wasi.cjs`)
1024
+ const browserBindingPath = join(
1025
+ dir,
1026
+ `${this.config.binaryName}.wasi-browser.js`,
1027
+ )
662
1028
  const workerPath = join(dir, 'wasi-worker.mjs')
663
- const declareCodes = `const { ${idents.join(
664
- ', ',
665
- )} } = __napiModule.exports\n`
666
- const exportsCode = idents
667
- .map((ident) => `module.exports.${ident} = ${ident}`)
668
- .join(',\n')
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')
669
1039
  await writeFileAsync(
670
- newPath,
671
- createWasiBinding(name, wasiRegisterFunctions) +
672
- declareCodes +
1040
+ bindingPath,
1041
+ createWasiBinding(
1042
+ name,
1043
+ this.config.packageName,
1044
+ this.config.wasm?.initialMemory,
1045
+ this.config.wasm?.maximumMemory,
1046
+ ) +
673
1047
  exportsCode +
674
1048
  '\n',
675
1049
  'utf8',
676
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
+ )
677
1071
  await writeFileAsync(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
678
- return {
679
- kind: 'wasm',
680
- path: newPath,
681
- } satisfies Output
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
682
1095
  }
683
- return null
684
1096
  }
685
1097
  }