@napi-rs/cli 3.7.4 → 3.8.1
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 +9249 -1312
- package/dist/index.cjs +9269 -1332
- package/dist/index.d.cts +175 -157
- package/dist/index.d.ts +175 -157
- package/dist/index.js +9249 -1312
- 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 +60 -15
- package/src/api/__tests__/new.spec.ts +41 -5
- package/src/api/artifacts.ts +934 -108
- package/src/api/build.ts +1317 -188
- package/src/api/create-npm-dirs.ts +475 -102
- 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__/__snapshots__/target.spec.ts.md +2 -2
- package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
- 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 +155 -47
- 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,35 +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,
|
|
19
30
|
getNapiDeriveDependentCrates,
|
|
31
|
+
getPackageReconciliationRoot,
|
|
20
32
|
getTargetLinker,
|
|
21
33
|
mkdirAsync,
|
|
22
34
|
type NapiConfig,
|
|
23
35
|
parseMetadata,
|
|
24
36
|
parseTriple,
|
|
25
|
-
|
|
37
|
+
processTypeDefs,
|
|
26
38
|
readFileAsync,
|
|
27
39
|
readNapiConfig,
|
|
40
|
+
rebaseDeclarationSpecifiers,
|
|
41
|
+
removeNodeStreamWebTypeImports,
|
|
42
|
+
rewriteUnboundNodeGlobalTypeQueries,
|
|
43
|
+
rewriteTypeImportReferences,
|
|
28
44
|
type Target,
|
|
29
45
|
targetToEnvVar,
|
|
30
46
|
tryInstallCargoBinary,
|
|
31
47
|
unlinkAsync,
|
|
32
|
-
|
|
48
|
+
wasiLoaderSuffix,
|
|
49
|
+
wasiTargetHasThreads,
|
|
50
|
+
writeFileAtomic,
|
|
51
|
+
withFileSystemReconciliation,
|
|
33
52
|
dirExistsAsync,
|
|
34
53
|
readdirAsync,
|
|
35
54
|
type CargoWorkspaceMetadata,
|
|
@@ -39,6 +58,8 @@ import { createCjsBinding, createEsmBinding } from './templates/index.js'
|
|
|
39
58
|
import {
|
|
40
59
|
createWasiBinding,
|
|
41
60
|
createWasiBrowserBinding,
|
|
61
|
+
createWasiDeferredBrowserBinding,
|
|
62
|
+
createWasiDeferredBrowserBindingTypeDef,
|
|
42
63
|
} from './templates/load-wasi-template.js'
|
|
43
64
|
import {
|
|
44
65
|
createWasiBrowserWorkerBinding,
|
|
@@ -47,13 +68,507 @@ import {
|
|
|
47
68
|
|
|
48
69
|
const debug = debugFactory('build')
|
|
49
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
|
|
50
79
|
|
|
51
80
|
type OutputKind = 'js' | 'dts' | 'node' | 'exe' | 'wasm'
|
|
52
81
|
type Output = { kind: OutputKind; path: string }
|
|
82
|
+
type WasiBindingMetadata = {
|
|
83
|
+
exports: string[]
|
|
84
|
+
bindingTypeDef?: string
|
|
85
|
+
}
|
|
53
86
|
|
|
54
87
|
type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
|
|
55
88
|
type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
|
|
56
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
|
+
|
|
57
572
|
export async function buildProject(rawOptions: BuildOptions) {
|
|
58
573
|
debug('napi build command receive options: %O', rawOptions)
|
|
59
574
|
|
|
@@ -80,10 +595,17 @@ export async function buildProject(rawOptions: BuildOptions) {
|
|
|
80
595
|
const resolvePath = (...paths: string[]) => resolve(options.cwd, ...paths)
|
|
81
596
|
|
|
82
597
|
const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
|
|
598
|
+
const metadataTarget = options.target ?? process.env.CARGO_BUILD_TARGET
|
|
83
599
|
const metadata = await parseMetadata(manifestPath, {
|
|
600
|
+
cwd: options.cwd,
|
|
601
|
+
featurePackage: options.package,
|
|
84
602
|
features: options.features,
|
|
85
603
|
allFeatures: options.allFeatures,
|
|
86
604
|
noDefaultFeatures: options.noDefaultFeatures,
|
|
605
|
+
cargoOptions: options.cargoOptions,
|
|
606
|
+
filterPlatform: metadataTarget
|
|
607
|
+
? parseTriple(metadataTarget).triple
|
|
608
|
+
: undefined,
|
|
87
609
|
})
|
|
88
610
|
|
|
89
611
|
const crate = metadata.packages.find((p) => {
|
|
@@ -396,9 +918,13 @@ class Builder {
|
|
|
396
918
|
|
|
397
919
|
private readonly target: Target
|
|
398
920
|
private readonly crateDir: string
|
|
399
|
-
private readonly
|
|
921
|
+
private readonly finalOutputDir: string
|
|
922
|
+
private outputDir: string
|
|
923
|
+
private readonly reconciliationRoot: string
|
|
400
924
|
private readonly targetDir: string
|
|
401
925
|
private readonly enableTypeDef: boolean = false
|
|
926
|
+
private readonly stagedOutputDestinations = new Map<string, string>()
|
|
927
|
+
private typeDefWithTypeImports: string | undefined
|
|
402
928
|
|
|
403
929
|
constructor(
|
|
404
930
|
private readonly metadata: CargoWorkspaceMetadata,
|
|
@@ -408,10 +934,15 @@ class Builder {
|
|
|
408
934
|
) {
|
|
409
935
|
this.target = resolveTarget(options.target)
|
|
410
936
|
this.crateDir = parse(crate.manifest_path).dir
|
|
411
|
-
this.
|
|
937
|
+
this.finalOutputDir = resolve(
|
|
412
938
|
this.options.cwd,
|
|
413
939
|
options.outputDir ?? this.crateDir,
|
|
414
940
|
)
|
|
941
|
+
this.outputDir = this.finalOutputDir
|
|
942
|
+
this.reconciliationRoot = getPackageReconciliationRoot(
|
|
943
|
+
this.options.cwd,
|
|
944
|
+
this.options.packageJsonPath,
|
|
945
|
+
)
|
|
415
946
|
this.targetDir =
|
|
416
947
|
options.targetDir ??
|
|
417
948
|
process.env.CARGO_BUILD_TARGET_DIR ??
|
|
@@ -460,7 +991,7 @@ class Builder {
|
|
|
460
991
|
)
|
|
461
992
|
}
|
|
462
993
|
|
|
463
|
-
build() {
|
|
994
|
+
async build() {
|
|
464
995
|
// Backstop only: `buildProject()` already validated these before running
|
|
465
996
|
// anything with a side effect (see the top of `buildProject`). Kept here
|
|
466
997
|
// so a directly constructed `Builder` cannot skip the validation.
|
|
@@ -484,14 +1015,13 @@ class Builder {
|
|
|
484
1015
|
}
|
|
485
1016
|
}
|
|
486
1017
|
|
|
487
|
-
|
|
1018
|
+
this.pickBinary()
|
|
488
1019
|
.setPackage()
|
|
489
1020
|
.setFeatures()
|
|
490
1021
|
.setTarget()
|
|
491
1022
|
.pickCrossToolchain()
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
.exec()
|
|
1023
|
+
await this.setEnvs()
|
|
1024
|
+
return this.setBypassArgs().exec()
|
|
495
1025
|
}
|
|
496
1026
|
|
|
497
1027
|
private pickCrossToolchain() {
|
|
@@ -588,6 +1118,100 @@ class Builder {
|
|
|
588
1118
|
}
|
|
589
1119
|
}
|
|
590
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
|
+
|
|
591
1215
|
private pickBinary() {
|
|
592
1216
|
let set = false
|
|
593
1217
|
if (this.options.watch) {
|
|
@@ -674,14 +1298,7 @@ class Builder {
|
|
|
674
1298
|
return this
|
|
675
1299
|
}
|
|
676
1300
|
|
|
677
|
-
private setEnvs() {
|
|
678
|
-
// TYPE DEF
|
|
679
|
-
if (this.enableTypeDef) {
|
|
680
|
-
this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
|
|
681
|
-
this.generateIntermediateTypeDefFolder()
|
|
682
|
-
this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
|
|
683
|
-
}
|
|
684
|
-
|
|
1301
|
+
private async setEnvs() {
|
|
685
1302
|
// RUSTFLAGS
|
|
686
1303
|
let rustflags =
|
|
687
1304
|
process.env.RUSTFLAGS ?? process.env.CARGO_BUILD_RUSTFLAGS ?? ''
|
|
@@ -697,6 +1314,13 @@ class Builder {
|
|
|
697
1314
|
rustflags += ' -C link-arg=-s'
|
|
698
1315
|
}
|
|
699
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
|
+
|
|
700
1324
|
if (rustflags.length) {
|
|
701
1325
|
this.envs.RUSTFLAGS = rustflags
|
|
702
1326
|
}
|
|
@@ -785,17 +1409,37 @@ class Builder {
|
|
|
785
1409
|
}
|
|
786
1410
|
|
|
787
1411
|
private setWasiEnv() {
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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',
|
|
793
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
|
+
}
|
|
794
1426
|
this.envs.EMNAPI_LINK_DIR = emnapi
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
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')
|
|
799
1443
|
|
|
800
1444
|
if (
|
|
801
1445
|
emnapiVersion !== emnapiCoreVersion ||
|
|
@@ -838,18 +1482,18 @@ class Builder {
|
|
|
838
1482
|
'TARGET_RANLIB',
|
|
839
1483
|
join(WASI_SDK_PATH, 'bin', 'ranlib'),
|
|
840
1484
|
)
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
)
|
|
849
|
-
this.setEnvIfNotExists(
|
|
850
|
-
`TARGET_LDFLAGS`,
|
|
851
|
-
`-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,
|
|
852
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)
|
|
853
1497
|
}
|
|
854
1498
|
}
|
|
855
1499
|
|
|
@@ -944,58 +1588,198 @@ class Builder {
|
|
|
944
1588
|
return this
|
|
945
1589
|
}
|
|
946
1590
|
|
|
947
|
-
private generateIntermediateTypeDefFolder() {
|
|
948
|
-
|
|
949
|
-
this.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
+
})
|
|
957
1623
|
|
|
958
1624
|
if (!this.options.dtsCache) {
|
|
959
|
-
|
|
960
|
-
folder
|
|
1625
|
+
await mkdirAsync(dirname(folder), { recursive: true })
|
|
1626
|
+
folder = await mkdtempAsync(`${folder}-`)
|
|
1627
|
+
} else {
|
|
1628
|
+
await mkdirAsync(folder, { recursive: true })
|
|
961
1629
|
}
|
|
962
1630
|
|
|
963
|
-
mkdirAsync(folder, { recursive: true })
|
|
964
|
-
|
|
965
1631
|
return folder
|
|
966
1632
|
}
|
|
967
1633
|
|
|
968
|
-
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
|
|
969
1694
|
try {
|
|
970
1695
|
debug(`Try to create output directory:`)
|
|
971
|
-
debug(' %i',
|
|
972
|
-
await mkdirAsync(
|
|
1696
|
+
debug(' %i', finalOutputDir)
|
|
1697
|
+
await mkdirAsync(finalOutputDir, { recursive: true })
|
|
973
1698
|
debug(`Output directory created`)
|
|
974
1699
|
} catch (e) {
|
|
975
|
-
throw new Error(`Failed to create output directory ${
|
|
1700
|
+
throw new Error(`Failed to create output directory ${finalOutputDir}`, {
|
|
976
1701
|
cause: e,
|
|
977
1702
|
})
|
|
978
1703
|
}
|
|
979
1704
|
|
|
980
|
-
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
|
|
981
1714
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
idents
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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
|
+
}
|
|
995
1732
|
}
|
|
996
|
-
}
|
|
997
1733
|
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1783
|
}
|
|
1000
1784
|
|
|
1001
1785
|
private async copyArtifact() {
|
|
@@ -1010,49 +1794,59 @@ class Builder {
|
|
|
1010
1794
|
debug(`Copy artifact from: [${src}]`)
|
|
1011
1795
|
const dest = join(this.outputDir, destName)
|
|
1012
1796
|
const isWasm = dest.endsWith('.wasm')
|
|
1797
|
+
const debugDest = isWasm
|
|
1798
|
+
? dest.replace(/\.wasm$/, '.debug.wasm')
|
|
1799
|
+
: undefined
|
|
1800
|
+
let artifactReplaced = false
|
|
1013
1801
|
|
|
1014
1802
|
try {
|
|
1015
|
-
if (await fileExists(dest)) {
|
|
1016
|
-
debug('Old artifact found, remove it first')
|
|
1017
|
-
await unlinkAsync(dest)
|
|
1018
|
-
}
|
|
1019
1803
|
debug('Copy artifact to:')
|
|
1020
1804
|
debug(' %i', dest)
|
|
1021
1805
|
if (isWasm) {
|
|
1022
1806
|
const { ModuleConfig } = await import('@napi-rs/wasm-tools')
|
|
1023
1807
|
debug('Generate debug wasm module')
|
|
1024
1808
|
try {
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
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',
|
|
1036
1819
|
)
|
|
1820
|
+
await writeFileAtomic(debugDest!, debugWasmBinary)
|
|
1037
1821
|
debug('Generate release wasm module')
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
|
1048
1836
|
} catch (e) {
|
|
1049
1837
|
debug.warn(
|
|
1050
1838
|
`Failed to generate debug wasm module: ${(e as any).message ?? e}`,
|
|
1051
1839
|
)
|
|
1052
|
-
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)
|
|
1053
1846
|
}
|
|
1054
1847
|
} else {
|
|
1055
|
-
await
|
|
1848
|
+
await copyFileAtomic(src, dest)
|
|
1849
|
+
artifactReplaced = true
|
|
1056
1850
|
}
|
|
1057
1851
|
this.outputs.push({
|
|
1058
1852
|
kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
|
|
@@ -1060,6 +1854,13 @@ class Builder {
|
|
|
1060
1854
|
})
|
|
1061
1855
|
return wasmBinaryName ? join(this.outputDir, wasmBinaryName) : null
|
|
1062
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
|
+
}
|
|
1063
1864
|
throw new Error('Failed to copy artifact', { cause: e })
|
|
1064
1865
|
}
|
|
1065
1866
|
}
|
|
@@ -1067,7 +1868,14 @@ class Builder {
|
|
|
1067
1868
|
private getArtifactNames() {
|
|
1068
1869
|
if (this.cdyLibName) {
|
|
1069
1870
|
const cdyLib = this.cdyLibName.replace(/-/g, '_')
|
|
1070
|
-
|
|
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')
|
|
1071
1879
|
|
|
1072
1880
|
const srcName =
|
|
1073
1881
|
this.target.platform === 'darwin'
|
|
@@ -1078,18 +1886,12 @@ class Builder {
|
|
|
1078
1886
|
? `${cdyLib}.wasm`
|
|
1079
1887
|
: `lib${cdyLib}.so`
|
|
1080
1888
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
}
|
|
1088
|
-
if (srcName.endsWith('.wasm')) {
|
|
1089
|
-
destName += '.wasm'
|
|
1090
|
-
} else {
|
|
1091
|
-
destName += '.node'
|
|
1092
|
-
}
|
|
1889
|
+
const destName = createArtifactDestinationName(
|
|
1890
|
+
this.config.binaryName,
|
|
1891
|
+
this.target,
|
|
1892
|
+
srcName,
|
|
1893
|
+
this.options.platform ?? false,
|
|
1894
|
+
)
|
|
1093
1895
|
|
|
1094
1896
|
return [
|
|
1095
1897
|
srcName,
|
|
@@ -1114,7 +1916,7 @@ class Builder {
|
|
|
1114
1916
|
return []
|
|
1115
1917
|
}
|
|
1116
1918
|
|
|
1117
|
-
const { exports, dts } = await generateTypeDef({
|
|
1919
|
+
const { exports, dts, dtsWithTypeImports } = await generateTypeDef({
|
|
1118
1920
|
typeDefDir,
|
|
1119
1921
|
noDtsHeader: this.options.noDtsHeader,
|
|
1120
1922
|
dtsHeader: this.options.dtsHeader,
|
|
@@ -1125,27 +1927,77 @@ class Builder {
|
|
|
1125
1927
|
this.options.runtimeStringEnum ?? this.config.runtimeStringEnum,
|
|
1126
1928
|
cwd: this.options.cwd,
|
|
1127
1929
|
})
|
|
1930
|
+
this.typeDefWithTypeImports = dtsWithTypeImports
|
|
1128
1931
|
|
|
1129
|
-
const
|
|
1932
|
+
const typeDefRelativePath = this.options.dts ?? 'index.d.ts'
|
|
1933
|
+
const finalDest = join(this.finalOutputDir, typeDefRelativePath)
|
|
1934
|
+
const dest = this.getStagedOutputPath(finalDest)
|
|
1130
1935
|
|
|
1131
1936
|
try {
|
|
1132
1937
|
debug('Writing type def to:')
|
|
1133
1938
|
debug(' %i', dest)
|
|
1134
|
-
|
|
1939
|
+
assertWritableOutputDestination(finalDest)
|
|
1940
|
+
await mkdirAsync(dirname(dest), { recursive: true })
|
|
1941
|
+
await writeFileAtomic(dest, dts, 'utf-8')
|
|
1135
1942
|
} catch (e) {
|
|
1136
|
-
|
|
1137
|
-
debug.error(e as Error)
|
|
1943
|
+
throw new Error(`Failed to write type def file ${dest}`, { cause: e })
|
|
1138
1944
|
}
|
|
1139
1945
|
|
|
1140
1946
|
if (exports.length > 0) {
|
|
1141
|
-
const dest = join(this.outputDir, this.options.dts ?? 'index.d.ts')
|
|
1142
1947
|
this.outputs.push({ kind: 'dts', path: dest })
|
|
1143
1948
|
}
|
|
1144
1949
|
|
|
1145
1950
|
return exports
|
|
1146
1951
|
}
|
|
1147
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
|
+
|
|
1148
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
|
+
]
|
|
1149
2001
|
return writeJsBinding({
|
|
1150
2002
|
platform: this.options.platform,
|
|
1151
2003
|
noJsBinding: this.options.noJsBinding,
|
|
@@ -1156,6 +2008,7 @@ class Builder {
|
|
|
1156
2008
|
packageName: this.options.jsPackageName ?? this.config.packageName,
|
|
1157
2009
|
version: process.env.npm_new_version ?? this.config.packageJson.version,
|
|
1158
2010
|
outputDir: this.outputDir,
|
|
2011
|
+
wasiFlavors,
|
|
1159
2012
|
})
|
|
1160
2013
|
}
|
|
1161
2014
|
|
|
@@ -1164,78 +2017,248 @@ class Builder {
|
|
|
1164
2017
|
idents: string[],
|
|
1165
2018
|
) {
|
|
1166
2019
|
if (distFileName) {
|
|
1167
|
-
const {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
.
|
|
1180
|
-
|
|
1181
|
-
|
|
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',
|
|
1182
2085
|
)
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
+
) +
|
|
1186
2135
|
createWasiBinding(
|
|
1187
2136
|
name,
|
|
1188
2137
|
this.config.packageName,
|
|
1189
2138
|
this.config.wasm?.initialMemory,
|
|
1190
2139
|
this.config.wasm?.maximumMemory,
|
|
2140
|
+
hasThreads,
|
|
2141
|
+
wasiTarget.platformArchABI,
|
|
2142
|
+
`${this.config.binaryName}.${wasiTarget.platformArchABI}`,
|
|
1191
2143
|
) +
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
+
),
|
|
1194
2222
|
'utf8',
|
|
1195
2223
|
)
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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(
|
|
1199
2241
|
name,
|
|
1200
2242
|
this.config.wasm?.initialMemory,
|
|
1201
2243
|
this.config.wasm?.maximumMemory,
|
|
1202
|
-
this.config.wasm?.browser?.fs,
|
|
1203
|
-
this.config.wasm?.browser?.asyncInit,
|
|
1204
2244
|
this.config.wasm?.browser?.buffer,
|
|
1205
|
-
|
|
1206
|
-
) +
|
|
1207
|
-
`export default __napiModule.exports\n` +
|
|
1208
|
-
idents
|
|
1209
|
-
.map(
|
|
1210
|
-
(ident) =>
|
|
1211
|
-
`export const ${ident} = __napiModule.exports.${ident}`,
|
|
1212
|
-
)
|
|
1213
|
-
.join('\n') +
|
|
1214
|
-
'\n',
|
|
2245
|
+
),
|
|
1215
2246
|
'utf8',
|
|
1216
2247
|
)
|
|
1217
|
-
await
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
this.
|
|
1222
|
-
this.config.wasm?.browser?.errorEvent ?? false,
|
|
2248
|
+
await writeFileAtomic(
|
|
2249
|
+
deferredTypeDefPath,
|
|
2250
|
+
createWasiDeferredBindingTypeDef(
|
|
2251
|
+
`./${this.config.binaryName}.${loaderSuffix}.cjs`,
|
|
2252
|
+
this.enableTypeDef,
|
|
1223
2253
|
),
|
|
1224
2254
|
'utf8',
|
|
1225
2255
|
)
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
2256
|
+
outputs.push(
|
|
2257
|
+
{ kind: 'js', path: deferredBindingPath },
|
|
2258
|
+
{ kind: 'dts', path: deferredTypeDefPath },
|
|
1229
2259
|
)
|
|
1230
|
-
return [
|
|
1231
|
-
{ kind: 'js', path: bindingPath },
|
|
1232
|
-
{ kind: 'js', path: browserBindingPath },
|
|
1233
|
-
{ kind: 'js', path: workerPath },
|
|
1234
|
-
{ kind: 'js', path: browserWorkerPath },
|
|
1235
|
-
{ kind: 'js', path: browserEntryPath },
|
|
1236
|
-
] satisfies Output[]
|
|
1237
2260
|
}
|
|
1238
|
-
return
|
|
2261
|
+
return outputs
|
|
1239
2262
|
}
|
|
1240
2263
|
|
|
1241
2264
|
private setEnvIfNotExists(env: string, value: string) {
|
|
@@ -1245,6 +2268,85 @@ class Builder {
|
|
|
1245
2268
|
}
|
|
1246
2269
|
}
|
|
1247
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
|
+
|
|
1248
2350
|
export interface WriteJsBindingOptions {
|
|
1249
2351
|
platform?: boolean
|
|
1250
2352
|
noJsBinding?: boolean
|
|
@@ -1255,21 +2357,37 @@ export interface WriteJsBindingOptions {
|
|
|
1255
2357
|
packageName: string
|
|
1256
2358
|
version: string
|
|
1257
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[]
|
|
1258
2367
|
}
|
|
1259
2368
|
|
|
1260
2369
|
export async function writeJsBinding(
|
|
1261
2370
|
options: WriteJsBindingOptions,
|
|
1262
2371
|
): Promise<Output | undefined> {
|
|
2372
|
+
const hasWasiFallback = Boolean(options.wasiFlavors?.length)
|
|
1263
2373
|
if (
|
|
1264
2374
|
!options.platform ||
|
|
1265
2375
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
1266
2376
|
options.noJsBinding ||
|
|
1267
|
-
options.idents.length === 0
|
|
2377
|
+
(options.idents.length === 0 && !hasWasiFallback)
|
|
1268
2378
|
) {
|
|
1269
2379
|
return
|
|
1270
2380
|
}
|
|
1271
2381
|
|
|
1272
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}`
|
|
1273
2391
|
|
|
1274
2392
|
const createBinding = options.esm ? createEsmBinding : createCjsBinding
|
|
1275
2393
|
const binding = createBinding(
|
|
@@ -1278,13 +2396,15 @@ export async function writeJsBinding(
|
|
|
1278
2396
|
options.idents,
|
|
1279
2397
|
// in npm preversion hook
|
|
1280
2398
|
options.version,
|
|
2399
|
+
options.wasiFlavors,
|
|
2400
|
+
localWasiSpecifier,
|
|
1281
2401
|
)
|
|
1282
2402
|
|
|
1283
2403
|
try {
|
|
1284
|
-
const dest = join(options.outputDir, name)
|
|
1285
2404
|
debug('Writing js binding to:')
|
|
1286
2405
|
debug(' %i', dest)
|
|
1287
|
-
await
|
|
2406
|
+
await mkdirAsync(dirname(dest), { recursive: true })
|
|
2407
|
+
await writeFileAtomic(dest, binding, 'utf-8')
|
|
1288
2408
|
return { kind: 'js', path: dest } satisfies Output
|
|
1289
2409
|
} catch (e) {
|
|
1290
2410
|
throw new Error('Failed to write js binding file', { cause: e })
|
|
@@ -1311,9 +2431,13 @@ export interface GenerateTypeDefOptions {
|
|
|
1311
2431
|
*/
|
|
1312
2432
|
export async function generateTypeDef(
|
|
1313
2433
|
options: GenerateTypeDefOptions,
|
|
1314
|
-
): Promise<{
|
|
2434
|
+
): Promise<{
|
|
2435
|
+
exports: string[]
|
|
2436
|
+
dts: string
|
|
2437
|
+
dtsWithTypeImports: string
|
|
2438
|
+
}> {
|
|
1315
2439
|
if (!(await dirExistsAsync(options.typeDefDir))) {
|
|
1316
|
-
return { exports: [], dts: '' }
|
|
2440
|
+
return { exports: [], dts: '', dtsWithTypeImports: '' }
|
|
1317
2441
|
}
|
|
1318
2442
|
|
|
1319
2443
|
let header = ''
|
|
@@ -1342,7 +2466,7 @@ export async function generateTypeDef(
|
|
|
1342
2466
|
|
|
1343
2467
|
if (!files.length) {
|
|
1344
2468
|
debug('No type def files found. Skip generating dts file.')
|
|
1345
|
-
return { exports: [], dts: '' }
|
|
2469
|
+
return { exports: [], dts: '', dtsWithTypeImports: '' }
|
|
1346
2470
|
}
|
|
1347
2471
|
|
|
1348
2472
|
const typeDefFiles = files
|
|
@@ -1357,18 +2481,17 @@ export async function generateTypeDef(
|
|
|
1357
2481
|
)
|
|
1358
2482
|
}
|
|
1359
2483
|
|
|
1360
|
-
const processedTypeDefs = await
|
|
1361
|
-
typeDefFiles.map((file) =>
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
runtimeStringEnum,
|
|
1366
|
-
),
|
|
1367
|
-
),
|
|
2484
|
+
const processedTypeDefs = await processTypeDefs(
|
|
2485
|
+
typeDefFiles.map((file) => join(options.typeDefDir, file.name)),
|
|
2486
|
+
constEnum,
|
|
2487
|
+
runtimeStringEnum,
|
|
2488
|
+
header,
|
|
1368
2489
|
)
|
|
1369
2490
|
|
|
1370
|
-
dts = processedTypeDefs.
|
|
1371
|
-
exports = processedTypeDefs.
|
|
2491
|
+
dts = processedTypeDefs.dts
|
|
2492
|
+
exports = processedTypeDefs.exports
|
|
2493
|
+
const dtsWithTypeImportMarkers = processedTypeDefs.dtsWithTypeImportMarkers
|
|
2494
|
+
const typeImports = processedTypeDefs.typeImports
|
|
1372
2495
|
|
|
1373
2496
|
if (dts.indexOf('ExternalObject<') > -1) {
|
|
1374
2497
|
header += `
|
|
@@ -1388,9 +2511,15 @@ export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array
|
|
|
1388
2511
|
}
|
|
1389
2512
|
|
|
1390
2513
|
dts = header + dts
|
|
2514
|
+
const dtsWithTypeImports = rewriteTypeImportReferences(
|
|
2515
|
+
header + dtsWithTypeImportMarkers,
|
|
2516
|
+
typeImports,
|
|
2517
|
+
true,
|
|
2518
|
+
)
|
|
1391
2519
|
|
|
1392
2520
|
return {
|
|
1393
2521
|
exports,
|
|
1394
2522
|
dts,
|
|
2523
|
+
dtsWithTypeImports,
|
|
1395
2524
|
}
|
|
1396
2525
|
}
|