@napi-rs/cli 3.7.3 → 3.8.0
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.
- package/README.md +33 -0
- package/cli.mjs +9 -12
- package/dist/cli.js +9277 -1313
- package/dist/index.cjs +9266 -1302
- package/dist/index.d.cts +176 -158
- package/dist/index.d.ts +176 -158
- package/dist/index.js +9277 -1313
- package/docs/artifacts.md +33 -0
- package/docs/build.md +58 -0
- package/docs/create-npm-dirs.md +32 -0
- package/docs/new.md +39 -0
- package/docs/pre-publish.md +37 -0
- package/docs/rename.md +37 -0
- package/docs/universalize.md +31 -0
- package/docs/version.md +31 -0
- package/docs/wasi.md +161 -0
- package/package.json +11 -10
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +1019 -114
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/artifacts.spec.ts +17 -0
- package/src/api/__tests__/create-npm-dirs.spec.ts +14 -15
- package/src/api/__tests__/new.spec.ts +41 -5
- package/src/api/artifacts.ts +934 -108
- package/src/api/build.ts +1325 -194
- package/src/api/create-npm-dirs.ts +472 -101
- package/src/api/new.ts +354 -35
- package/src/api/pre-publish.ts +2845 -99
- package/src/api/rename.ts +1638 -124
- package/src/api/templates/js-binding.ts +216 -38
- package/src/api/templates/load-wasi-template.ts +1641 -111
- package/src/api/templates/wasi-worker-template.ts +32 -3
- package/src/commands/rename.ts +8 -3
- package/src/def/artifacts.ts +2 -2
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__snapshots__/target.spec.ts.md +2 -2
- package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
- package/src/utils/__tests__/metadata.spec.ts +43 -2
- package/src/utils/__tests__/misc.spec.ts +124 -3
- package/src/utils/__tests__/version.spec.ts +47 -1
- package/src/utils/config.ts +15 -2
- package/src/utils/metadata.ts +206 -7
- package/src/utils/misc.ts +6079 -0
- package/src/utils/target.ts +66 -8
- package/src/utils/typegen.ts +1223 -63
- package/src/utils/version.ts +96 -0
package/src/api/build.ts
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
|
-
import { existsSync, mkdirSync,
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'
|
|
4
|
+
import { mkdtemp as mkdtempAsync, rm } from 'node:fs/promises'
|
|
4
5
|
import { createRequire } from 'node:module'
|
|
5
6
|
import { homedir } from 'node:os'
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
basename,
|
|
9
|
+
dirname,
|
|
10
|
+
isAbsolute,
|
|
11
|
+
join,
|
|
12
|
+
parse,
|
|
13
|
+
relative,
|
|
14
|
+
resolve,
|
|
15
|
+
sep,
|
|
16
|
+
} from 'node:path'
|
|
7
17
|
|
|
8
18
|
import * as colors from 'colorette'
|
|
9
19
|
|
|
10
20
|
import type { BuildOptions as RawBuildOptions } from '../def/build.js'
|
|
11
21
|
import {
|
|
12
22
|
CLI_VERSION,
|
|
13
|
-
|
|
23
|
+
commitFileSystemTransaction,
|
|
24
|
+
copyFileAtomic,
|
|
14
25
|
type Crate,
|
|
15
26
|
debugFactory,
|
|
16
27
|
DEFAULT_TYPE_DEF_HEADER,
|
|
17
28
|
fileExists,
|
|
18
29
|
getSystemDefaultTarget,
|
|
30
|
+
getNapiDeriveDependentCrates,
|
|
31
|
+
getPackageReconciliationRoot,
|
|
19
32
|
getTargetLinker,
|
|
20
33
|
mkdirAsync,
|
|
21
34
|
type NapiConfig,
|
|
22
35
|
parseMetadata,
|
|
23
36
|
parseTriple,
|
|
24
|
-
|
|
37
|
+
processTypeDefs,
|
|
25
38
|
readFileAsync,
|
|
26
39
|
readNapiConfig,
|
|
40
|
+
rebaseDeclarationSpecifiers,
|
|
41
|
+
removeNodeStreamWebTypeImports,
|
|
42
|
+
rewriteUnboundNodeGlobalTypeQueries,
|
|
43
|
+
rewriteTypeImportReferences,
|
|
27
44
|
type Target,
|
|
28
45
|
targetToEnvVar,
|
|
29
46
|
tryInstallCargoBinary,
|
|
30
47
|
unlinkAsync,
|
|
31
|
-
|
|
48
|
+
wasiLoaderSuffix,
|
|
49
|
+
wasiTargetHasThreads,
|
|
50
|
+
writeFileAtomic,
|
|
51
|
+
withFileSystemReconciliation,
|
|
32
52
|
dirExistsAsync,
|
|
33
53
|
readdirAsync,
|
|
34
54
|
type CargoWorkspaceMetadata,
|
|
@@ -38,6 +58,8 @@ import { createCjsBinding, createEsmBinding } from './templates/index.js'
|
|
|
38
58
|
import {
|
|
39
59
|
createWasiBinding,
|
|
40
60
|
createWasiBrowserBinding,
|
|
61
|
+
createWasiDeferredBrowserBinding,
|
|
62
|
+
createWasiDeferredBrowserBindingTypeDef,
|
|
41
63
|
} from './templates/load-wasi-template.js'
|
|
42
64
|
import {
|
|
43
65
|
createWasiBrowserWorkerBinding,
|
|
@@ -46,13 +68,507 @@ import {
|
|
|
46
68
|
|
|
47
69
|
const debug = debugFactory('build')
|
|
48
70
|
const require = createRequire(import.meta.url)
|
|
71
|
+
const MANAGED_WASI_FLAVORS = [
|
|
72
|
+
{ platformArchABI: 'wasm32-wasi', loaderSuffix: 'wasi', hasThreads: true },
|
|
73
|
+
{
|
|
74
|
+
platformArchABI: 'wasm32-wasip1',
|
|
75
|
+
loaderSuffix: 'wasip1',
|
|
76
|
+
hasThreads: false,
|
|
77
|
+
},
|
|
78
|
+
] as const
|
|
49
79
|
|
|
50
80
|
type OutputKind = 'js' | 'dts' | 'node' | 'exe' | 'wasm'
|
|
51
81
|
type Output = { kind: OutputKind; path: string }
|
|
82
|
+
type WasiBindingMetadata = {
|
|
83
|
+
exports: string[]
|
|
84
|
+
bindingTypeDef?: string
|
|
85
|
+
}
|
|
52
86
|
|
|
53
87
|
type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
|
|
54
88
|
type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
|
|
55
89
|
|
|
90
|
+
export const WASI_ARTIFACT_METADATA_PREFIX = '// napi-rs-artifact-metadata:'
|
|
91
|
+
|
|
92
|
+
type CargoConfigFingerprint = readonly [path: string, hash: string]
|
|
93
|
+
|
|
94
|
+
export function getCargoDependencyGraphFingerprint(
|
|
95
|
+
metadata: CargoWorkspaceMetadata,
|
|
96
|
+
rootPackageId: string,
|
|
97
|
+
): string {
|
|
98
|
+
const packages = new Map(metadata.packages.map((pkg) => [pkg.id, pkg]))
|
|
99
|
+
const nodes = new Map(
|
|
100
|
+
(metadata.resolve?.nodes ?? []).map((node) => [node.id, node]),
|
|
101
|
+
)
|
|
102
|
+
const pending = [rootPackageId]
|
|
103
|
+
const visited = new Set<string>()
|
|
104
|
+
const graph = []
|
|
105
|
+
|
|
106
|
+
while (pending.length > 0) {
|
|
107
|
+
const id = pending.pop()!
|
|
108
|
+
if (visited.has(id)) {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
visited.add(id)
|
|
112
|
+
|
|
113
|
+
const node = nodes.get(id)
|
|
114
|
+
const pkg = packages.get(id)
|
|
115
|
+
const dependencies = (node?.deps ?? [])
|
|
116
|
+
.map((dependency) => ({
|
|
117
|
+
name: dependency.name,
|
|
118
|
+
pkg: dependency.pkg,
|
|
119
|
+
kinds: dependency.dep_kinds
|
|
120
|
+
.map(({ kind, target }) => ({ kind, target }))
|
|
121
|
+
.sort(
|
|
122
|
+
(left, right) =>
|
|
123
|
+
(left.kind ?? '').localeCompare(right.kind ?? '') ||
|
|
124
|
+
(left.target ?? '').localeCompare(right.target ?? ''),
|
|
125
|
+
),
|
|
126
|
+
}))
|
|
127
|
+
.sort((left, right) =>
|
|
128
|
+
JSON.stringify(left).localeCompare(JSON.stringify(right)),
|
|
129
|
+
)
|
|
130
|
+
for (const dependency of dependencies) {
|
|
131
|
+
pending.push(dependency.pkg)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
graph.push({
|
|
135
|
+
id,
|
|
136
|
+
manifestPath: pkg?.manifest_path,
|
|
137
|
+
resolvedFeatures: [...(node?.features ?? [])].sort(),
|
|
138
|
+
dependencies,
|
|
139
|
+
declaredDependencies: (pkg?.dependencies ?? [])
|
|
140
|
+
.map((dependency) => ({
|
|
141
|
+
name: dependency.name,
|
|
142
|
+
source: dependency.source,
|
|
143
|
+
requirement: dependency.req,
|
|
144
|
+
kind: dependency.kind,
|
|
145
|
+
rename: dependency.rename,
|
|
146
|
+
optional: dependency.optional,
|
|
147
|
+
usesDefaultFeatures: dependency.uses_default_features,
|
|
148
|
+
features: [...dependency.features].sort(),
|
|
149
|
+
target: dependency.target,
|
|
150
|
+
registry: dependency.registry,
|
|
151
|
+
}))
|
|
152
|
+
.sort((left, right) =>
|
|
153
|
+
JSON.stringify(left).localeCompare(JSON.stringify(right)),
|
|
154
|
+
),
|
|
155
|
+
declaredFeatures: Object.entries(pkg?.features ?? {})
|
|
156
|
+
.map(([name, features]) => [name, [...features].sort()] as const)
|
|
157
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
graph.sort((left, right) => left.id.localeCompare(right.id))
|
|
162
|
+
return createHash('sha256').update(JSON.stringify(graph)).digest('hex')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function getTypeDefCacheFolder(options: {
|
|
166
|
+
targetDir: string
|
|
167
|
+
crateName: string
|
|
168
|
+
manifestPath: string
|
|
169
|
+
targetTriple: string
|
|
170
|
+
profile: string
|
|
171
|
+
features?: string[]
|
|
172
|
+
allFeatures?: boolean
|
|
173
|
+
noDefaultFeatures?: boolean
|
|
174
|
+
cargoOptions?: string[]
|
|
175
|
+
rustFlags?: Record<string, string | undefined>
|
|
176
|
+
cargoProfileEnv?: Record<string, string | undefined>
|
|
177
|
+
cargoConfig?: CargoConfigFingerprint[]
|
|
178
|
+
cargoDependencyGraph?: string
|
|
179
|
+
}) {
|
|
180
|
+
const features = [
|
|
181
|
+
...new Set(
|
|
182
|
+
(options.features ?? []).flatMap((feature) =>
|
|
183
|
+
feature.split(/[,\s]+/).filter(Boolean),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
].sort()
|
|
187
|
+
const rustFlags = Object.entries(options.rustFlags ?? {})
|
|
188
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
189
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
190
|
+
const cargoConfig = [...(options.cargoConfig ?? [])].sort(([left], [right]) =>
|
|
191
|
+
left.localeCompare(right),
|
|
192
|
+
)
|
|
193
|
+
const cargoProfileEnv = Object.entries(options.cargoProfileEnv ?? {})
|
|
194
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
195
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
196
|
+
const identity = JSON.stringify({
|
|
197
|
+
version: 4,
|
|
198
|
+
cliVersion: CLI_VERSION,
|
|
199
|
+
manifestPath: options.manifestPath,
|
|
200
|
+
cargoDependencyGraph: options.cargoDependencyGraph,
|
|
201
|
+
targetTriple: options.targetTriple,
|
|
202
|
+
profile: options.profile,
|
|
203
|
+
features: {
|
|
204
|
+
selected: features,
|
|
205
|
+
all: options.allFeatures === true,
|
|
206
|
+
noDefault: options.noDefaultFeatures === true,
|
|
207
|
+
},
|
|
208
|
+
cargoOptions: options.cargoOptions ?? [],
|
|
209
|
+
rustFlags,
|
|
210
|
+
cargoProfileEnv,
|
|
211
|
+
cargoConfig,
|
|
212
|
+
})
|
|
213
|
+
const hash = createHash('sha256')
|
|
214
|
+
.update(identity)
|
|
215
|
+
.digest('hex')
|
|
216
|
+
.substring(0, 16)
|
|
217
|
+
|
|
218
|
+
return join(options.targetDir, 'napi-rs', `${options.crateName}-${hash}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function createWasiCompilerFlags(
|
|
222
|
+
wasiSdkPath: string,
|
|
223
|
+
wasiTarget: string,
|
|
224
|
+
hasThreads: boolean,
|
|
225
|
+
shellEscapedFlags = true,
|
|
226
|
+
) {
|
|
227
|
+
const compileArguments = [
|
|
228
|
+
`--target=${wasiTarget}`,
|
|
229
|
+
`--sysroot=${join(wasiSdkPath, 'share', 'wasi-sysroot')}`,
|
|
230
|
+
...(hasThreads ? ['-pthread'] : []),
|
|
231
|
+
'-mllvm',
|
|
232
|
+
'-wasm-enable-sjlj',
|
|
233
|
+
]
|
|
234
|
+
const linkerArguments = [
|
|
235
|
+
`-fuse-ld=${join(wasiSdkPath, 'bin', 'wasm-ld')}`,
|
|
236
|
+
`--target=${wasiTarget}`,
|
|
237
|
+
]
|
|
238
|
+
return {
|
|
239
|
+
compileFlags: joinCompilerArguments(compileArguments, shellEscapedFlags),
|
|
240
|
+
linkerFlags: joinCompilerArguments(linkerArguments, shellEscapedFlags),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function createArtifactDestinationName(
|
|
245
|
+
binaryName: string,
|
|
246
|
+
target: Target,
|
|
247
|
+
sourceName: string,
|
|
248
|
+
platform: boolean,
|
|
249
|
+
) {
|
|
250
|
+
let destinationName = binaryName
|
|
251
|
+
if (platform || target.platform === 'wasi') {
|
|
252
|
+
destinationName += `.${target.platformArchABI}`
|
|
253
|
+
}
|
|
254
|
+
return `${destinationName}.${sourceName.endsWith('.wasm') ? 'wasm' : 'node'}`
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function joinCompilerArguments(
|
|
258
|
+
arguments_: string[],
|
|
259
|
+
shellEscapedFlags: boolean,
|
|
260
|
+
) {
|
|
261
|
+
if (!shellEscapedFlags) {
|
|
262
|
+
const argumentWithWhitespace = arguments_.find((argument) =>
|
|
263
|
+
/\s/.test(argument),
|
|
264
|
+
)
|
|
265
|
+
if (argumentWithWhitespace) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`CC_SHELL_ESCAPED_FLAGS disables shell parsing, so the WASI SDK argument cannot contain whitespace: ${argumentWithWhitespace}`,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
return arguments_.join(' ')
|
|
271
|
+
}
|
|
272
|
+
return arguments_
|
|
273
|
+
.map((argument) => `'${argument.replaceAll("'", "'\\''")}'`)
|
|
274
|
+
.join(' ')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function envBoolean(value: string | undefined) {
|
|
278
|
+
return (
|
|
279
|
+
value !== undefined &&
|
|
280
|
+
value !== '' &&
|
|
281
|
+
value !== '0' &&
|
|
282
|
+
value !== 'false' &&
|
|
283
|
+
value !== 'no'
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function createWasiArtifactMetadata(
|
|
288
|
+
rootEntry: string | null,
|
|
289
|
+
binaryName: string,
|
|
290
|
+
exports: string[],
|
|
291
|
+
) {
|
|
292
|
+
return `${WASI_ARTIFACT_METADATA_PREFIX}${JSON.stringify({
|
|
293
|
+
version: 2,
|
|
294
|
+
rootEntry,
|
|
295
|
+
exports,
|
|
296
|
+
managedRootEntries: [
|
|
297
|
+
'browser.js',
|
|
298
|
+
...(rootEntry ? [rootEntry] : []),
|
|
299
|
+
`${binaryName}.wasm`,
|
|
300
|
+
`${binaryName}.debug.wasm`,
|
|
301
|
+
],
|
|
302
|
+
})}\n`
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseExistingWasiArtifactMetadata(content: string) {
|
|
306
|
+
const firstLine = content.split(/\r?\n/, 1)[0]
|
|
307
|
+
if (!firstLine.startsWith(WASI_ARTIFACT_METADATA_PREFIX)) {
|
|
308
|
+
return undefined
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const metadata = JSON.parse(
|
|
312
|
+
firstLine.slice(WASI_ARTIFACT_METADATA_PREFIX.length),
|
|
313
|
+
) as Record<string, unknown>
|
|
314
|
+
if (
|
|
315
|
+
(metadata.version !== 1 && metadata.version !== 2) ||
|
|
316
|
+
(metadata.rootEntry !== null && typeof metadata.rootEntry !== 'string')
|
|
317
|
+
) {
|
|
318
|
+
return undefined
|
|
319
|
+
}
|
|
320
|
+
const managedRootEntries =
|
|
321
|
+
metadata.version === 1
|
|
322
|
+
? ['browser.js', ...(metadata.rootEntry ? [metadata.rootEntry] : [])]
|
|
323
|
+
: metadata.managedRootEntries
|
|
324
|
+
if (
|
|
325
|
+
!Array.isArray(managedRootEntries) ||
|
|
326
|
+
!managedRootEntries.every((entry) => typeof entry === 'string')
|
|
327
|
+
) {
|
|
328
|
+
return undefined
|
|
329
|
+
}
|
|
330
|
+
const exports = metadata.exports
|
|
331
|
+
return {
|
|
332
|
+
managedRootEntries,
|
|
333
|
+
exports:
|
|
334
|
+
Array.isArray(exports) &&
|
|
335
|
+
exports.every((entry) => typeof entry === 'string')
|
|
336
|
+
? exports
|
|
337
|
+
: undefined,
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
return undefined
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resolveManagedOutputPath(
|
|
345
|
+
outputDir: string,
|
|
346
|
+
entry: string,
|
|
347
|
+
description: string,
|
|
348
|
+
) {
|
|
349
|
+
if (!entry || isAbsolute(entry)) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`${description} must be a non-empty relative path: ${entry}`,
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
const outputRoot = resolve(outputDir)
|
|
355
|
+
const path = resolve(outputRoot, entry)
|
|
356
|
+
const relativePath = relative(outputRoot, path)
|
|
357
|
+
if (
|
|
358
|
+
relativePath === '' ||
|
|
359
|
+
relativePath === '..' ||
|
|
360
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
361
|
+
isAbsolute(relativePath)
|
|
362
|
+
) {
|
|
363
|
+
throw new Error(`${description} escapes its output directory: ${entry}`)
|
|
364
|
+
}
|
|
365
|
+
return path
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function selectWasiBrowserTarget(
|
|
369
|
+
buildTarget: Target,
|
|
370
|
+
configuredTargets: Target[],
|
|
371
|
+
emittedTargets: Target[],
|
|
372
|
+
) {
|
|
373
|
+
if (buildTarget.platform === 'wasi') {
|
|
374
|
+
return buildTarget
|
|
375
|
+
}
|
|
376
|
+
const configuredWasiTargets = configuredTargets.filter(
|
|
377
|
+
(target) => target.platform === 'wasi',
|
|
378
|
+
)
|
|
379
|
+
return (
|
|
380
|
+
configuredWasiTargets.find((target) => !wasiTargetHasThreads(target)) ??
|
|
381
|
+
configuredWasiTargets[0] ??
|
|
382
|
+
emittedTargets.find((target) => !wasiTargetHasThreads(target)) ??
|
|
383
|
+
emittedTargets[0]
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function createWasiBrowserEntry(
|
|
388
|
+
packageName: string,
|
|
389
|
+
platformArchABI: string,
|
|
390
|
+
idents: string[],
|
|
391
|
+
) {
|
|
392
|
+
const packageSpecifier = `${packageName}-${platformArchABI}`
|
|
393
|
+
return (
|
|
394
|
+
`export * from '${packageSpecifier}'\n` +
|
|
395
|
+
(idents.length === 0
|
|
396
|
+
? `export { default } from '${packageSpecifier}'\n`
|
|
397
|
+
: '')
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function createWasiDeferredBindingTypeDef(
|
|
402
|
+
bindingModuleSpecifier: string,
|
|
403
|
+
hasTypeDef: boolean,
|
|
404
|
+
) {
|
|
405
|
+
const typeDef = createWasiDeferredBrowserBindingTypeDef(
|
|
406
|
+
bindingModuleSpecifier,
|
|
407
|
+
)
|
|
408
|
+
if (hasTypeDef) {
|
|
409
|
+
return typeDef
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const rootBindingType = `typeof import('${bindingModuleSpecifier}')`
|
|
413
|
+
if (!typeDef.includes(rootBindingType)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
'The deferred WASI type definition no longer contains its root binding type',
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return typeDef.replace(rootBindingType, 'Record<string, unknown>')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function prepareWasiBindingTypeDef(
|
|
423
|
+
source: string,
|
|
424
|
+
sourcePath: string,
|
|
425
|
+
destinationPath: string,
|
|
426
|
+
hasThreads: boolean,
|
|
427
|
+
packageType?: 'module' | 'commonjs',
|
|
428
|
+
) {
|
|
429
|
+
if (
|
|
430
|
+
sourcePath.endsWith('.d.mts') ||
|
|
431
|
+
(sourcePath.endsWith('.d.ts') && packageType === 'module')
|
|
432
|
+
) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`Cannot emit the CommonJS WASI declaration ${destinationPath} from the ESM declaration ${sourcePath}. Use a .d.cts --dts path for WASI builds in module packages.`,
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
const targetSource = hasThreads
|
|
438
|
+
? source
|
|
439
|
+
: rewriteUnboundNodeGlobalTypeQueries(
|
|
440
|
+
removeNodeStreamWebTypeImports(source),
|
|
441
|
+
)
|
|
442
|
+
return rebaseDeclarationSpecifiers(targetSource, sourcePath, destinationPath)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function collectStaleWasiBuildOutputNames(
|
|
446
|
+
binaryName: string,
|
|
447
|
+
buildTarget: Target,
|
|
448
|
+
configuredTargets: Target[],
|
|
449
|
+
) {
|
|
450
|
+
const staleNames = new Set<string>()
|
|
451
|
+
const retainedFlavors = new Set(
|
|
452
|
+
[
|
|
453
|
+
...configuredTargets.filter((target) => target.platform === 'wasi'),
|
|
454
|
+
buildTarget,
|
|
455
|
+
].map((target) => target.platformArchABI),
|
|
456
|
+
)
|
|
457
|
+
for (const flavor of MANAGED_WASI_FLAVORS) {
|
|
458
|
+
const regenerated = flavor.platformArchABI === buildTarget.platformArchABI
|
|
459
|
+
if (!regenerated && retainedFlavors.has(flavor.platformArchABI)) {
|
|
460
|
+
continue
|
|
461
|
+
}
|
|
462
|
+
for (const suffix of [
|
|
463
|
+
`${flavor.loaderSuffix}.cjs`,
|
|
464
|
+
`${flavor.loaderSuffix}.d.cts`,
|
|
465
|
+
`${flavor.loaderSuffix}-browser.js`,
|
|
466
|
+
`${flavor.loaderSuffix}-deferred.js`,
|
|
467
|
+
`${flavor.loaderSuffix}-deferred.d.ts`,
|
|
468
|
+
...(!regenerated
|
|
469
|
+
? [
|
|
470
|
+
`${flavor.platformArchABI}.wasm`,
|
|
471
|
+
`${flavor.platformArchABI}.debug.wasm`,
|
|
472
|
+
]
|
|
473
|
+
: []),
|
|
474
|
+
]) {
|
|
475
|
+
staleNames.add(`${binaryName}.${suffix}`)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const regeneratesWorkers = wasiTargetHasThreads(buildTarget)
|
|
479
|
+
const retainsThreadedFlavor = MANAGED_WASI_FLAVORS.some(
|
|
480
|
+
(flavor) =>
|
|
481
|
+
flavor.hasThreads && retainedFlavors.has(flavor.platformArchABI),
|
|
482
|
+
)
|
|
483
|
+
if (regeneratesWorkers || !retainsThreadedFlavor) {
|
|
484
|
+
staleNames.add('wasi-worker.mjs')
|
|
485
|
+
staleNames.add('wasi-worker-browser.mjs')
|
|
486
|
+
}
|
|
487
|
+
return staleNames
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function removeWasmCustomSection(
|
|
491
|
+
binary: Uint8Array,
|
|
492
|
+
sectionName: string,
|
|
493
|
+
): Uint8Array {
|
|
494
|
+
if (
|
|
495
|
+
binary.length < 8 ||
|
|
496
|
+
binary[0] !== 0x00 ||
|
|
497
|
+
binary[1] !== 0x61 ||
|
|
498
|
+
binary[2] !== 0x73 ||
|
|
499
|
+
binary[3] !== 0x6d
|
|
500
|
+
) {
|
|
501
|
+
throw new Error('Invalid WebAssembly module header')
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const retainedChunks: Uint8Array[] = []
|
|
505
|
+
let retainedStart = 0
|
|
506
|
+
let offset = 8
|
|
507
|
+
let removed = false
|
|
508
|
+
|
|
509
|
+
while (offset < binary.length) {
|
|
510
|
+
const sectionStart = offset
|
|
511
|
+
const sectionId = binary[offset++]
|
|
512
|
+
const sectionSize = readWasmU32(binary, offset)
|
|
513
|
+
const payloadStart = sectionSize.nextOffset
|
|
514
|
+
const sectionEnd = payloadStart + sectionSize.value
|
|
515
|
+
if (sectionEnd > binary.length) {
|
|
516
|
+
throw new Error('Invalid WebAssembly section size')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let shouldRemove = false
|
|
520
|
+
if (sectionId === 0) {
|
|
521
|
+
const nameSize = readWasmU32(binary, payloadStart)
|
|
522
|
+
const nameEnd = nameSize.nextOffset + nameSize.value
|
|
523
|
+
if (nameEnd > sectionEnd) {
|
|
524
|
+
throw new Error('Invalid WebAssembly custom section name')
|
|
525
|
+
}
|
|
526
|
+
shouldRemove =
|
|
527
|
+
Buffer.from(binary.subarray(nameSize.nextOffset, nameEnd)).toString(
|
|
528
|
+
'utf8',
|
|
529
|
+
) === sectionName
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (shouldRemove) {
|
|
533
|
+
retainedChunks.push(binary.subarray(retainedStart, sectionStart))
|
|
534
|
+
retainedStart = sectionEnd
|
|
535
|
+
removed = true
|
|
536
|
+
}
|
|
537
|
+
offset = sectionEnd
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!removed) {
|
|
541
|
+
return binary
|
|
542
|
+
}
|
|
543
|
+
retainedChunks.push(binary.subarray(retainedStart))
|
|
544
|
+
const outputLength = retainedChunks.reduce(
|
|
545
|
+
(length, chunk) => length + chunk.length,
|
|
546
|
+
0,
|
|
547
|
+
)
|
|
548
|
+
const output = new Uint8Array(outputLength)
|
|
549
|
+
let outputOffset = 0
|
|
550
|
+
for (const chunk of retainedChunks) {
|
|
551
|
+
output.set(chunk, outputOffset)
|
|
552
|
+
outputOffset += chunk.length
|
|
553
|
+
}
|
|
554
|
+
return output
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function readWasmU32(binary: Uint8Array, start: number) {
|
|
558
|
+
let value = 0
|
|
559
|
+
let shift = 0
|
|
560
|
+
let offset = start
|
|
561
|
+
while (offset < binary.length && shift <= 28) {
|
|
562
|
+
const byte = binary[offset++]
|
|
563
|
+
value += (byte & 0x7f) * 2 ** shift
|
|
564
|
+
if ((byte & 0x80) === 0) {
|
|
565
|
+
return { value, nextOffset: offset }
|
|
566
|
+
}
|
|
567
|
+
shift += 7
|
|
568
|
+
}
|
|
569
|
+
throw new Error('Invalid WebAssembly unsigned LEB128 value')
|
|
570
|
+
}
|
|
571
|
+
|
|
56
572
|
export async function buildProject(rawOptions: BuildOptions) {
|
|
57
573
|
debug('napi build command receive options: %O', rawOptions)
|
|
58
574
|
|
|
@@ -79,7 +595,18 @@ export async function buildProject(rawOptions: BuildOptions) {
|
|
|
79
595
|
const resolvePath = (...paths: string[]) => resolve(options.cwd, ...paths)
|
|
80
596
|
|
|
81
597
|
const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
|
|
82
|
-
const
|
|
598
|
+
const metadataTarget = options.target ?? process.env.CARGO_BUILD_TARGET
|
|
599
|
+
const metadata = await parseMetadata(manifestPath, {
|
|
600
|
+
cwd: options.cwd,
|
|
601
|
+
featurePackage: options.package,
|
|
602
|
+
features: options.features,
|
|
603
|
+
allFeatures: options.allFeatures,
|
|
604
|
+
noDefaultFeatures: options.noDefaultFeatures,
|
|
605
|
+
cargoOptions: options.cargoOptions,
|
|
606
|
+
filterPlatform: metadataTarget
|
|
607
|
+
? parseTriple(metadataTarget).triple
|
|
608
|
+
: undefined,
|
|
609
|
+
})
|
|
83
610
|
|
|
84
611
|
const crate = metadata.packages.find((p) => {
|
|
85
612
|
// package with given name
|
|
@@ -391,9 +918,13 @@ class Builder {
|
|
|
391
918
|
|
|
392
919
|
private readonly target: Target
|
|
393
920
|
private readonly crateDir: string
|
|
394
|
-
private readonly
|
|
921
|
+
private readonly finalOutputDir: string
|
|
922
|
+
private outputDir: string
|
|
923
|
+
private readonly reconciliationRoot: string
|
|
395
924
|
private readonly targetDir: string
|
|
396
925
|
private readonly enableTypeDef: boolean = false
|
|
926
|
+
private readonly stagedOutputDestinations = new Map<string, string>()
|
|
927
|
+
private typeDefWithTypeImports: string | undefined
|
|
397
928
|
|
|
398
929
|
constructor(
|
|
399
930
|
private readonly metadata: CargoWorkspaceMetadata,
|
|
@@ -403,10 +934,15 @@ class Builder {
|
|
|
403
934
|
) {
|
|
404
935
|
this.target = resolveTarget(options.target)
|
|
405
936
|
this.crateDir = parse(crate.manifest_path).dir
|
|
406
|
-
this.
|
|
937
|
+
this.finalOutputDir = resolve(
|
|
407
938
|
this.options.cwd,
|
|
408
939
|
options.outputDir ?? this.crateDir,
|
|
409
940
|
)
|
|
941
|
+
this.outputDir = this.finalOutputDir
|
|
942
|
+
this.reconciliationRoot = getPackageReconciliationRoot(
|
|
943
|
+
this.options.cwd,
|
|
944
|
+
this.options.packageJsonPath,
|
|
945
|
+
)
|
|
410
946
|
this.targetDir =
|
|
411
947
|
options.targetDir ??
|
|
412
948
|
process.env.CARGO_BUILD_TARGET_DIR ??
|
|
@@ -455,7 +991,7 @@ class Builder {
|
|
|
455
991
|
)
|
|
456
992
|
}
|
|
457
993
|
|
|
458
|
-
build() {
|
|
994
|
+
async build() {
|
|
459
995
|
// Backstop only: `buildProject()` already validated these before running
|
|
460
996
|
// anything with a side effect (see the top of `buildProject`). Kept here
|
|
461
997
|
// so a directly constructed `Builder` cannot skip the validation.
|
|
@@ -479,14 +1015,13 @@ class Builder {
|
|
|
479
1015
|
}
|
|
480
1016
|
}
|
|
481
1017
|
|
|
482
|
-
|
|
1018
|
+
this.pickBinary()
|
|
483
1019
|
.setPackage()
|
|
484
1020
|
.setFeatures()
|
|
485
1021
|
.setTarget()
|
|
486
1022
|
.pickCrossToolchain()
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.exec()
|
|
1023
|
+
await this.setEnvs()
|
|
1024
|
+
return this.setBypassArgs().exec()
|
|
490
1025
|
}
|
|
491
1026
|
|
|
492
1027
|
private pickCrossToolchain() {
|
|
@@ -583,6 +1118,100 @@ class Builder {
|
|
|
583
1118
|
}
|
|
584
1119
|
}
|
|
585
1120
|
|
|
1121
|
+
private async collectStaleBuildOutputPaths() {
|
|
1122
|
+
const stalePaths = new Set<string>()
|
|
1123
|
+
const managedRootEntries = await this.readManagedWasiRootEntries()
|
|
1124
|
+
|
|
1125
|
+
for (const name of collectStaleWasiBuildOutputNames(
|
|
1126
|
+
this.config.binaryName,
|
|
1127
|
+
this.target,
|
|
1128
|
+
this.config.targets,
|
|
1129
|
+
)) {
|
|
1130
|
+
stalePaths.add(join(this.outputDir, name))
|
|
1131
|
+
}
|
|
1132
|
+
const hasWasiOutput =
|
|
1133
|
+
this.target.platform === 'wasi' ||
|
|
1134
|
+
this.config.targets.some((target) => target.platform === 'wasi') ||
|
|
1135
|
+
managedRootEntries.size > 0
|
|
1136
|
+
if (hasWasiOutput) {
|
|
1137
|
+
stalePaths.add(join(this.outputDir, 'browser.js'))
|
|
1138
|
+
stalePaths.add(join(this.outputDir, `${this.config.binaryName}.wasm`))
|
|
1139
|
+
stalePaths.add(
|
|
1140
|
+
join(this.outputDir, `${this.config.binaryName}.debug.wasm`),
|
|
1141
|
+
)
|
|
1142
|
+
if (this.options.platform && !this.options.noJsBinding) {
|
|
1143
|
+
stalePaths.add(
|
|
1144
|
+
join(this.outputDir, this.options.jsBinding ?? 'index.js'),
|
|
1145
|
+
)
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
for (const entry of managedRootEntries) {
|
|
1149
|
+
stalePaths.add(
|
|
1150
|
+
resolveManagedOutputPath(this.outputDir, entry, 'WASI root entry'),
|
|
1151
|
+
)
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return stalePaths
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
private async unlinkIfExists(path: string) {
|
|
1158
|
+
try {
|
|
1159
|
+
await unlinkAsync(path)
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
1162
|
+
throw error
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
private async readManagedWasiRootEntries() {
|
|
1168
|
+
const entries = new Set<string>()
|
|
1169
|
+
for (const { loaderSuffix } of MANAGED_WASI_FLAVORS) {
|
|
1170
|
+
const path = join(
|
|
1171
|
+
this.outputDir,
|
|
1172
|
+
`${this.config.binaryName}.${loaderSuffix}.cjs`,
|
|
1173
|
+
)
|
|
1174
|
+
if (!(await fileExists(path))) {
|
|
1175
|
+
continue
|
|
1176
|
+
}
|
|
1177
|
+
const metadata = parseExistingWasiArtifactMetadata(
|
|
1178
|
+
await readFileAsync(path, 'utf8'),
|
|
1179
|
+
)
|
|
1180
|
+
for (const entry of metadata?.managedRootEntries ?? []) {
|
|
1181
|
+
entries.add(entry)
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return entries
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private async readExistingWasiBindingMetadata(wasiTarget: Target) {
|
|
1188
|
+
const loaderSuffix = wasiLoaderSuffix(wasiTarget.platformArchABI)
|
|
1189
|
+
const bindingPath = join(
|
|
1190
|
+
this.finalOutputDir,
|
|
1191
|
+
`${this.config.binaryName}.${loaderSuffix}.cjs`,
|
|
1192
|
+
)
|
|
1193
|
+
const bindingTypeDefPath = join(
|
|
1194
|
+
this.finalOutputDir,
|
|
1195
|
+
`${this.config.binaryName}.${loaderSuffix}.d.cts`,
|
|
1196
|
+
)
|
|
1197
|
+
if (
|
|
1198
|
+
!(await fileExists(bindingPath)) ||
|
|
1199
|
+
!(await fileExists(bindingTypeDefPath))
|
|
1200
|
+
) {
|
|
1201
|
+
return undefined
|
|
1202
|
+
}
|
|
1203
|
+
const artifactMetadata = parseExistingWasiArtifactMetadata(
|
|
1204
|
+
await readFileAsync(bindingPath, 'utf8'),
|
|
1205
|
+
)
|
|
1206
|
+
if (artifactMetadata?.exports === undefined) {
|
|
1207
|
+
return undefined
|
|
1208
|
+
}
|
|
1209
|
+
return {
|
|
1210
|
+
exports: artifactMetadata.exports,
|
|
1211
|
+
bindingTypeDef: await readFileAsync(bindingTypeDefPath, 'utf8'),
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
586
1215
|
private pickBinary() {
|
|
587
1216
|
let set = false
|
|
588
1217
|
if (this.options.watch) {
|
|
@@ -669,14 +1298,7 @@ class Builder {
|
|
|
669
1298
|
return this
|
|
670
1299
|
}
|
|
671
1300
|
|
|
672
|
-
private setEnvs() {
|
|
673
|
-
// TYPE DEF
|
|
674
|
-
if (this.enableTypeDef) {
|
|
675
|
-
this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
|
|
676
|
-
this.generateIntermediateTypeDefFolder()
|
|
677
|
-
this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
|
|
678
|
-
}
|
|
679
|
-
|
|
1301
|
+
private async setEnvs() {
|
|
680
1302
|
// RUSTFLAGS
|
|
681
1303
|
let rustflags =
|
|
682
1304
|
process.env.RUSTFLAGS ?? process.env.CARGO_BUILD_RUSTFLAGS ?? ''
|
|
@@ -692,6 +1314,13 @@ class Builder {
|
|
|
692
1314
|
rustflags += ' -C link-arg=-s'
|
|
693
1315
|
}
|
|
694
1316
|
|
|
1317
|
+
// TYPE DEF
|
|
1318
|
+
if (this.enableTypeDef) {
|
|
1319
|
+
this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
|
|
1320
|
+
await this.generateIntermediateTypeDefFolder(rustflags)
|
|
1321
|
+
this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
695
1324
|
if (rustflags.length) {
|
|
696
1325
|
this.envs.RUSTFLAGS = rustflags
|
|
697
1326
|
}
|
|
@@ -734,11 +1363,8 @@ class Builder {
|
|
|
734
1363
|
|
|
735
1364
|
private setForceBuildEnvs(typeDefTmpFolder: string) {
|
|
736
1365
|
// dynamically check all napi-rs deps and set `NAPI_FORCE_BUILD_{uppercase(snake_case(name))} = timestamp`
|
|
737
|
-
this.metadata.
|
|
738
|
-
if (
|
|
739
|
-
crate.dependencies.some((d) => d.name === 'napi-derive') &&
|
|
740
|
-
!existsSync(join(typeDefTmpFolder, crate.name))
|
|
741
|
-
) {
|
|
1366
|
+
getNapiDeriveDependentCrates(this.metadata).forEach((crate) => {
|
|
1367
|
+
if (!existsSync(join(typeDefTmpFolder, crate.name))) {
|
|
742
1368
|
this.envs[
|
|
743
1369
|
`NAPI_FORCE_BUILD_${crate.name.replace(/-/g, '_').toUpperCase()}`
|
|
744
1370
|
] = Date.now().toString()
|
|
@@ -783,17 +1409,37 @@ class Builder {
|
|
|
783
1409
|
}
|
|
784
1410
|
|
|
785
1411
|
private setWasiEnv() {
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1412
|
+
const hasThreads = wasiTargetHasThreads(this.target)
|
|
1413
|
+
const wasiTarget = hasThreads ? 'wasm32-wasip1-threads' : 'wasm32-wasip1'
|
|
1414
|
+
const emnapi = join(require.resolve('emnapi'), '..', 'lib', wasiTarget)
|
|
1415
|
+
const emnapiVersion = require('emnapi/package.json').version
|
|
1416
|
+
// Keep this in sync with `emnapi_link_library` in `crates/build/src/wasi.rs`.
|
|
1417
|
+
const emnapiArchive = join(
|
|
1418
|
+
emnapi,
|
|
1419
|
+
hasThreads ? 'libemnapi-napi-rs-mt.a' : 'libemnapi-basic-napi-rs.a',
|
|
791
1420
|
)
|
|
1421
|
+
if (!existsSync(emnapiArchive)) {
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
`emnapi@${emnapiVersion} is missing the ${wasiTarget} archive required by napi-rs at ${emnapiArchive}. Install emnapi v2 with support for this target.`,
|
|
1424
|
+
)
|
|
1425
|
+
}
|
|
792
1426
|
this.envs.EMNAPI_LINK_DIR = emnapi
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const
|
|
1427
|
+
const projectRequire = createRequire(
|
|
1428
|
+
resolve(this.options.cwd, 'package.json'),
|
|
1429
|
+
)
|
|
1430
|
+
const projectPackageVersion = (name: string): string => {
|
|
1431
|
+
try {
|
|
1432
|
+
return projectRequire(`${name}/package.json`).version
|
|
1433
|
+
} catch {
|
|
1434
|
+
// emnapi v1 packages do not list `./package.json` in their export
|
|
1435
|
+
// maps, so fall back to the module's own `version` export to keep the
|
|
1436
|
+
// descriptive version-mismatch error below reachable for stale
|
|
1437
|
+
// installs instead of a bare ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
1438
|
+
return projectRequire(name).version
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const emnapiCoreVersion = projectPackageVersion('@emnapi/core')
|
|
1442
|
+
const emnapiRuntimeVersion = projectPackageVersion('@emnapi/runtime')
|
|
797
1443
|
|
|
798
1444
|
if (
|
|
799
1445
|
emnapiVersion !== emnapiCoreVersion ||
|
|
@@ -836,18 +1482,18 @@ class Builder {
|
|
|
836
1482
|
'TARGET_RANLIB',
|
|
837
1483
|
join(WASI_SDK_PATH, 'bin', 'ranlib'),
|
|
838
1484
|
)
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
)
|
|
847
|
-
this.setEnvIfNotExists(
|
|
848
|
-
`TARGET_LDFLAGS`,
|
|
849
|
-
`-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasip1-threads`,
|
|
1485
|
+
const { compileFlags, linkerFlags } = createWasiCompilerFlags(
|
|
1486
|
+
WASI_SDK_PATH,
|
|
1487
|
+
wasiTarget,
|
|
1488
|
+
hasThreads,
|
|
1489
|
+
process.env.CC_SHELL_ESCAPED_FLAGS
|
|
1490
|
+
? envBoolean(process.env.CC_SHELL_ESCAPED_FLAGS)
|
|
1491
|
+
: true,
|
|
850
1492
|
)
|
|
1493
|
+
this.setEnvIfNotExists('CC_SHELL_ESCAPED_FLAGS', '1')
|
|
1494
|
+
this.setEnvIfNotExists('TARGET_CFLAGS', compileFlags)
|
|
1495
|
+
this.setEnvIfNotExists('TARGET_CXXFLAGS', compileFlags)
|
|
1496
|
+
this.setEnvIfNotExists(`TARGET_LDFLAGS`, linkerFlags)
|
|
851
1497
|
}
|
|
852
1498
|
}
|
|
853
1499
|
|
|
@@ -942,58 +1588,198 @@ class Builder {
|
|
|
942
1588
|
return this
|
|
943
1589
|
}
|
|
944
1590
|
|
|
945
|
-
private generateIntermediateTypeDefFolder() {
|
|
946
|
-
|
|
947
|
-
this.
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1591
|
+
private async generateIntermediateTypeDefFolder(rustflags: string) {
|
|
1592
|
+
const targetRustFlagsEnv = `CARGO_TARGET_${targetToEnvVar(
|
|
1593
|
+
this.target.triple,
|
|
1594
|
+
)}_RUSTFLAGS`
|
|
1595
|
+
let folder = getTypeDefCacheFolder({
|
|
1596
|
+
targetDir: this.targetDir,
|
|
1597
|
+
crateName: this.crate.name,
|
|
1598
|
+
manifestPath: this.crate.manifest_path,
|
|
1599
|
+
targetTriple: this.target.triple,
|
|
1600
|
+
profile:
|
|
1601
|
+
this.options.profile ?? (this.options.release ? 'release' : 'dev'),
|
|
1602
|
+
features: this.options.features,
|
|
1603
|
+
allFeatures: this.options.allFeatures,
|
|
1604
|
+
noDefaultFeatures: this.options.noDefaultFeatures,
|
|
1605
|
+
cargoOptions: this.options.cargoOptions,
|
|
1606
|
+
rustFlags: {
|
|
1607
|
+
RUSTFLAGS: rustflags || undefined,
|
|
1608
|
+
CARGO_ENCODED_RUSTFLAGS: process.env.CARGO_ENCODED_RUSTFLAGS,
|
|
1609
|
+
CARGO_BUILD_RUSTFLAGS: process.env.CARGO_BUILD_RUSTFLAGS,
|
|
1610
|
+
[targetRustFlagsEnv]: process.env[targetRustFlagsEnv],
|
|
1611
|
+
},
|
|
1612
|
+
cargoProfileEnv: Object.fromEntries(
|
|
1613
|
+
Object.entries(process.env).filter(([name]) =>
|
|
1614
|
+
name.startsWith('CARGO_PROFILE_'),
|
|
1615
|
+
),
|
|
1616
|
+
),
|
|
1617
|
+
cargoConfig: this.getCargoConfigFingerprints(),
|
|
1618
|
+
cargoDependencyGraph: getCargoDependencyGraphFingerprint(
|
|
1619
|
+
this.metadata,
|
|
1620
|
+
this.crate.id,
|
|
1621
|
+
),
|
|
1622
|
+
})
|
|
955
1623
|
|
|
956
1624
|
if (!this.options.dtsCache) {
|
|
957
|
-
|
|
958
|
-
folder
|
|
1625
|
+
await mkdirAsync(dirname(folder), { recursive: true })
|
|
1626
|
+
folder = await mkdtempAsync(`${folder}-`)
|
|
1627
|
+
} else {
|
|
1628
|
+
await mkdirAsync(folder, { recursive: true })
|
|
959
1629
|
}
|
|
960
1630
|
|
|
961
|
-
mkdirAsync(folder, { recursive: true })
|
|
962
|
-
|
|
963
1631
|
return folder
|
|
964
1632
|
}
|
|
965
1633
|
|
|
966
|
-
private
|
|
1634
|
+
private getCargoConfigFingerprints(): CargoConfigFingerprint[] {
|
|
1635
|
+
const configPaths = new Set<string>()
|
|
1636
|
+
const addConfigIfPresent = (path: string) => {
|
|
1637
|
+
if (existsSync(path) && statSync(path).isFile()) {
|
|
1638
|
+
configPaths.add(path)
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const addConfigDirectory = (directory: string) => {
|
|
1642
|
+
addConfigIfPresent(join(directory, 'config.toml'))
|
|
1643
|
+
addConfigIfPresent(join(directory, 'config'))
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
let currentDirectory = resolve(this.options.cwd)
|
|
1647
|
+
while (true) {
|
|
1648
|
+
addConfigDirectory(join(currentDirectory, '.cargo'))
|
|
1649
|
+
const parentDirectory = dirname(currentDirectory)
|
|
1650
|
+
if (parentDirectory === currentDirectory) {
|
|
1651
|
+
break
|
|
1652
|
+
}
|
|
1653
|
+
currentDirectory = parentDirectory
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
addConfigDirectory(
|
|
1657
|
+
process.env.CARGO_HOME
|
|
1658
|
+
? resolve(this.options.cwd, process.env.CARGO_HOME)
|
|
1659
|
+
: join(homedir(), '.cargo'),
|
|
1660
|
+
)
|
|
1661
|
+
|
|
1662
|
+
const cargoOptions = this.options.cargoOptions ?? []
|
|
1663
|
+
for (let index = 0; index < cargoOptions.length; index++) {
|
|
1664
|
+
const option = cargoOptions[index]
|
|
1665
|
+
if (option === '--config') {
|
|
1666
|
+
const value = cargoOptions[index + 1]
|
|
1667
|
+
if (value) {
|
|
1668
|
+
addConfigIfPresent(resolve(this.options.cwd, value))
|
|
1669
|
+
index++
|
|
1670
|
+
}
|
|
1671
|
+
} else if (option.startsWith('--config=')) {
|
|
1672
|
+
addConfigIfPresent(
|
|
1673
|
+
resolve(this.options.cwd, option.slice('--config='.length)),
|
|
1674
|
+
)
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
return [...configPaths]
|
|
1679
|
+
.sort()
|
|
1680
|
+
.map((path) => [
|
|
1681
|
+
path,
|
|
1682
|
+
createHash('sha256').update(readFileSync(path)).digest('hex'),
|
|
1683
|
+
])
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
private postBuild() {
|
|
1687
|
+
return withFileSystemReconciliation(this.reconciliationRoot, () =>
|
|
1688
|
+
this.postBuildUnlocked(),
|
|
1689
|
+
)
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
private async postBuildUnlocked() {
|
|
1693
|
+
const finalOutputDir = this.outputDir
|
|
967
1694
|
try {
|
|
968
1695
|
debug(`Try to create output directory:`)
|
|
969
|
-
debug(' %i',
|
|
970
|
-
await mkdirAsync(
|
|
1696
|
+
debug(' %i', finalOutputDir)
|
|
1697
|
+
await mkdirAsync(finalOutputDir, { recursive: true })
|
|
971
1698
|
debug(`Output directory created`)
|
|
972
1699
|
} catch (e) {
|
|
973
|
-
throw new Error(`Failed to create output directory ${
|
|
1700
|
+
throw new Error(`Failed to create output directory ${finalOutputDir}`, {
|
|
974
1701
|
cause: e,
|
|
975
1702
|
})
|
|
976
1703
|
}
|
|
977
1704
|
|
|
978
|
-
const
|
|
1705
|
+
const stalePaths = this.options.watch
|
|
1706
|
+
? new Set<string>()
|
|
1707
|
+
: await this.collectStaleBuildOutputPaths()
|
|
1708
|
+
const stagingDir = await mkdtempAsync(
|
|
1709
|
+
join(dirname(finalOutputDir), `.${basename(finalOutputDir)}.napi-stage-`),
|
|
1710
|
+
)
|
|
1711
|
+
const outputStart = this.outputs.length
|
|
1712
|
+
this.stagedOutputDestinations.clear()
|
|
1713
|
+
this.outputDir = stagingDir
|
|
979
1714
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
idents
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1715
|
+
try {
|
|
1716
|
+
const wasmBinaryName = await this.copyArtifact()
|
|
1717
|
+
|
|
1718
|
+
// only for cdylib
|
|
1719
|
+
if (this.cdyLibName) {
|
|
1720
|
+
const idents = await this.generateTypeDef()
|
|
1721
|
+
const jsOutput = await this.writeJsBinding(idents)
|
|
1722
|
+
const wasmBindingsOutput = await this.writeWasiBinding(
|
|
1723
|
+
wasmBinaryName,
|
|
1724
|
+
idents,
|
|
1725
|
+
)
|
|
1726
|
+
if (jsOutput) {
|
|
1727
|
+
this.outputs.push(jsOutput)
|
|
1728
|
+
}
|
|
1729
|
+
if (wasmBindingsOutput) {
|
|
1730
|
+
this.outputs.push(...wasmBindingsOutput)
|
|
1731
|
+
}
|
|
993
1732
|
}
|
|
994
|
-
}
|
|
995
1733
|
|
|
996
|
-
|
|
1734
|
+
const stagedFiles = await collectFilesRecursively(stagingDir)
|
|
1735
|
+
const writes = [
|
|
1736
|
+
...stagedFiles
|
|
1737
|
+
.filter((source) => !this.stagedOutputDestinations.has(source))
|
|
1738
|
+
.map((source) => ({
|
|
1739
|
+
source,
|
|
1740
|
+
destination: join(finalOutputDir, relative(stagingDir, source)),
|
|
1741
|
+
})),
|
|
1742
|
+
...[...this.stagedOutputDestinations].map(([source, destination]) => ({
|
|
1743
|
+
source,
|
|
1744
|
+
destination,
|
|
1745
|
+
})),
|
|
1746
|
+
]
|
|
1747
|
+
const writtenDestinations = new Set(
|
|
1748
|
+
writes.map(({ destination }) => destination),
|
|
1749
|
+
)
|
|
1750
|
+
const transactionRoot = commonPathAncestor([
|
|
1751
|
+
finalOutputDir,
|
|
1752
|
+
...writtenDestinations,
|
|
1753
|
+
...stalePaths,
|
|
1754
|
+
])
|
|
1755
|
+
await commitFileSystemTransaction(
|
|
1756
|
+
transactionRoot,
|
|
1757
|
+
writes,
|
|
1758
|
+
[...stalePaths].filter((path) => !writtenDestinations.has(path)),
|
|
1759
|
+
)
|
|
1760
|
+
|
|
1761
|
+
const committedOutputs = this.outputs
|
|
1762
|
+
.slice(outputStart)
|
|
1763
|
+
.map((output) => ({
|
|
1764
|
+
...output,
|
|
1765
|
+
path:
|
|
1766
|
+
this.stagedOutputDestinations.get(output.path) ??
|
|
1767
|
+
join(finalOutputDir, relative(stagingDir, output.path)),
|
|
1768
|
+
}))
|
|
1769
|
+
this.outputs.splice(
|
|
1770
|
+
outputStart,
|
|
1771
|
+
this.outputs.length - outputStart,
|
|
1772
|
+
...committedOutputs,
|
|
1773
|
+
)
|
|
1774
|
+
return this.outputs
|
|
1775
|
+
} catch (error) {
|
|
1776
|
+
this.outputs.splice(outputStart)
|
|
1777
|
+
throw error
|
|
1778
|
+
} finally {
|
|
1779
|
+
this.outputDir = finalOutputDir
|
|
1780
|
+
this.stagedOutputDestinations.clear()
|
|
1781
|
+
await rm(stagingDir, { force: true, recursive: true })
|
|
1782
|
+
}
|
|
997
1783
|
}
|
|
998
1784
|
|
|
999
1785
|
private async copyArtifact() {
|
|
@@ -1008,49 +1794,59 @@ class Builder {
|
|
|
1008
1794
|
debug(`Copy artifact from: [${src}]`)
|
|
1009
1795
|
const dest = join(this.outputDir, destName)
|
|
1010
1796
|
const isWasm = dest.endsWith('.wasm')
|
|
1797
|
+
const debugDest = isWasm
|
|
1798
|
+
? dest.replace(/\.wasm$/, '.debug.wasm')
|
|
1799
|
+
: undefined
|
|
1800
|
+
let artifactReplaced = false
|
|
1011
1801
|
|
|
1012
1802
|
try {
|
|
1013
|
-
if (await fileExists(dest)) {
|
|
1014
|
-
debug('Old artifact found, remove it first')
|
|
1015
|
-
await unlinkAsync(dest)
|
|
1016
|
-
}
|
|
1017
1803
|
debug('Copy artifact to:')
|
|
1018
1804
|
debug(' %i', dest)
|
|
1019
1805
|
if (isWasm) {
|
|
1020
1806
|
const { ModuleConfig } = await import('@napi-rs/wasm-tools')
|
|
1021
1807
|
debug('Generate debug wasm module')
|
|
1022
1808
|
try {
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
debugWasmBinary,
|
|
1809
|
+
const debugWasmBinary = removeWasmCustomSection(
|
|
1810
|
+
new ModuleConfig()
|
|
1811
|
+
.generateDwarf(true)
|
|
1812
|
+
.generateNameSection(true)
|
|
1813
|
+
.generateProducersSection(true)
|
|
1814
|
+
.preserveCodeTransform(true)
|
|
1815
|
+
.strictValidate(false)
|
|
1816
|
+
.parse(await readFileAsync(src))
|
|
1817
|
+
.emitWasm(true),
|
|
1818
|
+
'build_id',
|
|
1034
1819
|
)
|
|
1820
|
+
await writeFileAtomic(debugDest!, debugWasmBinary)
|
|
1035
1821
|
debug('Generate release wasm module')
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1822
|
+
const releaseWasmBinary = removeWasmCustomSection(
|
|
1823
|
+
new ModuleConfig()
|
|
1824
|
+
.generateDwarf(false)
|
|
1825
|
+
.generateNameSection(false)
|
|
1826
|
+
.generateProducersSection(false)
|
|
1827
|
+
.preserveCodeTransform(false)
|
|
1828
|
+
.strictValidate(false)
|
|
1829
|
+
.onlyStableFeatures(false)
|
|
1830
|
+
.parse(debugWasmBinary)
|
|
1831
|
+
.emitWasm(false),
|
|
1832
|
+
'build_id',
|
|
1833
|
+
)
|
|
1834
|
+
await writeFileAtomic(dest, releaseWasmBinary)
|
|
1835
|
+
artifactReplaced = true
|
|
1046
1836
|
} catch (e) {
|
|
1047
1837
|
debug.warn(
|
|
1048
1838
|
`Failed to generate debug wasm module: ${(e as any).message ?? e}`,
|
|
1049
1839
|
)
|
|
1050
|
-
await
|
|
1840
|
+
await this.unlinkIfExists(debugDest!)
|
|
1841
|
+
await copyFileAtomic(src, dest)
|
|
1842
|
+
artifactReplaced = true
|
|
1843
|
+
}
|
|
1844
|
+
if (this.target.platform === 'wasi') {
|
|
1845
|
+
await verifyWasiReactor(dest)
|
|
1051
1846
|
}
|
|
1052
1847
|
} else {
|
|
1053
|
-
await
|
|
1848
|
+
await copyFileAtomic(src, dest)
|
|
1849
|
+
artifactReplaced = true
|
|
1054
1850
|
}
|
|
1055
1851
|
this.outputs.push({
|
|
1056
1852
|
kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
|
|
@@ -1058,6 +1854,13 @@ class Builder {
|
|
|
1058
1854
|
})
|
|
1059
1855
|
return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
|
|
1060
1856
|
} catch (e) {
|
|
1857
|
+
if (artifactReplaced) {
|
|
1858
|
+
await Promise.all(
|
|
1859
|
+
[dest, debugDest]
|
|
1860
|
+
.filter((path): path is string => path !== undefined)
|
|
1861
|
+
.map((path) => this.unlinkIfExists(path)),
|
|
1862
|
+
)
|
|
1863
|
+
}
|
|
1061
1864
|
throw new Error('Failed to copy artifact', { cause: e })
|
|
1062
1865
|
}
|
|
1063
1866
|
}
|
|
@@ -1065,7 +1868,14 @@ class Builder {
|
|
|
1065
1868
|
private getArtifactNames() {
|
|
1066
1869
|
if (this.cdyLibName) {
|
|
1067
1870
|
const cdyLib = this.cdyLibName.replace(/-/g, '_')
|
|
1068
|
-
|
|
1871
|
+
// When building a wasi target, name the wasm artifact after the flavor
|
|
1872
|
+
// being built (two wasi flavors may be declared side by side); for
|
|
1873
|
+
// non-wasi builds fall back to the first declared wasi target so the
|
|
1874
|
+
// loader set can still be regenerated deterministically.
|
|
1875
|
+
const wasiTarget =
|
|
1876
|
+
this.target.platform === 'wasi'
|
|
1877
|
+
? this.target
|
|
1878
|
+
: this.config.targets.find((t) => t.platform === 'wasi')
|
|
1069
1879
|
|
|
1070
1880
|
const srcName =
|
|
1071
1881
|
this.target.platform === 'darwin'
|
|
@@ -1076,18 +1886,12 @@ class Builder {
|
|
|
1076
1886
|
? `${cdyLib}.wasm`
|
|
1077
1887
|
: `lib${cdyLib}.so`
|
|
1078
1888
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
if (srcName.endsWith('.wasm')) {
|
|
1087
|
-
destName += '.wasm'
|
|
1088
|
-
} else {
|
|
1089
|
-
destName += '.node'
|
|
1090
|
-
}
|
|
1889
|
+
const destName = createArtifactDestinationName(
|
|
1890
|
+
this.config.binaryName,
|
|
1891
|
+
this.target,
|
|
1892
|
+
srcName,
|
|
1893
|
+
this.options.platform ?? false,
|
|
1894
|
+
)
|
|
1091
1895
|
|
|
1092
1896
|
return [
|
|
1093
1897
|
srcName,
|
|
@@ -1112,7 +1916,7 @@ class Builder {
|
|
|
1112
1916
|
return []
|
|
1113
1917
|
}
|
|
1114
1918
|
|
|
1115
|
-
const { exports, dts } = await generateTypeDef({
|
|
1919
|
+
const { exports, dts, dtsWithTypeImports } = await generateTypeDef({
|
|
1116
1920
|
typeDefDir,
|
|
1117
1921
|
noDtsHeader: this.options.noDtsHeader,
|
|
1118
1922
|
dtsHeader: this.options.dtsHeader,
|
|
@@ -1123,27 +1927,77 @@ class Builder {
|
|
|
1123
1927
|
this.options.runtimeStringEnum ?? this.config.runtimeStringEnum,
|
|
1124
1928
|
cwd: this.options.cwd,
|
|
1125
1929
|
})
|
|
1930
|
+
this.typeDefWithTypeImports = dtsWithTypeImports
|
|
1126
1931
|
|
|
1127
|
-
const
|
|
1932
|
+
const typeDefRelativePath = this.options.dts ?? 'index.d.ts'
|
|
1933
|
+
const finalDest = join(this.finalOutputDir, typeDefRelativePath)
|
|
1934
|
+
const dest = this.getStagedOutputPath(finalDest)
|
|
1128
1935
|
|
|
1129
1936
|
try {
|
|
1130
1937
|
debug('Writing type def to:')
|
|
1131
1938
|
debug(' %i', dest)
|
|
1132
|
-
|
|
1939
|
+
assertWritableOutputDestination(finalDest)
|
|
1940
|
+
await mkdirAsync(dirname(dest), { recursive: true })
|
|
1941
|
+
await writeFileAtomic(dest, dts, 'utf-8')
|
|
1133
1942
|
} catch (e) {
|
|
1134
|
-
|
|
1135
|
-
debug.error(e as Error)
|
|
1943
|
+
throw new Error(`Failed to write type def file ${dest}`, { cause: e })
|
|
1136
1944
|
}
|
|
1137
1945
|
|
|
1138
1946
|
if (exports.length > 0) {
|
|
1139
|
-
const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
|
|
1140
1947
|
this.outputs.push({ kind: 'dts', path: dest })
|
|
1141
1948
|
}
|
|
1142
1949
|
|
|
1143
1950
|
return exports
|
|
1144
1951
|
}
|
|
1145
1952
|
|
|
1953
|
+
private getStagedOutputPath(finalPath: string) {
|
|
1954
|
+
if (this.outputDir === this.finalOutputDir) {
|
|
1955
|
+
return finalPath
|
|
1956
|
+
}
|
|
1957
|
+
const relativePath = relative(this.finalOutputDir, finalPath)
|
|
1958
|
+
if (
|
|
1959
|
+
relativePath !== '..' &&
|
|
1960
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
1961
|
+
!isAbsolute(relativePath)
|
|
1962
|
+
) {
|
|
1963
|
+
return join(this.outputDir, relativePath)
|
|
1964
|
+
}
|
|
1965
|
+
const stagedPath = join(
|
|
1966
|
+
this.outputDir,
|
|
1967
|
+
'.napi-external',
|
|
1968
|
+
createHash('sha256').update(finalPath).digest('hex'),
|
|
1969
|
+
)
|
|
1970
|
+
this.stagedOutputDestinations.set(stagedPath, finalPath)
|
|
1971
|
+
return stagedPath
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1146
1974
|
private async writeJsBinding(idents: string[]) {
|
|
1975
|
+
// Default WASI fallback order: threaded first. The generated root loader
|
|
1976
|
+
// also lets consumers pin one exact declared flavor with
|
|
1977
|
+
// NAPI_RS_WASI_FLAVOR.
|
|
1978
|
+
const declaredWasiTargets = this.config.targets.filter(
|
|
1979
|
+
(t) => t.platform === 'wasi',
|
|
1980
|
+
)
|
|
1981
|
+
// A direct `napi build --target wasm32-wasip1` (or any wasi triple) must
|
|
1982
|
+
// participate even when the config does not declare that target —
|
|
1983
|
+
// `writeWasiBinding` emits its loader set, so the index chain has to
|
|
1984
|
+
// reference the same flavor.
|
|
1985
|
+
if (
|
|
1986
|
+
this.target.platform === 'wasi' &&
|
|
1987
|
+
!declaredWasiTargets.some(
|
|
1988
|
+
(t) => t.platformArchABI === this.target.platformArchABI,
|
|
1989
|
+
)
|
|
1990
|
+
) {
|
|
1991
|
+
declaredWasiTargets.push(this.target)
|
|
1992
|
+
}
|
|
1993
|
+
const wasiFlavors = [
|
|
1994
|
+
...new Set(
|
|
1995
|
+
[
|
|
1996
|
+
...declaredWasiTargets.filter(wasiTargetHasThreads),
|
|
1997
|
+
...declaredWasiTargets.filter((t) => !wasiTargetHasThreads(t)),
|
|
1998
|
+
].map((t) => t.platformArchABI),
|
|
1999
|
+
),
|
|
2000
|
+
]
|
|
1147
2001
|
return writeJsBinding({
|
|
1148
2002
|
platform: this.options.platform,
|
|
1149
2003
|
noJsBinding: this.options.noJsBinding,
|
|
@@ -1154,6 +2008,7 @@ class Builder {
|
|
|
1154
2008
|
packageName: this.options.jsPackageName ?? this.config.packageName,
|
|
1155
2009
|
version: process.env.npm_new_version ?? this.config.packageJson.version,
|
|
1156
2010
|
outputDir: this.outputDir,
|
|
2011
|
+
wasiFlavors,
|
|
1157
2012
|
})
|
|
1158
2013
|
}
|
|
1159
2014
|
|
|
@@ -1162,78 +2017,248 @@ class Builder {
|
|
|
1162
2017
|
idents: string[],
|
|
1163
2018
|
) {
|
|
1164
2019
|
if (distFileName) {
|
|
1165
|
-
const {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
.
|
|
1178
|
-
|
|
1179
|
-
|
|
2020
|
+
const { dir } = parse(distFileName)
|
|
2021
|
+
// A WASI build owns the export/declaration metadata for its flavor.
|
|
2022
|
+
// Non-WASI builds use that metadata to regenerate declared loader sets
|
|
2023
|
+
// without substituting the native surface. Legacy flavor files without
|
|
2024
|
+
// metadata remain untouched. Two triples mapping to the same
|
|
2025
|
+
// `platformArchABI` (e.g. `wasm32-wasip1-threads` and
|
|
2026
|
+
// `wasm32-wasi-preview1-threads`) describe the same artifact set, so
|
|
2027
|
+
// dedupe on it.
|
|
2028
|
+
const wasiTargets: Target[] = []
|
|
2029
|
+
const seen = new Set<string>()
|
|
2030
|
+
const declaredWasiTargets =
|
|
2031
|
+
this.target.platform === 'wasi'
|
|
2032
|
+
? [this.target]
|
|
2033
|
+
: this.config.targets.filter((t) => t.platform === 'wasi')
|
|
2034
|
+
for (const wasiTarget of declaredWasiTargets) {
|
|
2035
|
+
if (seen.has(wasiTarget.platformArchABI)) {
|
|
2036
|
+
continue
|
|
2037
|
+
}
|
|
2038
|
+
seen.add(wasiTarget.platformArchABI)
|
|
2039
|
+
wasiTargets.push(wasiTarget)
|
|
2040
|
+
}
|
|
2041
|
+
const outputs: Output[] = []
|
|
2042
|
+
const metadataByFlavor = new Map<string, WasiBindingMetadata>()
|
|
2043
|
+
for (const wasiTarget of wasiTargets) {
|
|
2044
|
+
const metadata =
|
|
2045
|
+
this.target.platform === 'wasi' &&
|
|
2046
|
+
wasiTarget.platformArchABI === this.target.platformArchABI
|
|
2047
|
+
? { exports: idents }
|
|
2048
|
+
: await this.readExistingWasiBindingMetadata(wasiTarget)
|
|
2049
|
+
if (!metadata) {
|
|
2050
|
+
continue
|
|
2051
|
+
}
|
|
2052
|
+
metadataByFlavor.set(wasiTarget.platformArchABI, metadata)
|
|
2053
|
+
outputs.push(
|
|
2054
|
+
...(await this.writeWasiBindingForTarget(wasiTarget, dir, metadata)),
|
|
2055
|
+
)
|
|
2056
|
+
}
|
|
2057
|
+
if (wasiTargets.length > 0) {
|
|
2058
|
+
// The browser entry re-exports a single flavor: the non-threaded one
|
|
2059
|
+
// when declared (browser environments without cross-origin isolation
|
|
2060
|
+
// cannot use the threaded flavor). An explicit WASI build target is
|
|
2061
|
+
// authoritative, even when package config declares another flavor.
|
|
2062
|
+
const browserFlavor = selectWasiBrowserTarget(
|
|
2063
|
+
this.target,
|
|
2064
|
+
this.config.targets,
|
|
2065
|
+
wasiTargets,
|
|
2066
|
+
)
|
|
2067
|
+
const browserEntryPath = join(dir, 'browser.js')
|
|
2068
|
+
const browserMetadata = metadataByFlavor.get(
|
|
2069
|
+
browserFlavor.platformArchABI,
|
|
2070
|
+
)
|
|
2071
|
+
if (browserMetadata) {
|
|
2072
|
+
await writeFileAtomic(
|
|
2073
|
+
browserEntryPath,
|
|
2074
|
+
createWasiBrowserEntry(
|
|
2075
|
+
this.config.packageName,
|
|
2076
|
+
browserFlavor.platformArchABI,
|
|
2077
|
+
browserMetadata.exports,
|
|
2078
|
+
),
|
|
2079
|
+
)
|
|
2080
|
+
outputs.push({ kind: 'js', path: browserEntryPath })
|
|
2081
|
+
} else {
|
|
2082
|
+
const existingBrowserEntryPath = join(
|
|
2083
|
+
this.finalOutputDir,
|
|
2084
|
+
'browser.js',
|
|
1180
2085
|
)
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
2086
|
+
if (await fileExists(existingBrowserEntryPath)) {
|
|
2087
|
+
await copyFileAtomic(existingBrowserEntryPath, browserEntryPath)
|
|
2088
|
+
outputs.push({ kind: 'js', path: browserEntryPath })
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return outputs
|
|
2093
|
+
}
|
|
2094
|
+
return []
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
private async writeWasiBindingForTarget(
|
|
2098
|
+
wasiTarget: Target,
|
|
2099
|
+
dir: string,
|
|
2100
|
+
metadata: WasiBindingMetadata,
|
|
2101
|
+
): Promise<Output[]> {
|
|
2102
|
+
const { exports: idents } = metadata
|
|
2103
|
+
const hasThreads = wasiTargetHasThreads(wasiTarget)
|
|
2104
|
+
const loaderSuffix = wasiLoaderSuffix(wasiTarget.platformArchABI)
|
|
2105
|
+
// the wasm file stem referenced from inside the loaders
|
|
2106
|
+
const name = `${this.config.binaryName}.${wasiTarget.platformArchABI}`
|
|
2107
|
+
const bindingPath = join(
|
|
2108
|
+
dir,
|
|
2109
|
+
`${this.config.binaryName}.${loaderSuffix}.cjs`,
|
|
2110
|
+
)
|
|
2111
|
+
const browserBindingPath = join(
|
|
2112
|
+
dir,
|
|
2113
|
+
`${this.config.binaryName}.${loaderSuffix}-browser.js`,
|
|
2114
|
+
)
|
|
2115
|
+
const bindingTypeDefPath = join(
|
|
2116
|
+
dir,
|
|
2117
|
+
`${this.config.binaryName}.${loaderSuffix}.d.cts`,
|
|
2118
|
+
)
|
|
2119
|
+
const exportsCode =
|
|
2120
|
+
`module.exports = __napiModule.exports\n` +
|
|
2121
|
+
idents
|
|
2122
|
+
.map(
|
|
2123
|
+
(ident) => `module.exports.${ident} = __napiModule.exports.${ident}`,
|
|
2124
|
+
)
|
|
2125
|
+
.join('\n')
|
|
2126
|
+
await writeFileAtomic(
|
|
2127
|
+
bindingPath,
|
|
2128
|
+
createWasiArtifactMetadata(
|
|
2129
|
+
this.options.platform && !this.options.noJsBinding
|
|
2130
|
+
? (this.options.jsBinding ?? 'index.js')
|
|
2131
|
+
: null,
|
|
2132
|
+
this.config.binaryName,
|
|
2133
|
+
idents,
|
|
2134
|
+
) +
|
|
1184
2135
|
createWasiBinding(
|
|
1185
2136
|
name,
|
|
1186
2137
|
this.config.packageName,
|
|
1187
2138
|
this.config.wasm?.initialMemory,
|
|
1188
2139
|
this.config.wasm?.maximumMemory,
|
|
2140
|
+
hasThreads,
|
|
2141
|
+
wasiTarget.platformArchABI,
|
|
2142
|
+
`${this.config.binaryName}.${wasiTarget.platformArchABI}`,
|
|
1189
2143
|
) +
|
|
1190
|
-
|
|
1191
|
-
|
|
2144
|
+
exportsCode +
|
|
2145
|
+
'\n',
|
|
2146
|
+
'utf8',
|
|
2147
|
+
)
|
|
2148
|
+
await writeFileAtomic(
|
|
2149
|
+
browserBindingPath,
|
|
2150
|
+
createWasiBrowserBinding(
|
|
2151
|
+
name,
|
|
2152
|
+
this.config.wasm?.initialMemory,
|
|
2153
|
+
this.config.wasm?.maximumMemory,
|
|
2154
|
+
this.config.wasm?.browser?.fs,
|
|
2155
|
+
this.config.wasm?.browser?.asyncInit,
|
|
2156
|
+
this.config.wasm?.browser?.buffer,
|
|
2157
|
+
this.config.wasm?.browser?.errorEvent,
|
|
2158
|
+
hasThreads,
|
|
2159
|
+
) +
|
|
2160
|
+
`export default __napiModule.exports\n` +
|
|
2161
|
+
idents
|
|
2162
|
+
.map(
|
|
2163
|
+
(ident) => `export const ${ident} = __napiModule.exports.${ident}`,
|
|
2164
|
+
)
|
|
2165
|
+
.join('\n') +
|
|
2166
|
+
'\n',
|
|
2167
|
+
'utf8',
|
|
2168
|
+
)
|
|
2169
|
+
let bindingTypeDef = metadata.bindingTypeDef
|
|
2170
|
+
if (bindingTypeDef === undefined) {
|
|
2171
|
+
const finalSourceTypeDefPath = join(
|
|
2172
|
+
this.finalOutputDir,
|
|
2173
|
+
this.options.dts ?? 'index.d.ts',
|
|
2174
|
+
)
|
|
2175
|
+
const sourceTypeDefPath = this.enableTypeDef
|
|
2176
|
+
? this.getStagedOutputPath(finalSourceTypeDefPath)
|
|
2177
|
+
: finalSourceTypeDefPath
|
|
2178
|
+
const sourceTypeDef = this.enableTypeDef
|
|
2179
|
+
? await readFileAsync(sourceTypeDefPath, 'utf8')
|
|
2180
|
+
: `${DEFAULT_TYPE_DEF_HEADER}
|
|
2181
|
+
declare const binding: Record<string, unknown>
|
|
2182
|
+
export = binding
|
|
2183
|
+
`
|
|
2184
|
+
const selectedSourceTypeDef =
|
|
2185
|
+
!hasThreads &&
|
|
2186
|
+
this.config.wasm?.browser?.buffer === true &&
|
|
2187
|
+
this.typeDefWithTypeImports
|
|
2188
|
+
? this.typeDefWithTypeImports
|
|
2189
|
+
: sourceTypeDef
|
|
2190
|
+
bindingTypeDef = this.enableTypeDef
|
|
2191
|
+
? prepareWasiBindingTypeDef(
|
|
2192
|
+
selectedSourceTypeDef,
|
|
2193
|
+
finalSourceTypeDefPath,
|
|
2194
|
+
join(
|
|
2195
|
+
this.finalOutputDir,
|
|
2196
|
+
relative(this.outputDir, bindingTypeDefPath),
|
|
2197
|
+
),
|
|
2198
|
+
hasThreads,
|
|
2199
|
+
this.config.packageJson.type,
|
|
2200
|
+
)
|
|
2201
|
+
: hasThreads
|
|
2202
|
+
? selectedSourceTypeDef
|
|
2203
|
+
: removeNodeStreamWebTypeImports(selectedSourceTypeDef)
|
|
2204
|
+
}
|
|
2205
|
+
await writeFileAtomic(bindingTypeDefPath, bindingTypeDef, 'utf8')
|
|
2206
|
+
const outputs: Output[] = [
|
|
2207
|
+
{ kind: 'js', path: bindingPath },
|
|
2208
|
+
{ kind: 'js', path: browserBindingPath },
|
|
2209
|
+
{ kind: 'dts', path: bindingTypeDefPath },
|
|
2210
|
+
]
|
|
2211
|
+
if (hasThreads) {
|
|
2212
|
+
// worker scripts are only referenced by the threaded loaders
|
|
2213
|
+
const workerPath = join(dir, 'wasi-worker.mjs')
|
|
2214
|
+
const browserWorkerPath = join(dir, 'wasi-worker-browser.mjs')
|
|
2215
|
+
await writeFileAtomic(workerPath, WASI_WORKER_TEMPLATE, 'utf8')
|
|
2216
|
+
await writeFileAtomic(
|
|
2217
|
+
browserWorkerPath,
|
|
2218
|
+
createWasiBrowserWorkerBinding(
|
|
2219
|
+
this.config.wasm?.browser?.fs ?? false,
|
|
2220
|
+
this.config.wasm?.browser?.errorEvent ?? false,
|
|
2221
|
+
),
|
|
1192
2222
|
'utf8',
|
|
1193
2223
|
)
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
2224
|
+
outputs.push(
|
|
2225
|
+
{ kind: 'js', path: workerPath },
|
|
2226
|
+
{ kind: 'js', path: browserWorkerPath },
|
|
2227
|
+
)
|
|
2228
|
+
} else {
|
|
2229
|
+
// the deferred workerd-safe loader only exists for non-threaded flavors
|
|
2230
|
+
const deferredBindingPath = join(
|
|
2231
|
+
dir,
|
|
2232
|
+
`${this.config.binaryName}.${loaderSuffix}-deferred.js`,
|
|
2233
|
+
)
|
|
2234
|
+
const deferredTypeDefPath = join(
|
|
2235
|
+
dir,
|
|
2236
|
+
`${this.config.binaryName}.${loaderSuffix}-deferred.d.ts`,
|
|
2237
|
+
)
|
|
2238
|
+
await writeFileAtomic(
|
|
2239
|
+
deferredBindingPath,
|
|
2240
|
+
createWasiDeferredBrowserBinding(
|
|
1197
2241
|
name,
|
|
1198
2242
|
this.config.wasm?.initialMemory,
|
|
1199
2243
|
this.config.wasm?.maximumMemory,
|
|
1200
|
-
this.config.wasm?.browser?.fs,
|
|
1201
|
-
this.config.wasm?.browser?.asyncInit,
|
|
1202
2244
|
this.config.wasm?.browser?.buffer,
|
|
1203
|
-
|
|
1204
|
-
) +
|
|
1205
|
-
`export default __napiModule.exports\n` +
|
|
1206
|
-
idents
|
|
1207
|
-
.map(
|
|
1208
|
-
(ident) =>
|
|
1209
|
-
`export const ${ident} = __napiModule.exports.${ident}`,
|
|
1210
|
-
)
|
|
1211
|
-
.join('\n') +
|
|
1212
|
-
'\n',
|
|
2245
|
+
),
|
|
1213
2246
|
'utf8',
|
|
1214
2247
|
)
|
|
1215
|
-
await
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
this.
|
|
1220
|
-
this.config.wasm?.browser?.errorEvent ?? false,
|
|
2248
|
+
await writeFileAtomic(
|
|
2249
|
+
deferredTypeDefPath,
|
|
2250
|
+
createWasiDeferredBindingTypeDef(
|
|
2251
|
+
`./${this.config.binaryName}.${loaderSuffix}.cjs`,
|
|
2252
|
+
this.enableTypeDef,
|
|
1221
2253
|
),
|
|
1222
2254
|
'utf8',
|
|
1223
2255
|
)
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
2256
|
+
outputs.push(
|
|
2257
|
+
{ kind: 'js', path: deferredBindingPath },
|
|
2258
|
+
{ kind: 'dts', path: deferredTypeDefPath },
|
|
1227
2259
|
)
|
|
1228
|
-
return [
|
|
1229
|
-
{ kind: 'js', path: bindingPath },
|
|
1230
|
-
{ kind: 'js', path: browserBindingPath },
|
|
1231
|
-
{ kind: 'js', path: workerPath },
|
|
1232
|
-
{ kind: 'js', path: browserWorkerPath },
|
|
1233
|
-
{ kind: 'js', path: browserEntryPath },
|
|
1234
|
-
] satisfies Output[]
|
|
1235
2260
|
}
|
|
1236
|
-
return
|
|
2261
|
+
return outputs
|
|
1237
2262
|
}
|
|
1238
2263
|
|
|
1239
2264
|
private setEnvIfNotExists(env: string, value: string) {
|
|
@@ -1243,6 +2268,85 @@ class Builder {
|
|
|
1243
2268
|
}
|
|
1244
2269
|
}
|
|
1245
2270
|
|
|
2271
|
+
export async function verifyWasiReactor(wasmPath: string): Promise<void> {
|
|
2272
|
+
const bytes = await readFileAsync(wasmPath)
|
|
2273
|
+
let module: WebAssembly.Module
|
|
2274
|
+
try {
|
|
2275
|
+
module = new WebAssembly.Module(bytes)
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
throw new Error(`Failed to validate WASI artifact ${wasmPath}`, {
|
|
2278
|
+
cause: error,
|
|
2279
|
+
})
|
|
2280
|
+
}
|
|
2281
|
+
if (
|
|
2282
|
+
!WebAssembly.Module.exports(module).some(
|
|
2283
|
+
({ name, kind }) => name === '_initialize' && kind === 'function',
|
|
2284
|
+
)
|
|
2285
|
+
) {
|
|
2286
|
+
throw new Error(
|
|
2287
|
+
`WASI artifact ${wasmPath} does not export _initialize. Ensure napi-build can locate crt1-reactor.o for the selected Rust target.`,
|
|
2288
|
+
)
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
function assertWritableOutputDestination(path: string) {
|
|
2293
|
+
if (existsSync(path) && statSync(path).isDirectory()) {
|
|
2294
|
+
throw new Error(`Output destination is a directory: ${path}`)
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
let parent = dirname(path)
|
|
2298
|
+
while (!existsSync(parent)) {
|
|
2299
|
+
const nextParent = dirname(parent)
|
|
2300
|
+
if (nextParent === parent) {
|
|
2301
|
+
return
|
|
2302
|
+
}
|
|
2303
|
+
parent = nextParent
|
|
2304
|
+
}
|
|
2305
|
+
if (!statSync(parent).isDirectory()) {
|
|
2306
|
+
throw new Error(`Output parent is not a directory: ${parent}`)
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function commonPathAncestor(paths: Iterable<string>) {
|
|
2311
|
+
const resolvedPaths = [...paths].map((path) => resolve(path))
|
|
2312
|
+
if (resolvedPaths.length === 0) {
|
|
2313
|
+
throw new Error('Cannot compute a common path for no filesystem outputs')
|
|
2314
|
+
}
|
|
2315
|
+
let common = resolvedPaths[0]
|
|
2316
|
+
for (const path of resolvedPaths.slice(1)) {
|
|
2317
|
+
while (true) {
|
|
2318
|
+
const relativePath = relative(common, path)
|
|
2319
|
+
if (
|
|
2320
|
+
relativePath === '' ||
|
|
2321
|
+
(relativePath !== '..' &&
|
|
2322
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
2323
|
+
!isAbsolute(relativePath))
|
|
2324
|
+
) {
|
|
2325
|
+
break
|
|
2326
|
+
}
|
|
2327
|
+
const parent = dirname(common)
|
|
2328
|
+
if (parent === common) {
|
|
2329
|
+
break
|
|
2330
|
+
}
|
|
2331
|
+
common = parent
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return common
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
async function collectFilesRecursively(root: string): Promise<string[]> {
|
|
2338
|
+
const files: string[] = []
|
|
2339
|
+
for (const entry of await readdirAsync(root, { withFileTypes: true })) {
|
|
2340
|
+
const path = join(root, entry.name)
|
|
2341
|
+
if (entry.isDirectory()) {
|
|
2342
|
+
files.push(...(await collectFilesRecursively(path)))
|
|
2343
|
+
} else if (entry.isFile()) {
|
|
2344
|
+
files.push(path)
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return files.sort()
|
|
2348
|
+
}
|
|
2349
|
+
|
|
1246
2350
|
export interface WriteJsBindingOptions {
|
|
1247
2351
|
platform?: boolean
|
|
1248
2352
|
noJsBinding?: boolean
|
|
@@ -1253,21 +2357,37 @@ export interface WriteJsBindingOptions {
|
|
|
1253
2357
|
packageName: string
|
|
1254
2358
|
version: string
|
|
1255
2359
|
outputDir: string
|
|
2360
|
+
/**
|
|
2361
|
+
* `platformArchABI`s of the declared WASI targets in fallback preference
|
|
2362
|
+
* order (threaded first). Defaults to the legacy `['wasm32-wasi']` chain
|
|
2363
|
+
* when omitted or empty. Generated loaders expose these exact identities
|
|
2364
|
+
* through `NAPI_RS_WASI_FLAVOR`.
|
|
2365
|
+
*/
|
|
2366
|
+
wasiFlavors?: string[]
|
|
1256
2367
|
}
|
|
1257
2368
|
|
|
1258
2369
|
export async function writeJsBinding(
|
|
1259
2370
|
options: WriteJsBindingOptions,
|
|
1260
2371
|
): Promise<Output | undefined> {
|
|
2372
|
+
const hasWasiFallback = Boolean(options.wasiFlavors?.length)
|
|
1261
2373
|
if (
|
|
1262
2374
|
!options.platform ||
|
|
1263
2375
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
1264
2376
|
options.noJsBinding ||
|
|
1265
|
-
options.idents.length === 0
|
|
2377
|
+
(options.idents.length === 0 && !hasWasiFallback)
|
|
1266
2378
|
) {
|
|
1267
2379
|
return
|
|
1268
2380
|
}
|
|
1269
2381
|
|
|
1270
2382
|
const name = options.jsBinding ?? 'index.js'
|
|
2383
|
+
const dest = join(options.outputDir, name)
|
|
2384
|
+
const localWasiName = relative(
|
|
2385
|
+
dirname(dest),
|
|
2386
|
+
join(options.outputDir, options.binaryName),
|
|
2387
|
+
).replaceAll('\\', '/')
|
|
2388
|
+
const localWasiSpecifier = localWasiName.startsWith('.')
|
|
2389
|
+
? localWasiName
|
|
2390
|
+
: `./${localWasiName}`
|
|
1271
2391
|
|
|
1272
2392
|
const createBinding = options.esm ? createEsmBinding : createCjsBinding
|
|
1273
2393
|
const binding = createBinding(
|
|
@@ -1276,13 +2396,15 @@ export async function writeJsBinding(
|
|
|
1276
2396
|
options.idents,
|
|
1277
2397
|
// in npm preversion hook
|
|
1278
2398
|
options.version,
|
|
2399
|
+
options.wasiFlavors,
|
|
2400
|
+
localWasiSpecifier,
|
|
1279
2401
|
)
|
|
1280
2402
|
|
|
1281
2403
|
try {
|
|
1282
|
-
const dest = join(options.outputDir, name)
|
|
1283
2404
|
debug('Writing js binding to:')
|
|
1284
2405
|
debug(' %i', dest)
|
|
1285
|
-
await
|
|
2406
|
+
await mkdirAsync(dirname(dest), { recursive: true })
|
|
2407
|
+
await writeFileAtomic(dest, binding, 'utf-8')
|
|
1286
2408
|
return { kind: 'js', path: dest } satisfies Output
|
|
1287
2409
|
} catch (e) {
|
|
1288
2410
|
throw new Error('Failed to write js binding file', { cause: e })
|
|
@@ -1309,9 +2431,13 @@ export interface GenerateTypeDefOptions {
|
|
|
1309
2431
|
*/
|
|
1310
2432
|
export async function generateTypeDef(
|
|
1311
2433
|
options: GenerateTypeDefOptions,
|
|
1312
|
-
): Promise<{
|
|
2434
|
+
): Promise<{
|
|
2435
|
+
exports: string[]
|
|
2436
|
+
dts: string
|
|
2437
|
+
dtsWithTypeImports: string
|
|
2438
|
+
}> {
|
|
1313
2439
|
if (!(await dirExistsAsync(options.typeDefDir))) {
|
|
1314
|
-
return { exports: [], dts: '' }
|
|
2440
|
+
return { exports: [], dts: '', dtsWithTypeImports: '' }
|
|
1315
2441
|
}
|
|
1316
2442
|
|
|
1317
2443
|
let header = ''
|
|
@@ -1340,7 +2466,7 @@ export async function generateTypeDef(
|
|
|
1340
2466
|
|
|
1341
2467
|
if (!files.length) {
|
|
1342
2468
|
debug('No type def files found. Skip generating dts file.')
|
|
1343
|
-
return { exports: [], dts: '' }
|
|
2469
|
+
return { exports: [], dts: '', dtsWithTypeImports: '' }
|
|
1344
2470
|
}
|
|
1345
2471
|
|
|
1346
2472
|
const typeDefFiles = files
|
|
@@ -1355,18 +2481,17 @@ export async function generateTypeDef(
|
|
|
1355
2481
|
)
|
|
1356
2482
|
}
|
|
1357
2483
|
|
|
1358
|
-
const processedTypeDefs = await
|
|
1359
|
-
typeDefFiles.map((file) =>
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
runtimeStringEnum,
|
|
1364
|
-
),
|
|
1365
|
-
),
|
|
2484
|
+
const processedTypeDefs = await processTypeDefs(
|
|
2485
|
+
typeDefFiles.map((file) => join(options.typeDefDir, file.name)),
|
|
2486
|
+
constEnum,
|
|
2487
|
+
runtimeStringEnum,
|
|
2488
|
+
header,
|
|
1366
2489
|
)
|
|
1367
2490
|
|
|
1368
|
-
dts = processedTypeDefs.
|
|
1369
|
-
exports = processedTypeDefs.
|
|
2491
|
+
dts = processedTypeDefs.dts
|
|
2492
|
+
exports = processedTypeDefs.exports
|
|
2493
|
+
const dtsWithTypeImportMarkers = processedTypeDefs.dtsWithTypeImportMarkers
|
|
2494
|
+
const typeImports = processedTypeDefs.typeImports
|
|
1370
2495
|
|
|
1371
2496
|
if (dts.indexOf('ExternalObject<') > -1) {
|
|
1372
2497
|
header += `
|
|
@@ -1386,9 +2511,15 @@ export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array
|
|
|
1386
2511
|
}
|
|
1387
2512
|
|
|
1388
2513
|
dts = header + dts
|
|
2514
|
+
const dtsWithTypeImports = rewriteTypeImportReferences(
|
|
2515
|
+
header + dtsWithTypeImportMarkers,
|
|
2516
|
+
typeImports,
|
|
2517
|
+
true,
|
|
2518
|
+
)
|
|
1389
2519
|
|
|
1390
2520
|
return {
|
|
1391
2521
|
exports,
|
|
1392
2522
|
dts,
|
|
2523
|
+
dtsWithTypeImports,
|
|
1393
2524
|
}
|
|
1394
2525
|
}
|