@microsoft/rayfin-docs 1.35.0-beta.0 → 1.36.0-alpha.1588
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/assets/catalog.json +2 -2
- package/dist/discovery.d.ts +11 -0
- package/dist/discovery.js +160 -16
- package/dist/site-cjs/discovery.js +159 -15
- package/package.json +1 -1
package/assets/catalog.json
CHANGED
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"function-bridge",
|
|
130
130
|
"typed"
|
|
131
131
|
],
|
|
132
|
-
"modules": []
|
|
132
|
+
"modules": ["rayfin-connectors"]
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"name": "@microsoft/rayfin-connector-fabric-semanticmodel",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"stability": "experimental",
|
|
161
161
|
"summary": "Experimental — Category A GraphQL-backed connector types. Provides the `GraphQLBackedConnector` entity marker and CRUD operation gating for the typed connectors surface.",
|
|
162
162
|
"topics": ["connector", "graphql", "fabric", "entity", "crud"],
|
|
163
|
-
"modules": []
|
|
163
|
+
"modules": ["rayfin-connector-fabric-graphql"]
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
"name": "@microsoft/rayfin-cli",
|
package/dist/discovery.d.ts
CHANGED
|
@@ -120,12 +120,23 @@ export interface InstalledPackageVersionResolution {
|
|
|
120
120
|
/** Optional policy applied to installed manifest versions. */
|
|
121
121
|
export interface InstalledPackageVersionResolutionOptions {
|
|
122
122
|
isVersionAllowed?: (version: string) => boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Also resolve packages that are installed but reachable only as an *indirect*
|
|
125
|
+
* dependency — one a strict/isolated install links inside its dependent's
|
|
126
|
+
* scope rather than anywhere `from` can see (see
|
|
127
|
+
* {@link resolvePackageJsonTransitively}).
|
|
128
|
+
*
|
|
129
|
+
* Off by default
|
|
130
|
+
*/
|
|
131
|
+
includeIndirect?: boolean;
|
|
123
132
|
}
|
|
124
133
|
/**
|
|
125
134
|
* Resolve actual installed versions for a bounded candidate set.
|
|
126
135
|
*
|
|
127
136
|
* Candidates are deduplicated and sorted. Missing, malformed, or mismatched
|
|
128
137
|
* manifests are omitted so telemetry collection remains best-effort.
|
|
138
|
+
*
|
|
139
|
+
* Resolves only what `from` can reach unless `includeIndirect` is set.
|
|
129
140
|
*/
|
|
130
141
|
export declare function resolveInstalledPackageVersions(packageNames: readonly string[], from: string, options?: InstalledPackageVersionResolutionOptions): InstalledPackageVersionResolution;
|
|
131
142
|
//# sourceMappingURL=discovery.d.ts.map
|
package/dist/discovery.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* candidate list, workspace package roots, or all-installed node_modules
|
|
18
18
|
* walking via `scanInstalledPackages: true`.
|
|
19
19
|
*/
|
|
20
|
-
import { readFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
20
|
+
import { readFileSync, existsSync, realpathSync, statSync, readdirSync, } from 'fs';
|
|
21
21
|
import { createRequire } from 'module';
|
|
22
22
|
import { dirname, isAbsolute, join, resolve } from 'path';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
@@ -35,7 +35,6 @@ export function discoverRayfinDocsPackages(options) {
|
|
|
35
35
|
const fromDir = options.from.startsWith('file:') || options.from.includes('://')
|
|
36
36
|
? dirname(fileURLToPath(options.from))
|
|
37
37
|
: resolveExistingDir(options.from);
|
|
38
|
-
const requireFromBase = createRequire(join(fromDir, 'noop.js'));
|
|
39
38
|
const nodeModulesChain = buildNodeModulesChain(fromDir);
|
|
40
39
|
const discovered = [];
|
|
41
40
|
const notInstalled = [];
|
|
@@ -53,7 +52,7 @@ export function discoverRayfinDocsPackages(options) {
|
|
|
53
52
|
packagesToRead = resolvePackageRootJsons(options.packageRoots);
|
|
54
53
|
}
|
|
55
54
|
else if (options.candidates !== undefined) {
|
|
56
|
-
packagesToRead = resolveCandidatePackageJsons(options.candidates,
|
|
55
|
+
packagesToRead = resolveCandidatePackageJsons(options.candidates, fromDir, notInstalled, untrustedSkipped, trust);
|
|
57
56
|
}
|
|
58
57
|
else if (options.scanInstalledPackages) {
|
|
59
58
|
packagesToRead = discoverInstalledPackageJsons(nodeModulesChain);
|
|
@@ -130,14 +129,19 @@ function resolvePackageRootJsons(packageRoots) {
|
|
|
130
129
|
pkgJsonPath: join(resolveExistingDir(packageRoot), 'package.json'),
|
|
131
130
|
}));
|
|
132
131
|
}
|
|
133
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Resolve explicitly named candidates. Direct-only by construction: docs
|
|
134
|
+
* discovery reports packages the caller named and trusts, so it must not reach
|
|
135
|
+
* through the dependency graph into packages nobody asked about.
|
|
136
|
+
*/
|
|
137
|
+
function resolveCandidatePackageJsons(candidates, fromDir, notInstalled, untrustedSkipped, trust) {
|
|
134
138
|
const resolved = [];
|
|
135
139
|
for (const candidate of candidates) {
|
|
136
140
|
if (!trust(candidate)) {
|
|
137
141
|
untrustedSkipped.push(candidate);
|
|
138
142
|
continue;
|
|
139
143
|
}
|
|
140
|
-
const pkgJsonPath =
|
|
144
|
+
const pkgJsonPath = resolvePackageJsonFrom(fromDir, candidate);
|
|
141
145
|
if (!pkgJsonPath) {
|
|
142
146
|
notInstalled.push(candidate);
|
|
143
147
|
continue;
|
|
@@ -300,8 +304,8 @@ export function docKindToModule(kind) {
|
|
|
300
304
|
}
|
|
301
305
|
/**
|
|
302
306
|
* Compute the chain of `node_modules` ancestor directories from
|
|
303
|
-
* `fromDir` upward, capped at 16 levels. Used by the tier
|
|
304
|
-
*
|
|
307
|
+
* `fromDir` upward, capped at 16 levels. Used by the ancestor tier in
|
|
308
|
+
* {@link resolvePackageJsonFrom} to avoid re-walking the same ancestors
|
|
305
309
|
* once per candidate (`9 candidates × 16 walks = 144 stat calls` in
|
|
306
310
|
* the original implementation; this caches the chain to 16 stats).
|
|
307
311
|
*/
|
|
@@ -317,18 +321,37 @@ function buildNodeModulesChain(fromDir) {
|
|
|
317
321
|
}
|
|
318
322
|
return chain;
|
|
319
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Dependency fields that describe packages actually present in an install.
|
|
326
|
+
*
|
|
327
|
+
* `devDependencies` is deliberately excluded: it is not installed for a
|
|
328
|
+
* transitive dependency, so following it would walk edges that do not exist in
|
|
329
|
+
* the consuming project's tree.
|
|
330
|
+
*/
|
|
331
|
+
const INSTALLED_DEPENDENCY_FIELDS = [
|
|
332
|
+
'dependencies',
|
|
333
|
+
'optionalDependencies',
|
|
334
|
+
'peerDependencies',
|
|
335
|
+
];
|
|
336
|
+
/**
|
|
337
|
+
* Bounds on the dependency-graph search. The walk only runs for a package the
|
|
338
|
+
* direct tiers already failed to find, so these cap a rare path rather than the
|
|
339
|
+
* common one.
|
|
340
|
+
*/
|
|
341
|
+
const MAX_TRANSITIVE_DEPTH = 4;
|
|
342
|
+
const MAX_TRANSITIVE_VISITS = 256;
|
|
320
343
|
/**
|
|
321
344
|
* Resolve actual installed versions for a bounded candidate set.
|
|
322
345
|
*
|
|
323
346
|
* Candidates are deduplicated and sorted. Missing, malformed, or mismatched
|
|
324
347
|
* manifests are omitted so telemetry collection remains best-effort.
|
|
348
|
+
*
|
|
349
|
+
* Resolves only what `from` can reach unless `includeIndirect` is set.
|
|
325
350
|
*/
|
|
326
351
|
export function resolveInstalledPackageVersions(packageNames, from, options = {}) {
|
|
327
352
|
const fromDir = from.startsWith('file:') || from.includes('://')
|
|
328
353
|
? dirname(fileURLToPath(from))
|
|
329
354
|
: resolveExistingDir(from);
|
|
330
|
-
const requireFromBase = createRequire(join(fromDir, 'noop.js'));
|
|
331
|
-
const nodeModulesChain = buildNodeModulesChain(fromDir);
|
|
332
355
|
const packages = [];
|
|
333
356
|
const unresolvedPackageNames = [];
|
|
334
357
|
for (const packageName of [...new Set(packageNames)].sort()) {
|
|
@@ -336,7 +359,10 @@ export function resolveInstalledPackageVersions(packageNames, from, options = {}
|
|
|
336
359
|
unresolvedPackageNames.push(packageName);
|
|
337
360
|
continue;
|
|
338
361
|
}
|
|
339
|
-
const packageJsonPath =
|
|
362
|
+
const packageJsonPath = resolvePackageJsonFrom(fromDir, packageName) ??
|
|
363
|
+
(options.includeIndirect
|
|
364
|
+
? resolvePackageJsonTransitively(fromDir, packageName)
|
|
365
|
+
: undefined);
|
|
340
366
|
if (!packageJsonPath) {
|
|
341
367
|
unresolvedPackageNames.push(packageName);
|
|
342
368
|
continue;
|
|
@@ -364,15 +390,21 @@ function isValidPackageName(packageName) {
|
|
|
364
390
|
/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName));
|
|
365
391
|
}
|
|
366
392
|
/**
|
|
367
|
-
* Find a package's `package.json`
|
|
368
|
-
* ESM-only, or the install is hoisted above the
|
|
393
|
+
* Find a package's `package.json` **as resolved from `fromDir`**, even when
|
|
394
|
+
* exports hide it, the package is ESM-only, or the install is hoisted above the
|
|
395
|
+
* resolution root.
|
|
369
396
|
*
|
|
370
397
|
* Packages with restricted exports often omit `./package.json`, causing direct
|
|
371
398
|
* subpath resolution to fail. ESM-only packages may also hide their main entry
|
|
372
|
-
* from CommonJS resolution, so the final tier searches the
|
|
399
|
+
* from CommonJS resolution, so the final tier searches the ancestor
|
|
373
400
|
* `node_modules` chain used by npm, pnpm, and Yarn-style installs.
|
|
401
|
+
*
|
|
402
|
+
* Scoped to one directory so the dependency-graph walk can reuse it verbatim:
|
|
403
|
+
* "resolve X from package Y's location" is the same question as "resolve X from
|
|
404
|
+
* the project root", just asked somewhere else in the tree.
|
|
374
405
|
*/
|
|
375
|
-
function
|
|
406
|
+
function resolvePackageJsonFrom(fromDir, packageName) {
|
|
407
|
+
const req = createRequire(join(fromDir, 'noop.js'));
|
|
376
408
|
// Tier 1: direct manifest resolution when exports allow the subpath.
|
|
377
409
|
try {
|
|
378
410
|
return req.resolve(`${packageName}/package.json`);
|
|
@@ -407,14 +439,126 @@ function resolvePackageJson(req, packageName, nodeModulesChain) {
|
|
|
407
439
|
catch {
|
|
408
440
|
// ESM-only package or similar: fall through to node_modules lookup.
|
|
409
441
|
}
|
|
410
|
-
// Tier 3: search
|
|
411
|
-
for (const dir of
|
|
442
|
+
// Tier 3: search ancestor node_modules roots, independent of exports.
|
|
443
|
+
for (const dir of buildNodeModulesChain(fromDir)) {
|
|
412
444
|
const candidate = join(dir, 'node_modules', packageName, 'package.json');
|
|
413
445
|
if (existsSync(candidate))
|
|
414
446
|
return candidate;
|
|
415
447
|
}
|
|
416
448
|
return undefined;
|
|
417
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Find a package that is installed but *not reachable from `fromDir`* by
|
|
452
|
+
* searching outward through the dependency graph.
|
|
453
|
+
*
|
|
454
|
+
* A strict/isolated install (pnpm's default, and Yarn's `nmMode: hardlinks`)
|
|
455
|
+
* links a purely transitive package only inside the dependency scope of the
|
|
456
|
+
* package that asked for it — never the consumer's own `node_modules` chain.
|
|
457
|
+
* Resolution from `fromDir` therefore fails for it, correctly: nothing at the
|
|
458
|
+
* project root may import it. It is still the version that ships, because the
|
|
459
|
+
* bundler reaches it through the dependent that does.
|
|
460
|
+
*
|
|
461
|
+
* So the search moves to where the answer is: walk the installed dependency
|
|
462
|
+
* graph breadth-first and re-ask {@link resolvePackageJsonFrom} at each package's
|
|
463
|
+
* own real location. Node's resolver supplies all the layout knowledge —
|
|
464
|
+
* following the symlink out of pnpm's virtual store and then finding the target
|
|
465
|
+
* as its sibling — instead of this code hard-coding store paths, and it costs
|
|
466
|
+
* nothing on the common path because it only runs after a direct miss.
|
|
467
|
+
*
|
|
468
|
+
* Known limitation: Yarn Plug'n'Play has no `node_modules` tree at all, so
|
|
469
|
+
* neither this nor the direct tiers can see it without the `.pnp.cjs` API.
|
|
470
|
+
*/
|
|
471
|
+
function resolvePackageJsonTransitively(fromDir, packageName) {
|
|
472
|
+
const visited = new Set();
|
|
473
|
+
let frontier = readInstalledDependencyNames(join(fromDir, 'package.json'));
|
|
474
|
+
// Not seeded from the manifest alone: a project whose own manifest is
|
|
475
|
+
// unreadable can still have an installed tree worth searching.
|
|
476
|
+
if (frontier.length === 0) {
|
|
477
|
+
frontier = readAncestorNodeModulesEntries(fromDir);
|
|
478
|
+
}
|
|
479
|
+
for (let depth = 0; depth < MAX_TRANSITIVE_DEPTH; depth += 1) {
|
|
480
|
+
const next = [];
|
|
481
|
+
for (const dependentName of frontier) {
|
|
482
|
+
if (visited.size >= MAX_TRANSITIVE_VISITS)
|
|
483
|
+
return undefined;
|
|
484
|
+
if (dependentName === packageName)
|
|
485
|
+
continue;
|
|
486
|
+
if (visited.has(dependentName))
|
|
487
|
+
continue;
|
|
488
|
+
visited.add(dependentName);
|
|
489
|
+
if (!isValidPackageName(dependentName))
|
|
490
|
+
continue;
|
|
491
|
+
const dependentManifest = resolvePackageJsonFrom(fromDir, dependentName);
|
|
492
|
+
if (!dependentManifest)
|
|
493
|
+
continue;
|
|
494
|
+
// The real location matters: resolving through the virtual-store symlink
|
|
495
|
+
// is what puts the target in scope as a sibling.
|
|
496
|
+
let dependentDir;
|
|
497
|
+
try {
|
|
498
|
+
dependentDir = realpathSync(dirname(dependentManifest));
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const found = resolvePackageJsonFrom(dependentDir, packageName);
|
|
504
|
+
if (found)
|
|
505
|
+
return found;
|
|
506
|
+
next.push(...readInstalledDependencyNames(join(dependentDir, 'package.json')));
|
|
507
|
+
}
|
|
508
|
+
if (next.length === 0)
|
|
509
|
+
return undefined;
|
|
510
|
+
frontier = next;
|
|
511
|
+
}
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
/** Names declared in the dependency fields that describe an installed tree. */
|
|
515
|
+
function readInstalledDependencyNames(manifestPath) {
|
|
516
|
+
try {
|
|
517
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
518
|
+
const names = [];
|
|
519
|
+
for (const field of INSTALLED_DEPENDENCY_FIELDS) {
|
|
520
|
+
const value = parsed[field];
|
|
521
|
+
if (value && typeof value === 'object') {
|
|
522
|
+
names.push(...Object.keys(value));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return names;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return [];
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Top-level package names installed in the nearest ancestor `node_modules`,
|
|
533
|
+
* used to seed the graph walk when no readable manifest declares anything.
|
|
534
|
+
*/
|
|
535
|
+
function readAncestorNodeModulesEntries(fromDir) {
|
|
536
|
+
for (const dir of buildNodeModulesChain(fromDir)) {
|
|
537
|
+
const nodeModules = join(dir, 'node_modules');
|
|
538
|
+
if (!existsSync(nodeModules))
|
|
539
|
+
continue;
|
|
540
|
+
try {
|
|
541
|
+
const names = [];
|
|
542
|
+
for (const entry of readdirSync(nodeModules, { withFileTypes: true })) {
|
|
543
|
+
if (entry.name.startsWith('.'))
|
|
544
|
+
continue;
|
|
545
|
+
if (entry.name.startsWith('@')) {
|
|
546
|
+
const scopeDir = join(nodeModules, entry.name);
|
|
547
|
+
for (const scoped of readdirSync(scopeDir)) {
|
|
548
|
+
names.push(`${entry.name}/${scoped}`);
|
|
549
|
+
}
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
names.push(entry.name);
|
|
553
|
+
}
|
|
554
|
+
return names;
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
418
562
|
/** Internal: ensure `from` exists for `createRequire` when given a dir. */
|
|
419
563
|
function resolveExistingDir(from) {
|
|
420
564
|
const absolute = resolve(from);
|
|
@@ -44,7 +44,6 @@ function discoverRayfinDocsPackages(options) {
|
|
|
44
44
|
const fromDir = options.from.startsWith('file:') || options.from.includes('://')
|
|
45
45
|
? (0, path_1.dirname)((0, url_1.fileURLToPath)(options.from))
|
|
46
46
|
: resolveExistingDir(options.from);
|
|
47
|
-
const requireFromBase = (0, module_1.createRequire)((0, path_1.join)(fromDir, 'noop.js'));
|
|
48
47
|
const nodeModulesChain = buildNodeModulesChain(fromDir);
|
|
49
48
|
const discovered = [];
|
|
50
49
|
const notInstalled = [];
|
|
@@ -62,7 +61,7 @@ function discoverRayfinDocsPackages(options) {
|
|
|
62
61
|
packagesToRead = resolvePackageRootJsons(options.packageRoots);
|
|
63
62
|
}
|
|
64
63
|
else if (options.candidates !== undefined) {
|
|
65
|
-
packagesToRead = resolveCandidatePackageJsons(options.candidates,
|
|
64
|
+
packagesToRead = resolveCandidatePackageJsons(options.candidates, fromDir, notInstalled, untrustedSkipped, trust);
|
|
66
65
|
}
|
|
67
66
|
else if (options.scanInstalledPackages) {
|
|
68
67
|
packagesToRead = discoverInstalledPackageJsons(nodeModulesChain);
|
|
@@ -139,14 +138,19 @@ function resolvePackageRootJsons(packageRoots) {
|
|
|
139
138
|
pkgJsonPath: (0, path_1.join)(resolveExistingDir(packageRoot), 'package.json'),
|
|
140
139
|
}));
|
|
141
140
|
}
|
|
142
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Resolve explicitly named candidates. Direct-only by construction: docs
|
|
143
|
+
* discovery reports packages the caller named and trusts, so it must not reach
|
|
144
|
+
* through the dependency graph into packages nobody asked about.
|
|
145
|
+
*/
|
|
146
|
+
function resolveCandidatePackageJsons(candidates, fromDir, notInstalled, untrustedSkipped, trust) {
|
|
143
147
|
const resolved = [];
|
|
144
148
|
for (const candidate of candidates) {
|
|
145
149
|
if (!trust(candidate)) {
|
|
146
150
|
untrustedSkipped.push(candidate);
|
|
147
151
|
continue;
|
|
148
152
|
}
|
|
149
|
-
const pkgJsonPath =
|
|
153
|
+
const pkgJsonPath = resolvePackageJsonFrom(fromDir, candidate);
|
|
150
154
|
if (!pkgJsonPath) {
|
|
151
155
|
notInstalled.push(candidate);
|
|
152
156
|
continue;
|
|
@@ -309,8 +313,8 @@ function docKindToModule(kind) {
|
|
|
309
313
|
}
|
|
310
314
|
/**
|
|
311
315
|
* Compute the chain of `node_modules` ancestor directories from
|
|
312
|
-
* `fromDir` upward, capped at 16 levels. Used by the tier
|
|
313
|
-
*
|
|
316
|
+
* `fromDir` upward, capped at 16 levels. Used by the ancestor tier in
|
|
317
|
+
* {@link resolvePackageJsonFrom} to avoid re-walking the same ancestors
|
|
314
318
|
* once per candidate (`9 candidates × 16 walks = 144 stat calls` in
|
|
315
319
|
* the original implementation; this caches the chain to 16 stats).
|
|
316
320
|
*/
|
|
@@ -326,18 +330,37 @@ function buildNodeModulesChain(fromDir) {
|
|
|
326
330
|
}
|
|
327
331
|
return chain;
|
|
328
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Dependency fields that describe packages actually present in an install.
|
|
335
|
+
*
|
|
336
|
+
* `devDependencies` is deliberately excluded: it is not installed for a
|
|
337
|
+
* transitive dependency, so following it would walk edges that do not exist in
|
|
338
|
+
* the consuming project's tree.
|
|
339
|
+
*/
|
|
340
|
+
const INSTALLED_DEPENDENCY_FIELDS = [
|
|
341
|
+
'dependencies',
|
|
342
|
+
'optionalDependencies',
|
|
343
|
+
'peerDependencies',
|
|
344
|
+
];
|
|
345
|
+
/**
|
|
346
|
+
* Bounds on the dependency-graph search. The walk only runs for a package the
|
|
347
|
+
* direct tiers already failed to find, so these cap a rare path rather than the
|
|
348
|
+
* common one.
|
|
349
|
+
*/
|
|
350
|
+
const MAX_TRANSITIVE_DEPTH = 4;
|
|
351
|
+
const MAX_TRANSITIVE_VISITS = 256;
|
|
329
352
|
/**
|
|
330
353
|
* Resolve actual installed versions for a bounded candidate set.
|
|
331
354
|
*
|
|
332
355
|
* Candidates are deduplicated and sorted. Missing, malformed, or mismatched
|
|
333
356
|
* manifests are omitted so telemetry collection remains best-effort.
|
|
357
|
+
*
|
|
358
|
+
* Resolves only what `from` can reach unless `includeIndirect` is set.
|
|
334
359
|
*/
|
|
335
360
|
function resolveInstalledPackageVersions(packageNames, from, options = {}) {
|
|
336
361
|
const fromDir = from.startsWith('file:') || from.includes('://')
|
|
337
362
|
? (0, path_1.dirname)((0, url_1.fileURLToPath)(from))
|
|
338
363
|
: resolveExistingDir(from);
|
|
339
|
-
const requireFromBase = (0, module_1.createRequire)((0, path_1.join)(fromDir, 'noop.js'));
|
|
340
|
-
const nodeModulesChain = buildNodeModulesChain(fromDir);
|
|
341
364
|
const packages = [];
|
|
342
365
|
const unresolvedPackageNames = [];
|
|
343
366
|
for (const packageName of [...new Set(packageNames)].sort()) {
|
|
@@ -345,7 +368,10 @@ function resolveInstalledPackageVersions(packageNames, from, options = {}) {
|
|
|
345
368
|
unresolvedPackageNames.push(packageName);
|
|
346
369
|
continue;
|
|
347
370
|
}
|
|
348
|
-
const packageJsonPath =
|
|
371
|
+
const packageJsonPath = resolvePackageJsonFrom(fromDir, packageName) ??
|
|
372
|
+
(options.includeIndirect
|
|
373
|
+
? resolvePackageJsonTransitively(fromDir, packageName)
|
|
374
|
+
: undefined);
|
|
349
375
|
if (!packageJsonPath) {
|
|
350
376
|
unresolvedPackageNames.push(packageName);
|
|
351
377
|
continue;
|
|
@@ -373,15 +399,21 @@ function isValidPackageName(packageName) {
|
|
|
373
399
|
/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName));
|
|
374
400
|
}
|
|
375
401
|
/**
|
|
376
|
-
* Find a package's `package.json`
|
|
377
|
-
* ESM-only, or the install is hoisted above the
|
|
402
|
+
* Find a package's `package.json` **as resolved from `fromDir`**, even when
|
|
403
|
+
* exports hide it, the package is ESM-only, or the install is hoisted above the
|
|
404
|
+
* resolution root.
|
|
378
405
|
*
|
|
379
406
|
* Packages with restricted exports often omit `./package.json`, causing direct
|
|
380
407
|
* subpath resolution to fail. ESM-only packages may also hide their main entry
|
|
381
|
-
* from CommonJS resolution, so the final tier searches the
|
|
408
|
+
* from CommonJS resolution, so the final tier searches the ancestor
|
|
382
409
|
* `node_modules` chain used by npm, pnpm, and Yarn-style installs.
|
|
410
|
+
*
|
|
411
|
+
* Scoped to one directory so the dependency-graph walk can reuse it verbatim:
|
|
412
|
+
* "resolve X from package Y's location" is the same question as "resolve X from
|
|
413
|
+
* the project root", just asked somewhere else in the tree.
|
|
383
414
|
*/
|
|
384
|
-
function
|
|
415
|
+
function resolvePackageJsonFrom(fromDir, packageName) {
|
|
416
|
+
const req = (0, module_1.createRequire)((0, path_1.join)(fromDir, 'noop.js'));
|
|
385
417
|
// Tier 1: direct manifest resolution when exports allow the subpath.
|
|
386
418
|
try {
|
|
387
419
|
return req.resolve(`${packageName}/package.json`);
|
|
@@ -416,14 +448,126 @@ function resolvePackageJson(req, packageName, nodeModulesChain) {
|
|
|
416
448
|
catch {
|
|
417
449
|
// ESM-only package or similar: fall through to node_modules lookup.
|
|
418
450
|
}
|
|
419
|
-
// Tier 3: search
|
|
420
|
-
for (const dir of
|
|
451
|
+
// Tier 3: search ancestor node_modules roots, independent of exports.
|
|
452
|
+
for (const dir of buildNodeModulesChain(fromDir)) {
|
|
421
453
|
const candidate = (0, path_1.join)(dir, 'node_modules', packageName, 'package.json');
|
|
422
454
|
if ((0, fs_1.existsSync)(candidate))
|
|
423
455
|
return candidate;
|
|
424
456
|
}
|
|
425
457
|
return undefined;
|
|
426
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Find a package that is installed but *not reachable from `fromDir`* by
|
|
461
|
+
* searching outward through the dependency graph.
|
|
462
|
+
*
|
|
463
|
+
* A strict/isolated install (pnpm's default, and Yarn's `nmMode: hardlinks`)
|
|
464
|
+
* links a purely transitive package only inside the dependency scope of the
|
|
465
|
+
* package that asked for it — never the consumer's own `node_modules` chain.
|
|
466
|
+
* Resolution from `fromDir` therefore fails for it, correctly: nothing at the
|
|
467
|
+
* project root may import it. It is still the version that ships, because the
|
|
468
|
+
* bundler reaches it through the dependent that does.
|
|
469
|
+
*
|
|
470
|
+
* So the search moves to where the answer is: walk the installed dependency
|
|
471
|
+
* graph breadth-first and re-ask {@link resolvePackageJsonFrom} at each package's
|
|
472
|
+
* own real location. Node's resolver supplies all the layout knowledge —
|
|
473
|
+
* following the symlink out of pnpm's virtual store and then finding the target
|
|
474
|
+
* as its sibling — instead of this code hard-coding store paths, and it costs
|
|
475
|
+
* nothing on the common path because it only runs after a direct miss.
|
|
476
|
+
*
|
|
477
|
+
* Known limitation: Yarn Plug'n'Play has no `node_modules` tree at all, so
|
|
478
|
+
* neither this nor the direct tiers can see it without the `.pnp.cjs` API.
|
|
479
|
+
*/
|
|
480
|
+
function resolvePackageJsonTransitively(fromDir, packageName) {
|
|
481
|
+
const visited = new Set();
|
|
482
|
+
let frontier = readInstalledDependencyNames((0, path_1.join)(fromDir, 'package.json'));
|
|
483
|
+
// Not seeded from the manifest alone: a project whose own manifest is
|
|
484
|
+
// unreadable can still have an installed tree worth searching.
|
|
485
|
+
if (frontier.length === 0) {
|
|
486
|
+
frontier = readAncestorNodeModulesEntries(fromDir);
|
|
487
|
+
}
|
|
488
|
+
for (let depth = 0; depth < MAX_TRANSITIVE_DEPTH; depth += 1) {
|
|
489
|
+
const next = [];
|
|
490
|
+
for (const dependentName of frontier) {
|
|
491
|
+
if (visited.size >= MAX_TRANSITIVE_VISITS)
|
|
492
|
+
return undefined;
|
|
493
|
+
if (dependentName === packageName)
|
|
494
|
+
continue;
|
|
495
|
+
if (visited.has(dependentName))
|
|
496
|
+
continue;
|
|
497
|
+
visited.add(dependentName);
|
|
498
|
+
if (!isValidPackageName(dependentName))
|
|
499
|
+
continue;
|
|
500
|
+
const dependentManifest = resolvePackageJsonFrom(fromDir, dependentName);
|
|
501
|
+
if (!dependentManifest)
|
|
502
|
+
continue;
|
|
503
|
+
// The real location matters: resolving through the virtual-store symlink
|
|
504
|
+
// is what puts the target in scope as a sibling.
|
|
505
|
+
let dependentDir;
|
|
506
|
+
try {
|
|
507
|
+
dependentDir = (0, fs_1.realpathSync)((0, path_1.dirname)(dependentManifest));
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const found = resolvePackageJsonFrom(dependentDir, packageName);
|
|
513
|
+
if (found)
|
|
514
|
+
return found;
|
|
515
|
+
next.push(...readInstalledDependencyNames((0, path_1.join)(dependentDir, 'package.json')));
|
|
516
|
+
}
|
|
517
|
+
if (next.length === 0)
|
|
518
|
+
return undefined;
|
|
519
|
+
frontier = next;
|
|
520
|
+
}
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
/** Names declared in the dependency fields that describe an installed tree. */
|
|
524
|
+
function readInstalledDependencyNames(manifestPath) {
|
|
525
|
+
try {
|
|
526
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
527
|
+
const names = [];
|
|
528
|
+
for (const field of INSTALLED_DEPENDENCY_FIELDS) {
|
|
529
|
+
const value = parsed[field];
|
|
530
|
+
if (value && typeof value === 'object') {
|
|
531
|
+
names.push(...Object.keys(value));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return names;
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Top-level package names installed in the nearest ancestor `node_modules`,
|
|
542
|
+
* used to seed the graph walk when no readable manifest declares anything.
|
|
543
|
+
*/
|
|
544
|
+
function readAncestorNodeModulesEntries(fromDir) {
|
|
545
|
+
for (const dir of buildNodeModulesChain(fromDir)) {
|
|
546
|
+
const nodeModules = (0, path_1.join)(dir, 'node_modules');
|
|
547
|
+
if (!(0, fs_1.existsSync)(nodeModules))
|
|
548
|
+
continue;
|
|
549
|
+
try {
|
|
550
|
+
const names = [];
|
|
551
|
+
for (const entry of (0, fs_1.readdirSync)(nodeModules, { withFileTypes: true })) {
|
|
552
|
+
if (entry.name.startsWith('.'))
|
|
553
|
+
continue;
|
|
554
|
+
if (entry.name.startsWith('@')) {
|
|
555
|
+
const scopeDir = (0, path_1.join)(nodeModules, entry.name);
|
|
556
|
+
for (const scoped of (0, fs_1.readdirSync)(scopeDir)) {
|
|
557
|
+
names.push(`${entry.name}/${scoped}`);
|
|
558
|
+
}
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
names.push(entry.name);
|
|
562
|
+
}
|
|
563
|
+
return names;
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return [];
|
|
570
|
+
}
|
|
427
571
|
/** Internal: ensure `from` exists for `createRequire` when given a dir. */
|
|
428
572
|
function resolveExistingDir(from) {
|
|
429
573
|
const absolute = (0, path_1.resolve)(from);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/rayfin-docs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0-alpha.1588",
|
|
4
4
|
"description": "Rayfin docs indexing and search library — used by the MCP server, CLI, and other doc-facing surfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|