@napi-rs/cli 3.8.3 → 3.8.5

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.
@@ -1,8 +1,18 @@
1
+ import { mkdtempSync } from 'node:fs'
2
+ import { mkdir, rm, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { dirname, join } from 'node:path'
5
+
1
6
  import test from 'ava'
2
7
 
3
8
  import {
9
+ collectRootPackagePathReferences,
4
10
  parseNpmPackFiles,
5
11
  resolveRootOptionalDependencies,
12
+ resolveRootPublisher,
13
+ rootPublisherRewritesRootExports,
14
+ sniffRewritingPublisherFromUserAgent,
15
+ validateRootFacadePacklist,
6
16
  } from '../pre-publish.js'
7
17
  import { parseTriple } from '../../utils/index.js'
8
18
 
@@ -139,3 +149,412 @@ test('rejects an unexpected npm pack JSON result', (t) => {
139
149
  message: 'npm pack returned an unexpected JSON result',
140
150
  })
141
151
  })
152
+
153
+ // pnpm and yarn berry wholesale-replace `exports` with `publishConfig.exports`
154
+ // in the published manifest, so under those publishers the root facade
155
+ // validation must target the effective map: local-only conditions like
156
+ // `dev: ./src/*.ts` never reach the registry and must not fail the packlist
157
+ // check. npm and Yarn Classic keep the raw `exports` untouched (publishConfig
158
+ // is publish-time configuration only), so both maps must stay valid there.
159
+ //
160
+ // `prePublish` does not publish the root package, so the publisher is declared
161
+ // explicitly (`--root-publisher` / `napi.rootPublisher`). `npm_config_user_agent`
162
+ // names the process that invoked this command, which may differ from the root
163
+ // publisher, so it never selects a branch — it only enriches the failure.
164
+ const PNPM_USER_AGENT = 'pnpm/11.9.0 npm/? node/v24.19.0 darwin arm64'
165
+ const YARN_USER_AGENT = 'yarn/4.6.0 npm/? node/v24.19.0 darwin arm64'
166
+ const YARN_CLASSIC_USER_AGENT = 'yarn/1.22.22 npm/? node/v24.19.0 darwin arm64'
167
+ const NPM_USER_AGENT = 'npm/11.6.2 node/v24.19.0 darwin arm64 workspaces/false'
168
+ // A truncated version token must not be read as a major version: an agent we
169
+ // cannot parse names no package manager at all.
170
+ const MALFORMED_YARN_USER_AGENT = 'yarn/2garbage npm/? node/v24.19.0'
171
+ const MALFORMED_PNPM_USER_AGENT = 'pnpm/2garbage npm/? node/v24.19.0'
172
+ const PNPM_HINT =
173
+ ' This ran under pnpm, which replaces "exports" with "publishConfig.exports" when it packs: if pnpm also publishes the root package, set napi.rootPublisher to "pnpm" (or pass --root-publisher pnpm) to validate the publish-effective export map instead.'
174
+
175
+ function withNpmUserAgent<T>(agent: string | undefined, fn: () => T): T {
176
+ const previous = process.env.npm_config_user_agent
177
+ if (agent === undefined) {
178
+ delete process.env.npm_config_user_agent
179
+ } else {
180
+ process.env.npm_config_user_agent = agent
181
+ }
182
+ try {
183
+ return fn()
184
+ } finally {
185
+ if (previous === undefined) {
186
+ delete process.env.npm_config_user_agent
187
+ } else {
188
+ process.env.npm_config_user_agent = previous
189
+ }
190
+ }
191
+ }
192
+
193
+ const EFFECTIVE_EXPORTS_MANIFEST = {
194
+ name: PACKAGE_NAME,
195
+ version: VERSION,
196
+ main: './dist/index.cjs',
197
+ types: './dist/index.d.ts',
198
+ files: ['dist'],
199
+ exports: {
200
+ '.': {
201
+ dev: './src/index.ts',
202
+ types: './dist/index.d.ts',
203
+ default: './dist/index.cjs',
204
+ },
205
+ './helper': {
206
+ dev: './src/helper.ts',
207
+ default: './dist/helper.cjs',
208
+ },
209
+ },
210
+ publishConfig: {
211
+ exports: {
212
+ '.': {
213
+ types: './dist/index.d.ts',
214
+ default: './dist/index.cjs',
215
+ },
216
+ './helper': './dist/helper.cjs',
217
+ },
218
+ },
219
+ }
220
+
221
+ async function createStagedRoot(
222
+ manifest: Record<string, unknown>,
223
+ stagedFiles: string[],
224
+ ) {
225
+ const rootDir = mkdtempSync(join(tmpdir(), 'napi-pre-publish-spec-'))
226
+ await writeFile(join(rootDir, 'package.json'), JSON.stringify(manifest))
227
+ for (const file of stagedFiles) {
228
+ await mkdir(join(rootDir, dirname(file)), { recursive: true })
229
+ await writeFile(join(rootDir, file), '// content\n')
230
+ }
231
+ return rootDir
232
+ }
233
+
234
+ test('only declared pnpm and yarn publishers rewrite the root exports', (t) => {
235
+ t.true(rootPublisherRewritesRootExports('pnpm'))
236
+ t.true(rootPublisherRewritesRootExports('yarn'))
237
+ t.false(rootPublisherRewritesRootExports('npm'))
238
+ t.false(rootPublisherRewritesRootExports('yarn-classic'))
239
+ t.false(rootPublisherRewritesRootExports(undefined))
240
+ })
241
+
242
+ test('resolves the root publisher from the CLI option over the config field', (t) => {
243
+ t.is(resolveRootPublisher(undefined, undefined), undefined)
244
+ t.is(resolveRootPublisher(undefined, 'pnpm'), 'pnpm')
245
+ t.is(resolveRootPublisher('npm', 'pnpm'), 'npm')
246
+ t.is(resolveRootPublisher('yarn-classic', undefined), 'yarn-classic')
247
+ t.throws(() => resolveRootPublisher('pnpm@10', undefined), {
248
+ message:
249
+ 'Unknown --root-publisher value "pnpm@10". Expected one of: npm, pnpm, yarn, yarn-classic.',
250
+ })
251
+ t.throws(() => resolveRootPublisher(undefined, 'berry'), {
252
+ message:
253
+ 'Unknown napi.rootPublisher value "berry". Expected one of: npm, pnpm, yarn, yarn-classic.',
254
+ })
255
+ })
256
+
257
+ test('sniffs rewriting package managers from the user agent', (t) => {
258
+ t.is(sniffRewritingPublisherFromUserAgent(PNPM_USER_AGENT), 'pnpm')
259
+ t.is(sniffRewritingPublisherFromUserAgent(YARN_USER_AGENT), 'yarn')
260
+ t.is(
261
+ sniffRewritingPublisherFromUserAgent('yarn/2.4.3 npm/? node/v24.19.0'),
262
+ 'yarn',
263
+ )
264
+ t.is(sniffRewritingPublisherFromUserAgent(YARN_CLASSIC_USER_AGENT), undefined)
265
+ t.is(
266
+ sniffRewritingPublisherFromUserAgent('yarn/ npm/? node/v24.19.0'),
267
+ undefined,
268
+ )
269
+ t.is(sniffRewritingPublisherFromUserAgent(NPM_USER_AGENT), undefined)
270
+ t.is(
271
+ sniffRewritingPublisherFromUserAgent('bun/1.2.0 node/v24.19.0'),
272
+ undefined,
273
+ )
274
+ // An explicit `undefined` argument falls back to the ambient environment,
275
+ // so pin the environment to "absent" for the no-agent case.
276
+ withNpmUserAgent(undefined, () => {
277
+ t.is(sniffRewritingPublisherFromUserAgent(), undefined)
278
+ })
279
+ })
280
+
281
+ test('requires a complete version token to sniff a rewriting publisher', (t) => {
282
+ // `yarn/2garbage` is not yarn 2 and `pnpm/x` is not pnpm: matching only the
283
+ // numeric prefix (or the bare name) would name the wrong manager in the hint.
284
+ t.is(
285
+ sniffRewritingPublisherFromUserAgent(MALFORMED_YARN_USER_AGENT),
286
+ undefined,
287
+ )
288
+ t.is(sniffRewritingPublisherFromUserAgent('yarn/2garbage'), undefined)
289
+ t.is(sniffRewritingPublisherFromUserAgent('yarn/4.6.0.1 npm/?'), undefined)
290
+ t.is(
291
+ sniffRewritingPublisherFromUserAgent(MALFORMED_PNPM_USER_AGENT),
292
+ undefined,
293
+ )
294
+ t.is(
295
+ sniffRewritingPublisherFromUserAgent('pnpm/x npm/? node/v24.19.0'),
296
+ undefined,
297
+ )
298
+ t.is(sniffRewritingPublisherFromUserAgent('pnpm/'), undefined)
299
+ // Well-formed prerelease and build metadata tails still parse.
300
+ t.is(
301
+ sniffRewritingPublisherFromUserAgent('yarn/4.10.0-git.20250101 npm/?'),
302
+ 'yarn',
303
+ )
304
+ t.is(
305
+ sniffRewritingPublisherFromUserAgent('pnpm/10.15.1+sha512.ab npm/?'),
306
+ 'pnpm',
307
+ )
308
+ })
309
+
310
+ test('collects publishConfig.exports only for a declared rewriting publisher', (t) => {
311
+ const effectiveOnly = ['dist/helper.cjs', 'dist/index.cjs', 'dist/index.d.ts']
312
+ for (const rootPublisher of ['pnpm', 'yarn'] as const) {
313
+ t.deepEqual(
314
+ collectRootPackagePathReferences(
315
+ EFFECTIVE_EXPORTS_MANIFEST,
316
+ rootPublisher,
317
+ ).sort(),
318
+ effectiveOnly,
319
+ )
320
+ }
321
+ })
322
+
323
+ test('collects the union of exports and publishConfig.exports for every other publisher', (t) => {
324
+ const union = [
325
+ 'dist/helper.cjs',
326
+ 'dist/index.cjs',
327
+ 'dist/index.d.ts',
328
+ 'src/helper.ts',
329
+ 'src/index.ts',
330
+ ]
331
+ for (const rootPublisher of ['npm', 'yarn-classic', undefined] as const) {
332
+ t.deepEqual(
333
+ collectRootPackagePathReferences(
334
+ EFFECTIVE_EXPORTS_MANIFEST,
335
+ rootPublisher,
336
+ ).sort(),
337
+ union,
338
+ )
339
+ }
340
+ })
341
+
342
+ test('never lets the ambient user agent widen or narrow the validated map', (t) => {
343
+ const union = [
344
+ 'dist/helper.cjs',
345
+ 'dist/index.cjs',
346
+ 'dist/index.d.ts',
347
+ 'src/helper.ts',
348
+ 'src/index.ts',
349
+ ]
350
+ const effectiveOnly = ['dist/helper.cjs', 'dist/index.cjs', 'dist/index.d.ts']
351
+ for (const agent of [
352
+ PNPM_USER_AGENT,
353
+ YARN_USER_AGENT,
354
+ NPM_USER_AGENT,
355
+ YARN_CLASSIC_USER_AGENT,
356
+ MALFORMED_YARN_USER_AGENT,
357
+ MALFORMED_PNPM_USER_AGENT,
358
+ undefined,
359
+ ]) {
360
+ withNpmUserAgent(agent, () => {
361
+ // No declared publisher: always the conservative union, even under pnpm.
362
+ t.deepEqual(
363
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST).sort(),
364
+ union,
365
+ )
366
+ // A declared publisher wins over whatever the agent claims.
367
+ t.deepEqual(
368
+ collectRootPackagePathReferences(
369
+ EFFECTIVE_EXPORTS_MANIFEST,
370
+ 'npm',
371
+ ).sort(),
372
+ union,
373
+ )
374
+ t.deepEqual(
375
+ collectRootPackagePathReferences(
376
+ EFFECTIVE_EXPORTS_MANIFEST,
377
+ 'pnpm',
378
+ ).sort(),
379
+ effectiveOnly,
380
+ )
381
+ })
382
+ }
383
+ })
384
+
385
+ test('falls back to raw exports without publishConfig.exports', (t) => {
386
+ const manifest = {
387
+ ...EFFECTIVE_EXPORTS_MANIFEST,
388
+ publishConfig: { access: 'public' },
389
+ }
390
+ const rawRefs = [
391
+ 'dist/helper.cjs',
392
+ 'dist/index.cjs',
393
+ 'dist/index.d.ts',
394
+ 'src/helper.ts',
395
+ 'src/index.ts',
396
+ ]
397
+ for (const rootPublisher of ['pnpm', 'npm', undefined] as const) {
398
+ t.deepEqual(
399
+ collectRootPackagePathReferences(manifest, rootPublisher).sort(),
400
+ rawRefs,
401
+ )
402
+ }
403
+ })
404
+
405
+ test('accepts dev-only export conditions replaced by publishConfig.exports under a declared pnpm publisher', async (t) => {
406
+ // The raw `exports` references src/*.ts files that are neither on disk nor
407
+ // in the packlist; under a rewriting publisher only the publish-effective
408
+ // references must survive pack.
409
+ const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
410
+ 'dist/index.cjs',
411
+ 'dist/index.d.ts',
412
+ 'dist/helper.cjs',
413
+ ])
414
+ t.teardown(() => rm(rootDir, { recursive: true, force: true }))
415
+
416
+ t.notThrows(() =>
417
+ validateRootFacadePacklist(
418
+ rootDir,
419
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST, 'pnpm'),
420
+ 'pnpm',
421
+ ),
422
+ )
423
+ })
424
+
425
+ test('rejects dev-only raw export references when no root publisher is declared', async (t) => {
426
+ // Nobody declared a rewriting publisher, so the raw `exports` may ship: the
427
+ // dev sources exist on disk but `files: ['dist']` keeps them out of the
428
+ // packlist. Running under npm adds no hint.
429
+ const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
430
+ 'dist/index.cjs',
431
+ 'dist/index.d.ts',
432
+ 'dist/helper.cjs',
433
+ 'src/index.ts',
434
+ 'src/helper.ts',
435
+ ])
436
+ t.teardown(() => rm(rootDir, { recursive: true, force: true }))
437
+
438
+ for (const agent of [NPM_USER_AGENT, YARN_CLASSIC_USER_AGENT, undefined]) {
439
+ t.throws(
440
+ () =>
441
+ withNpmUserAgent(agent, () =>
442
+ validateRootFacadePacklist(
443
+ rootDir,
444
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
445
+ ),
446
+ ),
447
+ {
448
+ message:
449
+ 'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.',
450
+ },
451
+ )
452
+ }
453
+ // Declaring npm explicitly keeps the same failure, still without a hint.
454
+ t.throws(
455
+ () =>
456
+ withNpmUserAgent(PNPM_USER_AGENT, () =>
457
+ validateRootFacadePacklist(
458
+ rootDir,
459
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST, 'npm'),
460
+ 'npm',
461
+ ),
462
+ ),
463
+ {
464
+ message:
465
+ 'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.',
466
+ },
467
+ )
468
+ })
469
+
470
+ test('points an undeclared publisher at the option when a rewriting manager is running', async (t) => {
471
+ // The union branch failed while pnpm invoked us: pnpm may well be the root
472
+ // publisher, so name the option instead of leaving the user to guess.
473
+ const rootDir = await createStagedRoot(EFFECTIVE_EXPORTS_MANIFEST, [
474
+ 'dist/index.cjs',
475
+ 'dist/index.d.ts',
476
+ 'dist/helper.cjs',
477
+ 'src/index.ts',
478
+ 'src/helper.ts',
479
+ ])
480
+ t.teardown(() => rm(rootDir, { recursive: true, force: true }))
481
+
482
+ t.throws(
483
+ () =>
484
+ withNpmUserAgent(PNPM_USER_AGENT, () =>
485
+ validateRootFacadePacklist(
486
+ rootDir,
487
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
488
+ ),
489
+ ),
490
+ {
491
+ message: `The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts. Add them to package.json "files" or remove the matching .npmignore rules.${PNPM_HINT}`,
492
+ },
493
+ )
494
+ // Yarn berry gets its own name; a malformed agent gets no hint at all.
495
+ const yarnError = t.throws(() =>
496
+ withNpmUserAgent(YARN_USER_AGENT, () =>
497
+ validateRootFacadePacklist(
498
+ rootDir,
499
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
500
+ ),
501
+ ),
502
+ )
503
+ t.true(yarnError!.message.includes('set napi.rootPublisher to "yarn"'))
504
+ const malformedError = t.throws(() =>
505
+ withNpmUserAgent(MALFORMED_PNPM_USER_AGENT, () =>
506
+ validateRootFacadePacklist(
507
+ rootDir,
508
+ collectRootPackagePathReferences(EFFECTIVE_EXPORTS_MANIFEST),
509
+ ),
510
+ ),
511
+ )
512
+ t.false(malformedError!.message.includes('napi.rootPublisher'))
513
+ })
514
+
515
+ test('still rejects publishConfig.exports references omitted by npm pack', async (t) => {
516
+ const manifest = {
517
+ ...EFFECTIVE_EXPORTS_MANIFEST,
518
+ // Restrict the packlist so dist/helper.cjs exists on disk but is not
519
+ // packed: the effective exports validation must keep failing under both
520
+ // publisher kinds.
521
+ files: ['dist/index.cjs', 'dist/index.d.ts'],
522
+ }
523
+ const rootDir = await createStagedRoot(manifest, [
524
+ 'dist/index.cjs',
525
+ 'dist/index.d.ts',
526
+ 'dist/helper.cjs',
527
+ 'src/index.ts',
528
+ 'src/helper.ts',
529
+ ])
530
+ t.teardown(() => rm(rootDir, { recursive: true, force: true }))
531
+
532
+ t.throws(
533
+ () =>
534
+ withNpmUserAgent(PNPM_USER_AGENT, () =>
535
+ validateRootFacadePacklist(
536
+ rootDir,
537
+ collectRootPackagePathReferences(manifest, 'pnpm'),
538
+ 'pnpm',
539
+ ),
540
+ ),
541
+ {
542
+ message:
543
+ 'The threadless WASI root package references paths omitted by npm pack: dist/helper.cjs. Add them to package.json "files" or remove the matching .npmignore rules.',
544
+ },
545
+ )
546
+ for (const rootPublisher of ['npm', 'yarn-classic'] as const) {
547
+ t.throws(
548
+ () =>
549
+ validateRootFacadePacklist(
550
+ rootDir,
551
+ collectRootPackagePathReferences(manifest, rootPublisher),
552
+ rootPublisher,
553
+ ),
554
+ {
555
+ message:
556
+ 'The threadless WASI root package references paths omitted by npm pack: src/index.ts, src/helper.ts, dist/helper.cjs. Add them to package.json "files" or remove the matching .npmignore rules.',
557
+ },
558
+ )
559
+ }
560
+ })
@@ -2,7 +2,11 @@ import ava, { type ExecutionContext } from 'ava'
2
2
  import { parseSync } from 'oxc-parser'
3
3
 
4
4
  import { createCjsBinding, createEsmBinding } from '../templates/js-binding.js'
5
- import { createWasiBrowserBinding } from '../templates/load-wasi-template.js'
5
+ import {
6
+ createWasiBinding,
7
+ createWasiBrowserBinding,
8
+ createWasiDeferredBrowserBinding,
9
+ } from '../templates/load-wasi-template.js'
6
10
  import { createWasiBrowserWorkerBinding } from '../templates/wasi-worker-template.js'
7
11
 
8
12
  const test = ava
@@ -200,6 +204,86 @@ for (const { name, code } of cjsBindingCases) {
200
204
  })
201
205
  }
202
206
 
207
+ // `napi_prepare_wasm_env_cleanup` only *queues* the promise settlements of the
208
+ // tasks it cancels: `napi_call_threadsafe_function` appends to the
209
+ // threadsafe-function queue, and @emnapi/core dispatches that queue from a
210
+ // macrotask two coalescing turns later. `Context.destroy()` then drains the
211
+ // queue with a null env and discards it. Measured against @emnapi/core
212
+ // 2.0.0-alpha.3: zero microtask checkpoints ever deliver the settlement, and it
213
+ // takes exactly two macrotask turns.
214
+ //
215
+ // So a loader that emits the barrier and `destroy()` back to back strands
216
+ // exactly the promises the barrier exists to settle, and it does so silently.
217
+ // `examples/custom-async-runtime` covers the hand-written loader and the two
218
+ // emitted flavors it builds (node cjs threadless, deferred/workerd)
219
+ // behaviorally; the other flavors are never instantiated by any test, so assert
220
+ // the shape here as well.
221
+ const wasiLoaderCases: Array<{ name: string; code: string }> = [
222
+ { name: 'node cjs', code: createWasiBinding('test', '@scope/test') },
223
+ {
224
+ name: 'node cjs threadless',
225
+ code: createWasiBinding('test', '@scope/test', 4000, 65536, false),
226
+ },
227
+ { name: 'browser esm', code: createWasiBrowserBinding('test') },
228
+ { name: 'deferred/workerd', code: createWasiDeferredBrowserBinding('test') },
229
+ ]
230
+
231
+ for (const { name, code } of wasiLoaderCases) {
232
+ test(`WASI loader waits for queued settlements before destroy: ${name}`, (t) => {
233
+ t.true(
234
+ code.includes('napi_prepare_wasm_env_cleanup'),
235
+ 'loader must run the pre-teardown barrier',
236
+ )
237
+ t.true(
238
+ code.includes('napi_wasm_env_cleanup_pending'),
239
+ 'loader must poll napi_wasm_env_cleanup_pending; without it the barrier queues settlements that Context.destroy() then discards',
240
+ )
241
+ t.true(
242
+ code.includes('__drainWasmEnvCleanup'),
243
+ 'loader must call the settlement drain on its disposal path',
244
+ )
245
+ t.true(
246
+ code.includes('__scheduleMacrotask'),
247
+ 'the drain must yield real macrotask turns; microtask checkpoints never let the emnapi dispatch run',
248
+ )
249
+ // Initialization rollback is the same hazard, one step earlier. Registration
250
+ // runs with a live environment, so a module-init hook can start a task and
251
+ // *then* return an error; the barrier cancels the task and queues its
252
+ // rejection, and a rollback that destroys the context in the same turn
253
+ // discards it — stranding a promise that already escaped into JavaScript.
254
+ t.true(
255
+ initializationRollbackBody(code).includes(
256
+ DRAIN_CALL_BY_ROLLBACK_FLAVOR[
257
+ code.includes(EAGER_ROLLBACK_SIGNATURE) ? 'eager' : 'deferred'
258
+ ],
259
+ ),
260
+ 'initialization rollback must drain queued settlements before destroying the context',
261
+ )
262
+ })
263
+ }
264
+
265
+ const EAGER_ROLLBACK_SIGNATURE = 'function __rollbackWasiInitialization() {'
266
+ const DEFERRED_ROLLBACK_SIGNATURE = "__lifecycleState = 'failed'"
267
+ const DRAIN_CALL_BY_ROLLBACK_FLAVOR = {
268
+ eager: '__drainWasmEnvCleanup',
269
+ deferred: '__prepareForDisposal',
270
+ } as const
271
+
272
+ /**
273
+ * The body of a loader's initialization-failure path: `__rollbackWasiInitialization`
274
+ * for the eager loaders, `__createInstance`'s catch for the deferred one.
275
+ * Sliced rather than searched whole-file, so a drain that only runs on the
276
+ * ordinary disposal path cannot satisfy the assertion.
277
+ */
278
+ function initializationRollbackBody(code: string): string {
279
+ const eagerStart = code.indexOf(EAGER_ROLLBACK_SIGNATURE)
280
+ if (eagerStart !== -1) {
281
+ return code.slice(eagerStart, code.indexOf('\n}', eagerStart))
282
+ }
283
+ const deferredStart = code.indexOf(DEFERRED_ROLLBACK_SIGNATURE)
284
+ return code.slice(deferredStart, code.indexOf('throw error', deferredStart))
285
+ }
286
+
203
287
  test('createEsmBinding is Node 12 compatible', (t) => {
204
288
  const code = createEsmBinding('test', '@scope/test', ['sum'])
205
289
  assertValidJS(t, code, 'esm')