@suss/adapter-typescript 0.15.0 → 0.16.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/adapter.ts
2
- import path9 from "path";
2
+ import path10 from "path";
3
3
  import {
4
4
  Node as Node58,
5
5
  Project,
@@ -1827,8 +1827,8 @@ function loadImportGraphsDepthFirstFromPaths(project, rootPaths) {
1827
1827
  stack.pop();
1828
1828
  loadOrder.push(top.path);
1829
1829
  let deepest = 0;
1830
- for (const path12 of top.deps) {
1831
- deepest = Math.max(deepest, chainDepth.get(path12) ?? 0);
1830
+ for (const path13 of top.deps) {
1831
+ deepest = Math.max(deepest, chainDepth.get(path13) ?? 0);
1832
1832
  }
1833
1833
  chainDepth.set(top.path, deepest + 1);
1834
1834
  }
@@ -2173,11 +2173,11 @@ function walkAndWarm(root, reach) {
2173
2173
  if (warmedFiles.get(target) !== void 0) {
2174
2174
  continue;
2175
2175
  }
2176
- const path12 = target.getFilePath();
2177
- if (visited.has(path12)) {
2176
+ const path13 = target.getFilePath();
2177
+ if (visited.has(path13)) {
2178
2178
  continue;
2179
2179
  }
2180
- visited.add(path12);
2180
+ visited.add(path13);
2181
2181
  stack.push(frameOf(target, reach));
2182
2182
  continue;
2183
2183
  }
@@ -2194,7 +2194,7 @@ function sitsOnWarmChains(file) {
2194
2194
  return false;
2195
2195
  }
2196
2196
  const imported = importedFilePathsOf(project, file.getFilePath());
2197
- return imported.length > 0 && imported.every((path12) => warmed.has(path12));
2197
+ return imported.length > 0 && imported.every((path13) => warmed.has(path13));
2198
2198
  }
2199
2199
  function markWarmed(file) {
2200
2200
  warmedFiles.set(file, true);
@@ -5818,9 +5818,9 @@ function createSourceFileLookup(project) {
5818
5818
  if (sf === null) {
5819
5819
  return null;
5820
5820
  }
5821
- const path12 = sf.getFilePath();
5822
- const index = functionsByFile.get(path12) ?? indexFunctions(sf);
5823
- functionsByFile.set(path12, index);
5821
+ const path13 = sf.getFilePath();
5822
+ const index = functionsByFile.get(path13) ?? indexFunctions(sf);
5823
+ functionsByFile.set(path13, index);
5824
5824
  return index.get(rangeKey(location.range)) ?? null;
5825
5825
  }
5826
5826
  };
@@ -6347,7 +6347,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
6347
6347
  }
6348
6348
  const responses = [];
6349
6349
  let method;
6350
- let path12;
6350
+ let path13;
6351
6351
  for (const prop of endpointNode.getProperties()) {
6352
6352
  if (!Node27.isPropertyAssignment(prop)) {
6353
6353
  continue;
@@ -6362,7 +6362,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
6362
6362
  if (propName === pattern.pathProperty) {
6363
6363
  const val = prop.getInitializer();
6364
6364
  if (val !== void 0 && Node27.isStringLiteral(val)) {
6365
- path12 = val.getLiteralValue();
6365
+ path13 = val.getLiteralValue();
6366
6366
  }
6367
6367
  }
6368
6368
  if (propName === pattern.responseExtraction.property) {
@@ -6386,10 +6386,10 @@ function extractEndpointContract(endpointNode, pattern, framework) {
6386
6386
  if (responses.length === 0) {
6387
6387
  return null;
6388
6388
  }
6389
- const boundaryBinding = method !== void 0 || path12 !== void 0 ? restBinding({
6389
+ const boundaryBinding = method !== void 0 || path13 !== void 0 ? restBinding({
6390
6390
  transport: "http",
6391
6391
  method: method ?? null,
6392
- path: path12 ?? null,
6392
+ path: path13 ?? null,
6393
6393
  recognition: framework
6394
6394
  }) : null;
6395
6395
  return {
@@ -8311,7 +8311,34 @@ function joinRoutePath(prefix, suffix) {
8311
8311
  }
8312
8312
  return `/${segments.join("/")}`;
8313
8313
  }
8314
- function discoverDecoratedRoutes(sourceFile, match, kind, resolution) {
8314
+ function declaredChannelOf(source, decorator, resolution) {
8315
+ if (source.from === "literal") {
8316
+ return source.value;
8317
+ }
8318
+ if (source.from === "unstated") {
8319
+ return null;
8320
+ }
8321
+ if (!Node36.isDecorator(decorator)) {
8322
+ return null;
8323
+ }
8324
+ const arg = decorator.getArguments()[source.position];
8325
+ if (arg === void 0) {
8326
+ return null;
8327
+ }
8328
+ const text = literalStringOf(arg, resolution);
8329
+ return text === null || text === "" ? null : text;
8330
+ }
8331
+ function literalStringOf(node, resolution) {
8332
+ if (Node36.isStringLiteral(node) || Node36.isNoSubstitutionTemplateLiteral(node)) {
8333
+ return node.getLiteralValue();
8334
+ }
8335
+ const written = resolution?.resolveWrittenValue(node) ?? null;
8336
+ if (written === null || written === node) {
8337
+ return null;
8338
+ }
8339
+ return literalStringOf(written, resolution);
8340
+ }
8341
+ function discoverDecoratedRoutes(sourceFile, match, kind, resolution, binding) {
8315
8342
  const acceptedModules = Array.isArray(match.importModule) ? match.importModule : [match.importModule];
8316
8343
  const routeDecoratorNames = Object.keys(match.methodDecoratorRouteMap);
8317
8344
  const importedRouteDecorators = /* @__PURE__ */ new Set();
@@ -8343,6 +8370,22 @@ function discoverDecoratedRoutes(sourceFile, match, kind, resolution) {
8343
8370
  const pathPrefix = routePathOf(marker.args, resolution);
8344
8371
  const className = cls.getName() ?? "<anon-class>";
8345
8372
  for (const handler of decoratedCallablesOf(cls, routeDecoratorNames)) {
8373
+ if (binding !== void 0) {
8374
+ results.push({
8375
+ func: handler.func,
8376
+ kind,
8377
+ name: `${className}.${handler.name}`,
8378
+ channelInfo: {
8379
+ messageBus: binding.messageBus,
8380
+ channel: declaredChannelOf(
8381
+ binding.channel,
8382
+ handler.decorator,
8383
+ resolution
8384
+ )
8385
+ }
8386
+ });
8387
+ continue;
8388
+ }
8346
8389
  const httpMethod = match.methodDecoratorRouteMap[handler.standsFor];
8347
8390
  const pathSuffix = resolveRoutePathArg(handler.decorator, resolution);
8348
8391
  const routePath = joinRoutePath(pathPrefix, pathSuffix);
@@ -9362,7 +9405,7 @@ function attributeValueOf(node, name) {
9362
9405
  }
9363
9406
  return null;
9364
9407
  }
9365
- function literalStringOf(value) {
9408
+ function literalStringOf2(value) {
9366
9409
  const unwrapped2 = Node41.isJsxExpression(value) ? value.getExpression() ?? value : value;
9367
9410
  if (Node41.isStringLiteral(unwrapped2) || Node41.isNoSubstitutionTemplateLiteral(unwrapped2)) {
9368
9411
  return unwrapped2.getLiteralValue();
@@ -9372,7 +9415,7 @@ function literalStringOf(value) {
9372
9415
  function ownPathOfJsx(node, match) {
9373
9416
  const pathValue = attributeValueOf(node, match.pathAttribute);
9374
9417
  if (pathValue !== null) {
9375
- const literal = literalStringOf(pathValue);
9418
+ const literal = literalStringOf2(pathValue);
9376
9419
  return literal !== null ? { kind: "literal", value: literal } : { kind: "unreadable" };
9377
9420
  }
9378
9421
  if (match.indexAttribute !== void 0 && attributeValueOf(node, match.indexAttribute) !== null) {
@@ -9463,14 +9506,14 @@ function unitForJsxRoute(node, match, routeNames, kind, resolution) {
9463
9506
  }
9464
9507
  return routeUnit(node, kind, match.method, resolved.path, target, resolution);
9465
9508
  }
9466
- function routeUnit(at, kind, method, path12, target, resolution) {
9467
- const routeInfo = { method, path: path12 };
9509
+ function routeUnit(at, kind, method, path13, target, resolution) {
9510
+ const routeInfo = { method, path: path13 };
9468
9511
  if (target.kind === "unreadable") {
9469
9512
  return {
9470
9513
  func: null,
9471
9514
  announcedAt: at,
9472
9515
  kind,
9473
- name: path12,
9516
+ name: path13,
9474
9517
  nameKind: "label",
9475
9518
  routeInfo
9476
9519
  };
@@ -9640,7 +9683,7 @@ function unitsUnderRouteObject(object, match, kind, resolution, prefix, ancestry
9640
9683
  function ownPathOfObject(object, match) {
9641
9684
  const pathValue = routePropertyOf(object, match.pathAttribute);
9642
9685
  if (pathValue !== null) {
9643
- const literal = literalStringOf(pathValue);
9686
+ const literal = literalStringOf2(pathValue);
9644
9687
  return literal !== null ? { kind: "literal", value: literal } : { kind: "unreadable" };
9645
9688
  }
9646
9689
  if (match.indexAttribute !== void 0) {
@@ -9900,6 +9943,9 @@ function resolvePackageExportsCached(packageJsonPath) {
9900
9943
  return fresh;
9901
9944
  }
9902
9945
  function discoverPackageExports(sourceFile, match, kind) {
9946
+ if (match.packageJsonPath === void 0) {
9947
+ return [];
9948
+ }
9903
9949
  const { entries } = resolvePackageExportsCached(match.packageJsonPath);
9904
9950
  const filePath = sourceFile.getFilePath();
9905
9951
  const matching = [];
@@ -10287,12 +10333,12 @@ function originEndOf(flattened) {
10287
10333
  const slash = flattened.indexOf("/", opener[0].length);
10288
10334
  return slash === -1 ? flattened.length : slash;
10289
10335
  }
10290
- function appendPathText(path12, text) {
10336
+ function appendPathText(path13, text) {
10291
10337
  const idx = text.search(/[?#]/);
10292
10338
  if (idx === -1) {
10293
- return { path: path12 + text, stop: false };
10339
+ return { path: path13 + text, stop: false };
10294
10340
  }
10295
- return { path: path12 + text.slice(0, idx), stop: true };
10341
+ return { path: path13 + text.slice(0, idx), stop: true };
10296
10342
  }
10297
10343
  function parseAbsoluteUrl(text) {
10298
10344
  try {
@@ -10316,8 +10362,8 @@ function pathnameOfAbsoluteLiteral(text) {
10316
10362
  return stripQueryAndFragment(stripOriginManually(text));
10317
10363
  }
10318
10364
  function pathFromLiteralUrl(text) {
10319
- const path12 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
10320
- return path12 === "" ? void 0 : path12;
10365
+ const path13 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
10366
+ return path13 === "" ? void 0 : path13;
10321
10367
  }
10322
10368
  function pathFromTemplateUrl(arg, resolution) {
10323
10369
  const headText = arg.getHead().getLiteralText();
@@ -10325,7 +10371,7 @@ function pathFromTemplateUrl(arg, resolution) {
10325
10371
  const tails = spans.map((span) => span.getLiteral().getLiteralText());
10326
10372
  const originEnd = originEndOf([headText, ...tails].join(SUBSTITUTION));
10327
10373
  const head = appendPathText("", headText.slice(originEnd));
10328
- let path12 = head.path;
10374
+ let path13 = head.path;
10329
10375
  let stop = head.stop;
10330
10376
  let at = headText.length;
10331
10377
  for (let i = 0; i < spans.length && !stop; i++) {
@@ -10335,18 +10381,18 @@ function pathFromTemplateUrl(arg, resolution) {
10335
10381
  continue;
10336
10382
  }
10337
10383
  if (at >= originEnd) {
10338
- path12 += holeText(span.getExpression(), resolution);
10384
+ path13 += holeText(span.getExpression(), resolution);
10339
10385
  }
10340
10386
  at += SUBSTITUTION.length;
10341
10387
  const appended = appendPathText(
10342
- path12,
10388
+ path13,
10343
10389
  tailText.slice(Math.max(0, originEnd - at))
10344
10390
  );
10345
- path12 = appended.path;
10391
+ path13 = appended.path;
10346
10392
  stop = appended.stop;
10347
10393
  at += tailText.length;
10348
10394
  }
10349
- return path12 === "" ? void 0 : path12;
10395
+ return path13 === "" ? void 0 : path13;
10350
10396
  }
10351
10397
  function pathFromUrlNode(node, resolution) {
10352
10398
  if (Node46.isStringLiteral(node)) {
@@ -10513,9 +10559,9 @@ function withMountPrefix(routeInfo, subjectNode, mountPrefixes) {
10513
10559
  const prefix = mountPrefixes.effectivePrefixFor(subjectNode);
10514
10560
  return prefix === "" ? routeInfo : { ...routeInfo, path: joinMountedPath(prefix, routeInfo.path) };
10515
10561
  }
10516
- function joinMountedPath(prefix, path12) {
10562
+ function joinMountedPath(prefix, path13) {
10517
10563
  const trimmed = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
10518
- return trimmed === "" ? path12 : trimmed + path12;
10564
+ return trimmed === "" ? path13 : trimmed + path13;
10519
10565
  }
10520
10566
  function registrationSubjectsOf(sourceFile, importModule, importName) {
10521
10567
  let importedLocalName = null;
@@ -10679,11 +10725,11 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
10679
10725
  return null;
10680
10726
  }
10681
10727
  const method2 = stringProperty(routeObject, binding.method.property);
10682
- const path13 = stringProperty(routeObject, binding.path.property);
10683
- if (method2 === null || path13 === null) {
10728
+ const path14 = stringProperty(routeObject, binding.path.property);
10729
+ if (method2 === null || path14 === null) {
10684
10730
  return null;
10685
10731
  }
10686
- return { method: method2.toUpperCase(), path: path13 };
10732
+ return { method: method2.toUpperCase(), path: path14 };
10687
10733
  }
10688
10734
  if (binding.method.type !== "fromRegistration" || binding.path.type !== "fromArgument") {
10689
10735
  return null;
@@ -10700,8 +10746,8 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
10700
10746
  method = arg.getLiteralValue().toUpperCase();
10701
10747
  }
10702
10748
  const pathArg = call.getArguments()[binding.path.position];
10703
- const path12 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
10704
- return path12 === void 0 ? null : { method, path: path12 };
10749
+ const path13 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
10750
+ return path13 === void 0 ? null : { method, path: path13 };
10705
10751
  }
10706
10752
 
10707
10753
  // src/discovery/registrationLoop.ts
@@ -10824,7 +10870,7 @@ function bodyCallsOn(loop, routables) {
10824
10870
  }
10825
10871
  function readRouteSpec(element, shape, resolution) {
10826
10872
  let method = null;
10827
- let path12 = null;
10873
+ let path13 = null;
10828
10874
  let handler = null;
10829
10875
  for (const prop of element.getProperties()) {
10830
10876
  if (Node48.isMethodDeclaration(prop) && prop.getName() === shape.handlerKey) {
@@ -10842,16 +10888,16 @@ function readRouteSpec(element, shape, resolution) {
10842
10888
  if (name === shape.methodKey) {
10843
10889
  method = readStringLiteralValue(init);
10844
10890
  } else if (name === shape.pathKey) {
10845
- path12 = readStringLiteralValue(init);
10891
+ path13 = readStringLiteralValue(init);
10846
10892
  } else if (name === shape.handlerKey) {
10847
10893
  const func = functionValueOf(init, resolution);
10848
10894
  handler = func === null ? null : { func, name: handlerName(init) };
10849
10895
  }
10850
10896
  }
10851
- if (method === null || path12 === null || handler === null) {
10897
+ if (method === null || path13 === null || handler === null) {
10852
10898
  return null;
10853
10899
  }
10854
- return { method, path: path12, handler: handler.func, handlerName: handler.name };
10900
+ return { method, path: path13, handler: handler.func, handlerName: handler.name };
10855
10901
  }
10856
10902
  function readStringLiteralValue(node) {
10857
10903
  if (Node48.isStringLiteral(node) || Node48.isNoSubstitutionTemplateLiteral(node)) {
@@ -10880,8 +10926,8 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
10880
10926
  }
10881
10927
  const args = node.getArguments();
10882
10928
  for (const reg of match.registrations) {
10883
- const path12 = substitutePath(reg.pathTemplate, args);
10884
- if (path12 === null) {
10929
+ const path13 = substitutePath(reg.pathTemplate, args);
10930
+ if (path13 === null) {
10885
10931
  continue;
10886
10932
  }
10887
10933
  const handler = resolveHandler(reg.handlerArg, args, resolution);
@@ -10894,7 +10940,7 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
10894
10940
  name: handler.name,
10895
10941
  routeInfo: {
10896
10942
  method: reg.method.toUpperCase(),
10897
- path: path12
10943
+ path: path13
10898
10944
  }
10899
10945
  });
10900
10946
  }
@@ -11220,7 +11266,8 @@ function runPattern(sourceFile, pattern, resolution, mountPrefixes) {
11220
11266
  sourceFile,
11221
11267
  pattern.match,
11222
11268
  pattern.kind,
11223
- resolution
11269
+ resolution,
11270
+ pattern.binding
11224
11271
  );
11225
11272
  }
11226
11273
  if (pattern.match.type === "jsxElementRoute") {
@@ -12508,7 +12555,38 @@ var REACHABLE_RULES = [
12508
12555
  )
12509
12556
  ];
12510
12557
  var NO_RECOGNIZERS = { invocation: [], access: [] };
12511
- function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = []) {
12558
+ function recognizerOnlyRoots(packsByFile) {
12559
+ const roots = [];
12560
+ for (const [sourceFile, packs] of packsByFile) {
12561
+ const recognizerOnly = packs.some(
12562
+ (pack) => pack.discovery.length === 0 && pack.discoverUnits === void 0 && ((pack.invocationRecognizers?.length ?? 0) > 0 || (pack.accessRecognizers?.length ?? 0) > 0)
12563
+ );
12564
+ if (!recognizerOnly) {
12565
+ continue;
12566
+ }
12567
+ for (const declaration of sourceFile.getFunctions()) {
12568
+ if (declaration.isExported() && hasBody(declaration)) {
12569
+ roots.push({
12570
+ func: declaration,
12571
+ name: declaration.getName() ?? "<anon>"
12572
+ });
12573
+ }
12574
+ }
12575
+ for (const variableStatement of sourceFile.getVariableStatements()) {
12576
+ if (!variableStatement.isExported()) {
12577
+ continue;
12578
+ }
12579
+ for (const declaration of variableStatement.getDeclarations()) {
12580
+ const init = declaration.getInitializer();
12581
+ if (init !== void 0 && (Node54.isArrowFunction(init) || Node54.isFunctionExpression(init)) && hasBody(init)) {
12582
+ roots.push({ func: init, name: declaration.getName() });
12583
+ }
12584
+ }
12585
+ }
12586
+ }
12587
+ return roots;
12588
+ }
12589
+ function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = [], extraRoots = []) {
12512
12590
  const lookup = createSourceFileLookup(project);
12513
12591
  const db = facts?.db ?? new Database3();
12514
12592
  const functionByKey = /* @__PURE__ */ new Map();
@@ -12528,6 +12606,13 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
12528
12606
  db.add("entry", [key]);
12529
12607
  }
12530
12608
  }
12609
+ for (const root of extraRoots) {
12610
+ const key = nodeKey2(root.func);
12611
+ if (!functionByKey.has(key)) {
12612
+ functionByKey.set(key, root);
12613
+ }
12614
+ db.add("entry", [key]);
12615
+ }
12531
12616
  const knownKeys = /* @__PURE__ */ new Set();
12532
12617
  for (const summary of alreadySummarized) {
12533
12618
  const func = lookup.functionAt(summary.location);
@@ -13595,6 +13680,185 @@ function computeAdapterPacksDigest(packVersions) {
13595
13680
  return `${adapterStamp}|${sortedPacks.join(",")}`;
13596
13681
  }
13597
13682
 
13683
+ // src/workspacePatterns.ts
13684
+ import { createHash as createHash4 } from "crypto";
13685
+ import fs8 from "fs";
13686
+ import path9 from "path";
13687
+ function readJson(filePath) {
13688
+ try {
13689
+ return JSON.parse(fs8.readFileSync(filePath, "utf8"));
13690
+ } catch {
13691
+ return void 0;
13692
+ }
13693
+ }
13694
+ function pnpmWorkspaceGlobs(yamlText) {
13695
+ const globs = [];
13696
+ let inPackages = false;
13697
+ for (const line of yamlText.split("\n")) {
13698
+ if (/^packages\s*:/.test(line)) {
13699
+ inPackages = true;
13700
+ continue;
13701
+ }
13702
+ if (inPackages) {
13703
+ const item = line.match(/^\s+-\s+["']?([^"'#\s]+)["']?/);
13704
+ if (item !== null) {
13705
+ globs.push(item[1]);
13706
+ continue;
13707
+ }
13708
+ if (/^\S/.test(line)) {
13709
+ inPackages = false;
13710
+ }
13711
+ }
13712
+ }
13713
+ return globs;
13714
+ }
13715
+ function workspaceGlobsAt(dir) {
13716
+ const pkg = readJson(path9.join(dir, "package.json"));
13717
+ if (typeof pkg === "object" && pkg !== null && "workspaces" in pkg) {
13718
+ const ws = pkg.workspaces;
13719
+ if (Array.isArray(ws)) {
13720
+ return ws.filter((g) => typeof g === "string");
13721
+ }
13722
+ if (typeof ws === "object" && ws !== null && Array.isArray(ws.packages)) {
13723
+ return ws.packages.filter(
13724
+ (g) => typeof g === "string"
13725
+ );
13726
+ }
13727
+ }
13728
+ const pnpmPath = path9.join(dir, "pnpm-workspace.yaml");
13729
+ if (fs8.existsSync(pnpmPath)) {
13730
+ try {
13731
+ return pnpmWorkspaceGlobs(fs8.readFileSync(pnpmPath, "utf8"));
13732
+ } catch {
13733
+ return null;
13734
+ }
13735
+ }
13736
+ return null;
13737
+ }
13738
+ function isDirectory(candidate) {
13739
+ try {
13740
+ return fs8.statSync(candidate).isDirectory();
13741
+ } catch {
13742
+ return false;
13743
+ }
13744
+ }
13745
+ function subdirectoriesOf(dir) {
13746
+ try {
13747
+ return fs8.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && name !== "node_modules");
13748
+ } catch {
13749
+ return [];
13750
+ }
13751
+ }
13752
+ function segmentToRegExp(segment) {
13753
+ const escaped = segment.replace(/[.+^${}()|[\]\\]/g, "\\$&");
13754
+ return new RegExp(`^${escaped.replace(/\*/g, "[^/]*")}$`);
13755
+ }
13756
+ function directoriesMatchingGlob(root, glob) {
13757
+ let current2 = [root];
13758
+ for (const segment of glob.split("/").filter((s) => s.length > 0)) {
13759
+ const next = [];
13760
+ if (segment.includes("*")) {
13761
+ const pattern = segmentToRegExp(segment);
13762
+ for (const dir of current2) {
13763
+ for (const entry of subdirectoriesOf(dir)) {
13764
+ if (pattern.test(entry)) {
13765
+ next.push(path9.join(dir, entry));
13766
+ }
13767
+ }
13768
+ }
13769
+ } else {
13770
+ for (const dir of current2) {
13771
+ next.push(path9.join(dir, segment));
13772
+ }
13773
+ }
13774
+ current2 = next.filter(isDirectory);
13775
+ }
13776
+ return current2;
13777
+ }
13778
+ function packageAt(dir) {
13779
+ const packageJsonPath = path9.join(dir, "package.json");
13780
+ const pkg = readJson(packageJsonPath);
13781
+ if (typeof pkg === "object" && pkg !== null && typeof pkg.name === "string") {
13782
+ return { name: pkg.name, packageJsonPath };
13783
+ }
13784
+ return null;
13785
+ }
13786
+ function workspacePackagesFrom(anchor) {
13787
+ let dir = path9.resolve(anchor);
13788
+ for (; ; ) {
13789
+ const globs = workspaceGlobsAt(dir);
13790
+ if (globs !== null) {
13791
+ const found = /* @__PURE__ */ new Map();
13792
+ for (const glob of globs) {
13793
+ if (glob.startsWith("!")) {
13794
+ continue;
13795
+ }
13796
+ for (const match of directoriesMatchingGlob(dir, glob)) {
13797
+ const pkg = packageAt(match);
13798
+ if (pkg !== null) {
13799
+ found.set(pkg.packageJsonPath, pkg);
13800
+ }
13801
+ }
13802
+ }
13803
+ return [...found.values()].sort(
13804
+ (a, b) => a.packageJsonPath.localeCompare(b.packageJsonPath)
13805
+ );
13806
+ }
13807
+ const parent = path9.dirname(dir);
13808
+ if (parent === dir) {
13809
+ return [];
13810
+ }
13811
+ dir = parent;
13812
+ }
13813
+ }
13814
+ function isWorkspaceMarked(pattern) {
13815
+ const match = pattern.match;
13816
+ return (match.type === "packageExports" || match.type === "packageImport") && match.workspaces === true;
13817
+ }
13818
+ function expandPattern(pattern, packages) {
13819
+ const match = pattern.match;
13820
+ if (match.type === "packageExports" && match.workspaces === true) {
13821
+ const { workspaces: _workspaces, ...rest } = match;
13822
+ return packages.map((pkg) => ({
13823
+ ...pattern,
13824
+ match: { ...rest, packageJsonPath: pkg.packageJsonPath }
13825
+ }));
13826
+ }
13827
+ if (match.type === "packageImport" && match.workspaces === true) {
13828
+ const listed = match.packages ?? [];
13829
+ const names = [.../* @__PURE__ */ new Set([...listed, ...packages.map((p) => p.name)])];
13830
+ if (names.length === 0) {
13831
+ return [];
13832
+ }
13833
+ return [{ ...pattern, match: { type: "packageImport", packages: names } }];
13834
+ }
13835
+ return [pattern];
13836
+ }
13837
+ function workspaceExpansionStamp(frameworks) {
13838
+ const paths = frameworks.flatMap((pack) => pack.discovery).flatMap(
13839
+ (pattern) => pattern.match.type === "packageExports" && pattern.match.packageJsonPath !== void 0 ? [pattern.match.packageJsonPath] : []
13840
+ ).sort();
13841
+ if (paths.length === 0) {
13842
+ return "none";
13843
+ }
13844
+ return createHash4("sha256").update(paths.join("\n")).digest("hex").slice(0, 12);
13845
+ }
13846
+ function expandWorkspacePatterns(frameworks, anchor) {
13847
+ const marked = frameworks.some(
13848
+ (pack) => pack.discovery.some(isWorkspaceMarked)
13849
+ );
13850
+ if (!marked) {
13851
+ return frameworks;
13852
+ }
13853
+ const packages = anchor !== void 0 ? workspacePackagesFrom(anchor) : [];
13854
+ return frameworks.map((pack) => ({
13855
+ ...pack,
13856
+ discovery: pack.discovery.flatMap(
13857
+ (pattern) => expandPattern(pattern, packages)
13858
+ )
13859
+ }));
13860
+ }
13861
+
13598
13862
  // src/adapter.ts
13599
13863
  var raise = (msg) => {
13600
13864
  throw new Error(msg);
@@ -14005,11 +14269,11 @@ function extractConsumerBinding(unit, pattern, pack, resolution) {
14005
14269
  return null;
14006
14270
  }
14007
14271
  const method = extractBindingMethod(binding, callSite, pack, resolution);
14008
- const path12 = extractBindingPath(binding, callSite, pack, resolution);
14272
+ const path13 = extractBindingPath(binding, callSite, pack, resolution);
14009
14273
  return restBinding2({
14010
14274
  transport: pack.protocol,
14011
14275
  method: method ?? null,
14012
- path: path12 ?? null,
14276
+ path: path13 ?? null,
14013
14277
  recognition: pack.name
14014
14278
  });
14015
14279
  }
@@ -14621,8 +14885,8 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
14621
14885
  if (pathArg === void 0) {
14622
14886
  continue;
14623
14887
  }
14624
- const path12 = pathFromArgument(pathArg, resolution);
14625
- if (path12 === void 0) {
14888
+ const path13 = pathFromArgument(pathArg, resolution);
14889
+ if (path13 === void 0) {
14626
14890
  continue;
14627
14891
  }
14628
14892
  const callerFunc = enclosingFunction(callExpr);
@@ -14634,7 +14898,7 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
14634
14898
  continue;
14635
14899
  }
14636
14900
  seen.add(dedupKey);
14637
- out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path12, options));
14901
+ out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path13, options));
14638
14902
  }
14639
14903
  return out;
14640
14904
  }
@@ -14691,7 +14955,7 @@ function callerName(func) {
14691
14955
  }
14692
14956
  return "anonymous";
14693
14957
  }
14694
- function buildCallerSummary(wrapper, callerFunc, callExpr, path12, options) {
14958
+ function buildCallerSummary(wrapper, callerFunc, callExpr, path13, options) {
14695
14959
  const wrapperBinding = wrapper.summary.identity.boundaryBinding;
14696
14960
  const wrapperRest = wrapperBinding?.semantics.name === "rest" ? wrapperBinding.semantics : null;
14697
14961
  const syntheticPack = {
@@ -14723,7 +14987,7 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path12, options) {
14723
14987
  raw.boundaryBinding = restBinding2({
14724
14988
  transport: wrapperBinding?.transport ?? "http",
14725
14989
  method: wrapperRest?.method ?? null,
14726
- path: path12,
14990
+ path: path13,
14727
14991
  recognition: wrapperBinding?.recognition ?? "unknown"
14728
14992
  });
14729
14993
  const summary = assembleSummary3(raw, options);
@@ -14789,25 +15053,32 @@ function loadRunFiles(project, candidatePaths, timer) {
14789
15053
  );
14790
15054
  return { sourceFiles, deep };
14791
15055
  }
14792
- function createTypeScriptAdapter(config) {
15056
+ function createTypeScriptAdapter(suppliedConfig) {
15057
+ const config = {
15058
+ ...suppliedConfig,
15059
+ frameworks: expandWorkspacePatterns(
15060
+ suppliedConfig.frameworks,
15061
+ suppliedConfig.projectRoot ?? (suppliedConfig.tsConfigFilePath !== void 0 ? path10.dirname(suppliedConfig.tsConfigFilePath) : void 0)
15062
+ )
15063
+ };
14793
15064
  const project = config.project ?? new Project(
14794
15065
  config.tsConfigFilePath !== void 0 ? {
14795
15066
  tsConfigFilePath: config.tsConfigFilePath,
14796
15067
  skipAddingFilesFromTsConfig: true
14797
15068
  } : { skipAddingFilesFromTsConfig: true }
14798
15069
  );
14799
- const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(path9.dirname(config.tsConfigFilePath)) : void 0);
15070
+ const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(path10.dirname(config.tsConfigFilePath)) : void 0);
14800
15071
  let lazyBootstrapped = false;
14801
15072
  let projectFileSet;
14802
15073
  const cacheDir = declineWhenRunFromSource(
14803
- config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? path9.join(path9.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
15074
+ config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? path10.join(path10.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
14804
15075
  );
14805
15076
  const cache = createCacheLayer(cacheDir);
14806
15077
  const adapterPacksDigest = `${computeAdapterPacksDigest(
14807
15078
  config.frameworks.map(
14808
15079
  (p) => p.version !== void 0 ? { name: p.name, version: p.version } : { name: p.name }
14809
15080
  )
14810
- )}|${extractionConfigStamp(config)}`;
15081
+ )}|${extractionConfigStamp(config)}|ws:${workspaceExpansionStamp(config.frameworks)}`;
14811
15082
  const packWrappers = config.frameworks.flatMap(
14812
15083
  (pack) => pack.transparentWrappers ?? []
14813
15084
  );
@@ -15043,7 +15314,12 @@ function createTypeScriptAdapter(config) {
15043
15314
  },
15044
15315
  // Reached units the cache already serves emit nothing,
15045
15316
  // the way a cold run's seeds do not.
15046
- reused?.summaries ?? []
15317
+ reused?.summaries ?? [],
15318
+ // A recognizer-only pack's effects need a function to
15319
+ // live on even when nothing discovers units in its
15320
+ // files, so those files' exports join the walk as
15321
+ // roots.
15322
+ recognizerOnlyRoots(packsByFile)
15047
15323
  )
15048
15324
  ) : withSubUnits;
15049
15325
  const closureOwnership = caching && config.includeReachable !== false ? timer.time(
@@ -15440,8 +15716,8 @@ function buildSubUnitSummary(subUnit, parent, invocationRecognizers, accessRecog
15440
15716
  }
15441
15717
 
15442
15718
  // src/bootstrap/noTsconfigProject.ts
15443
- import fs8 from "fs";
15444
- import path10 from "path";
15719
+ import fs9 from "fs";
15720
+ import path11 from "path";
15445
15721
  import {
15446
15722
  ModuleKind,
15447
15723
  ModuleResolutionKind,
@@ -15477,7 +15753,7 @@ function findSourceFiles(rootDir, depth = 0) {
15477
15753
  const found = [];
15478
15754
  let entries;
15479
15755
  try {
15480
- entries = fs8.readdirSync(rootDir, { withFileTypes: true });
15756
+ entries = fs9.readdirSync(rootDir, { withFileTypes: true });
15481
15757
  } catch {
15482
15758
  return [];
15483
15759
  }
@@ -15485,10 +15761,10 @@ function findSourceFiles(rootDir, depth = 0) {
15485
15761
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name)) {
15486
15762
  continue;
15487
15763
  }
15488
- const full = path10.join(rootDir, entry.name);
15764
+ const full = path11.join(rootDir, entry.name);
15489
15765
  if (entry.isDirectory()) {
15490
15766
  found.push(...findSourceFiles(full, depth + 1));
15491
- } else if (SOURCE_EXTENSIONS.has(path10.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
15767
+ } else if (SOURCE_EXTENSIONS.has(path11.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
15492
15768
  found.push(full);
15493
15769
  }
15494
15770
  }
@@ -15523,15 +15799,15 @@ function createProjectWithoutTsconfig(rootDir) {
15523
15799
  return { project, files };
15524
15800
  }
15525
15801
  function findNearestTsconfig(startDir) {
15526
- let dir = path10.resolve(startDir);
15802
+ let dir = path11.resolve(startDir);
15527
15803
  while (true) {
15528
15804
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
15529
- const candidate = path10.join(dir, name);
15530
- if (fs8.existsSync(candidate)) {
15805
+ const candidate = path11.join(dir, name);
15806
+ if (fs9.existsSync(candidate)) {
15531
15807
  return candidate;
15532
15808
  }
15533
15809
  }
15534
- const parent = path10.dirname(dir);
15810
+ const parent = path11.dirname(dir);
15535
15811
  if (parent === dir) {
15536
15812
  return null;
15537
15813
  }
@@ -15540,7 +15816,7 @@ function findNearestTsconfig(startDir) {
15540
15816
  }
15541
15817
 
15542
15818
  // src/facts/why.ts
15543
- import path11 from "path";
15819
+ import path12 from "path";
15544
15820
  import { Node as Node59, Project as Project3, SyntaxKind as SyntaxKind13 } from "ts-morph";
15545
15821
  import { explainResolutionProof, renderExplanation } from "@suss/resolution";
15546
15822
  var JS_PHRASES = {
@@ -15553,7 +15829,7 @@ var WhySession = class {
15553
15829
  store;
15554
15830
  root;
15555
15831
  constructor(options) {
15556
- this.root = path11.resolve(options.dir);
15832
+ this.root = path12.resolve(options.dir);
15557
15833
  const tsconfig = findNearestTsconfig(this.root);
15558
15834
  this.project = tsconfig === null ? createProjectWithoutTsconfig(this.root).project : new Project3({ tsConfigFilePath: tsconfig });
15559
15835
  this.store = new ResolutionStore();
@@ -15642,7 +15918,7 @@ var WhySession = class {
15642
15918
  };
15643
15919
  }
15644
15920
  sourceFileAt(file) {
15645
- const absolute = path11.resolve(this.root, file);
15921
+ const absolute = path12.resolve(this.root, file);
15646
15922
  const loaded = this.project.getSourceFile(absolute);
15647
15923
  if (loaded !== void 0) {
15648
15924
  return loaded;
@@ -15656,7 +15932,7 @@ var WhySession = class {
15656
15932
  /** A module key: a file path said relative to the root, or a package name. */
15657
15933
  displayPath(key) {
15658
15934
  if (key.startsWith(this.root)) {
15659
- return path11.relative(this.root, key);
15935
+ return path12.relative(this.root, key);
15660
15936
  }
15661
15937
  return key;
15662
15938
  }
@@ -15727,6 +16003,17 @@ function funnelDrops(packs) {
15727
16003
  }
15728
16004
  return violations;
15729
16005
  }
16006
+ function recognizersWithNoUnits(packs) {
16007
+ return packs.filter(
16008
+ (funnel) => funnel.recognizes && !funnel.discovers && funnel.gates.length > 0 && funnel.unresolvedGates.length === 0 && funnel.candidateFiles > 0 && funnel.unitsInGatedFiles === 0 && // The closure walks a recognizer-only pack's gated exports as
16009
+ // roots, so effects recognized there mean the run worked and
16010
+ // the missing framework pack costs attribution, not existence.
16011
+ funnel.effectsRecognized === 0
16012
+ ).map((funnel) => ({
16013
+ label: funnel.pack,
16014
+ detail: `${funnel.candidateFiles} gated files, and no pack in this run discovered a unit in them. ${funnel.pack} reads calls inside units another pack finds, so add the pack that finds this project's handlers (-f express, -f fastify, ...), or run suss init to work out which.`
16015
+ }));
16016
+ }
15730
16017
  function unversionedPacks(packs) {
15731
16018
  return packs.filter((funnel) => funnel.version === null).map((funnel) => ({
15732
16019
  label: funnel.pack,
@@ -15821,6 +16108,12 @@ function evaluatePackHealth(report) {
15821
16108
  audience: "run",
15822
16109
  violations: selfCollisions(report.packs)
15823
16110
  },
16111
+ {
16112
+ name: "every recognizer had units to look inside",
16113
+ code: "no-units",
16114
+ audience: "run",
16115
+ violations: recognizersWithNoUnits(report.packs)
16116
+ },
15824
16117
  {
15825
16118
  name: "every pack declares a version",
15826
16119
  code: "no-version",