@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
|
@@ -1,26 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lstat,
|
|
3
|
+
mkdtemp,
|
|
4
|
+
readFile,
|
|
5
|
+
readdir,
|
|
6
|
+
rm,
|
|
7
|
+
rmdir,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from 'node:fs/promises'
|
|
1
10
|
import { createRequire } from 'node:module'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { Comparator, Range, minVersion, subset } from 'semver'
|
|
11
|
+
import { tmpdir } from 'node:os'
|
|
12
|
+
import { basename, isAbsolute, join, resolve } from 'node:path'
|
|
5
13
|
|
|
6
14
|
const require = createRequire(import.meta.url)
|
|
7
|
-
const
|
|
15
|
+
const directBufferDependency = '^6.0.3'
|
|
8
16
|
|
|
9
17
|
import {
|
|
10
18
|
applyDefaultCreateNpmDirsOptions,
|
|
11
19
|
type CreateNpmDirsOptions,
|
|
12
20
|
} from '../def/create-npm-dirs.js'
|
|
13
21
|
import {
|
|
22
|
+
commitFileSystemTransaction,
|
|
23
|
+
createWasmModuleTypeDef,
|
|
14
24
|
debugFactory,
|
|
25
|
+
MINIMUM_WASI_NODE_VERSION,
|
|
26
|
+
parseTriple,
|
|
15
27
|
readNapiConfig,
|
|
16
|
-
mkdirAsync as rawMkdirAsync,
|
|
17
28
|
pick,
|
|
18
|
-
|
|
29
|
+
resolvePackageReconciliationPaths,
|
|
30
|
+
restrictWasiNodeEngine,
|
|
31
|
+
wasiLoaderSuffix,
|
|
32
|
+
wasiTargetHasThreads,
|
|
33
|
+
withFileSystemReconciliation,
|
|
34
|
+
type FileSystemTransactionWrite,
|
|
19
35
|
type Target,
|
|
20
36
|
type CommonPackageJsonFields,
|
|
21
37
|
} from '../utils/index.js'
|
|
22
38
|
|
|
23
39
|
const debug = debugFactory('create-npm-dirs')
|
|
40
|
+
const MANAGED_WASI_PACKAGE_TARGETS = new Map([
|
|
41
|
+
['wasm32-wasi', 'wasm32-wasip1-threads'],
|
|
42
|
+
['wasm32-wasip1', 'wasm32-wasip1'],
|
|
43
|
+
])
|
|
44
|
+
const MANAGED_WASI_PACKAGE_DIRS = [...MANAGED_WASI_PACKAGE_TARGETS.keys()]
|
|
24
45
|
|
|
25
46
|
export interface PackageMeta {
|
|
26
47
|
'dist-tags': { [index: string]: string }
|
|
@@ -28,6 +49,22 @@ export interface PackageMeta {
|
|
|
28
49
|
|
|
29
50
|
const WASM_RUNTIME_PACKAGE_NAME = '@napi-rs/wasm-runtime'
|
|
30
51
|
|
|
52
|
+
interface PendingMetadataWrite {
|
|
53
|
+
content: string
|
|
54
|
+
destination: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ManagedPackageDirectory {
|
|
58
|
+
name: string
|
|
59
|
+
path: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface OwnedWasiPackage {
|
|
63
|
+
binaryName: string
|
|
64
|
+
packageName: string
|
|
65
|
+
target: Target
|
|
66
|
+
}
|
|
67
|
+
|
|
31
68
|
async function getLatestWasmRuntimeVersion() {
|
|
32
69
|
const npmRegistryBase =
|
|
33
70
|
process.env.npm_config_registry?.replace(/\/?$/, '/') ??
|
|
@@ -72,57 +109,367 @@ async function getLatestWasmRuntimeVersion() {
|
|
|
72
109
|
return latestVersion.trim()
|
|
73
110
|
}
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
function assertSafeManagedPathSegment(value: string, label: string) {
|
|
113
|
+
if (
|
|
114
|
+
value.length === 0 ||
|
|
115
|
+
value === '.' ||
|
|
116
|
+
value === '..' ||
|
|
117
|
+
value.includes('\0') ||
|
|
118
|
+
value.includes('/') ||
|
|
119
|
+
value.includes('\\') ||
|
|
120
|
+
isAbsolute(value) ||
|
|
121
|
+
basename(value) !== value
|
|
122
|
+
) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`${label} must be a single filesystem path segment: ${value}`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
77
128
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
129
|
+
async function lstatIfExists(path: string) {
|
|
130
|
+
try {
|
|
131
|
+
return await lstat(path)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
81
134
|
return
|
|
82
135
|
}
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
}
|
|
83
139
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
140
|
+
async function assertSafeTargetDirectory(path: string) {
|
|
141
|
+
const stats = await lstatIfExists(path)
|
|
142
|
+
if (!stats) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (stats.isSymbolicLink()) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`npm target directory must not be a symbolic link or junction: ${path}`,
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
if (!stats.isDirectory()) {
|
|
151
|
+
throw new Error(`npm target path is not a directory: ${path}`)
|
|
87
152
|
}
|
|
153
|
+
}
|
|
88
154
|
|
|
89
|
-
|
|
90
|
-
|
|
155
|
+
async function resolveManagedPackageDirectories(
|
|
156
|
+
options: ReturnType<typeof applyDefaultCreateNpmDirsOptions>,
|
|
157
|
+
initialPaths: ReturnType<typeof resolvePackageReconciliationPaths>,
|
|
158
|
+
targets: Target[],
|
|
159
|
+
) {
|
|
160
|
+
const directoryNames = [
|
|
161
|
+
...new Set([
|
|
162
|
+
...targets.map((target) => target.platformArchABI),
|
|
163
|
+
...MANAGED_WASI_PACKAGE_DIRS,
|
|
164
|
+
]),
|
|
165
|
+
]
|
|
166
|
+
for (const directoryName of directoryNames) {
|
|
167
|
+
assertSafeManagedPathSegment(
|
|
168
|
+
directoryName,
|
|
169
|
+
`Target output identity ${directoryName}`,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
91
172
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
173
|
+
const requestedNpmPath = resolve(options.cwd, options.npmDir)
|
|
174
|
+
const requestedTargetPaths = directoryNames.map((directoryName) =>
|
|
175
|
+
join(requestedNpmPath, directoryName),
|
|
176
|
+
)
|
|
177
|
+
await Promise.all(requestedTargetPaths.map(assertSafeTargetDirectory))
|
|
178
|
+
|
|
179
|
+
const resolvedPaths = resolvePackageReconciliationPaths(
|
|
180
|
+
options.cwd,
|
|
181
|
+
options.packageJsonPath,
|
|
182
|
+
[options.npmDir, ...requestedTargetPaths],
|
|
183
|
+
)
|
|
184
|
+
if (resolvedPaths.boundary !== initialPaths.boundary) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Managed npm target paths changed the reconciliation boundary from ${initialPaths.boundary} to ${resolvedPaths.boundary}`,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return new Map(
|
|
191
|
+
directoryNames.map((name, index) => [
|
|
192
|
+
name,
|
|
193
|
+
{
|
|
194
|
+
name,
|
|
195
|
+
path: resolvedPaths.managedPaths[index + 1],
|
|
196
|
+
} satisfies ManagedPackageDirectory,
|
|
197
|
+
]),
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function managedWasiGeneratedFiles(binaryName: string, packageDir: string) {
|
|
202
|
+
assertSafeManagedPathSegment(binaryName, 'Configured binary name')
|
|
203
|
+
const targetTriple = MANAGED_WASI_PACKAGE_TARGETS.get(packageDir)
|
|
204
|
+
if (!targetTriple) {
|
|
205
|
+
return new Set<string>()
|
|
206
|
+
}
|
|
207
|
+
const target = parseTriple(targetTriple)
|
|
208
|
+
const loaderSuffix = wasiLoaderSuffix(packageDir)
|
|
209
|
+
const files = new Set([
|
|
210
|
+
`${binaryName}.${packageDir}.wasm`,
|
|
211
|
+
`${binaryName}.${packageDir}.debug.wasm`,
|
|
212
|
+
`${binaryName}.${loaderSuffix}.cjs`,
|
|
213
|
+
`${binaryName}.${loaderSuffix}.d.cts`,
|
|
214
|
+
`${binaryName}.${loaderSuffix}-browser.js`,
|
|
215
|
+
])
|
|
216
|
+
if (wasiTargetHasThreads(target)) {
|
|
217
|
+
files.add('wasi-worker.mjs')
|
|
218
|
+
files.add('wasi-worker-browser.mjs')
|
|
219
|
+
} else {
|
|
220
|
+
for (const file of [
|
|
221
|
+
`${binaryName}.${packageDir}.wasm.d.ts`,
|
|
222
|
+
`${binaryName}.${packageDir}.wasm.d.mts`,
|
|
223
|
+
`${binaryName}.${packageDir}.workerd.mjs`,
|
|
224
|
+
`${binaryName}.${packageDir}.workerd.d.mts`,
|
|
225
|
+
`${binaryName}.${loaderSuffix}-deferred.js`,
|
|
226
|
+
`${binaryName}.${loaderSuffix}-deferred.d.ts`,
|
|
227
|
+
]) {
|
|
228
|
+
files.add(file)
|
|
95
229
|
}
|
|
230
|
+
}
|
|
231
|
+
return files
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function asJsonRecord(value: unknown): Record<string, unknown> | undefined {
|
|
235
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
236
|
+
? (value as Record<string, unknown>)
|
|
237
|
+
: undefined
|
|
238
|
+
}
|
|
96
239
|
|
|
97
|
-
|
|
240
|
+
async function inspectOwnedWasiPackage(
|
|
241
|
+
directory: ManagedPackageDirectory,
|
|
242
|
+
): Promise<OwnedWasiPackage | undefined> {
|
|
243
|
+
const targetTriple = MANAGED_WASI_PACKAGE_TARGETS.get(directory.name)
|
|
244
|
+
if (!targetTriple) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
const manifestPath = join(directory.path, 'package.json')
|
|
248
|
+
const stats = await lstatIfExists(manifestPath)
|
|
249
|
+
if (!stats?.isFile()) {
|
|
250
|
+
return
|
|
98
251
|
}
|
|
99
252
|
|
|
100
|
-
|
|
101
|
-
|
|
253
|
+
let manifest: Record<string, unknown> | undefined
|
|
254
|
+
try {
|
|
255
|
+
manifest = asJsonRecord(JSON.parse(await readFile(manifestPath, 'utf8')))
|
|
256
|
+
} catch {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if (!manifest || typeof manifest.name !== 'string') {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
102
262
|
|
|
263
|
+
const packageNameSuffix = `-${directory.name}`
|
|
264
|
+
if (
|
|
265
|
+
!manifest.name.endsWith(packageNameSuffix) ||
|
|
266
|
+
manifest.name.length === packageNameSuffix.length ||
|
|
267
|
+
typeof manifest.version !== 'string' ||
|
|
268
|
+
typeof manifest.main !== 'string' ||
|
|
269
|
+
!Array.isArray(manifest.files) ||
|
|
270
|
+
!manifest.files.every((file) => typeof file === 'string') ||
|
|
271
|
+
manifest.type !== 'module'
|
|
272
|
+
) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const loaderSuffix = wasiLoaderSuffix(directory.name)
|
|
277
|
+
const mainSuffix = `.${loaderSuffix}.cjs`
|
|
278
|
+
if (
|
|
279
|
+
!manifest.main.endsWith(mainSuffix) ||
|
|
280
|
+
manifest.main.length === mainSuffix.length
|
|
281
|
+
) {
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
const binaryName = manifest.main.slice(0, -mainSuffix.length)
|
|
285
|
+
try {
|
|
286
|
+
assertSafeManagedPathSegment(binaryName, 'Managed WASI binary name')
|
|
287
|
+
} catch {
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const target = parseTriple(targetTriple)
|
|
292
|
+
const requiredFiles = [
|
|
293
|
+
`${binaryName}.${directory.name}.wasm`,
|
|
294
|
+
`${binaryName}.${loaderSuffix}.cjs`,
|
|
295
|
+
`${binaryName}.${loaderSuffix}.d.cts`,
|
|
296
|
+
`${binaryName}.${loaderSuffix}-browser.js`,
|
|
297
|
+
...(wasiTargetHasThreads(target)
|
|
298
|
+
? ['wasi-worker.mjs', 'wasi-worker-browser.mjs']
|
|
299
|
+
: [
|
|
300
|
+
`${binaryName}.${loaderSuffix}-deferred.js`,
|
|
301
|
+
`${binaryName}.${loaderSuffix}-deferred.d.ts`,
|
|
302
|
+
`${binaryName}.${directory.name}.wasm.d.ts`,
|
|
303
|
+
]),
|
|
304
|
+
]
|
|
305
|
+
const files = new Set(manifest.files)
|
|
306
|
+
if (
|
|
307
|
+
manifest.types !== `${binaryName}.${loaderSuffix}.d.cts` ||
|
|
308
|
+
manifest.browser !== `${binaryName}.${loaderSuffix}-browser.js` ||
|
|
309
|
+
!requiredFiles.every((file) => files.has(file))
|
|
310
|
+
) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
binaryName,
|
|
316
|
+
packageName: manifest.name.slice(0, -packageNameSuffix.length),
|
|
317
|
+
target,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function collectStaleWasiPackageRemovals(
|
|
322
|
+
directory: ManagedPackageDirectory,
|
|
323
|
+
binaryName: string,
|
|
324
|
+
) {
|
|
325
|
+
let entries
|
|
326
|
+
try {
|
|
327
|
+
entries = await readdir(directory.path, { withFileTypes: true })
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
330
|
+
return []
|
|
331
|
+
}
|
|
332
|
+
throw error
|
|
333
|
+
}
|
|
334
|
+
const entriesByName = new Map(entries.map((entry) => [entry.name, entry]))
|
|
335
|
+
const owner = await inspectOwnedWasiPackage(directory)
|
|
336
|
+
const generatedFiles = managedWasiGeneratedFiles(binaryName, directory.name)
|
|
337
|
+
if (owner) {
|
|
338
|
+
for (const file of managedWasiGeneratedFiles(
|
|
339
|
+
owner.binaryName,
|
|
340
|
+
directory.name,
|
|
341
|
+
)) {
|
|
342
|
+
generatedFiles.add(file)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const removals: string[] = []
|
|
347
|
+
for (const file of generatedFiles) {
|
|
348
|
+
const entry = entriesByName.get(file)
|
|
349
|
+
if (!entry) {
|
|
350
|
+
continue
|
|
351
|
+
}
|
|
352
|
+
if (!entry.isFile()) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Managed WASI package file must be a regular file: ${join(directory.path, file)}`,
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
removals.push(join(directory.path, file))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (owner) {
|
|
361
|
+
removals.push(join(directory.path, 'package.json'))
|
|
362
|
+
const readmePath = join(directory.path, 'README.md')
|
|
363
|
+
if (
|
|
364
|
+
entriesByName.get('README.md')?.isFile() &&
|
|
365
|
+
(await readFile(readmePath, 'utf8')) ===
|
|
366
|
+
readme(owner.packageName, owner.target)
|
|
367
|
+
) {
|
|
368
|
+
removals.push(readmePath)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return removals
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function publishPackageMetadata(
|
|
375
|
+
reconciliationRoot: string,
|
|
376
|
+
pendingWrites: PendingMetadataWrite[],
|
|
377
|
+
removals: string[],
|
|
378
|
+
) {
|
|
379
|
+
const stagingRoot = await mkdtemp(
|
|
380
|
+
join(tmpdir(), 'napi-rs-create-npm-dirs-stage-'),
|
|
381
|
+
)
|
|
382
|
+
try {
|
|
383
|
+
const writes: FileSystemTransactionWrite[] = []
|
|
384
|
+
for (const [index, pendingWrite] of pendingWrites.entries()) {
|
|
385
|
+
const source = join(stagingRoot, String(index))
|
|
386
|
+
await writeFile(source, pendingWrite.content)
|
|
387
|
+
writes.push({
|
|
388
|
+
destination: pendingWrite.destination,
|
|
389
|
+
source,
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
if (writes.length > 0 || removals.length > 0) {
|
|
393
|
+
await commitFileSystemTransaction(reconciliationRoot, writes, removals)
|
|
394
|
+
}
|
|
395
|
+
} finally {
|
|
396
|
+
await rm(stagingRoot, { force: true, recursive: true })
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function removeEmptyStalePackageDirectories(
|
|
401
|
+
directories: ManagedPackageDirectory[],
|
|
402
|
+
) {
|
|
403
|
+
for (const directory of directories) {
|
|
404
|
+
try {
|
|
405
|
+
await rmdir(directory.path)
|
|
406
|
+
} catch (error) {
|
|
407
|
+
const code = (error as NodeJS.ErrnoException).code
|
|
408
|
+
if (code !== 'ENOENT' && code !== 'ENOTEMPTY' && code !== 'EEXIST') {
|
|
409
|
+
debug.warn(
|
|
410
|
+
`Failed to remove empty stale npm package directory ${directory.path}: ${String(error)}`,
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function createNpmDirsUnlocked(
|
|
418
|
+
options: ReturnType<typeof applyDefaultCreateNpmDirsOptions>,
|
|
419
|
+
initialPaths: ReturnType<typeof resolvePackageReconciliationPaths>,
|
|
420
|
+
) {
|
|
421
|
+
const packageJsonPath = initialPaths.packageJsonPath
|
|
103
422
|
debug(`Read content from [${options.configPath ?? packageJsonPath}]`)
|
|
104
423
|
|
|
105
|
-
const { targets, binaryName, packageName, packageJson } =
|
|
424
|
+
const { targets, binaryName, packageName, packageJson, wasm } =
|
|
106
425
|
await readNapiConfig(
|
|
107
426
|
packageJsonPath,
|
|
108
427
|
options.configPath ? resolve(options.cwd, options.configPath) : undefined,
|
|
109
428
|
)
|
|
429
|
+
assertSafeManagedPathSegment(binaryName, 'Configured binary name')
|
|
430
|
+
const packageDirectories = await resolveManagedPackageDirectories(
|
|
431
|
+
options,
|
|
432
|
+
initialPaths,
|
|
433
|
+
targets,
|
|
434
|
+
)
|
|
435
|
+
const configuredPackageDirs = new Set(
|
|
436
|
+
targets.map((target) => target.platformArchABI),
|
|
437
|
+
)
|
|
438
|
+
const staleWasiDirectories = MANAGED_WASI_PACKAGE_DIRS.filter(
|
|
439
|
+
(packageDir) => !configuredPackageDirs.has(packageDir),
|
|
440
|
+
).map((packageDir) => packageDirectories.get(packageDir)!)
|
|
441
|
+
const staleWasiRemovals = (
|
|
442
|
+
await Promise.all(
|
|
443
|
+
staleWasiDirectories.map((directory) =>
|
|
444
|
+
collectStaleWasiPackageRemovals(directory, binaryName),
|
|
445
|
+
),
|
|
446
|
+
)
|
|
447
|
+
).flat()
|
|
110
448
|
const wasmRuntimeVersion = targets.some((target) => target.arch === 'wasm32')
|
|
111
449
|
? await getLatestWasmRuntimeVersion()
|
|
112
450
|
: undefined
|
|
451
|
+
const pendingWrites: PendingMetadataWrite[] = []
|
|
113
452
|
|
|
114
453
|
for (const target of targets) {
|
|
115
|
-
const targetDir =
|
|
116
|
-
|
|
454
|
+
const targetDir = packageDirectories.get(target.platformArchABI)!.path
|
|
455
|
+
debug('Plan npm package dir: %i', targetDir)
|
|
117
456
|
|
|
118
457
|
const binaryFileName =
|
|
119
458
|
target.arch === 'wasm32'
|
|
120
459
|
? `${binaryName}.${target.platformArchABI}.wasm`
|
|
121
460
|
: `${binaryName}.${target.platformArchABI}.node`
|
|
461
|
+
let wasmModuleTypeDef: string | undefined
|
|
122
462
|
const scopedPackageJson: CommonPackageJsonFields = {
|
|
123
463
|
name: `${packageName}-${target.platformArchABI}`,
|
|
124
464
|
version: packageJson.version,
|
|
125
|
-
|
|
465
|
+
// WASI modules execute inside a normal host Node/browser/workerd process.
|
|
466
|
+
// Marking them as cpu=wasm32 makes npm reject direct installation and
|
|
467
|
+
// silently skip the package when it is an optional dependency on x64 or
|
|
468
|
+
// arm64 hosts.
|
|
469
|
+
cpu:
|
|
470
|
+
target.arch !== 'universal' && target.arch !== 'wasm32'
|
|
471
|
+
? [target.arch]
|
|
472
|
+
: undefined,
|
|
126
473
|
main: binaryFileName,
|
|
127
474
|
files: [binaryFileName],
|
|
128
475
|
...pick(
|
|
@@ -148,26 +495,74 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
|
|
|
148
495
|
if (target.arch !== 'wasm32') {
|
|
149
496
|
scopedPackageJson.os = [target.platform]
|
|
150
497
|
} else {
|
|
151
|
-
const
|
|
498
|
+
const loaderSuffix = wasiLoaderSuffix(target.platformArchABI)
|
|
499
|
+
const entry = `${binaryName}.${loaderSuffix}.cjs`
|
|
500
|
+
const loaderTypeDef = `${binaryName}.${loaderSuffix}.d.cts`
|
|
152
501
|
scopedPackageJson.main = entry
|
|
153
|
-
scopedPackageJson.
|
|
502
|
+
scopedPackageJson.types = loaderTypeDef
|
|
503
|
+
scopedPackageJson.browser = `${binaryName}.${loaderSuffix}-browser.js`
|
|
504
|
+
scopedPackageJson.type = 'module'
|
|
154
505
|
scopedPackageJson.files?.push(
|
|
155
506
|
entry,
|
|
507
|
+
loaderTypeDef,
|
|
156
508
|
scopedPackageJson.browser,
|
|
157
|
-
`wasi-worker.mjs`,
|
|
158
|
-
`wasi-worker-browser.mjs`,
|
|
159
509
|
)
|
|
510
|
+
if (wasiTargetHasThreads(target)) {
|
|
511
|
+
// worker scripts are only referenced by the threaded loaders
|
|
512
|
+
scopedPackageJson.files?.push(
|
|
513
|
+
`wasi-worker.mjs`,
|
|
514
|
+
`wasi-worker-browser.mjs`,
|
|
515
|
+
)
|
|
516
|
+
} else {
|
|
517
|
+
const deferredEntry = `${binaryName}.${loaderSuffix}-deferred.js`
|
|
518
|
+
const deferredTypeDef = `${binaryName}.${loaderSuffix}-deferred.d.ts`
|
|
519
|
+
wasmModuleTypeDef = `${binaryFileName}.d.ts`
|
|
520
|
+
// the deferred workerd-safe loader is only emitted for non-threaded
|
|
521
|
+
// WASI builds (mirrors `hasThreads` in `writeWasiBinding`)
|
|
522
|
+
scopedPackageJson.files?.push(
|
|
523
|
+
deferredEntry,
|
|
524
|
+
deferredTypeDef,
|
|
525
|
+
wasmModuleTypeDef,
|
|
526
|
+
)
|
|
527
|
+
scopedPackageJson.exports = {
|
|
528
|
+
'.': {
|
|
529
|
+
types: `./${loaderTypeDef}`,
|
|
530
|
+
browser: `./${scopedPackageJson.browser}`,
|
|
531
|
+
require: `./${entry}`,
|
|
532
|
+
default: `./${entry}`,
|
|
533
|
+
},
|
|
534
|
+
'./workerd': {
|
|
535
|
+
types: `./${deferredTypeDef}`,
|
|
536
|
+
default: `./${deferredEntry}`,
|
|
537
|
+
},
|
|
538
|
+
'./wasm': {
|
|
539
|
+
types: `./${wasmModuleTypeDef}`,
|
|
540
|
+
default: `./${binaryFileName}`,
|
|
541
|
+
},
|
|
542
|
+
'./wasm.wasm': {
|
|
543
|
+
types: `./${wasmModuleTypeDef}`,
|
|
544
|
+
default: `./${binaryFileName}`,
|
|
545
|
+
},
|
|
546
|
+
'./package.json': './package.json',
|
|
547
|
+
}
|
|
548
|
+
}
|
|
160
549
|
scopedPackageJson.engines = {
|
|
161
550
|
...scopedPackageJson.engines,
|
|
162
551
|
node: scopedPackageJson.engines?.node
|
|
163
552
|
? restrictWasiNodeEngine(scopedPackageJson.engines.node)
|
|
164
|
-
:
|
|
553
|
+
: MINIMUM_WASI_NODE_VERSION,
|
|
165
554
|
}
|
|
166
555
|
const emnapiVersion = require('emnapi/package.json').version
|
|
167
556
|
scopedPackageJson.dependencies = {
|
|
168
|
-
|
|
557
|
+
// Runtime minor releases can target a different emnapi generation.
|
|
558
|
+
// Keep generated packages on the resolved minor while allowing fixes.
|
|
559
|
+
'@napi-rs/wasm-runtime': `~${wasmRuntimeVersion}`,
|
|
169
560
|
'@emnapi/core': emnapiVersion,
|
|
170
561
|
'@emnapi/runtime': emnapiVersion,
|
|
562
|
+
...(wasm?.browser?.buffer === true &&
|
|
563
|
+
(wasm.browser.fs !== true || !wasiTargetHasThreads(target))
|
|
564
|
+
? { buffer: directBufferDependency }
|
|
565
|
+
: {}),
|
|
171
566
|
}
|
|
172
567
|
}
|
|
173
568
|
|
|
@@ -178,86 +573,64 @@ export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
|
|
|
178
573
|
}
|
|
179
574
|
|
|
180
575
|
const targetPackageJson = join(targetDir, 'package.json')
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
576
|
+
pendingWrites.push({
|
|
577
|
+
content: JSON.stringify(scopedPackageJson, null, 2) + '\n',
|
|
578
|
+
destination: targetPackageJson,
|
|
579
|
+
})
|
|
580
|
+
if (wasmModuleTypeDef) {
|
|
581
|
+
pendingWrites.push({
|
|
582
|
+
content: createWasmModuleTypeDef(),
|
|
583
|
+
destination: join(targetDir, wasmModuleTypeDef),
|
|
584
|
+
})
|
|
585
|
+
}
|
|
185
586
|
const targetReadme = join(targetDir, 'README.md')
|
|
186
|
-
|
|
587
|
+
pendingWrites.push({
|
|
588
|
+
content: readme(packageName, target),
|
|
589
|
+
destination: targetReadme,
|
|
590
|
+
})
|
|
187
591
|
|
|
188
592
|
debug.info(`${packageName} -${target.platformArchABI} created`)
|
|
189
593
|
}
|
|
190
|
-
}
|
|
191
594
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
`
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function restrictWasiNodeEngine(nodeRange: string) {
|
|
200
|
-
try {
|
|
201
|
-
if (subset(nodeRange, minimumWasiNodeVersion)) {
|
|
202
|
-
return nodeRange
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (subset(minimumWasiNodeVersion, nodeRange)) {
|
|
206
|
-
return minimumWasiNodeVersion
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const minimumComparator = new Comparator(minimumWasiNodeVersion)
|
|
210
|
-
const restrictedRangeSets = new Range(nodeRange).set
|
|
211
|
-
.map((comparators) =>
|
|
212
|
-
normalizeComparatorSet([...comparators, minimumComparator]),
|
|
213
|
-
)
|
|
214
|
-
.filter(
|
|
215
|
-
(candidate): candidate is string =>
|
|
216
|
-
candidate !== undefined && minVersion(candidate) !== null,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
if (restrictedRangeSets.length > 0) {
|
|
220
|
-
return restrictedRangeSets.join(' || ')
|
|
595
|
+
for (const { content, destination } of pendingWrites) {
|
|
596
|
+
debug('Writing file %i', destination)
|
|
597
|
+
if (options.dryRun) {
|
|
598
|
+
debug(content)
|
|
221
599
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
600
|
+
}
|
|
601
|
+
for (const removal of staleWasiRemovals) {
|
|
602
|
+
debug('Removing stale managed file %i', removal)
|
|
603
|
+
}
|
|
604
|
+
if (options.dryRun) {
|
|
605
|
+
return
|
|
224
606
|
}
|
|
225
607
|
|
|
226
|
-
|
|
608
|
+
await publishPackageMetadata(
|
|
609
|
+
initialPaths.boundary,
|
|
610
|
+
pendingWrites,
|
|
611
|
+
staleWasiRemovals,
|
|
612
|
+
)
|
|
613
|
+
await removeEmptyStalePackageDirectories(staleWasiDirectories)
|
|
227
614
|
}
|
|
228
615
|
|
|
229
|
-
function
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
let upperBound: Comparator | undefined
|
|
239
|
-
|
|
240
|
-
for (const comparator of comparators) {
|
|
241
|
-
if (comparator.operator === '>' || comparator.operator === '>=') {
|
|
242
|
-
if (
|
|
243
|
-
!lowerBound ||
|
|
244
|
-
comparator.semver.compare(lowerBound.semver) > 0 ||
|
|
245
|
-
(comparator.semver.compare(lowerBound.semver) === 0 &&
|
|
246
|
-
comparator.operator === '>')
|
|
247
|
-
) {
|
|
248
|
-
lowerBound = comparator
|
|
249
|
-
}
|
|
250
|
-
} else if (comparator.operator === '<' || comparator.operator === '<=') {
|
|
251
|
-
if (
|
|
252
|
-
!upperBound ||
|
|
253
|
-
comparator.semver.compare(upperBound.semver) < 0 ||
|
|
254
|
-
(comparator.semver.compare(upperBound.semver) === 0 &&
|
|
255
|
-
comparator.operator === '<')
|
|
256
|
-
) {
|
|
257
|
-
upperBound = comparator
|
|
258
|
-
}
|
|
259
|
-
}
|
|
616
|
+
export async function createNpmDirs(userOptions: CreateNpmDirsOptions) {
|
|
617
|
+
const options = applyDefaultCreateNpmDirsOptions(userOptions)
|
|
618
|
+
const resolvedPaths = resolvePackageReconciliationPaths(
|
|
619
|
+
options.cwd,
|
|
620
|
+
options.packageJsonPath,
|
|
621
|
+
[options.npmDir],
|
|
622
|
+
)
|
|
623
|
+
if (options.dryRun) {
|
|
624
|
+
return createNpmDirsUnlocked(options, resolvedPaths)
|
|
260
625
|
}
|
|
626
|
+
return withFileSystemReconciliation(resolvedPaths.boundary, () =>
|
|
627
|
+
createNpmDirsUnlocked(options, resolvedPaths),
|
|
628
|
+
)
|
|
629
|
+
}
|
|
261
630
|
|
|
262
|
-
|
|
631
|
+
function readme(packageName: string, target: Target) {
|
|
632
|
+
return `# \`${packageName}-${target.platformArchABI}\`
|
|
633
|
+
|
|
634
|
+
This is the **${target.triple}** binary for \`${packageName}\`
|
|
635
|
+
`
|
|
263
636
|
}
|