@napi-rs/cli 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/cli.js +10359 -8729
- package/dist/index.cjs +10381 -8751
- package/dist/index.d.cts +83 -16
- package/dist/index.d.ts +83 -16
- package/dist/index.js +10359 -8729
- package/docs/wasi.md +318 -4
- package/package.json +5 -6
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +4003 -53
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build-regressions.spec.ts +210 -3
- package/src/api/__tests__/build.spec.ts +2333 -3
- package/src/api/__tests__/create-npm-dirs.spec.ts +105 -3
- package/src/api/__tests__/pre-publish.spec.ts +153 -1
- package/src/api/__tests__/templates.spec.ts +1309 -1
- package/src/api/build.ts +756 -30
- package/src/api/create-npm-dirs.ts +23 -10
- package/src/api/new.ts +13 -18
- package/src/api/pre-publish.ts +34 -11
- package/src/api/rename.ts +10 -21
- package/src/api/templates/binding-target.ts +176 -0
- package/src/api/templates/index.ts +1 -0
- package/src/api/templates/js-binding.ts +54 -10
- package/src/api/templates/load-wasi-template.ts +661 -58
- package/src/api/templates/wasi-worker-template.ts +36 -31
- package/src/commands/build.ts +1 -1
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +18 -28
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
- package/src/utils/__tests__/misc.spec.ts +4 -0
- package/src/utils/__tests__/reconciliation.spec.ts +676 -0
- package/src/utils/__tests__/serialize.spec.ts +55 -0
- package/src/utils/__tests__/target.spec.ts +221 -0
- package/src/utils/__tests__/typegen.spec.ts +115 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +351 -79
- package/src/utils/serialize.ts +47 -0
- package/src/utils/target.ts +150 -1
- package/src/utils/typegen.ts +608 -42
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
|
|
3
|
+
import { serializeJson, serializeToml, serializeYaml } from '../serialize.js'
|
|
4
|
+
|
|
5
|
+
test('serializeJson uses 2-space indent and a trailing newline', (t) => {
|
|
6
|
+
t.is(
|
|
7
|
+
serializeJson({ name: 'pkg', files: ['a.node'] }),
|
|
8
|
+
`{
|
|
9
|
+
"name": "pkg",
|
|
10
|
+
"files": [
|
|
11
|
+
"a.node"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
`,
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('serializeJson always ends with exactly one newline', (t) => {
|
|
19
|
+
const serialized = serializeJson({ ok: true })
|
|
20
|
+
t.true(serialized.endsWith('\n'))
|
|
21
|
+
t.false(serialized.endsWith('\n\n'))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('serializeToml strips the leading blank line @std/toml inserts', (t) => {
|
|
25
|
+
const serialized = serializeToml({
|
|
26
|
+
package: { name: 'demo', version: '0.1.0' },
|
|
27
|
+
dependencies: {
|
|
28
|
+
napi: { version: '3.0.0', features: ['napi4'] },
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
t.false(serialized.startsWith('\n'))
|
|
32
|
+
t.true(serialized.startsWith('[package]\n'))
|
|
33
|
+
t.true(serialized.endsWith('\n'))
|
|
34
|
+
t.false(serialized.endsWith('\n\n'))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('serializeYaml always ends with a trailing newline', (t) => {
|
|
38
|
+
const serialized = serializeYaml({ on: { push: null } })
|
|
39
|
+
t.true(serialized.endsWith('\n'))
|
|
40
|
+
t.false(serialized.endsWith('\n\n'))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('serializeToml does not rewrite commas inside quoted strings', (t) => {
|
|
44
|
+
const serialized = serializeToml({
|
|
45
|
+
lib: { include: ['src/a,b.rs'] },
|
|
46
|
+
package: {
|
|
47
|
+
name: 'demo',
|
|
48
|
+
description: 'uses = [a,b] in a scalar',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
t.true(serialized.includes('include = ["src/a,b.rs"]'))
|
|
52
|
+
t.true(serialized.includes('description = "uses = [a,b] in a scalar"'))
|
|
53
|
+
t.false(serialized.includes('src/a, b.rs'))
|
|
54
|
+
t.false(serialized.includes('= [a, b]'))
|
|
55
|
+
})
|
|
@@ -1,13 +1,49 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs'
|
|
2
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
|
1
3
|
import os from 'os'
|
|
4
|
+
import { join } from 'node:path'
|
|
2
5
|
|
|
3
6
|
import test from 'ava'
|
|
4
7
|
|
|
5
8
|
import {
|
|
9
|
+
wasiLibcHasNewFutexAbi,
|
|
6
10
|
parseTriple,
|
|
7
11
|
getSystemDefaultTarget,
|
|
12
|
+
rustBundledWasiLibc,
|
|
13
|
+
wasiSdkMajorVersion,
|
|
8
14
|
AVAILABLE_TARGETS,
|
|
9
15
|
} from '../target.js'
|
|
10
16
|
|
|
17
|
+
async function withWasiSdkDir(
|
|
18
|
+
setup: (wasiSdkPath: string) => Promise<void>,
|
|
19
|
+
assertion: (wasiSdkPath: string) => void,
|
|
20
|
+
) {
|
|
21
|
+
const wasiSdkPath = await mkdtemp(join(os.tmpdir(), 'napi-rs-wasi-sdk-'))
|
|
22
|
+
try {
|
|
23
|
+
await setup(wasiSdkPath)
|
|
24
|
+
assertion(wasiSdkPath)
|
|
25
|
+
} finally {
|
|
26
|
+
await rm(wasiSdkPath, { recursive: true, force: true })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function writeVersionFile(wasiSdkPath: string, content: string) {
|
|
31
|
+
await writeFile(join(wasiSdkPath, 'VERSION'), content)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function writeVersionHeader(wasiSdkPath: string, content: string) {
|
|
35
|
+
const headerDir = join(
|
|
36
|
+
wasiSdkPath,
|
|
37
|
+
'share',
|
|
38
|
+
'wasi-sysroot',
|
|
39
|
+
'include',
|
|
40
|
+
'wasm32-wasip1-threads',
|
|
41
|
+
'wasi',
|
|
42
|
+
)
|
|
43
|
+
await mkdir(headerDir, { recursive: true })
|
|
44
|
+
await writeFile(join(headerDir, 'version.h'), content)
|
|
45
|
+
}
|
|
46
|
+
|
|
11
47
|
test('should parse triple correctly', (t) => {
|
|
12
48
|
t.snapshot(AVAILABLE_TARGETS.map(parseTriple))
|
|
13
49
|
})
|
|
@@ -17,3 +53,188 @@ test('should get system default target correctly', (t) => {
|
|
|
17
53
|
|
|
18
54
|
t.is(target.platform, os.platform())
|
|
19
55
|
})
|
|
56
|
+
|
|
57
|
+
test('should read the wasi-sdk major version from VERSION', async (t) => {
|
|
58
|
+
await withWasiSdkDir(
|
|
59
|
+
(wasiSdkPath) => writeVersionFile(wasiSdkPath, '34.0\n'),
|
|
60
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), 34),
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('should ignore the build suffix in VERSION', async (t) => {
|
|
65
|
+
await withWasiSdkDir(
|
|
66
|
+
(wasiSdkPath) => writeVersionFile(wasiSdkPath, '33.0+m\n'),
|
|
67
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), 33),
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('should read a pre wasi-sdk 30 VERSION', async (t) => {
|
|
72
|
+
await withWasiSdkDir(
|
|
73
|
+
(wasiSdkPath) => writeVersionFile(wasiSdkPath, '27.0\n'),
|
|
74
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), 27),
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('should fall back to the wasi/version.h header', async (t) => {
|
|
79
|
+
await withWasiSdkDir(
|
|
80
|
+
(wasiSdkPath) =>
|
|
81
|
+
writeVersionHeader(
|
|
82
|
+
wasiSdkPath,
|
|
83
|
+
'#define __wasi_sdk_major__ 30\n#define __wasi_sdk_minor__ 0\n',
|
|
84
|
+
),
|
|
85
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), 30),
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('should prefer VERSION over the wasi/version.h header', async (t) => {
|
|
90
|
+
await withWasiSdkDir(
|
|
91
|
+
async (wasiSdkPath) => {
|
|
92
|
+
await writeVersionFile(wasiSdkPath, '34.0\n')
|
|
93
|
+
await writeVersionHeader(wasiSdkPath, '#define __wasi_sdk_major__ 33\n')
|
|
94
|
+
},
|
|
95
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), 34),
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('should return null when no version signal exists', async (t) => {
|
|
100
|
+
await withWasiSdkDir(
|
|
101
|
+
() => Promise.resolve(),
|
|
102
|
+
(wasiSdkPath) => t.is(wasiSdkMajorVersion(wasiSdkPath), null),
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('should return null instead of throwing on a malformed VERSION', async (t) => {
|
|
107
|
+
await withWasiSdkDir(
|
|
108
|
+
(wasiSdkPath) => writeVersionFile(wasiSdkPath, 'not a version at all\n'),
|
|
109
|
+
(wasiSdkPath) => {
|
|
110
|
+
t.notThrows(() => wasiSdkMajorVersion(wasiSdkPath))
|
|
111
|
+
t.is(wasiSdkMajorVersion(wasiSdkPath), null)
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('should return null when the wasi-sdk path does not exist', (t) => {
|
|
117
|
+
t.is(wasiSdkMajorVersion(join(os.tmpdir(), 'napi-rs-missing-wasi-sdk')), null)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('should detect the new futex ABI from a wasi-libc archive', async (t) => {
|
|
121
|
+
const dir = await mkdtemp(join(os.tmpdir(), 'napi-rs-wasi-libc-'))
|
|
122
|
+
try {
|
|
123
|
+
// wasi-libc moved the futex helpers into `futex.c` when it dropped the
|
|
124
|
+
// unused `int op` parameter. `__wait.c` survives either way, so only the
|
|
125
|
+
// presence of `futex.c` separates the two ABIs.
|
|
126
|
+
const newAbi = join(dir, 'libc-new.a')
|
|
127
|
+
const legacyAbi = join(dir, 'libc-legacy.a')
|
|
128
|
+
await writeFile(newAbi, '!<arch>\n__wait.c.obj\nfutex.c.obj\n')
|
|
129
|
+
await writeFile(
|
|
130
|
+
legacyAbi,
|
|
131
|
+
'!<arch>\n__wait.c.obj\n__wasilibc_busywait.c.obj\n',
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
t.true(wasiLibcHasNewFutexAbi(newAbi))
|
|
135
|
+
t.false(wasiLibcHasNewFutexAbi(legacyAbi))
|
|
136
|
+
} finally {
|
|
137
|
+
await rm(dir, { recursive: true, force: true })
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('should return null when the wasi-libc archive is unavailable', (t) => {
|
|
142
|
+
t.is(wasiLibcHasNewFutexAbi(null), null)
|
|
143
|
+
t.is(
|
|
144
|
+
wasiLibcHasNewFutexAbi(join(os.tmpdir(), 'napi-rs-missing-libc.a')),
|
|
145
|
+
null,
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// A fake `rustc` must be an executable file, and `execFileSync` cannot run a
|
|
150
|
+
// Windows `.cmd` shim without a shell. The behaviour under test does not
|
|
151
|
+
// depend on the platform, so cover it on POSIX only.
|
|
152
|
+
const posixOnly = process.platform === 'win32' ? test.serial.skip : test.serial
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Installs a `rustc` that reports its own working directory as the sysroot,
|
|
156
|
+
* so a test can tell which directory the probe ran in.
|
|
157
|
+
*/
|
|
158
|
+
async function withFakeRustc(
|
|
159
|
+
envVar: 'RUSTC' | 'CARGO_BUILD_RUSTC',
|
|
160
|
+
body: (dir: string) => Promise<void> | void,
|
|
161
|
+
) {
|
|
162
|
+
const dir = await mkdtemp(join(os.tmpdir(), 'napi-rs-fake-rustc-'))
|
|
163
|
+
const rustc = join(dir, 'rustc')
|
|
164
|
+
// `$PWD` comes from the inherited environment, so ask the kernel for the
|
|
165
|
+
// real working directory. `-P` also resolves the `/var` symlink on macOS.
|
|
166
|
+
await writeFile(rustc, '#!/bin/sh\necho "$(pwd -P)/sysroot"\n')
|
|
167
|
+
await chmod(rustc, 0o755)
|
|
168
|
+
const previous = process.env[envVar]
|
|
169
|
+
process.env[envVar] = rustc
|
|
170
|
+
try {
|
|
171
|
+
await body(dir)
|
|
172
|
+
} finally {
|
|
173
|
+
if (previous === undefined) {
|
|
174
|
+
delete process.env[envVar]
|
|
175
|
+
} else {
|
|
176
|
+
process.env[envVar] = previous
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
posixOnly('probes the Rust sysroot from the build cwd', async (t) => {
|
|
182
|
+
await withFakeRustc('RUSTC', async (dir) => {
|
|
183
|
+
const caller = join(dir, 'caller')
|
|
184
|
+
const project = join(dir, 'project')
|
|
185
|
+
await mkdir(caller)
|
|
186
|
+
await mkdir(project)
|
|
187
|
+
|
|
188
|
+
// `rustc` is a rustup shim, so the directory it runs in picks the
|
|
189
|
+
// toolchain. Cargo is spawned with the build cwd; the probe must match it.
|
|
190
|
+
t.is(
|
|
191
|
+
rustBundledWasiLibc('wasm32-wasip1-threads', project),
|
|
192
|
+
join(
|
|
193
|
+
realpathSync(project),
|
|
194
|
+
'sysroot',
|
|
195
|
+
'lib',
|
|
196
|
+
'rustlib',
|
|
197
|
+
'wasm32-wasip1-threads',
|
|
198
|
+
'lib',
|
|
199
|
+
'self-contained',
|
|
200
|
+
'libc.a',
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
t.not(
|
|
204
|
+
rustBundledWasiLibc('wasm32-wasip1-threads', caller),
|
|
205
|
+
rustBundledWasiLibc('wasm32-wasip1-threads', project),
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
posixOnly('falls back to CARGO_BUILD_RUSTC when RUSTC is unset', async (t) => {
|
|
211
|
+
const previousRustc = process.env.RUSTC
|
|
212
|
+
delete process.env.RUSTC
|
|
213
|
+
try {
|
|
214
|
+
await withFakeRustc('CARGO_BUILD_RUSTC', (dir) => {
|
|
215
|
+
t.true(
|
|
216
|
+
rustBundledWasiLibc('wasm32-wasip1-threads', dir)?.startsWith(
|
|
217
|
+
realpathSync(dir),
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
} finally {
|
|
222
|
+
if (previousRustc !== undefined) {
|
|
223
|
+
process.env.RUSTC = previousRustc
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
posixOnly('returns null when the compiler cannot be run', (t) => {
|
|
229
|
+
const previous = process.env.RUSTC
|
|
230
|
+
process.env.RUSTC = join(os.tmpdir(), 'napi-rs-no-such-rustc')
|
|
231
|
+
try {
|
|
232
|
+
t.is(rustBundledWasiLibc('wasm32-wasip1-threads'), null)
|
|
233
|
+
} finally {
|
|
234
|
+
if (previous === undefined) {
|
|
235
|
+
delete process.env.RUSTC
|
|
236
|
+
} else {
|
|
237
|
+
process.env.RUSTC = previous
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
1
3
|
import { join } from 'path'
|
|
2
4
|
import { fileURLToPath } from 'url'
|
|
3
5
|
|
|
@@ -5,6 +7,24 @@ import test from 'ava'
|
|
|
5
7
|
|
|
6
8
|
import { correctStringIdent, processTypeDef } from '../typegen.js'
|
|
7
9
|
|
|
10
|
+
async function processInlineTypeDef(
|
|
11
|
+
lines: Array<Record<string, unknown>>,
|
|
12
|
+
constEnum = true,
|
|
13
|
+
runtimeStringEnum = false,
|
|
14
|
+
) {
|
|
15
|
+
const dir = await mkdtemp(join(tmpdir(), 'napi-typegen-'))
|
|
16
|
+
const file = join(dir, 'defs')
|
|
17
|
+
await writeFile(
|
|
18
|
+
file,
|
|
19
|
+
lines.map((line) => JSON.stringify(line)).join('\n') + '\n',
|
|
20
|
+
)
|
|
21
|
+
try {
|
|
22
|
+
return await processTypeDef(file, constEnum, runtimeStringEnum)
|
|
23
|
+
} finally {
|
|
24
|
+
await rm(dir, { recursive: true, force: true })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
test('should ident string correctly', (t) => {
|
|
9
29
|
const input = `
|
|
10
30
|
/**
|
|
@@ -83,3 +103,98 @@ test('runtimeStringEnum is a no-op when constEnum is set', async (t) => {
|
|
|
83
103
|
|
|
84
104
|
t.snapshot(dts)
|
|
85
105
|
})
|
|
106
|
+
|
|
107
|
+
test('simple type aliases stay on one line', async (t) => {
|
|
108
|
+
const { dts } = await processInlineTypeDef([
|
|
109
|
+
{ kind: 'type', name: 'CustomU32', def: 'number' },
|
|
110
|
+
])
|
|
111
|
+
t.true(dts.includes('export type CustomU32 = number'))
|
|
112
|
+
t.false(/export type CustomU32 =\s*\n/.test(dts))
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('empty classes do not contain a blank body', async (t) => {
|
|
116
|
+
const { dts } = await processInlineTypeDef([
|
|
117
|
+
{ kind: 'struct', name: 'Blake2BKey', def: '' },
|
|
118
|
+
])
|
|
119
|
+
t.true(dts.includes('export declare class Blake2BKey {}'))
|
|
120
|
+
t.false(dts.includes('class Blake2BKey {\n\n}'))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('no-const-enum string enums emit a spaced union', async (t) => {
|
|
124
|
+
const { dts } = await processTypeDef(flagFixture, false, false)
|
|
125
|
+
t.true(dts.includes("export type Status = 'Active' | 'Inactive'"))
|
|
126
|
+
t.false(dts.includes("'Active'|"))
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('no-const-enum string enums keep commas inside variant docs', async (t) => {
|
|
130
|
+
const { dts } = await processInlineTypeDef(
|
|
131
|
+
[
|
|
132
|
+
{
|
|
133
|
+
kind: 'string_enum',
|
|
134
|
+
name: 'Status',
|
|
135
|
+
def: "/** First, documented variant */\n Active = 'Active',\n Inactive = 'Inactive'",
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
false,
|
|
139
|
+
false,
|
|
140
|
+
)
|
|
141
|
+
t.true(dts.includes("export type Status = 'Active' | 'Inactive'"))
|
|
142
|
+
t.false(dts.includes('/** First |'))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('no-const-enum string enums keep Unicode variant names', async (t) => {
|
|
146
|
+
const { dts } = await processInlineTypeDef(
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
kind: 'string_enum',
|
|
150
|
+
name: 'Status',
|
|
151
|
+
def: "成功 = '成功',\n Failed = 'Failed'",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
false,
|
|
155
|
+
false,
|
|
156
|
+
)
|
|
157
|
+
t.true(dts.includes("export type Status = '成功' | 'Failed'"))
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('no-const-enum string enums keep quoted JS property names', async (t) => {
|
|
161
|
+
const { dts } = await processInlineTypeDef(
|
|
162
|
+
[
|
|
163
|
+
{
|
|
164
|
+
kind: 'string_enum',
|
|
165
|
+
name: 'Header',
|
|
166
|
+
def: "'content-type' = 'content-type',\n Plain = 'plain'",
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
false,
|
|
170
|
+
false,
|
|
171
|
+
)
|
|
172
|
+
t.true(dts.includes("export type Header = 'content-type' | 'plain'"))
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('no-const-enum string enums keep commas and escapes inside values', async (t) => {
|
|
176
|
+
const { dts } = await processInlineTypeDef(
|
|
177
|
+
[
|
|
178
|
+
{
|
|
179
|
+
kind: 'string_enum',
|
|
180
|
+
name: 'Label',
|
|
181
|
+
def: "Csv = 'a,b',\n Quoted = 'say \\'hi\\'',\n Count = 2",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
false,
|
|
185
|
+
false,
|
|
186
|
+
)
|
|
187
|
+
t.true(dts.includes("export type Label = 'a,b' | 'say \\'hi\\'' | 2"))
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('correctStringIdent keeps nested object-type fields indented', (t) => {
|
|
191
|
+
const input = `export declare function bufferComplexOverride(value: {
|
|
192
|
+
Buffer(): "line"
|
|
193
|
+
mapped: { [Buffer in keyof T]: T[Buffer] }
|
|
194
|
+
external: Buffer
|
|
195
|
+
}): void`
|
|
196
|
+
const formatted = correctStringIdent(input, 0)
|
|
197
|
+
t.true(formatted.includes(' mapped: { [Buffer in keyof T]: T[Buffer] }'))
|
|
198
|
+
t.true(formatted.includes(' external: Buffer'))
|
|
199
|
+
t.false(formatted.includes('\nmapped:'))
|
|
200
|
+
})
|
package/src/utils/config.ts
CHANGED
|
@@ -95,6 +95,27 @@ export interface UserNapiConfig {
|
|
|
95
95
|
* @default 4000 pages (250 MiB), or 1024 pages (64 MiB) for the deferred workerd loader
|
|
96
96
|
*/
|
|
97
97
|
initialMemory?: number
|
|
98
|
+
/**
|
|
99
|
+
* Initial linear memory, in 64 KiB pages, for the **threadless**
|
|
100
|
+
* (`wasm32-wasip1`) loaders only: the Node CJS loader, the browser loader
|
|
101
|
+
* and the deferred `./workerd` loader.
|
|
102
|
+
*
|
|
103
|
+
* The threaded loaders allocate one `shared: true` memory that every
|
|
104
|
+
* wasi-threads worker maps, so every worker stack and every thread's
|
|
105
|
+
* allocations come out of it and growing it is a cross-thread event; it is
|
|
106
|
+
* sized for the whole pool up front. The threadless loaders drive a single
|
|
107
|
+
* thread through a plain growable `ArrayBuffer`: they only have to clear
|
|
108
|
+
* the module's own link-time floor (`-zstack-size` plus static data) and
|
|
109
|
+
* grow on demand, which is what lets the same build fit a host with a hard
|
|
110
|
+
* isolate cap such as workerd's 128 MiB.
|
|
111
|
+
*
|
|
112
|
+
* Falls back to `initialMemory` when unset, so existing projects are
|
|
113
|
+
* unaffected.
|
|
114
|
+
*
|
|
115
|
+
* @default `initialMemory` (4000 pages), or 1024 pages for the deferred
|
|
116
|
+
* `./workerd` loader
|
|
117
|
+
*/
|
|
118
|
+
threadlessInitialMemory?: number
|
|
98
119
|
/**
|
|
99
120
|
* @default 65536 pages (4GiB)
|
|
100
121
|
*/
|
|
@@ -119,6 +140,35 @@ export interface UserNapiConfig {
|
|
|
119
140
|
*/
|
|
120
141
|
optionalDependency?: boolean
|
|
121
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Whether the generated WASI loaders bootstrap the `napi-async-runtime`
|
|
145
|
+
* CurrentThread JavaScript hosts.
|
|
146
|
+
*
|
|
147
|
+
* A binding built with the `napi-async-runtime` crate runs the
|
|
148
|
+
* `CurrentThread` flavor on every WebAssembly target (it is the only
|
|
149
|
+
* flavor there). Its futures make no progress until a task host is
|
|
150
|
+
* registered, and its timers never fire until a timer host is, so the
|
|
151
|
+
* loader must install both right after instantiation and unregister them
|
|
152
|
+
* before the environment is destroyed.
|
|
153
|
+
*
|
|
154
|
+
* Enabling this emits that bootstrap in the node, browser and deferred
|
|
155
|
+
* (workerd) loaders, and declares `@napi-rs/async-runtime` in the
|
|
156
|
+
* generated `<packageName>-wasm32-*` packages. The loader delegates the
|
|
157
|
+
* whole host contract — the seven exports, contract version 4, the
|
|
158
|
+
* reservation identity, the liveness probe and the rollback — to that
|
|
159
|
+
* package, so a binding that does not actually expose the contract fails
|
|
160
|
+
* loudly at load with `ERR_NAPI_ASYNC_RUNTIME_BINDING_MISMATCH`.
|
|
161
|
+
*
|
|
162
|
+
* The bootstrap runs after instantiation, so after `#[module_init]`: the
|
|
163
|
+
* host registration functions are exports of the binding itself. Code that
|
|
164
|
+
* runs during module registration must not create timers (`sleep_until`
|
|
165
|
+
* fails loud at creation without a timer host) and cannot expect task
|
|
166
|
+
* progress until the loader has returned.
|
|
167
|
+
*
|
|
168
|
+
* @default false
|
|
169
|
+
*/
|
|
170
|
+
asyncRuntime?: boolean
|
|
171
|
+
|
|
122
172
|
/**
|
|
123
173
|
* Browser wasm binding configuration
|
|
124
174
|
*/
|