bare-module-traverse 2.5.9 → 2.7.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 CHANGED
@@ -219,12 +219,16 @@ Options are the same as `traverse()` for all functions.
219
219
 
220
220
  ### `const generator = traverse.package(url, source, artifacts, visited[, options])`
221
221
 
222
- ### `const generator = traverse.preresolved(url, source, resolutions, artifacts, visited[, options])`
222
+ ### `const generator = traverse.preresolved(url, source, attributes, resolutions, artifacts, visited[, options])`
223
223
 
224
224
  ### `const generator = traverse.imports(parentURL, source, imports, artifacts, lexer, visited[, options])`
225
225
 
226
226
  ### `const generator = traverse.link(entry, specifier, condition, parentURL, imports, artifacts, visited[, options])`
227
227
 
228
+ ### `traverse.assertAddonContext(url, scope, context, addonContexts)`
229
+
230
+ Record in `addonContexts` that the package at `scope` loads its addons in `context`, an object of the `host` and the URL of the `wasi` provider, either `null` for the defaults. Throws if it was recorded for another context, or if `url` doesn't load on `context.host`.
231
+
228
232
  ### `const generator = traverse.addons(parentURL, artifacts, visited[, options])`
229
233
 
230
234
  ### `const generator = traverse.assets(patterns, parentURL, artifacts, visited[, options])`
package/index.d.ts CHANGED
@@ -3,9 +3,10 @@ import Buffer from 'bare-buffer'
3
3
  import {
4
4
  type ConditionalSpecifier,
5
5
  type ImportsMap,
6
- type ResolutionsMap
6
+ type ResolutionsMap,
7
+ type ResolveOptions as ModuleResolveOptions
7
8
  } from 'bare-module-resolve'
8
- import { type ResolveOptions, type Resolver } from 'bare-addon-resolve'
9
+ import { type ResolveOptions as AddonResolveOptions, type Resolver } from 'bare-addon-resolve'
9
10
  import { type Import, type Export } from 'bare-module-lexer'
10
11
 
11
12
  interface Dependency {
@@ -41,6 +42,8 @@ type AliasableExtension =
41
42
  | '.bin'
42
43
  | '.txt'
43
44
 
45
+ interface ResolveOptions extends ModuleResolveOptions, AddonResolveOptions {}
46
+
44
47
  interface TraverseOptions extends ResolveOptions {
45
48
  /**
46
49
  * The type to assume for a module that has no type of its own, such as one
@@ -174,9 +177,22 @@ declare namespace traverse {
174
177
  opts?: TraverseOptions
175
178
  ): Traversal
176
179
 
180
+ interface AddonContext {
181
+ host: string | null
182
+ wasi: string | null
183
+ }
184
+
185
+ export function assertAddonContext(
186
+ url: URL,
187
+ scope: string,
188
+ context: Partial<AddonContext>,
189
+ addonContexts: Map<string, AddonContext>
190
+ ): void
191
+
177
192
  export function preresolved(
178
193
  url: URL,
179
194
  source: string | Buffer,
195
+ attributes: Record<string, string> | null,
180
196
  resolution: ResolutionsMap,
181
197
  artifacts: Artifacts,
182
198
  visited: Set<string>,
package/index.js CHANGED
@@ -98,7 +98,7 @@ module.exports = exports = function traverse(
98
98
  }
99
99
 
100
100
  while (queue.length > 0 || deferred.length > 0) {
101
- yield* drive(queue.length > 0 ? queue.pop() : deferred.shift())
101
+ yield* drive(queue.length > 0 ? queue.shift() : deferred.shift())
102
102
  }
103
103
 
104
104
  return artifacts
@@ -160,7 +160,7 @@ module.exports = exports = function traverse(
160
160
  }
161
161
 
162
162
  while (queue.length > 0 || deferred.length > 0) {
163
- yield* drive(queue.length > 0 ? queue.pop() : deferred.shift())
163
+ yield* drive(queue.length > 0 ? queue.shift() : deferred.shift())
164
164
  }
165
165
 
166
166
  return artifacts
@@ -192,10 +192,36 @@ exports.alias = function alias(url, opts = {}) {
192
192
  }
193
193
 
194
194
  exports.module = function* (url, source, attributes, artifacts, visited, opts = {}) {
195
- const { resolutions = null, asset = false, probed, types } = opts
196
-
197
195
  attributes = attributes || {}
198
196
 
197
+ if (opts.contexts === undefined) opts = { ...opts, contexts: new Map() }
198
+ if (opts.addonContexts === undefined) opts = { ...opts, addonContexts: new Map() }
199
+ if (opts.resolvedImports === undefined) opts = { ...opts, resolvedImports: new Map() }
200
+
201
+ // The host and WASI attributes set the context that the addons of the module
202
+ // and everything it imports load in. A graph is walked again for each context
203
+ // it is imported under, even where it was visited before. The conditions of
204
+ // the host are added to those that may be targeted, so that the order of the
205
+ // conditions in a package still decides between them and the native ones.
206
+ if (attributes.host !== undefined || attributes.wasi !== undefined) {
207
+ const { addonHost = null, addonWASI = null } = opts
208
+
209
+ const host = attributes.host === undefined ? addonHost : addonHostForAttribute(attributes.host)
210
+ const wasi = attributes.wasi === undefined ? addonWASI : attributes.wasi
211
+
212
+ if (host !== addonHost || wasi !== addonWASI) {
213
+ opts = { ...opts, addonHost: host, addonWASI: wasi }
214
+
215
+ if (host !== addonHost) {
216
+ opts.conditions = withConditions(opts.conditions, host.split('-'))
217
+ }
218
+
219
+ visited = visitedFor(opts.contexts, host, wasi)
220
+ }
221
+ }
222
+
223
+ const { resolutions = null, asset = false, probed, types } = opts
224
+
199
225
  if (visited.has(url.href)) {
200
226
  // An asset is only read for its bytes, so it doesn't care what type the
201
227
  // module is imported as elsewhere.
@@ -214,7 +240,9 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
214
240
  if (opts.prefixes === undefined) opts = { ...opts, prefixes: new Map() }
215
241
  if (opts.types === undefined) opts = { ...opts, types: new Map() }
216
242
 
217
- const artifact = asset === true || moduleType(url, attributes, null, opts) === constants.ADDON
243
+ const artifact =
244
+ asset === true ||
245
+ (moduleType(url, attributes, null, opts) === constants.ADDON && !isWebAssembly(url))
218
246
 
219
247
  if (source === null) {
220
248
  if (url.protocol === 'data:') {
@@ -234,8 +262,17 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
234
262
  }
235
263
  }
236
264
 
237
- if (resolutions) {
238
- if (yield* exports.preresolved(url, source, resolutions, artifacts, visited, opts)) {
265
+ // A module resolves its imports once, however many contexts it is loaded in,
266
+ // so a context that reaches it after another walks what the other resolved.
267
+ // The addons that are reached are then checked against the context.
268
+ const resolved = opts.resolvedImports.get(url.href)
269
+
270
+ const preresolved = resolved === undefined ? resolutions : { [url.href]: resolved }
271
+
272
+ if (preresolved) {
273
+ if (
274
+ yield* exports.preresolved(url, source, attributes, preresolved, artifacts, visited, opts)
275
+ ) {
239
276
  return true
240
277
  }
241
278
  }
@@ -290,15 +327,22 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
290
327
  }
291
328
  }
292
329
 
330
+ const compressed = compressImportsMap(imports)
331
+
332
+ opts.resolvedImports.set(url.href, compressed)
333
+
293
334
  yield {
294
- dependency: {
295
- url: exports.alias(url, opts),
296
- source,
297
- type,
298
- naturalType,
299
- imports: compressImportsMap(imports),
300
- lexer
301
- }
335
+ dependency: withWASI(
336
+ {
337
+ url: exports.alias(url, opts),
338
+ source,
339
+ type,
340
+ naturalType,
341
+ imports: compressed,
342
+ lexer
343
+ },
344
+ opts
345
+ )
302
346
  }
303
347
 
304
348
  return true
@@ -342,7 +386,15 @@ exports.package = function* (url, source, artifacts, visited, opts = {}) {
342
386
  return false
343
387
  }
344
388
 
345
- exports.preresolved = function* (url, source, resolutions, artifacts, visited, opts = {}) {
389
+ exports.preresolved = function* (
390
+ url,
391
+ source,
392
+ attributes,
393
+ resolutions,
394
+ artifacts,
395
+ visited,
396
+ opts = {}
397
+ ) {
346
398
  const {
347
399
  builtinProtocol = 'builtin:',
348
400
  linkedProtocol = 'linked:',
@@ -353,6 +405,17 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
353
405
 
354
406
  if (typeof imports !== 'object' || imports === null) return false
355
407
 
408
+ attributes = attributes || {}
409
+
410
+ const parentURL = url
411
+
412
+ // The resolutions map leaves out the import attributes, so they are read
413
+ // from the source instead.
414
+ const lexed =
415
+ opts.asset !== true && isJavaScript(moduleType(url, attributes, null, opts))
416
+ ? lex(source)
417
+ : null
418
+
356
419
  let info = null
357
420
 
358
421
  for (const [specifier, entry] of Object.entries(imports)) {
@@ -382,7 +445,11 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
382
445
  } else if (url.protocol === deferredProtocol) {
383
446
  continue
384
447
  } else if (url.protocol === builtinProtocol || url.protocol === linkedProtocol) {
385
- if (addon && artifacts !== null) addURL(artifacts.addons, url)
448
+ if (addon) {
449
+ assertImportedAddonContext(url, imports, parentURL, opts)
450
+
451
+ if (artifacts !== null) addURL(artifacts.addons, url)
452
+ }
386
453
  } else if (asset) {
387
454
  if (artifacts !== null) {
388
455
  const expanded = yield* expandAsset(url, artifacts, visited, { ...opts, importType: 0 })
@@ -402,13 +469,24 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
402
469
  }
403
470
  }
404
471
  } else {
405
- if (addon && artifacts !== null) addURL(artifacts.addons, url)
472
+ if (addon) {
473
+ assertImportedAddonContext(url, imports, parentURL, opts)
474
+
475
+ if (artifacts !== null) addURL(artifacts.addons, url)
476
+ }
406
477
 
407
478
  yield {
408
- children: exports.module(url, null, {}, artifacts, visited, {
409
- ...opts,
410
- importType: 0
411
- }),
479
+ children: exports.module(
480
+ url,
481
+ null,
482
+ addon ? { type: 'addon' } : importAttributes(lexed, specifier, imports),
483
+ artifacts,
484
+ visited,
485
+ {
486
+ ...opts,
487
+ importType: 0
488
+ }
489
+ ),
412
490
  deferred: false
413
491
  }
414
492
  }
@@ -424,26 +502,27 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
424
502
  }
425
503
  }
426
504
 
427
- const type = moduleType(url, {}, info, opts)
505
+ const type = moduleType(url, attributes, info, opts)
428
506
 
429
507
  const lexer = { imports: [], exports: [] }
430
508
 
431
- if (opts.asset !== true && (type === constants.SCRIPT || type === constants.MODULE)) {
432
- const lexed = lex(source)
433
-
509
+ if (lexed !== null && isJavaScript(type)) {
434
510
  lexer.imports = lexed.imports
435
511
  lexer.exports = lexed.exports
436
512
  }
437
513
 
438
514
  yield {
439
- dependency: {
440
- url: exports.alias(url, opts),
441
- source,
442
- type,
443
- naturalType: type,
444
- imports: compressImportsMap(imports),
445
- lexer
446
- }
515
+ dependency: withWASI(
516
+ {
517
+ url: exports.alias(url, opts),
518
+ source,
519
+ type,
520
+ naturalType: naturalModuleType(url, attributes, info, type, opts),
521
+ imports: compressImportsMap(imports),
522
+ lexer
523
+ },
524
+ opts
525
+ )
447
526
  }
448
527
 
449
528
  return true
@@ -491,8 +570,10 @@ exports.link = function* (
491
570
  visited,
492
571
  opts = {}
493
572
  ) {
494
- if (entry.attributes.imports) {
495
- const specifier = entry.attributes.imports
573
+ for (const name of ['imports', 'wasi']) {
574
+ const specifier = entry.attributes[name]
575
+
576
+ if (!specifier) continue
496
577
 
497
578
  yield* resolveImport(
498
579
  { type: 0, specifier, names: [], attributes: {}, position: [0, 0, 0] },
@@ -515,7 +596,8 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
515
596
  builtinProtocol = 'builtin:',
516
597
  linkedProtocol = 'linked:',
517
598
  deferredProtocol = 'deferred:',
518
- deferUnresolved = false
599
+ deferUnresolved = false,
600
+ loadable = null
519
601
  } = opts
520
602
 
521
603
  const matchedConditions = []
@@ -524,7 +606,11 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
524
606
 
525
607
  matchedConditions.push(condition)
526
608
 
527
- const resolver = resolve(entry, parentURL, opts)
609
+ const resolver = resolve(
610
+ entry,
611
+ parentURL,
612
+ condition === 'addon' && opts.addonHost ? { ...opts, hosts: [opts.addonHost] } : opts
613
+ )
528
614
  const candidates = []
529
615
 
530
616
  let next = resolver.next()
@@ -548,6 +634,8 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
548
634
  url.protocol === linkedProtocol ||
549
635
  url.protocol === deferredProtocol
550
636
  ) {
637
+ if (condition === 'addon') assertImportedAddonContext(url, imports, parentURL, opts)
638
+
551
639
  addResolution(imports, specifier, matchedConditions, url)
552
640
 
553
641
  resolved = true
@@ -565,7 +653,7 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
565
653
  condition === 'addon' ||
566
654
  moduleType(url, entry.attributes, null, opts) === constants.ADDON
567
655
  ) {
568
- let exists = yield { probe: url }
656
+ let exists = isWebAssembly(url) ? undefined : yield { probe: url }
569
657
  let source = null
570
658
 
571
659
  if (exists === undefined) {
@@ -573,13 +661,15 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
573
661
  exists = source !== null
574
662
  }
575
663
 
576
- if (exists) {
577
- resolution = yield* postresolve(url)
664
+ if (exists) resolution = yield* postresolve(url)
665
+
666
+ if (exists && (loadable === null || loadable(resolution))) {
667
+ assertImportedAddonContext(resolution, imports, parentURL, opts)
578
668
 
579
669
  addResolution(imports, specifier, matchedConditions, exports.alias(resolution, opts))
580
670
 
581
671
  yield {
582
- children: exports.module(resolution, source, {}, artifacts, visited, {
672
+ children: exports.module(resolution, source, { type: 'addon' }, artifacts, visited, {
583
673
  ...opts,
584
674
  probed: true
585
675
  }),
@@ -608,6 +698,10 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
608
698
  attributes = { ...attributes, imports: imports[attributes.imports].default }
609
699
  }
610
700
 
701
+ if (attributes.wasi) {
702
+ attributes = { ...attributes, wasi: imports[attributes.wasi].default }
703
+ }
704
+
611
705
  yield {
612
706
  children: exports.module(resolution, source, attributes, artifacts, visited, {
613
707
  ...opts,
@@ -657,7 +751,115 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
657
751
  }
658
752
  }
659
753
 
660
- const ADDON_EXTENSION = /\.(bare|node)$/
754
+ const ADDON_EXTENSION = /\.(bare|node|wasm)$/
755
+
756
+ function importAttributes(lexed, specifier, imports) {
757
+ const attributes = {}
758
+
759
+ if (lexed === null) return attributes
760
+
761
+ for (const entry of lexed.imports) {
762
+ if (entry.specifier !== specifier) continue
763
+
764
+ for (const name of ['type', 'host', 'wasi']) {
765
+ if (typeof entry.attributes[name] === 'string') attributes[name] = entry.attributes[name]
766
+ }
767
+ }
768
+
769
+ if (attributes.wasi !== undefined) attributes.wasi = defaultResolution(imports[attributes.wasi])
770
+
771
+ return attributes
772
+ }
773
+
774
+ function defaultResolution(entry) {
775
+ return typeof entry === 'string' ? entry : entry.default
776
+ }
777
+
778
+ // Only hosts that load on any platform may be asked for, so that an import
779
+ // can't pick the native addons of another platform.
780
+ const portableHosts = ['wasi-wasm32']
781
+
782
+ function addonHostForAttribute(host) {
783
+ if (portableHosts.includes(host)) return host
784
+
785
+ throw errors.UNKNOWN_ADDON_HOST(`Addon host '${host}' is not supported`)
786
+ }
787
+
788
+ function withConditions(conditions = [], added) {
789
+ if (conditions.every((condition) => typeof condition === 'string')) {
790
+ return [...conditions, ...added]
791
+ }
792
+
793
+ return conditions.map((subset) => withConditions(subset, added))
794
+ }
795
+
796
+ function visitedFor(contexts, host, wasi) {
797
+ const key = JSON.stringify([host, wasi])
798
+
799
+ let context = contexts.get(key)
800
+
801
+ if (context === undefined) contexts.set(key, (context = { host, wasi, visited: new Set() }))
802
+
803
+ return context.visited
804
+ }
805
+
806
+ function withWASI(dependency, opts) {
807
+ const { addonWASI = null } = opts
808
+
809
+ if (addonWASI !== null && dependency.type === constants.ADDON && isWebAssembly(dependency.url)) {
810
+ dependency.wasi = addonWASI
811
+ }
812
+
813
+ return dependency
814
+ }
815
+
816
+ function assertImportedAddonContext(url, imports, parentURL, opts) {
817
+ const { addonHost = null, addonWASI = null, addonContexts } = opts
818
+
819
+ exports.assertAddonContext(
820
+ url,
821
+ imports['#package'] || parentURL.href,
822
+ { host: addonHost, wasi: addonWASI },
823
+ addonContexts
824
+ )
825
+ }
826
+
827
+ exports.assertAddonContext = function assertAddonContext(url, scope, context, addonContexts) {
828
+ if (url.protocol === 'builtin:') return
829
+
830
+ const { host = null } = context
831
+
832
+ if (host !== null && !isWebAssembly(url)) {
833
+ throw errors.ADDON_HOST_INCOMPATIBLE(
834
+ `Addon '${url.href}' is not a WebAssembly addon, but '${host}' was asked for`
835
+ )
836
+ }
837
+
838
+ // Only WebAssembly addons are given WASI capabilities.
839
+ const wasi = isWebAssembly(url) ? context.wasi || null : null
840
+
841
+ const recorded = addonContexts.get(scope)
842
+
843
+ if (recorded === undefined) {
844
+ addonContexts.set(scope, { host, wasi })
845
+ } else if (recorded.host !== host) {
846
+ throw errors.ADDON_HOST_INCOMPATIBLE(
847
+ `Addons of '${scope}' are already resolved for ${recorded.host === null ? 'the default hosts' : `'${recorded.host}'`}`
848
+ )
849
+ } else if (recorded.wasi !== wasi) {
850
+ throw errors.ADDON_WASI_INCOMPATIBLE(
851
+ `Addons of '${scope}' are already given ${recorded.wasi === null ? 'the default WASI capabilities' : `the WASI capabilities of '${recorded.wasi}'`}`
852
+ )
853
+ }
854
+ }
855
+
856
+ function isJavaScript(type) {
857
+ return type === constants.SCRIPT || type === constants.MODULE
858
+ }
859
+
860
+ function isWebAssembly(url) {
861
+ return url.pathname.endsWith('.wasm')
862
+ }
661
863
 
662
864
  exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
663
865
  let yielded = false
@@ -0,0 +1,23 @@
1
+ // One set of conditions for each host, each with the targeted conditions. A
2
+ // host whose conditions are targeted in every set already has its resolutions,
3
+ // so it gets no set of its own.
4
+ module.exports = function conditions(base, hosts, targets) {
5
+ const subsets = leaves(targets)
6
+
7
+ const own = hosts.filter(
8
+ (host) =>
9
+ !subsets.every((subset) => host.split('-').every((condition) => subset.includes(condition)))
10
+ )
11
+
12
+ return (own.length > 0 ? own : hosts).flatMap((host) =>
13
+ subsets.map((subset) => [...base, ...host.split('-'), ...subset])
14
+ )
15
+ }
16
+
17
+ // Conditions nest to any depth and are tried depth first, so they match as
18
+ // their innermost lists would in order.
19
+ function leaves(conditions) {
20
+ if (conditions.every((condition) => typeof condition === 'string')) return [conditions]
21
+
22
+ return conditions.flatMap(leaves)
23
+ }
package/lib/errors.js CHANGED
@@ -49,6 +49,18 @@ module.exports = class ModuleTraverseError extends Error {
49
49
  return new ModuleTraverseError(msg, ModuleTraverseError.TYPE_INCOMPATIBLE)
50
50
  }
51
51
 
52
+ static UNKNOWN_ADDON_HOST(msg) {
53
+ return new ModuleTraverseError(msg, ModuleTraverseError.UNKNOWN_ADDON_HOST)
54
+ }
55
+
56
+ static ADDON_HOST_INCOMPATIBLE(msg) {
57
+ return new ModuleTraverseError(msg, ModuleTraverseError.ADDON_HOST_INCOMPATIBLE)
58
+ }
59
+
60
+ static ADDON_WASI_INCOMPATIBLE(msg) {
61
+ return new ModuleTraverseError(msg, ModuleTraverseError.ADDON_WASI_INCOMPATIBLE)
62
+ }
63
+
52
64
  static AMBIGUOUS_MODULE_TYPE(msg) {
53
65
  return new ModuleTraverseError(msg, ModuleTraverseError.AMBIGUOUS_MODULE_TYPE)
54
66
  }
@@ -1,10 +1,17 @@
1
+ import URL from 'bare-url'
1
2
  import { type Import } from 'bare-module-lexer'
2
- import { type ResolveOptions, type Resolver } from 'bare-addon-resolve'
3
+ import { type Conditions, type ResolveOptions as ModuleResolveOptions } from 'bare-module-resolve'
4
+ import { type ResolveOptions as AddonResolveOptions, type Resolver } from 'bare-addon-resolve'
3
5
 
4
- interface BareResolveOptions extends ResolveOptions {
6
+ interface BareResolveOptions extends ModuleResolveOptions, AddonResolveOptions {
5
7
  linked?: boolean
6
8
  host?: string
7
9
  hosts?: string[]
10
+ /**
11
+ * The conditions that may be targeted in addition to those of each host,
12
+ * either a list or nested lists to try in turn.
13
+ */
14
+ conditions?: Conditions
8
15
  }
9
16
 
10
17
  /**
@@ -1,13 +1,19 @@
1
1
  const lex = require('bare-module-lexer')
2
2
  const resolve = require('../resolve')
3
3
  const runtime = require('../runtime')
4
+ const hostConditions = require('../conditions')
4
5
 
5
6
  module.exports = function (entry, parentURL, opts = {}) {
6
- const { linked = false, host = runtime.host, hosts = [host] } = opts
7
+ const {
8
+ linked = false,
9
+ host = runtime.host,
10
+ hosts = [host, 'wasi-wasm32'],
11
+ conditions: targets = []
12
+ } = opts
7
13
 
8
14
  let directories = false
9
15
  let extensions
10
- let conditions = hosts.map((host) => ['bare', 'node', ...host.split('-')])
16
+ let conditions = hostConditions(['bare', 'node'], hosts, targets)
11
17
 
12
18
  if (entry.type & lex.constants.ADDON) {
13
19
  extensions = linked ? [] : ['.bare', '.node']
@@ -1,5 +1,9 @@
1
+ import URL from 'bare-url'
1
2
  import { type Import } from 'bare-module-lexer'
2
- import { type ResolveOptions, type Resolver } from 'bare-addon-resolve'
3
+ import { type ResolveOptions as ModuleResolveOptions } from 'bare-module-resolve'
4
+ import { type ResolveOptions as AddonResolveOptions, type Resolver } from 'bare-addon-resolve'
5
+
6
+ interface ResolveOptions extends ModuleResolveOptions, AddonResolveOptions {}
3
7
 
4
8
  /**
5
9
  * @param entry - The import to resolve, as produced by `bare-module-lexer`.
@@ -1,9 +1,16 @@
1
+ import URL from 'bare-url'
1
2
  import { type Import } from 'bare-module-lexer'
2
- import { type ResolveOptions, type Resolver } from 'bare-addon-resolve'
3
+ import { type Conditions, type ResolveOptions as ModuleResolveOptions } from 'bare-module-resolve'
4
+ import { type ResolveOptions as AddonResolveOptions, type Resolver } from 'bare-addon-resolve'
3
5
 
4
- interface NodeResolveOptions extends ResolveOptions {
6
+ interface NodeResolveOptions extends ModuleResolveOptions, AddonResolveOptions {
5
7
  host?: string
6
8
  hosts?: string[]
9
+ /**
10
+ * The conditions that may be targeted in addition to those of each host,
11
+ * either a list or nested lists to try in turn.
12
+ */
13
+ conditions?: Conditions
7
14
  }
8
15
 
9
16
  /**
@@ -1,13 +1,14 @@
1
1
  const lex = require('bare-module-lexer')
2
2
  const resolve = require('../resolve')
3
3
  const runtime = require('../runtime')
4
+ const hostConditions = require('../conditions')
4
5
 
5
6
  module.exports = function (entry, parentURL, opts = {}) {
6
- const { host = runtime.host, hosts = [host] } = opts
7
+ const { host = runtime.host, hosts = [host], conditions: targets = [] } = opts
7
8
 
8
9
  let directories = false
9
10
  let extensions
10
- let conditions = hosts.map((host) => ['node', ...host.split('-')])
11
+ let conditions = hostConditions(['node'], hosts, targets)
11
12
 
12
13
  if (entry.type & lex.constants.ADDON) {
13
14
  extensions = ['.node']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.5.9",
3
+ "version": "2.7.0",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {