@volter/twin-standard 1.0.88 → 1.0.89

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.
@@ -36,7 +36,7 @@ export async function assessPack(packDir, options = {}) {
36
36
  const units = [packDir, ...lanes.map((name) => join(packDir, name))];
37
37
  if (packDir.split(/[\\/]/).at(-1) !== vendor)
38
38
  throw new Error('Place the artifact in a directory named for its vendor before assessment');
39
- usePacks(join(packDir, '..'));
39
+ usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
40
40
  const gates = await p3Gate(units);
41
41
  const life = expandFills(read(join(packDir, 'journeys/customer-life.json')));
42
42
  if (!Array.isArray(life.steps) || !life.steps.length)
@@ -356,7 +356,7 @@ async function lifeServed(pack) {
356
356
  if (!existsSync(lifePath))
357
357
  return ['journeys/customer-life.json is missing'];
358
358
  const { inMemoryWorld, usePacks, walk } = await import("./walk/behavior-journey.js");
359
- usePacks(dirname(pack));
359
+ usePacks(dirname(pack), { dependenciesFrom: pack });
360
360
  const env = globalThis.process.env;
361
361
  const served = async (root, options) => {
362
362
  const before = env.VOLTER_WORLD_BOOT_ID;
@@ -1,6 +1,8 @@
1
1
  import { type TwinStream } from '@volter/world-core/runtime';
2
2
  import { type Captured, type GitStep, type Journey, type JourneyStep, type LineStep, type RespStep } from './journey-kit.js';
3
- export declare function usePacks(dir: string): void;
3
+ export declare function usePacks(dir: string, options?: {
4
+ dependenciesFrom?: string;
5
+ }): void;
4
6
  /** The walk's World: the twin's own code on its in-memory World and blob stores (the backends a serverless
5
7
  * entry runs on), so a walk of any length is state changes in memory and never touches the disk; the World
6
8
  * clock, pinned to 2026-01-01, is world state in the same store, and the World is sealed. Each root is a fresh key
@@ -22,6 +22,8 @@ import { walkPackDirectory } from "./pack-directory.js";
22
22
  import { xmlMembers } from "./xml-view.js";
23
23
  /** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
24
24
  let packsRoot;
25
+ /** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
26
+ let dependenciesRoot;
25
27
  /** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
26
28
  function socketAt(open, host) {
27
29
  if (!/^wss?:\/\//.test(open))
@@ -34,8 +36,9 @@ let walkFacts = baselineFacts;
34
36
  let walkHosts = createHostTable(walkFacts).hosts;
35
37
  const directories = new Map();
36
38
  const installedFacts = new Set();
37
- export function usePacks(dir) {
39
+ export function usePacks(dir, options = {}) {
38
40
  packsRoot = dir;
41
+ dependenciesRoot = options.dependenciesFrom ?? dir;
39
42
  const installed = {};
40
43
  for (const name of readdirSync(dir)) {
41
44
  const file = join(dir, name, 'generated/pack-facts.json');
@@ -53,9 +56,10 @@ export function usePacks(dir) {
53
56
  }
54
57
  const packsDir = () => { if (packsRoot === undefined)
55
58
  throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
59
+ const dependenciesDir = () => dependenciesRoot ?? packsDir();
56
60
  function directoryFor(vendor) {
57
61
  if (!directories.has(vendor)) {
58
- const directory = walkPackDirectory(packsDir(), vendor);
62
+ const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
59
63
  directories.set(vendor, directory);
60
64
  if (directory) {
61
65
  const name = vendor.split('/')[0];
@@ -119,7 +123,7 @@ function defaultType(body) {
119
123
  return 'text/plain;charset=UTF-8';
120
124
  }
121
125
  }
122
- const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor);
126
+ const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
123
127
  const shapes = new Map();
124
128
  const shapeOf = (vendor) => { if (!shapes.has(vendor))
125
129
  shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor); };
@@ -218,7 +222,7 @@ export async function walk(journey, root, collect, opts) {
218
222
  return {};
219
223
  }
220
224
  const factory = await vendorFetch(journey.vendor);
221
- const views = await vendorAnswerViews(packsDir(), journey.vendor);
225
+ const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
222
226
  if (!factory) {
223
227
  collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` });
224
228
  return {};
@@ -370,7 +374,7 @@ export async function walk(journey, root, collect, opts) {
370
374
  }
371
375
  const viaServed = served?.stream;
372
376
  if (!viaServed)
373
- streams ??= await vendorStream(packsDir(), journey.vendor);
377
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
374
378
  if (!viaServed && !streams) {
375
379
  collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` });
376
380
  return;
@@ -437,7 +441,7 @@ export async function walk(journey, root, collect, opts) {
437
441
  if (served?.stream && Object.keys(step.connect).length)
438
442
  throw new Error('a served RESP walk configures its server, not the client connection');
439
443
  if (!served?.stream)
440
- streams ??= await vendorStream(packsDir(), journey.vendor);
444
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
441
445
  const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect));
442
446
  if (!factory)
443
447
  throw new Error('no byte-stream adapter');
@@ -511,7 +515,7 @@ export async function walk(journey, root, collect, opts) {
511
515
  let body;
512
516
  if (step.signIn) {
513
517
  if (!served?.stream)
514
- streams ??= await vendorStream(packsDir(), journey.vendor);
518
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
515
519
  const factory = served?.stream ?? streams?.(root, {});
516
520
  if (!factory)
517
521
  throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
@@ -1 +1 @@
1
- export declare function walkPackDirectory(from: string, vendor: string): string | undefined;
1
+ export declare function walkPackDirectory(from: string, vendor: string, dependenciesFrom?: string): string | undefined;
@@ -2,14 +2,14 @@
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
5
- export function walkPackDirectory(from, vendor) {
5
+ export function walkPackDirectory(from, vendor, dependenciesFrom = from) {
6
6
  if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor))
7
7
  return undefined;
8
8
  const [name, lane] = vendor.split('/');
9
9
  const sibling = join(from, name);
10
10
  const root = isDerivedPack(sibling) && packVendor(sibling) === name
11
11
  ? sibling
12
- : installedVendorPacks(from, { vendors: [name] }).get(name);
12
+ : installedVendorPacks(dependenciesFrom, { vendors: [name] }).get(name);
13
13
  const unit = root && (lane ? join(root, lane) : root);
14
14
  return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
15
15
  }
@@ -7,14 +7,14 @@ export type VendorFetchFactory = (root: string, options?: {
7
7
  scenarioPath?: string;
8
8
  application?: ApplicationStandIn;
9
9
  }) => (request: Request) => Response | Promise<Response>;
10
- export declare function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined>;
10
+ export declare function vendorFetch(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorFetchFactory | undefined>;
11
11
  /** Named views of an answer: what a journey step's `view` asserts against. The kit's own: `jwt:<field>`, the payload of the
12
12
  * JSON Web Token at that field of the answer, decoded as the application verifying it reads it (a session token's
13
13
  * claims). A pack's own (`answerViews` in its index) beside them. */
14
14
  export type AnswerViews = Record<string, (body: unknown) => unknown>;
15
- export declare function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews>;
15
+ export declare function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<AnswerViews>;
16
16
  /** A vendor's byte-stream wire (a line protocol), in process: the pack's `create<Name>TwinStream`, what a
17
17
  * hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
18
18
  export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
19
- export declare function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined>;
19
+ export declare function vendorStream(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorStreamFactory | undefined>;
20
20
  export {};
@@ -13,8 +13,8 @@ import { join } from 'node:path';
13
13
  import { startPgliteHost } from "./walk-database.js";
14
14
  import { worldNow } from '@volter/world-core/runtime';
15
15
  import { walkPackDirectory } from "./pack-directory.js";
16
- export async function vendorFetch(twinDir, vendor) {
17
- const directory = walkPackDirectory(twinDir, vendor);
16
+ export async function vendorFetch(twinDir, vendor, dependenciesFrom = twinDir) {
17
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
18
18
  if (!directory)
19
19
  return undefined;
20
20
  let mod;
@@ -59,9 +59,9 @@ const jwtView = (field) => (body) => {
59
59
  return null;
60
60
  }
61
61
  };
62
- export async function vendorAnswerViews(twinDir, vendor) {
62
+ export async function vendorAnswerViews(twinDir, vendor, dependenciesFrom = twinDir) {
63
63
  let own = {};
64
- const directory = walkPackDirectory(twinDir, vendor);
64
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
65
65
  try {
66
66
  if (directory)
67
67
  own = (await import(__rewriteRelativeImportExtension(join(directory, 'src', 'index.ts')))).answerViews ?? {};
@@ -69,8 +69,8 @@ export async function vendorAnswerViews(twinDir, vendor) {
69
69
  catch { /* none of its own */ }
70
70
  return new Proxy(own, { get: (views, name) => (typeof name === 'string' && name.startsWith('jwt:') ? jwtView(name.slice(4)) : views[name]) });
71
71
  }
72
- export async function vendorStream(twinDir, vendor) {
73
- const directory = walkPackDirectory(twinDir, vendor);
72
+ export async function vendorStream(twinDir, vendor, dependenciesFrom = twinDir) {
73
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
74
74
  if (!directory)
75
75
  return undefined;
76
76
  let mod;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.88",
3
+ "version": "1.0.89",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.96",
78
+ "@volter/world-core": "3.0.97",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
package/src/assessment.ts CHANGED
@@ -27,7 +27,7 @@ export async function assessPack(packDir: string, options: AssessmentOptions = {
27
27
  const lanes = readdirSync(packDir).filter((name) => existsSync(join(packDir, name, 'src/manifest.ts')) && !existsSync(join(packDir, name, 'package.json')));
28
28
  const units = [packDir, ...lanes.map((name) => join(packDir, name))];
29
29
  if (packDir.split(/[\\/]/).at(-1) !== vendor) throw new Error('Place the artifact in a directory named for its vendor before assessment');
30
- usePacks(join(packDir, '..'));
30
+ usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
31
31
  const gates = await p3Gate(units);
32
32
  const life = expandFills(read(join(packDir, 'journeys/customer-life.json'))) as Journey;
33
33
  if (!Array.isArray(life.steps) || !life.steps.length) throw new Error('Assessment needs a nonempty customer journey');
@@ -318,7 +318,7 @@ async function lifeServed(pack: string): Promise<string[]> {
318
318
  const lifePath = join(pack, 'journeys', 'customer-life.json');
319
319
  if (!existsSync(lifePath)) return ['journeys/customer-life.json is missing'];
320
320
  const { inMemoryWorld, usePacks, walk } = await import('./walk/behavior-journey.ts');
321
- usePacks(dirname(pack));
321
+ usePacks(dirname(pack), { dependenciesFrom: pack });
322
322
  const env = (globalThis as { process: { env: Record<string, string | undefined> } }).process.env;
323
323
  const served = async (root: string, options: { scenarioPath?: string }) => {
324
324
  const before = env.VOLTER_WORLD_BOOT_ID;
@@ -24,6 +24,8 @@ import { xmlMembers } from './xml-view.ts';
24
24
 
25
25
  /** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
26
26
  let packsRoot: string | undefined;
27
+ /** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
28
+ let dependenciesRoot: string | undefined;
27
29
  /** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
28
30
  function socketAt(open: string, host: string): { host: string; path: string } {
29
31
  if (!/^wss?:\/\//.test(open)) return { host, path: open };
@@ -36,8 +38,9 @@ let walkFacts: HostFacts = baselineFacts;
36
38
  let walkHosts = createHostTable(walkFacts).hosts;
37
39
  const directories = new Map<string, string | undefined>();
38
40
  const installedFacts = new Set<string>();
39
- export function usePacks(dir: string): void {
41
+ export function usePacks(dir: string, options: { dependenciesFrom?: string } = {}): void {
40
42
  packsRoot = dir;
43
+ dependenciesRoot = options.dependenciesFrom ?? dir;
41
44
  const installed: Record<string, HostFacts[string]> = {};
42
45
  for (const name of readdirSync(dir)) {
43
46
  const file = join(dir, name, 'generated/pack-facts.json');
@@ -51,9 +54,10 @@ export function usePacks(dir: string): void {
51
54
  directories.clear();
52
55
  }
53
56
  const packsDir = (): string => { if (packsRoot === undefined) throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
57
+ const dependenciesDir = (): string => dependenciesRoot ?? packsDir();
54
58
  function directoryFor(vendor: string): string {
55
59
  if (!directories.has(vendor)) {
56
- const directory = walkPackDirectory(packsDir(), vendor);
60
+ const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
57
61
  directories.set(vendor, directory);
58
62
  if (directory) {
59
63
  const name = vendor.split('/')[0]!;
@@ -107,7 +111,7 @@ function defaultType(body: unknown): string {
107
111
  if (typeof body !== 'string') return 'application/json';
108
112
  try { parseExactJson(body); return 'application/json'; } catch { return 'text/plain;charset=UTF-8'; }
109
113
  }
110
- const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor);
114
+ const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
111
115
  const shapes = new Map<string, Promise<ShapeChecker | undefined>>();
112
116
  const shapeOf = (vendor: string): Promise<ShapeChecker | undefined> => { if (!shapes.has(vendor)) shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor)!; };
113
117
 
@@ -210,7 +214,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
210
214
  // the life's host is a hostname (the vendor's, `api.x.ai`); a URL there would put a second scheme in every request
211
215
  if (journey.host !== undefined && !/^[A-Za-z0-9.-]+(:\d+)?$/.test(journey.host)) { collect.push({ step: '(setup)', beat: 'HOST', detail: `the life's host "${journey.host}" is no hostname: write the vendor's host alone (api.example.com), no scheme or path` }); return {}; }
212
216
  const factory = await vendorFetch(journey.vendor);
213
- const views = await vendorAnswerViews(packsDir(), journey.vendor);
217
+ const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
214
218
  if (!factory) { collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` }); return {}; }
215
219
  // the life's companions (`companions`): the packs its vendor calls, each over this walk's root, on the same World and
216
220
  // clock; reached in process by the vendor's ctx.vendorFetch and by a step to a URL their host rules claim
@@ -323,7 +327,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
323
327
  // never a way back to the in-process stream (which would pass a listener wired apart from the World)
324
328
  if (served?.stream && Object.keys(step.connect).length) { collect.push({ step: step.name, beat: 'CONNECTION', detail: `a served walk opens "${step.on}" as the application does, over TCP to the listener; connect options (${Object.keys(step.connect).join(', ')}) configure only the in-process stream` }); return; }
325
329
  const viaServed = served?.stream;
326
- if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor);
330
+ if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
327
331
  if (!viaServed && !streams) { collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` }); return; }
328
332
  const opened: Connection = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false, shape: {} };
329
333
  opened.conn = (viaServed ?? streams!(root, step.connect))({ write: (bytes) => { opened.out += decoder.decode(bytes); }, end: () => { opened.closed = true; } }, '127.0.0.1');
@@ -367,7 +371,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
367
371
  try {
368
372
  if (step.connect) {
369
373
  if (served?.stream && Object.keys(step.connect).length) throw new Error('a served RESP walk configures its server, not the client connection');
370
- if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
374
+ if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
371
375
  const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect)); if (!factory) throw new Error('no byte-stream adapter');
372
376
  const c = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false };
373
377
  c.conn = factory({ write: (b) => { c.out += redis.binaryText(b); }, end: () => { c.closed = true; } }, '127.0.0.1');
@@ -405,7 +409,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
405
409
  try {
406
410
  let body: Record<string, unknown> | undefined;
407
411
  if (step.signIn) {
408
- if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
412
+ if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
409
413
  const factory = served?.stream ?? streams?.(root, {}); if (!factory) throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
410
414
  let out: number[] = [];
411
415
  const conn = factory({ write: (b) => { out.push(...b); }, end: () => {} }, '127.0.0.1');
@@ -3,13 +3,13 @@ import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
5
5
 
6
- export function walkPackDirectory(from: string, vendor: string): string | undefined {
6
+ export function walkPackDirectory(from: string, vendor: string, dependenciesFrom = from): string | undefined {
7
7
  if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor)) return undefined;
8
8
  const [name, lane] = vendor.split('/');
9
9
  const sibling = join(from, name!);
10
10
  const root = isDerivedPack(sibling) && packVendor(sibling) === name
11
11
  ? sibling
12
- : installedVendorPacks(from, { vendors: [name!] }).get(name!);
12
+ : installedVendorPacks(dependenciesFrom, { vendors: [name!] }).get(name!);
13
13
  const unit = root && (lane ? join(root, lane) : root);
14
14
  return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
15
15
  }
@@ -13,8 +13,8 @@ type ApplicationStandIn = NonNullable<PackFetchOptions['application']>;
13
13
  * life's application answers stand in for the application the vendor asks (`application`, this fetch's alone). */
14
14
  export type VendorFetchFactory = (root: string, options?: { scenarioPath?: string; application?: ApplicationStandIn }) => (request: Request) => Response | Promise<Response>;
15
15
 
16
- export async function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined> {
17
- const directory = walkPackDirectory(twinDir, vendor);
16
+ export async function vendorFetch(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorFetchFactory | undefined> {
17
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
18
18
  if (!directory) return undefined;
19
19
  let mod: Record<string, unknown>;
20
20
  try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }
@@ -52,9 +52,9 @@ const jwtView = (field: string) => (body: unknown): unknown => {
52
52
  try { return JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')); } catch { return null; }
53
53
  };
54
54
 
55
- export async function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews> {
55
+ export async function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<AnswerViews> {
56
56
  let own: AnswerViews = {};
57
- const directory = walkPackDirectory(twinDir, vendor);
57
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
58
58
  try {
59
59
  if (directory) own = ((await import(join(directory, 'src', 'index.ts'))) as { answerViews?: AnswerViews }).answerViews ?? {};
60
60
  } catch { /* none of its own */ }
@@ -65,8 +65,8 @@ export async function vendorAnswerViews(twinDir: string, vendor: string): Promis
65
65
  * hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
66
66
  export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
67
67
 
68
- export async function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined> {
69
- const directory = walkPackDirectory(twinDir, vendor);
68
+ export async function vendorStream(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorStreamFactory | undefined> {
69
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
70
70
  if (!directory) return undefined;
71
71
  let mod: Record<string, unknown>;
72
72
  try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }