@napi-rs/cli 3.7.1 → 3.7.3
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/dist/cli.js +213 -63
- package/dist/index.cjs +212 -62
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +213 -63
- package/package.json +4 -4
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +353 -0
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/android-build.spec.ts +113 -0
- package/src/api/__tests__/artifacts.spec.ts +95 -0
- package/src/api/__tests__/build-regressions.spec.ts +128 -0
- package/src/api/__tests__/build.spec.ts +1020 -0
- package/src/api/__tests__/create-npm-dirs.spec.ts +561 -0
- package/src/api/__tests__/new-node-api-version.spec.ts +87 -0
- package/src/api/__tests__/new.spec.ts +527 -0
- package/src/api/__tests__/rename.spec.ts +181 -0
- package/src/api/__tests__/templates.spec.ts +164 -0
- package/src/api/__tests__/universalize.spec.ts +128 -0
- package/src/api/artifacts.ts +7 -16
- package/src/api/build.ts +346 -105
- package/src/api/new.ts +65 -1
- package/src/api/templates/js-binding.ts +12 -11
- package/src/commands/__tests__/new.spec.ts +13 -0
- package/src/def/build.ts +6 -6
- package/src/utils/__tests__/__fixtures__/napi_type_def +202 -0
- package/src/utils/__tests__/__fixtures__/runtime_string_enum_flag +2 -0
- package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
- package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
- package/src/utils/__tests__/__snapshots__/target.spec.ts.md +180 -0
- package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +1197 -0
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
- package/src/utils/__tests__/__snapshots__/version.spec.ts.md +22 -0
- package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
- package/src/utils/__tests__/config.spec.ts +81 -0
- package/src/utils/__tests__/metadata.spec.ts +28 -0
- package/src/utils/__tests__/misc.spec.ts +56 -0
- package/src/utils/__tests__/target.spec.ts +19 -0
- package/src/utils/__tests__/typegen.spec.ts +85 -0
- package/src/utils/__tests__/version.spec.ts +7 -0
- package/src/utils/version.ts +2 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { createServer } from 'node:http'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import ava, { type TestFn } from 'ava'
|
|
9
|
+
|
|
10
|
+
import { createNpmDirs } from '../create-npm-dirs.js'
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url)
|
|
13
|
+
|
|
14
|
+
const test = ava as TestFn<{
|
|
15
|
+
tmpDir: string
|
|
16
|
+
packageJsonPath: string
|
|
17
|
+
npmConfigRegistry?: string
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
test.beforeEach(async (t) => {
|
|
21
|
+
// Create a unique temp directory for tests
|
|
22
|
+
const timestamp = Date.now()
|
|
23
|
+
const random = Math.random().toString(36).substring(7)
|
|
24
|
+
const tmpDir = join(
|
|
25
|
+
tmpdir(),
|
|
26
|
+
'napi-rs-test',
|
|
27
|
+
`create-npm-dirs-${timestamp}-${random}`,
|
|
28
|
+
)
|
|
29
|
+
const packageJsonPath = join(tmpDir, 'package.json')
|
|
30
|
+
|
|
31
|
+
// Create the directory
|
|
32
|
+
await mkdir(tmpDir, { recursive: true })
|
|
33
|
+
|
|
34
|
+
t.context = {
|
|
35
|
+
tmpDir,
|
|
36
|
+
packageJsonPath,
|
|
37
|
+
npmConfigRegistry: process.env.npm_config_registry,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test.afterEach.always(async (t) => {
|
|
42
|
+
if (t.context.npmConfigRegistry === undefined) {
|
|
43
|
+
delete process.env.npm_config_registry
|
|
44
|
+
} else {
|
|
45
|
+
process.env.npm_config_registry = t.context.npmConfigRegistry
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Clean up any created directories
|
|
49
|
+
if (existsSync(t.context.tmpDir)) {
|
|
50
|
+
await rm(t.context.tmpDir, { recursive: true, force: true })
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
async function startRegistryServer(
|
|
55
|
+
responseBody: Record<string, unknown> = {
|
|
56
|
+
'dist-tags': {
|
|
57
|
+
latest: '1.2.3',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
) {
|
|
61
|
+
const requests: string[] = []
|
|
62
|
+
const server = createServer((req, res) => {
|
|
63
|
+
requests.push(req.url ?? '')
|
|
64
|
+
res.setHeader('content-type', 'application/json')
|
|
65
|
+
res.end(JSON.stringify(responseBody))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await new Promise<void>((resolve, reject) => {
|
|
69
|
+
server.once('error', reject)
|
|
70
|
+
server.listen(0, '127.0.0.1', () => resolve())
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const address = server.address()
|
|
74
|
+
|
|
75
|
+
if (!address || typeof address === 'string') {
|
|
76
|
+
server.close()
|
|
77
|
+
throw new Error('Failed to resolve test registry server address')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
requests,
|
|
82
|
+
close: () =>
|
|
83
|
+
new Promise<void>((resolve, reject) => {
|
|
84
|
+
server.close((error) => {
|
|
85
|
+
if (error) {
|
|
86
|
+
reject(error)
|
|
87
|
+
} else {
|
|
88
|
+
resolve()
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}),
|
|
92
|
+
origin: `http://127.0.0.1:${address.port}`,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
test('should omit exports fields from publishConfig in scoped packages', async (t) => {
|
|
97
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
98
|
+
|
|
99
|
+
// Create a package.json with publishConfig that includes exports field
|
|
100
|
+
const packageJson = {
|
|
101
|
+
name: 'test-package',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
publishConfig: {
|
|
104
|
+
registry: 'https://custom-registry.com',
|
|
105
|
+
access: 'public',
|
|
106
|
+
exports: {
|
|
107
|
+
'.': './dist/index.js',
|
|
108
|
+
'./package.json': './package.json',
|
|
109
|
+
},
|
|
110
|
+
tag: 'beta',
|
|
111
|
+
},
|
|
112
|
+
napi: {
|
|
113
|
+
binaryName: 'test-package',
|
|
114
|
+
targets: ['x86_64-unknown-linux-gnu'],
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
119
|
+
|
|
120
|
+
await createNpmDirs({
|
|
121
|
+
cwd: tmpDir,
|
|
122
|
+
packageJsonPath: 'package.json',
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Check that the scoped package directory was created
|
|
126
|
+
const scopedDir = join(tmpDir, 'npm', 'linux-x64-gnu')
|
|
127
|
+
t.true(existsSync(scopedDir))
|
|
128
|
+
|
|
129
|
+
// Read the generated package.json for the scoped package
|
|
130
|
+
const scopedPackageJsonPath = join(scopedDir, 'package.json')
|
|
131
|
+
t.true(existsSync(scopedPackageJsonPath))
|
|
132
|
+
|
|
133
|
+
const scopedPackageJson = JSON.parse(
|
|
134
|
+
await readFile(scopedPackageJsonPath, 'utf-8'),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// Verify that publishConfig only contains registry and access, not exports
|
|
138
|
+
t.truthy(scopedPackageJson.publishConfig)
|
|
139
|
+
t.is(scopedPackageJson.publishConfig.registry, 'https://custom-registry.com')
|
|
140
|
+
t.is(scopedPackageJson.publishConfig.access, 'public')
|
|
141
|
+
t.is(scopedPackageJson.publishConfig.exports, undefined)
|
|
142
|
+
t.is(scopedPackageJson.publishConfig.tag, undefined)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('should handle package without publishConfig', async (t) => {
|
|
146
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
147
|
+
|
|
148
|
+
// Create a package.json without publishConfig
|
|
149
|
+
const packageJson = {
|
|
150
|
+
name: 'test-package-no-config',
|
|
151
|
+
version: '1.0.0',
|
|
152
|
+
napi: {
|
|
153
|
+
binaryName: 'test-package-no-config',
|
|
154
|
+
targets: ['x86_64-unknown-linux-gnu'],
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
159
|
+
|
|
160
|
+
await createNpmDirs({
|
|
161
|
+
cwd: tmpDir,
|
|
162
|
+
packageJsonPath: 'package.json',
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Check that the scoped package directory was created
|
|
166
|
+
const scopedDir = join(tmpDir, 'npm', 'linux-x64-gnu')
|
|
167
|
+
t.true(existsSync(scopedDir))
|
|
168
|
+
|
|
169
|
+
// Read the generated package.json for the scoped package
|
|
170
|
+
const scopedPackageJsonPath = join(scopedDir, 'package.json')
|
|
171
|
+
const scopedPackageJson = JSON.parse(
|
|
172
|
+
await readFile(scopedPackageJsonPath, 'utf-8'),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
// Verify that publishConfig is not present when not in source
|
|
176
|
+
t.is(scopedPackageJson.publishConfig, undefined)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('should preserve only registry and access in publishConfig', async (t) => {
|
|
180
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
181
|
+
|
|
182
|
+
// Create a package.json with minimal publishConfig
|
|
183
|
+
const packageJson = {
|
|
184
|
+
name: 'test-package-minimal',
|
|
185
|
+
version: '1.0.0',
|
|
186
|
+
publishConfig: {
|
|
187
|
+
registry: 'https://npm.company.com',
|
|
188
|
+
access: 'restricted',
|
|
189
|
+
},
|
|
190
|
+
napi: {
|
|
191
|
+
binaryName: 'test-package-minimal',
|
|
192
|
+
targets: ['aarch64-apple-darwin'],
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
197
|
+
|
|
198
|
+
await createNpmDirs({
|
|
199
|
+
cwd: tmpDir,
|
|
200
|
+
packageJsonPath: 'package.json',
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Check that the scoped package directory was created
|
|
204
|
+
const scopedDir = join(tmpDir, 'npm', 'darwin-arm64')
|
|
205
|
+
t.true(existsSync(scopedDir))
|
|
206
|
+
|
|
207
|
+
// Read the generated package.json for the scoped package
|
|
208
|
+
const scopedPackageJsonPath = join(scopedDir, 'package.json')
|
|
209
|
+
const scopedPackageJson = JSON.parse(
|
|
210
|
+
await readFile(scopedPackageJsonPath, 'utf-8'),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// Verify that publishConfig contains exactly registry and access
|
|
214
|
+
t.truthy(scopedPackageJson.publishConfig)
|
|
215
|
+
t.is(scopedPackageJson.publishConfig.registry, 'https://npm.company.com')
|
|
216
|
+
t.is(scopedPackageJson.publishConfig.access, 'restricted')
|
|
217
|
+
t.is(Object.keys(scopedPackageJson.publishConfig).length, 2)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('should handle WASM targets correctly with publishConfig', async (t) => {
|
|
221
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
222
|
+
const registryServer = await startRegistryServer()
|
|
223
|
+
|
|
224
|
+
// Create a package.json for WASM target
|
|
225
|
+
const packageJson = {
|
|
226
|
+
name: 'test-wasm-package',
|
|
227
|
+
version: '1.0.0',
|
|
228
|
+
publishConfig: {
|
|
229
|
+
registry: `${registryServer.origin}/wasm`,
|
|
230
|
+
access: 'public',
|
|
231
|
+
exports: './wasm.js',
|
|
232
|
+
browser: './browser.js',
|
|
233
|
+
},
|
|
234
|
+
napi: {
|
|
235
|
+
binaryName: 'test-wasm-package',
|
|
236
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await createNpmDirs({
|
|
244
|
+
cwd: tmpDir,
|
|
245
|
+
packageJsonPath: 'package.json',
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Check that the scoped package directory was created
|
|
249
|
+
const scopedDir = join(tmpDir, 'npm', 'wasm32-wasi')
|
|
250
|
+
t.true(existsSync(scopedDir))
|
|
251
|
+
|
|
252
|
+
// Read the generated package.json for the scoped package
|
|
253
|
+
const scopedPackageJsonPath = join(scopedDir, 'package.json')
|
|
254
|
+
const scopedPackageJson = JSON.parse(
|
|
255
|
+
await readFile(scopedPackageJsonPath, 'utf-8'),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// Verify that publishConfig is correctly filtered for WASM too
|
|
259
|
+
t.truthy(scopedPackageJson.publishConfig)
|
|
260
|
+
t.is(
|
|
261
|
+
scopedPackageJson.publishConfig.registry,
|
|
262
|
+
`${registryServer.origin}/wasm`,
|
|
263
|
+
)
|
|
264
|
+
t.is(scopedPackageJson.publishConfig.access, 'public')
|
|
265
|
+
t.is(scopedPackageJson.publishConfig.exports, undefined)
|
|
266
|
+
t.is(scopedPackageJson.publishConfig.browser, undefined)
|
|
267
|
+
|
|
268
|
+
// Verify WASM-specific fields are set correctly
|
|
269
|
+
t.truthy(scopedPackageJson.main)
|
|
270
|
+
t.truthy(scopedPackageJson.browser)
|
|
271
|
+
t.truthy(scopedPackageJson.dependencies)
|
|
272
|
+
} finally {
|
|
273
|
+
await registryServer.close()
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('should preserve stricter node engine ranges for WASM targets', async (t) => {
|
|
278
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
279
|
+
|
|
280
|
+
const packageJson = {
|
|
281
|
+
name: 'test-wasm-engines',
|
|
282
|
+
version: '1.0.0',
|
|
283
|
+
engines: {
|
|
284
|
+
node: '>=18',
|
|
285
|
+
},
|
|
286
|
+
napi: {
|
|
287
|
+
binaryName: 'test-wasm-engines',
|
|
288
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
293
|
+
|
|
294
|
+
await createNpmDirs({
|
|
295
|
+
cwd: tmpDir,
|
|
296
|
+
packageJsonPath: 'package.json',
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const scopedPackageJson = JSON.parse(
|
|
300
|
+
await readFile(join(tmpDir, 'npm', 'wasm32-wasi', 'package.json'), 'utf-8'),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
t.is(scopedPackageJson.engines.node, '>=18')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
test('should intersect mixed node engine ranges with the WASI minimum', async (t) => {
|
|
307
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
308
|
+
|
|
309
|
+
const packageJson = {
|
|
310
|
+
name: 'test-wasm-mixed-engines',
|
|
311
|
+
version: '1.0.0',
|
|
312
|
+
engines: {
|
|
313
|
+
node: '>=12 <14 || >=18',
|
|
314
|
+
},
|
|
315
|
+
napi: {
|
|
316
|
+
binaryName: 'test-wasm-mixed-engines',
|
|
317
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
322
|
+
|
|
323
|
+
await createNpmDirs({
|
|
324
|
+
cwd: tmpDir,
|
|
325
|
+
packageJsonPath: 'package.json',
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const scopedPackageJson = JSON.parse(
|
|
329
|
+
await readFile(join(tmpDir, 'npm', 'wasm32-wasi', 'package.json'), 'utf-8'),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
t.is(scopedPackageJson.engines.node, '>=18.0.0')
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('should preserve sibling engine constraints when node is missing for WASM targets', async (t) => {
|
|
336
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
337
|
+
|
|
338
|
+
const packageJson = {
|
|
339
|
+
name: 'test-wasm-sibling-engines',
|
|
340
|
+
version: '1.0.0',
|
|
341
|
+
engines: {
|
|
342
|
+
npm: '>=10',
|
|
343
|
+
},
|
|
344
|
+
napi: {
|
|
345
|
+
binaryName: 'test-wasm-sibling-engines',
|
|
346
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
351
|
+
|
|
352
|
+
await createNpmDirs({
|
|
353
|
+
cwd: tmpDir,
|
|
354
|
+
packageJsonPath: 'package.json',
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
const scopedPackageJson = JSON.parse(
|
|
358
|
+
await readFile(join(tmpDir, 'npm', 'wasm32-wasi', 'package.json'), 'utf-8'),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
t.deepEqual(scopedPackageJson.engines, {
|
|
362
|
+
npm: '>=10',
|
|
363
|
+
node: '>=14.0.0',
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('should replace an exact node engine below the WASI minimum for WASM targets', async (t) => {
|
|
368
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
369
|
+
|
|
370
|
+
const packageJson = {
|
|
371
|
+
name: 'test-wasm-exact-node-engine',
|
|
372
|
+
version: '1.0.0',
|
|
373
|
+
engines: {
|
|
374
|
+
node: '13.0.0',
|
|
375
|
+
},
|
|
376
|
+
napi: {
|
|
377
|
+
binaryName: 'test-wasm-exact-node-engine',
|
|
378
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
383
|
+
|
|
384
|
+
await createNpmDirs({
|
|
385
|
+
cwd: tmpDir,
|
|
386
|
+
packageJsonPath: 'package.json',
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
const scopedPackageJson = JSON.parse(
|
|
390
|
+
await readFile(join(tmpDir, 'npm', 'wasm32-wasi', 'package.json'), 'utf-8'),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
t.is(scopedPackageJson.engines.node, '>=14.0.0')
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
test('should drop exact node engine branches below the WASI minimum for WASM targets', async (t) => {
|
|
397
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
398
|
+
|
|
399
|
+
const packageJson = {
|
|
400
|
+
name: 'test-wasm-exact-node-branch',
|
|
401
|
+
version: '1.0.0',
|
|
402
|
+
engines: {
|
|
403
|
+
node: '13.0.0 || >=18',
|
|
404
|
+
},
|
|
405
|
+
napi: {
|
|
406
|
+
binaryName: 'test-wasm-exact-node-branch',
|
|
407
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
412
|
+
|
|
413
|
+
await createNpmDirs({
|
|
414
|
+
cwd: tmpDir,
|
|
415
|
+
packageJsonPath: 'package.json',
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
const scopedPackageJson = JSON.parse(
|
|
419
|
+
await readFile(join(tmpDir, 'npm', 'wasm32-wasi', 'package.json'), 'utf-8'),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
t.is(scopedPackageJson.engines.node, '>=18.0.0')
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('should set @emnapi/core and @emnapi/runtime versions to match emnapi for WASM targets', async (t) => {
|
|
426
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
427
|
+
|
|
428
|
+
const packageJson = {
|
|
429
|
+
name: 'test-emnapi-versions',
|
|
430
|
+
version: '1.0.0',
|
|
431
|
+
napi: {
|
|
432
|
+
binaryName: 'test-emnapi-versions',
|
|
433
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
434
|
+
},
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
438
|
+
|
|
439
|
+
await createNpmDirs({
|
|
440
|
+
cwd: tmpDir,
|
|
441
|
+
packageJsonPath: 'package.json',
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
const scopedDir = join(tmpDir, 'npm', 'wasm32-wasi')
|
|
445
|
+
const scopedPackageJson = JSON.parse(
|
|
446
|
+
await readFile(join(scopedDir, 'package.json'), 'utf-8'),
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
const emnapiVersion = require('emnapi/package.json').version
|
|
450
|
+
t.is(scopedPackageJson.dependencies['@emnapi/core'], emnapiVersion)
|
|
451
|
+
t.is(scopedPackageJson.dependencies['@emnapi/runtime'], emnapiVersion)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
test.serial(
|
|
455
|
+
'should reject an empty latest dist-tag when resolving wasm runtime metadata',
|
|
456
|
+
async (t) => {
|
|
457
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
458
|
+
const registryServer = await startRegistryServer({
|
|
459
|
+
'dist-tags': {
|
|
460
|
+
latest: ' ',
|
|
461
|
+
},
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
process.env.npm_config_registry = `${registryServer.origin}/npm`
|
|
465
|
+
|
|
466
|
+
const packageJson = {
|
|
467
|
+
name: 'test-wasm-empty-latest-tag',
|
|
468
|
+
version: '1.0.0',
|
|
469
|
+
napi: {
|
|
470
|
+
binaryName: 'test-wasm-empty-latest-tag',
|
|
471
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const error = await t.throwsAsync(() =>
|
|
479
|
+
createNpmDirs({
|
|
480
|
+
cwd: tmpDir,
|
|
481
|
+
packageJsonPath: 'package.json',
|
|
482
|
+
}),
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
t.regex(error.message, /did not include a latest dist-tag/)
|
|
486
|
+
t.deepEqual(registryServer.requests, ['/npm/@napi-rs/wasm-runtime'])
|
|
487
|
+
} finally {
|
|
488
|
+
await registryServer.close()
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
test.serial(
|
|
494
|
+
'should ignore publishConfig.registry when resolving wasm runtime metadata',
|
|
495
|
+
async (t) => {
|
|
496
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
497
|
+
const registryServer = await startRegistryServer()
|
|
498
|
+
const envRegistryServer = await startRegistryServer()
|
|
499
|
+
|
|
500
|
+
process.env.npm_config_registry = `${envRegistryServer.origin}/env`
|
|
501
|
+
|
|
502
|
+
const packageJson = {
|
|
503
|
+
name: 'test-wasm-publish-registry',
|
|
504
|
+
version: '1.0.0',
|
|
505
|
+
publishConfig: {
|
|
506
|
+
registry: `${registryServer.origin}/custom`,
|
|
507
|
+
},
|
|
508
|
+
napi: {
|
|
509
|
+
binaryName: 'test-wasm-publish-registry',
|
|
510
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
511
|
+
},
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
await createNpmDirs({
|
|
518
|
+
cwd: tmpDir,
|
|
519
|
+
packageJsonPath: 'package.json',
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
t.deepEqual(registryServer.requests, [])
|
|
523
|
+
t.deepEqual(envRegistryServer.requests, ['/env/@napi-rs/wasm-runtime'])
|
|
524
|
+
} finally {
|
|
525
|
+
await registryServer.close()
|
|
526
|
+
await envRegistryServer.close()
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
test.serial(
|
|
532
|
+
'should resolve wasm runtime metadata from npm_config_registry',
|
|
533
|
+
async (t) => {
|
|
534
|
+
const { tmpDir, packageJsonPath } = t.context
|
|
535
|
+
const registryServer = await startRegistryServer()
|
|
536
|
+
|
|
537
|
+
process.env.npm_config_registry = `${registryServer.origin}/npm`
|
|
538
|
+
|
|
539
|
+
const packageJson = {
|
|
540
|
+
name: 'test-wasm-env-registry',
|
|
541
|
+
version: '1.0.0',
|
|
542
|
+
napi: {
|
|
543
|
+
binaryName: 'test-wasm-env-registry',
|
|
544
|
+
targets: ['wasm32-wasi-preview1-threads'],
|
|
545
|
+
},
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
await createNpmDirs({
|
|
552
|
+
cwd: tmpDir,
|
|
553
|
+
packageJsonPath: 'package.json',
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
t.deepEqual(registryServer.requests, ['/npm/@napi-rs/wasm-runtime'])
|
|
557
|
+
} finally {
|
|
558
|
+
await registryServer.close()
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { parse as parseToml } from '@std/toml'
|
|
6
|
+
import test, { type ExecutionContext } from 'ava'
|
|
7
|
+
|
|
8
|
+
import { newProject, updateCargoTomlNodeApiVersion } from '../new.js'
|
|
9
|
+
|
|
10
|
+
async function rewriteNapiDependency(
|
|
11
|
+
t: ExecutionContext,
|
|
12
|
+
dependency: string,
|
|
13
|
+
minNodeApiVersion: number,
|
|
14
|
+
) {
|
|
15
|
+
const directory = await mkdtemp(join(tmpdir(), 'napi-rs-new-node-api-'))
|
|
16
|
+
t.teardown(() => rm(directory, { recursive: true, force: true }))
|
|
17
|
+
|
|
18
|
+
const cargoTomlPath = join(directory, 'Cargo.toml')
|
|
19
|
+
await writeFile(
|
|
20
|
+
cargoTomlPath,
|
|
21
|
+
`[package]\nname = "fixture"\nversion = "0.0.0"\n\n[dependencies]\nnapi = ${dependency}\n`,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
await updateCargoTomlNodeApiVersion(cargoTomlPath, minNodeApiVersion)
|
|
25
|
+
|
|
26
|
+
const cargoToml = parseToml(await readFile(cargoTomlPath, 'utf8')) as any
|
|
27
|
+
return cargoToml.dependencies.napi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('sets the default Node-API feature to napi4', async (t) => {
|
|
31
|
+
const napiDependency = await rewriteNapiDependency(t, '"3.0.0"', 4)
|
|
32
|
+
|
|
33
|
+
t.is(napiDependency.version, '3.0.0')
|
|
34
|
+
t.deepEqual(napiDependency.features, ['napi4'])
|
|
35
|
+
t.false('default-features' in napiDependency)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('sets a requested Node-API feature and replaces the template default', async (t) => {
|
|
39
|
+
const napiDependency = await rewriteNapiDependency(
|
|
40
|
+
t,
|
|
41
|
+
'{ version = "3.0.0", features = ["napi4", "serde-json"] }',
|
|
42
|
+
6,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
t.deepEqual(napiDependency.features, ['napi6', 'serde-json'])
|
|
46
|
+
t.false('default-features' in napiDependency)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('sets the supported Node-API 10 feature', async (t) => {
|
|
50
|
+
const napiDependency = await rewriteNapiDependency(t, '"3.0.0"', 10)
|
|
51
|
+
|
|
52
|
+
t.deepEqual(napiDependency.features, ['napi10'])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('disables napi4 defaults for lower Node-API versions while retaining dynamic symbol loading', async (t) => {
|
|
56
|
+
const napiDependency = await rewriteNapiDependency(t, '"3.0.0"', 3)
|
|
57
|
+
|
|
58
|
+
t.is(napiDependency['default-features'], false)
|
|
59
|
+
t.deepEqual(napiDependency.features, ['napi3', 'dyn-symbols'])
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('preserves an explicitly disabled default feature configuration', async (t) => {
|
|
63
|
+
const napiDependency = await rewriteNapiDependency(
|
|
64
|
+
t,
|
|
65
|
+
'{ version = "3.0.0", default-features = false, features = ["napi8", "serde-json"] }',
|
|
66
|
+
2,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
t.is(napiDependency['default-features'], false)
|
|
70
|
+
t.deepEqual(napiDependency.features, ['napi2', 'serde-json'])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
for (const invalidVersion of [0, 11, 1.5]) {
|
|
74
|
+
test(`rejects invalid Node-API version ${invalidVersion}`, async (t) => {
|
|
75
|
+
const error = await t.throwsAsync(
|
|
76
|
+
newProject({
|
|
77
|
+
path: join(tmpdir(), 'unused-napi-rs-new-project'),
|
|
78
|
+
enableDefaultTargets: true,
|
|
79
|
+
minNodeApiVersion: invalidVersion,
|
|
80
|
+
dryRun: true,
|
|
81
|
+
}),
|
|
82
|
+
{ instanceOf: RangeError },
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
t.regex(error.message, /Unsupported Node-API version/)
|
|
86
|
+
})
|
|
87
|
+
}
|