@napi-rs/cli 3.0.0-alpha.9 → 3.0.0-alpha.90

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 (163) hide show
  1. package/cli.mjs +1 -1
  2. package/dist/api/__tests__/new.spec.d.ts +1 -0
  3. package/dist/api/__tests__/new.spec.js +283 -0
  4. package/dist/api/__tests__/new.spec.js.map +1 -0
  5. package/dist/api/artifacts.js +28 -7
  6. package/dist/api/artifacts.js.map +1 -1
  7. package/dist/api/build.d.ts +1 -1
  8. package/dist/api/build.js +324 -115
  9. package/dist/api/build.js.map +1 -1
  10. package/dist/api/create-npm-dirs.js +46 -9
  11. package/dist/api/create-npm-dirs.js.map +1 -1
  12. package/dist/api/new.js +312 -107
  13. package/dist/api/new.js.map +1 -1
  14. package/dist/api/pre-publish.js +9 -7
  15. package/dist/api/pre-publish.js.map +1 -1
  16. package/dist/api/rename.js +74 -12
  17. package/dist/api/rename.js.map +1 -1
  18. package/dist/api/templates/cargo.toml.js +1 -0
  19. package/dist/api/templates/cargo.toml.js.map +1 -1
  20. package/dist/api/templates/ci-template.d.ts +3 -1
  21. package/dist/api/templates/ci-template.js +140 -125
  22. package/dist/api/templates/ci-template.js.map +1 -1
  23. package/dist/api/templates/ci.yml.d.ts +3 -1
  24. package/dist/api/templates/ci.yml.js +21 -5
  25. package/dist/api/templates/ci.yml.js.map +1 -1
  26. package/dist/api/templates/js-binding.d.ts +1 -0
  27. package/dist/api/templates/js-binding.js +142 -222
  28. package/dist/api/templates/js-binding.js.map +1 -1
  29. package/dist/api/templates/load-wasi-template.d.ts +2 -0
  30. package/dist/api/templates/load-wasi-template.js +168 -0
  31. package/dist/api/templates/load-wasi-template.js.map +1 -0
  32. package/dist/api/templates/package.json.d.ts +9 -2
  33. package/dist/api/templates/package.json.js +26 -4
  34. package/dist/api/templates/package.json.js.map +1 -1
  35. package/dist/api/templates/wasi-worker-template.d.ts +2 -0
  36. package/dist/api/templates/wasi-worker-template.js +122 -0
  37. package/dist/api/templates/wasi-worker-template.js.map +1 -0
  38. package/dist/api/universalize.js +5 -5
  39. package/dist/api/universalize.js.map +1 -1
  40. package/dist/api/version.js +2 -2
  41. package/dist/api/version.js.map +1 -1
  42. package/dist/commands/artifacts.js +2 -2
  43. package/dist/commands/artifacts.js.map +1 -1
  44. package/dist/commands/build.js +1 -1
  45. package/dist/commands/build.js.map +1 -1
  46. package/dist/commands/new.js +34 -34
  47. package/dist/commands/new.js.map +1 -1
  48. package/dist/def/artifacts.d.ts +21 -1
  49. package/dist/def/artifacts.js +10 -2
  50. package/dist/def/artifacts.js.map +1 -1
  51. package/dist/def/build.d.ts +149 -4
  52. package/dist/def/build.js +26 -4
  53. package/dist/def/build.js.map +1 -1
  54. package/dist/def/create-npm-dirs.d.ts +10 -0
  55. package/dist/def/create-npm-dirs.js +4 -0
  56. package/dist/def/create-npm-dirs.js.map +1 -1
  57. package/dist/def/new.d.ts +25 -7
  58. package/dist/def/new.js +11 -1
  59. package/dist/def/new.js.map +1 -1
  60. package/dist/def/pre-publish.d.ts +12 -2
  61. package/dist/def/pre-publish.js +7 -3
  62. package/dist/def/pre-publish.js.map +1 -1
  63. package/dist/def/rename.d.ts +16 -6
  64. package/dist/def/rename.js +5 -1
  65. package/dist/def/rename.js.map +1 -1
  66. package/dist/def/universalize.d.ts +10 -0
  67. package/dist/def/universalize.js +4 -0
  68. package/dist/def/universalize.js.map +1 -1
  69. package/dist/def/version.d.ts +10 -0
  70. package/dist/def/version.js +4 -0
  71. package/dist/def/version.js.map +1 -1
  72. package/dist/index.cjs +8743 -14849
  73. package/dist/utils/__tests__/config.spec.d.ts +1 -0
  74. package/dist/utils/__tests__/config.spec.js +63 -0
  75. package/dist/utils/__tests__/config.spec.js.map +1 -0
  76. package/dist/utils/cargo.js +2 -2
  77. package/dist/utils/cargo.js.map +1 -1
  78. package/dist/utils/config.d.ts +48 -4
  79. package/dist/utils/config.js +36 -4
  80. package/dist/utils/config.js.map +1 -1
  81. package/dist/utils/index.d.ts +1 -1
  82. package/dist/utils/index.js +1 -1
  83. package/dist/utils/index.js.map +1 -1
  84. package/dist/utils/log.js +1 -1
  85. package/dist/utils/log.js.map +1 -1
  86. package/dist/utils/metadata.d.ts +13 -1
  87. package/dist/utils/metadata.js +31 -11
  88. package/dist/utils/metadata.js.map +1 -1
  89. package/dist/utils/misc.d.ts +3 -3
  90. package/dist/utils/misc.js +13 -11
  91. package/dist/utils/misc.js.map +1 -1
  92. package/dist/utils/read-config.d.ts +7 -0
  93. package/dist/utils/read-config.js +8 -0
  94. package/dist/utils/read-config.js.map +1 -0
  95. package/dist/utils/target.d.ts +7 -5
  96. package/dist/utils/target.js +17 -3
  97. package/dist/utils/target.js.map +1 -1
  98. package/dist/utils/typegen.d.ts +1 -1
  99. package/dist/utils/typegen.js +63 -32
  100. package/dist/utils/typegen.js.map +1 -1
  101. package/dist/utils/version.d.ts +2 -1
  102. package/dist/utils/version.js +10 -8
  103. package/dist/utils/version.js.map +1 -1
  104. package/package.json +34 -32
  105. package/src/api/__tests__/new.spec.ts +429 -0
  106. package/src/api/artifacts.ts +81 -9
  107. package/src/api/build.ts +498 -149
  108. package/src/api/create-npm-dirs.ts +57 -9
  109. package/src/api/new.ts +389 -122
  110. package/src/api/pre-publish.ts +13 -7
  111. package/src/api/rename.ts +103 -17
  112. package/src/api/templates/cargo.toml.ts +1 -0
  113. package/src/api/templates/ci-template.ts +150 -125
  114. package/src/api/templates/ci.yml.ts +27 -6
  115. package/src/api/templates/js-binding.ts +148 -220
  116. package/src/api/templates/load-wasi-template.ts +182 -0
  117. package/src/api/templates/package.json.ts +47 -5
  118. package/src/api/templates/wasi-worker-template.ts +122 -0
  119. package/src/api/universalize.ts +13 -8
  120. package/src/api/version.ts +5 -2
  121. package/src/commands/artifacts.ts +2 -2
  122. package/src/commands/build.ts +1 -1
  123. package/src/commands/new.ts +46 -45
  124. package/src/def/artifacts.ts +22 -3
  125. package/src/def/build.ts +55 -8
  126. package/src/def/create-npm-dirs.ts +9 -0
  127. package/src/def/new.ts +28 -3
  128. package/src/def/pre-publish.ts +12 -3
  129. package/src/def/rename.ts +11 -2
  130. package/src/def/universalize.ts +9 -0
  131. package/src/def/version.ts +9 -0
  132. package/src/utils/__tests__/__fixtures__/napi_type_def +130 -130
  133. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  134. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  135. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +28 -0
  136. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  137. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +316 -332
  138. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  139. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +8 -7
  140. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  141. package/src/utils/__tests__/config.spec.ts +81 -0
  142. package/src/utils/cargo.ts +2 -2
  143. package/src/utils/config.ts +110 -9
  144. package/src/utils/index.ts +1 -1
  145. package/src/utils/log.ts +1 -1
  146. package/src/utils/metadata.ts +52 -14
  147. package/src/utils/misc.ts +21 -12
  148. package/src/utils/read-config.ts +20 -0
  149. package/src/utils/target.ts +22 -4
  150. package/src/utils/typegen.ts +87 -52
  151. package/src/utils/version.ts +10 -8
  152. package/dist/api/load-wasi-template.d.ts +0 -1
  153. package/dist/api/load-wasi-template.js +0 -64
  154. package/dist/api/load-wasi-template.js.map +0 -1
  155. package/dist/api/wasi-worker-template.d.ts +0 -1
  156. package/dist/api/wasi-worker-template.js +0 -53
  157. package/dist/api/wasi-worker-template.js.map +0 -1
  158. package/dist/utils/ci.d.ts +0 -11
  159. package/dist/utils/ci.js +0 -96
  160. package/dist/utils/ci.js.map +0 -1
  161. package/src/api/load-wasi-template.ts +0 -66
  162. package/src/api/wasi-worker-template.ts +0 -52
  163. 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,138 @@ 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.CC && !process.env.TARGET_CC) {
274
+ this.envs[`CC`] = join(toolchainPath, 'bin', `${crossTargetName}-gcc`)
275
+ this.envs[`TARGET_CC`] = join(
276
+ toolchainPath,
277
+ 'bin',
278
+ `${crossTargetName}-gcc`,
279
+ )
280
+ }
281
+ if (!process.env.CXX && !process.env.TARGET_CXX) {
282
+ this.envs[`CXX`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`)
283
+ this.envs[`TARGET_CXX`] = join(
284
+ toolchainPath,
285
+ 'bin',
286
+ `${crossTargetName}-g++`,
287
+ )
288
+ }
289
+ if (
290
+ (process.env.CC?.startsWith('clang') &&
291
+ (process.env.TARGET_CC?.startsWith('clang') ||
292
+ !process.env.TARGET_CC)) ||
293
+ process.env.TARGET_CC?.startsWith('clang')
294
+ ) {
295
+ const CFLAGS = process.env.CFLAGS ?? ''
296
+ this.envs.CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${CFLAGS}`
297
+ }
298
+ if (
299
+ (process.env.CXX?.startsWith('clang++') &&
300
+ (process.env.TARGET_CXX?.startsWith('clang++') ||
301
+ !process.env.TARGET_CXX)) ||
302
+ process.env.TARGET_CXX?.startsWith('clang++')
303
+ ) {
304
+ const CXXFLAGS = process.env.CXXFLAGS ?? ''
305
+ this.envs.CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${CXXFLAGS} ${CXXFLAGS}`
306
+ }
307
+ this.envs.PATH = this.envs.PATH
308
+ ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}`
309
+ : `${toolchainPath}/bin:${process.env.PATH}`
310
+ } catch (e) {
311
+ debug.warn('Pick cross toolchain failed', e as Error)
312
+ // ignore, do nothing
313
+ }
314
+ return this
315
+ }
316
+
151
317
  private exec() {
152
318
  debug(`Start building crate: ${this.crate.name}`)
153
319
  debug(' %i', `cargo ${this.args.join(' ')}`)
@@ -156,15 +322,17 @@ class Builder {
156
322
 
157
323
  const watch = this.options.watch
158
324
  const buildTask = new Promise<void>((resolve, reject) => {
325
+ if (this.options.useCross && this.options.crossCompile) {
326
+ throw new Error(
327
+ '`--use-cross` and `--cross-compile` can not be used together',
328
+ )
329
+ }
159
330
  const command =
160
331
  process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo')
161
332
  const buildProcess = spawn(command, this.args, {
162
- env: {
163
- ...process.env,
164
- ...this.envs,
165
- },
333
+ env: { ...process.env, ...this.envs },
166
334
  stdio: watch ? ['inherit', 'inherit', 'pipe'] : 'inherit',
167
- cwd: this.cwd,
335
+ cwd: this.options.cwd,
168
336
  signal: controller.signal,
169
337
  })
170
338
 
@@ -178,18 +346,14 @@ class Builder {
178
346
  })
179
347
 
180
348
  buildProcess.once('error', (e) => {
181
- reject(
182
- new Error(`Build failed with error: ${e.message}`, {
183
- cause: e,
184
- }),
185
- )
349
+ reject(new Error(`Build failed with error: ${e.message}`, { cause: e }))
186
350
  })
187
351
 
188
352
  // watch mode only, they are piped through stderr
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
  })
@@ -313,13 +477,12 @@ class Builder {
313
477
  }
314
478
 
315
479
  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
480
+ // TYPE DEF
481
+ if (this.enableTypeDef) {
482
+ this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
483
+ this.generateIntermediateTypeDefFolder()
484
+ this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
485
+ }
323
486
 
324
487
  // RUSTFLAGS
325
488
  let rustflags =
@@ -352,7 +515,7 @@ class Builder {
352
515
  const linkerEnv = `CARGO_TARGET_${targetToEnvVar(
353
516
  this.target.triple,
354
517
  )}_LINKER`
355
- if (linker && !process.env[linkerEnv]) {
518
+ if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) {
356
519
  this.envs[linkerEnv] = linker
357
520
  }
358
521
 
@@ -378,10 +541,10 @@ class Builder {
378
541
  Object.assign(this.envs, {
379
542
  CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
380
543
  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`,
544
+ TARGET_CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
545
+ TARGET_CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
546
+ TARGET_AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
547
+ TARGET_RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
385
548
  ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
386
549
  PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin:${process.env.PATH}`,
387
550
  })
@@ -396,6 +559,47 @@ class Builder {
396
559
  'wasm32-wasi-threads',
397
560
  )
398
561
  this.envs.EMNAPI_LINK_DIR = emnapi
562
+ this.envs.SETJMP_LINK_DIR = setjmpLib
563
+ const { WASI_SDK_PATH } = process.env
564
+
565
+ if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
566
+ this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(
567
+ WASI_SDK_PATH,
568
+ 'bin',
569
+ 'wasm-ld',
570
+ )
571
+ this.envs.CARGO_TARGET_WASM32_WASIP1_LINKER = join(
572
+ WASI_SDK_PATH,
573
+ 'bin',
574
+ 'wasm-ld',
575
+ )
576
+ this.envs.CARGO_TARGET_WASM32_WASIP1_THREADS_LINKER = join(
577
+ WASI_SDK_PATH,
578
+ 'bin',
579
+ 'wasm-ld',
580
+ )
581
+ this.envs.CARGO_TARGET_WASM32_WASIP2_LINKER = join(
582
+ WASI_SDK_PATH,
583
+ 'bin',
584
+ 'wasm-ld',
585
+ )
586
+ this.setEnvIfNotExists('CC', join(WASI_SDK_PATH, 'bin', 'clang'))
587
+ this.setEnvIfNotExists('CXX', join(WASI_SDK_PATH, 'bin', 'clang++'))
588
+ this.setEnvIfNotExists('AR', join(WASI_SDK_PATH, 'bin', 'ar'))
589
+ this.setEnvIfNotExists('RANLIB', join(WASI_SDK_PATH, 'bin', 'ranlib'))
590
+ this.setEnvIfNotExists(
591
+ 'CFLAGS',
592
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
593
+ )
594
+ this.setEnvIfNotExists(
595
+ 'CXXFLAGS',
596
+ `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj -I${setjmpInclude}`,
597
+ )
598
+ this.setEnvIfNotExists(
599
+ `LDFLAGS`,
600
+ `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`,
601
+ )
602
+ }
399
603
  }
400
604
 
401
605
  debug('Set envs: ')
@@ -406,13 +610,33 @@ class Builder {
406
610
  return this
407
611
  }
408
612
 
613
+ private setForceBuildEnvs(typeDefTmpFolder: string) {
614
+ // dynamically check all napi-rs deps and set `NAPI_FORCE_BUILD_{uppercase(snake_case(name))} = timestamp`
615
+ this.metadata.packages.forEach((crate) => {
616
+ if (
617
+ crate.dependencies.some((d) => d.name === 'napi-derive') &&
618
+ !existsSync(join(typeDefTmpFolder, crate.name))
619
+ ) {
620
+ this.envs[
621
+ `NAPI_FORCE_BUILD_${crate.name.replace(/-/g, '_').toUpperCase()}`
622
+ ] = Date.now().toString()
623
+ }
624
+ })
625
+ }
626
+
409
627
  private setFeatures() {
410
628
  const args = []
629
+ if (this.options.allFeatures && this.options.noDefaultFeatures) {
630
+ throw new Error(
631
+ 'Cannot specify --all-features and --no-default-features together',
632
+ )
633
+ }
411
634
  if (this.options.allFeatures) {
412
635
  args.push('--all-features')
413
636
  } else if (this.options.noDefaultFeatures) {
414
637
  args.push('--no-default-features')
415
- } else if (this.options.features) {
638
+ }
639
+ if (this.options.features) {
416
640
  args.push('--features', ...this.options.features)
417
641
  }
418
642
 
@@ -440,6 +664,10 @@ class Builder {
440
664
  this.args.push('--profile', this.options.profile)
441
665
  }
442
666
 
667
+ if (this.options.manifestPath) {
668
+ this.args.push('--manifest-path', this.options.manifestPath)
669
+ }
670
+
443
671
  if (this.options.cargoOptions?.length) {
444
672
  this.args.push(...this.options.cargoOptions)
445
673
  }
@@ -447,26 +675,25 @@ class Builder {
447
675
  return this
448
676
  }
449
677
 
450
- private getIntermediateTypeFile() {
451
- return join(
452
- tmpdir(),
678
+ private generateIntermediateTypeDefFolder() {
679
+ let folder = join(
680
+ this.targetDir,
681
+ 'napi-rs',
453
682
  `${this.crate.name}-${createHash('sha256')
454
683
  .update(this.crate.manifest_path)
455
684
  .update(CLI_VERSION)
456
685
  .digest('hex')
457
- .substring(0, 8)}.napi_type_def.tmp`,
686
+ .substring(0, 8)}`,
458
687
  )
459
- }
460
688
 
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
- )
689
+ if (!this.options.dtsCache) {
690
+ rmSync(folder, { recursive: true, force: true })
691
+ folder += `_${Date.now()}`
692
+ }
693
+
694
+ mkdirAsync(folder, { recursive: true })
695
+
696
+ return folder
470
697
  }
471
698
 
472
699
  private async postBuild() {
@@ -481,38 +708,21 @@ class Builder {
481
708
  })
482
709
  }
483
710
 
484
- const dest = await this.copyArtifact()
711
+ const wasmBinaryName = await this.copyArtifact()
485
712
 
486
713
  // only for cdylib
487
714
  if (this.cdyLibName) {
488
715
  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
716
  const jsOutput = await this.writeJsBinding(idents)
506
- const wasmOutput = await this.writeWasiBinding(
507
- wasiRegisterFunctions,
508
- dest ?? 'index.wasm',
717
+ const wasmBindingsOutput = await this.writeWasiBinding(
718
+ wasmBinaryName,
509
719
  idents,
510
720
  )
511
721
  if (jsOutput) {
512
722
  this.outputs.push(jsOutput)
513
723
  }
514
- if (wasmOutput) {
515
- this.outputs.push(wasmOutput)
724
+ if (wasmBindingsOutput) {
725
+ this.outputs.push(...wasmBindingsOutput)
516
726
  }
517
727
  }
518
728
 
@@ -520,7 +730,7 @@ class Builder {
520
730
  }
521
731
 
522
732
  private async copyArtifact() {
523
- const [srcName, destName] = this.getArtifactNames()
733
+ const [srcName, destName, wasmBinaryName] = this.getArtifactNames()
524
734
  if (!srcName || !destName) {
525
735
  return
526
736
  }
@@ -530,6 +740,7 @@ class Builder {
530
740
  const src = join(this.targetDir, this.target.triple, profile, srcName)
531
741
  debug(`Copy artifact from: [${src}]`)
532
742
  const dest = join(this.outputDir, destName)
743
+ const isWasm = dest.endsWith('.wasm')
533
744
 
534
745
  try {
535
746
  if (await fileExists(dest)) {
@@ -538,22 +749,56 @@ class Builder {
538
749
  }
539
750
  debug('Copy artifact to:')
540
751
  debug(' %i', dest)
541
- await copyFileAsync(src, dest)
752
+ if (isWasm) {
753
+ const { ModuleConfig } = await import('@napi-rs/wasm-tools')
754
+ debug('Generate debug wasm module')
755
+ try {
756
+ const debugWasmModule = new ModuleConfig()
757
+ .generateDwarf(true)
758
+ .generateNameSection(true)
759
+ .generateProducersSection(true)
760
+ .preserveCodeTransform(true)
761
+ .strictValidate(false)
762
+ .parse(await readFileAsync(src))
763
+ const debugWasmBinary = debugWasmModule.emitWasm(true)
764
+ await writeFileAsync(
765
+ dest.replace(/\.wasm$/, '.debug.wasm'),
766
+ debugWasmBinary,
767
+ )
768
+ debug('Generate release wasm module')
769
+ const releaseWasmModule = new ModuleConfig()
770
+ .generateDwarf(false)
771
+ .generateNameSection(false)
772
+ .generateProducersSection(false)
773
+ .preserveCodeTransform(false)
774
+ .strictValidate(false)
775
+ .onlyStableFeatures(false)
776
+ .parse(debugWasmBinary)
777
+ const releaseWasmBinary = releaseWasmModule.emitWasm(false)
778
+ await writeFileAsync(dest, releaseWasmBinary)
779
+ } catch (e) {
780
+ debug.warn(
781
+ `Failed to generate debug wasm module: ${(e as any).message ?? e}`,
782
+ )
783
+ await copyFileAsync(src, dest)
784
+ }
785
+ } else {
786
+ await copyFileAsync(src, dest)
787
+ }
542
788
  this.outputs.push({
543
- kind: dest.endsWith('.node') ? 'node' : 'exe',
789
+ kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
544
790
  path: dest,
545
791
  })
546
- return dest
792
+ return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
547
793
  } catch (e) {
548
- throw new Error('Failed to copy artifact', {
549
- cause: e,
550
- })
794
+ throw new Error('Failed to copy artifact', { cause: e })
551
795
  }
552
796
  }
553
797
 
554
798
  private getArtifactNames() {
555
799
  if (this.cdyLibName) {
556
800
  const cdyLib = this.cdyLibName.replace(/-/g, '_')
801
+ const wasiTarget = this.config.targets.find((t) => t.platform === 'wasi')
557
802
 
558
803
  const srcName =
559
804
  this.target.platform === 'darwin'
@@ -572,12 +817,18 @@ class Builder {
572
817
  destName += `.${this.target.platformArchABI}`
573
818
  }
574
819
  if (srcName.endsWith('.wasm')) {
575
- destName += '.wasi-wasm32.wasm'
820
+ destName += '.wasm'
576
821
  } else {
577
822
  destName += '.node'
578
823
  }
579
824
 
580
- return [srcName, destName]
825
+ return [
826
+ srcName,
827
+ destName,
828
+ wasiTarget
829
+ ? `${this.config.binaryName}.${wasiTarget.platformArchABI}.wasm`
830
+ : null,
831
+ ]
581
832
  } else if (this.binName) {
582
833
  const srcName =
583
834
  this.target.platform === 'win32' ? `${this.binName}.exe` : this.binName
@@ -589,28 +840,78 @@ class Builder {
589
840
  }
590
841
 
591
842
  private async generateTypeDef() {
592
- if (!(await fileExists(this.envs.TYPE_DEF_TMP_PATH))) {
843
+ const typeDefDir = this.envs.NAPI_TYPE_DEF_TMP_FOLDER
844
+ if (!this.enableTypeDef || !(await dirExistsAsync(typeDefDir))) {
593
845
  return []
594
846
  }
595
847
 
596
848
  const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
597
849
 
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
- )
850
+ let header = ''
851
+ let dts = ''
852
+ let exports: string[] = []
853
+
854
+ if (!this.options.noDtsHeader) {
855
+ const dtsHeader = this.options.dtsHeader ?? this.config.dtsHeader
856
+ // `dtsHeaderFile` in config > `dtsHeader` in cli flag > `dtsHeader` in config
857
+ if (this.config.dtsHeaderFile) {
858
+ try {
859
+ header = await readFileAsync(
860
+ join(this.options.cwd, this.config.dtsHeaderFile),
861
+ 'utf-8',
862
+ )
863
+ } catch (e) {
864
+ debug.warn(
865
+ `Failed to read dts header file ${this.config.dtsHeaderFile}`,
866
+ e,
867
+ )
868
+ }
869
+ } else if (dtsHeader) {
870
+ header = dtsHeader
871
+ } else {
872
+ header = DEFAULT_TYPE_DEF_HEADER
873
+ }
874
+ }
875
+
876
+ const files = await readdirAsync(typeDefDir, { withFileTypes: true })
877
+
878
+ if (!files.length) {
879
+ debug('No type def files found. Skip generating dts file.')
880
+ return []
881
+ }
882
+
883
+ for (const file of files) {
884
+ if (!file.isFile()) {
885
+ continue
886
+ }
887
+
888
+ const { dts: fileDts, exports: fileExports } = await processTypeDef(
889
+ join(typeDefDir, file.name),
890
+ this.options.constEnum ?? this.config.constEnum ?? true,
891
+ )
892
+
893
+ dts += fileDts
894
+ exports.push(...fileExports)
895
+ }
896
+
897
+ if (dts.indexOf('ExternalObject<') > -1) {
898
+ header += `
899
+ export declare class ExternalObject<T> {
900
+ readonly '': {
901
+ readonly '': unique symbol
902
+ [K: symbol]: T
903
+ }
904
+ }
905
+ `
906
+ }
907
+
908
+ dts = header + dts
605
909
 
606
910
  try {
607
911
  debug('Writing type def to:')
608
912
  debug(' %i', dest)
609
913
  await writeFileAsync(dest, dts, 'utf-8')
610
- this.outputs.push({
611
- kind: 'dts',
612
- path: dest,
613
- })
914
+ this.outputs.push({ kind: 'dts', path: dest })
614
915
  } catch (e) {
615
916
  debug.error('Failed to write type def file')
616
917
  debug.error(e as Error)
@@ -629,55 +930,103 @@ class Builder {
629
930
  return
630
931
  }
631
932
 
632
- const name = parse(this.options.jsBinding ?? 'index.js').name
933
+ const name = this.options.jsBinding ?? 'index.js'
633
934
 
634
- const cjs = createCjsBinding(
935
+ const createBinding = this.options.esm ? createEsmBinding : createCjsBinding
936
+ const binding = createBinding(
635
937
  this.config.binaryName,
636
938
  this.config.packageName,
637
939
  idents,
638
940
  )
639
941
 
640
942
  try {
641
- const dest = join(this.outputDir, `${name}.js`)
943
+ const dest = join(this.outputDir, name)
642
944
  debug('Writing js binding to:')
643
945
  debug(' %i', dest)
644
- await writeFileAsync(dest, cjs, 'utf-8')
645
- return {
646
- kind: 'js',
647
- path: dest,
648
- } satisfies Output
946
+ await writeFileAsync(dest, binding, 'utf-8')
947
+ return { kind: 'js', path: dest } satisfies Output
649
948
  } catch (e) {
650
949
  throw new Error('Failed to write js binding file', { cause: e })
651
950
  }
652
951
  }
653
952
 
654
953
  private async writeWasiBinding(
655
- wasiRegisterFunctions: string[],
656
- distFileName: string | undefined,
954
+ distFileName: string | undefined | null,
657
955
  idents: string[],
658
956
  ) {
659
- if (distFileName && wasiRegisterFunctions.length) {
957
+ if (distFileName) {
660
958
  const { name, dir } = parse(distFileName)
661
- const newPath = join(dir, `${this.config.binaryName}.wasi.mjs`)
959
+ const bindingPath = join(dir, `${this.config.binaryName}.wasi.cjs`)
960
+ const browserBindingPath = join(
961
+ dir,
962
+ `${this.config.binaryName}.wasi-browser.js`,
963
+ )
662
964
  const workerPath = join(dir, 'wasi-worker.mjs')
663
- const declareCodes = `const { ${idents.join(', ')} } = binding\n`
664
- const exportsCode = `export {\n${idents
665
- .map((ident) => ` ${ident}`)
666
- .join(',\n')}\n}`
965
+ const browserWorkerPath = join(dir, 'wasi-worker-browser.mjs')
966
+ const browserEntryPath = join(dir, 'browser.js')
967
+ const exportsCode =
968
+ `module.exports = __napiModule.exports\n` +
969
+ idents
970
+ .map(
971
+ (ident) =>
972
+ `module.exports.${ident} = __napiModule.exports.${ident}`,
973
+ )
974
+ .join('\n')
667
975
  await writeFileAsync(
668
- newPath,
669
- createWasiBinding(name, wasiRegisterFunctions) +
670
- declareCodes +
976
+ bindingPath,
977
+ createWasiBinding(
978
+ name,
979
+ this.config.packageName,
980
+ this.config.wasm?.initialMemory,
981
+ this.config.wasm?.maximumMemory,
982
+ ) +
671
983
  exportsCode +
672
984
  '\n',
673
985
  'utf8',
674
986
  )
987
+ await writeFileAsync(
988
+ browserBindingPath,
989
+ createWasiBrowserBinding(
990
+ name,
991
+ this.config.wasm?.initialMemory,
992
+ this.config.wasm?.maximumMemory,
993
+ this.config.wasm?.browser?.fs,
994
+ this.config.wasm?.browser?.asyncInit,
995
+ ) +
996
+ `export default __napiModule.exports\n` +
997
+ idents
998
+ .map(
999
+ (ident) =>
1000
+ `export const ${ident} = __napiModule.exports.${ident}`,
1001
+ )
1002
+ .join('\n') +
1003
+ '\n',
1004
+ 'utf8',
1005
+ )
675
1006
  await writeFileAsync(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
676
- return {
677
- kind: 'wasm',
678
- path: newPath,
679
- } satisfies Output
1007
+ await writeFileAsync(
1008
+ browserWorkerPath,
1009
+ createWasiBrowserWorkerBinding(this.config.wasm?.browser?.fs ?? false),
1010
+ 'utf8',
1011
+ )
1012
+ await writeFileAsync(
1013
+ browserEntryPath,
1014
+ `export * from '${this.config.packageName}-wasm32-wasi'\n`,
1015
+ )
1016
+ return [
1017
+ { kind: 'js', path: bindingPath },
1018
+ { kind: 'js', path: browserBindingPath },
1019
+ { kind: 'js', path: workerPath },
1020
+ { kind: 'js', path: browserWorkerPath },
1021
+ { kind: 'js', path: browserEntryPath },
1022
+ ] satisfies Output[]
1023
+ }
1024
+ return []
1025
+ }
1026
+
1027
+ private setEnvIfNotExists(env: string, value: string) {
1028
+ if (!process.env[env]) {
1029
+ this.envs[env] = value
680
1030
  }
681
- return null
682
1031
  }
683
1032
  }