@dependably/npm-check 1.9.0 → 1.10.1
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 +47 -0
- package/bin/cli.js +93 -1
- package/package.json +20 -3
- package/src/audit-config.js +98 -68
- package/src/audit.js +198 -58
- package/src/exceptions.js +82 -66
- package/src/facts/collect.js +201 -0
- package/src/facts/document.js +151 -0
- package/src/facts/errors.js +19 -0
- package/src/facts/index.js +37 -0
- package/src/facts/lockfile-graph.js +467 -0
- package/src/facts/modulegraph.js +289 -0
- package/src/facts/resolve.js +493 -0
- package/src/facts/scan.js +755 -0
- package/src/facts/sourcescan.js +221 -0
- package/src/facts/specifier.js +89 -0
- package/src/facts/ts.js +47 -0
- package/src/facts/types.d.ts +579 -0
- package/src/facts/version.js +16 -0
- package/src/facts/workspace.js +278 -0
- package/src/npmrc-validator.js +2 -1
- package/src/pnpm-workspace-validator.js +8 -2
- package/src/report.js +12 -6
- package/src/schema.js +54 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/facts/collect.js
|
|
2
|
+
// One call that gathers every import fact the modules under `src/facts/` can
|
|
3
|
+
// establish about a tree: the workspace (manifests, aliases, source files),
|
|
4
|
+
// every first-party file's scan with each site resolved to the installed
|
|
5
|
+
// copy it loads, the module graph through node_modules, the lockfile graph,
|
|
6
|
+
// and — load-bearing — the list of everything that was NOT read.
|
|
7
|
+
//
|
|
8
|
+
// It reports; it never judges. There is no "reachable" here, no verdict
|
|
9
|
+
// vocabulary at all: a consumer that owns those words (sbom-reach's npm
|
|
10
|
+
// analyzer) builds them from these facts. What this module guarantees is
|
|
11
|
+
// that the facts are complete OR say where they are not (`unanalyzable`):
|
|
12
|
+
// a file that could not be read used to be a silent `continue` in the
|
|
13
|
+
// consumer, and "nothing imports it" then rested on a file nobody read.
|
|
14
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
15
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
16
|
+
import { discoverLockfileGraphs } from './lockfile-graph.js';
|
|
17
|
+
import { walkModuleGraph } from './modulegraph.js';
|
|
18
|
+
import { ModuleResolver } from './resolve.js';
|
|
19
|
+
import { scanSource } from './scan.js';
|
|
20
|
+
import { specifierToPackage } from './specifier.js';
|
|
21
|
+
import { loadTypeScript } from './ts.js';
|
|
22
|
+
import { discoverWorkspace } from './workspace.js';
|
|
23
|
+
|
|
24
|
+
/** @typedef {import('./types.d.ts').CollectOptions} CollectOptions */
|
|
25
|
+
/** @typedef {import('./types.d.ts').FirstPartyFile} FirstPartyFile */
|
|
26
|
+
/** @typedef {import('./types.d.ts').ImportFacts} ImportFacts */
|
|
27
|
+
/** @typedef {import('./types.d.ts').ModuleGraph} ModuleGraph */
|
|
28
|
+
/** @typedef {import('./types.d.ts').ResolvedSite} ResolvedSite */
|
|
29
|
+
/** @typedef {import('./types.d.ts').UnanalyzableEntry} UnanalyzableEntry */
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Relativizes paths against `srcDir` — realpath-aware. First-party files are
|
|
33
|
+
* spelled under `srcDir` as given; resolved node_modules files are realpath'd
|
|
34
|
+
* (pnpm symlinks, macOS `/var` → `/private/var`, `/tmp` → `/private/tmp`),
|
|
35
|
+
* and `srcDir` as given may not be. Relativize against whichever spelling of
|
|
36
|
+
* `srcDir` the file actually sits under: the one that needs FEWER `..`
|
|
37
|
+
* segments. "Prefer the realpath only when it lands inside the tree" was the
|
|
38
|
+
* first rule and it was wrong — with node_modules hoisted ABOVE the target
|
|
39
|
+
* and a symlinked prefix on `srcDir`, both spellings start with `..`, and the
|
|
40
|
+
* as-given one is a `../../../..`-to-root chain followed by the whole
|
|
41
|
+
* absolute path (adversarial review), poisoning every path in the document.
|
|
42
|
+
* Always POSIX separators: the document must compare across machines. The
|
|
43
|
+
* tree itself relativizes to `''`, exactly as sbom-reach's `relOf` does — a
|
|
44
|
+
* consumer emits that spelling verbatim, so it must not change.
|
|
45
|
+
* @param {string} srcDir
|
|
46
|
+
* @param {string} realSrcDir
|
|
47
|
+
* @returns {(file: string) => string}
|
|
48
|
+
*/
|
|
49
|
+
export function makeRelOf(srcDir, realSrcDir) {
|
|
50
|
+
return (file) => {
|
|
51
|
+
const direct = relative(srcDir, file);
|
|
52
|
+
if (realSrcDir === srcDir) return direct.split(sep).join('/');
|
|
53
|
+
const viaReal = relative(realSrcDir, file);
|
|
54
|
+
const chosen = parentSegments(viaReal) < parentSegments(direct) ? viaReal : direct;
|
|
55
|
+
return chosen.split(sep).join('/');
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* How many leading `..` segments a relative path climbs through.
|
|
61
|
+
* @param {string} rel
|
|
62
|
+
* @returns {number}
|
|
63
|
+
*/
|
|
64
|
+
function parentSegments(rel) {
|
|
65
|
+
let n = 0;
|
|
66
|
+
for (const part of rel.split(sep)) {
|
|
67
|
+
if (part !== '..') break;
|
|
68
|
+
n++;
|
|
69
|
+
}
|
|
70
|
+
return n;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Collect the import facts of the tree at `srcDir`.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} srcDir - the tree to describe (relative paths resolve against cwd)
|
|
77
|
+
* @param {CollectOptions} [options]
|
|
78
|
+
* @returns {ImportFacts}
|
|
79
|
+
* @throws {import('./errors.js').FactsError} `TYPESCRIPT_MISSING` before any
|
|
80
|
+
* work is done, when the optional `typescript` peer is not installed.
|
|
81
|
+
*/
|
|
82
|
+
export function collectImportFacts(srcDir, options = {}) {
|
|
83
|
+
// Fail before touching the tree: every other module here parses with the
|
|
84
|
+
// compiler, so there is nothing partial worth returning without it.
|
|
85
|
+
loadTypeScript();
|
|
86
|
+
const { moduleGraph = true, maxFiles, maxFileBytes, scan = scanSource } = options;
|
|
87
|
+
|
|
88
|
+
const absSrcDir = resolve(srcDir);
|
|
89
|
+
let realSrcDir = absSrcDir;
|
|
90
|
+
try {
|
|
91
|
+
realSrcDir = realpathSync(absSrcDir);
|
|
92
|
+
} catch {
|
|
93
|
+
// keep as given
|
|
94
|
+
}
|
|
95
|
+
const relOf = makeRelOf(absSrcDir, realSrcDir);
|
|
96
|
+
|
|
97
|
+
const workspace = discoverWorkspace(absSrcDir);
|
|
98
|
+
const resolver = new ModuleResolver(workspace.aliasScope);
|
|
99
|
+
|
|
100
|
+
/** @type {UnanalyzableEntry[]} */
|
|
101
|
+
const unanalyzable = [];
|
|
102
|
+
/** @type {FirstPartyFile[]} */
|
|
103
|
+
const files = [];
|
|
104
|
+
let dynamicUnknownTotal = 0;
|
|
105
|
+
|
|
106
|
+
for (const file of workspace.sourceFiles) {
|
|
107
|
+
const rel = relOf(file);
|
|
108
|
+
/** @type {string} */
|
|
109
|
+
let content;
|
|
110
|
+
try {
|
|
111
|
+
content = readFileSync(file, 'utf8');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// The consumer used to `continue` here silently. A file nobody read is
|
|
114
|
+
// a file whose imports are unknown, and it must be listed. The reason
|
|
115
|
+
// is the error CODE, never Node's message — that carries the absolute
|
|
116
|
+
// path, and reasons must stay machine-independent (`file` says where).
|
|
117
|
+
unanalyzable.push({ file: rel, kind: 'file', reason: `unreadable: ${errorCode(err)}` });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const result = scan(rel, content);
|
|
121
|
+
dynamicUnknownTotal += result.dynamicUnknown;
|
|
122
|
+
if (result.parseErrors && result.parseErrors.length > 0) {
|
|
123
|
+
// Sites collected before/around the problem are still reported; what
|
|
124
|
+
// this entry says is that their absence for some package is not a
|
|
125
|
+
// clean negative.
|
|
126
|
+
unanalyzable.push({ file: rel, kind: 'file-partial', reason: result.parseErrors.join('; ') });
|
|
127
|
+
}
|
|
128
|
+
/** @type {ResolvedSite[]} */
|
|
129
|
+
const sites = result.sites.map((site) => {
|
|
130
|
+
const pkg = specifierToPackage(site.specifier, workspace.aliasScope.for(file));
|
|
131
|
+
// Which installed copy does this statement load? Version-accurate
|
|
132
|
+
// attribution is the consumer's, but the fact — the copy the resolver
|
|
133
|
+
// lands in — is established here, once, with the same resolver the
|
|
134
|
+
// graph walk uses.
|
|
135
|
+
const resolution = resolver.resolve(file, site.specifier, site.kind === 'require' ? 'require' : 'import');
|
|
136
|
+
const installed =
|
|
137
|
+
(resolution.kind === 'file' || resolution.kind === 'asset') && resolution.pkg !== undefined
|
|
138
|
+
? {
|
|
139
|
+
name: resolution.pkg.name,
|
|
140
|
+
dirName: resolution.pkg.dirName,
|
|
141
|
+
version: resolution.pkg.version,
|
|
142
|
+
root: resolution.pkg.root
|
|
143
|
+
}
|
|
144
|
+
: undefined;
|
|
145
|
+
return { ...site, package: pkg, ...(installed ? { installed } : {}) };
|
|
146
|
+
});
|
|
147
|
+
files.push({ file, rel, scan: result, sites });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @type {ModuleGraph | undefined} */
|
|
151
|
+
let graph;
|
|
152
|
+
let nodeModulesMissing = false;
|
|
153
|
+
if (moduleGraph) {
|
|
154
|
+
graph = walkModuleGraph({
|
|
155
|
+
srcDir: absSrcDir,
|
|
156
|
+
resolver,
|
|
157
|
+
roots: files.map((f) => ({ file: f.file, scan: f.scan })),
|
|
158
|
+
scan,
|
|
159
|
+
...(maxFiles !== undefined ? { maxFiles } : {}),
|
|
160
|
+
...(maxFileBytes !== undefined ? { maxFileBytes } : {})
|
|
161
|
+
});
|
|
162
|
+
nodeModulesMissing = graph.reached.size === 0 && graph.unresolved > 0 && !existsSync(join(absSrcDir, 'node_modules'));
|
|
163
|
+
for (const entry of graph.unanalyzable) {
|
|
164
|
+
unanalyzable.push({ file: relOf(entry.file), kind: 'node-modules-file', reason: entry.reason });
|
|
165
|
+
}
|
|
166
|
+
if (graph.truncated) {
|
|
167
|
+
unanalyzable.push({
|
|
168
|
+
file: 'node_modules',
|
|
169
|
+
kind: 'walk',
|
|
170
|
+
reason:
|
|
171
|
+
`file budget ${graph.filesParsed} reached; ${graph.filesPastBudget} resolved file(s) past that frontier ` +
|
|
172
|
+
'were not parsed, so packages they load are unobserved, not absent'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const lockfile = discoverLockfileGraphs(absSrcDir);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
srcDir: absSrcDir,
|
|
181
|
+
realSrcDir,
|
|
182
|
+
workspace,
|
|
183
|
+
files,
|
|
184
|
+
graph,
|
|
185
|
+
nodeModulesMissing,
|
|
186
|
+
lockfile,
|
|
187
|
+
unanalyzable,
|
|
188
|
+
dynamicUnknownTotal
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A path-free spelling of an I/O failure: the `code` (`EACCES`, `EISDIR`,
|
|
194
|
+
* `ENOENT`…) when there is one, else the constructor name.
|
|
195
|
+
* @param {unknown} err
|
|
196
|
+
* @returns {string}
|
|
197
|
+
*/
|
|
198
|
+
export function errorCode(err) {
|
|
199
|
+
if (err && typeof err === 'object' && 'code' in err && typeof err.code === 'string') return err.code;
|
|
200
|
+
return err instanceof Error ? err.name : 'error';
|
|
201
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/facts/document.js
|
|
2
|
+
// The JSON shape of an import-facts document: the in-memory `ImportFacts`
|
|
3
|
+
// (Maps, Sets, absolute paths) rendered as plain data a consumer can parse
|
|
4
|
+
// on any machine — Maps and Sets become sorted arrays, every absolute path
|
|
5
|
+
// becomes a target-relative POSIX path, and the module-graph keys keep their
|
|
6
|
+
// `\0` separator with the path half relativized the same way, so a key in
|
|
7
|
+
// `chain`/`fromPackage` still matches its `reached` entry.
|
|
8
|
+
//
|
|
9
|
+
// Language facts only: no purls, no verdicts, no severities. The envelope
|
|
10
|
+
// identity (`tool`, `toolVersion`, `schemaVersion`, `documentType`, `target`)
|
|
11
|
+
// is added by `schema.js`'s `buildFactsEnvelope`; this module produces the
|
|
12
|
+
// `summary` and the body sections.
|
|
13
|
+
import { makeRelOf } from './collect.js';
|
|
14
|
+
|
|
15
|
+
/** @typedef {import('./types.d.ts').ImportFacts} ImportFacts */
|
|
16
|
+
/** @typedef {import('./types.d.ts').FactsDocumentBody} FactsDocumentBody */
|
|
17
|
+
/** @typedef {import('./types.d.ts').FactsSummary} FactsSummary */
|
|
18
|
+
/** @typedef {import('./types.d.ts').ReachedPackage} ReachedPackage */
|
|
19
|
+
/** @typedef {import('./types.d.ts').DocumentReachedPackage} DocumentReachedPackage */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render collected facts as the document body: `summary` plus every section.
|
|
23
|
+
* `target` is recorded by the envelope, not here; paths are relative to the
|
|
24
|
+
* facts' own `srcDir` (which is the target the CLI scanned).
|
|
25
|
+
*
|
|
26
|
+
* @param {ImportFacts} facts
|
|
27
|
+
* @param {{ exitCode?: number }} [options] `exitCode` lands in `summary.exitCode`
|
|
28
|
+
* and MUST equal the process exit code the run will return (0 here — a
|
|
29
|
+
* successful scan never gates).
|
|
30
|
+
* @returns {FactsDocumentBody}
|
|
31
|
+
*/
|
|
32
|
+
export function factsDocument(facts, options = {}) {
|
|
33
|
+
const exitCode = options.exitCode ?? 0;
|
|
34
|
+
const relOf = makeRelOf(facts.srcDir, facts.realSrcDir);
|
|
35
|
+
/** @param {string} key */
|
|
36
|
+
const relKey = (key) => {
|
|
37
|
+
const nul = key.indexOf('\0');
|
|
38
|
+
return nul === -1 ? key : `${key.slice(0, nul)}\0${relOf(key.slice(nul + 1))}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const imports = facts.files.map((f) => ({
|
|
42
|
+
file: f.rel,
|
|
43
|
+
dynamicUnknown: f.scan.dynamicUnknown,
|
|
44
|
+
parseErrors: f.scan.parseErrors ?? [],
|
|
45
|
+
sites: f.sites.map((s) => ({
|
|
46
|
+
specifier: s.specifier,
|
|
47
|
+
package: s.package ?? null,
|
|
48
|
+
line: s.line,
|
|
49
|
+
snippet: s.snippet,
|
|
50
|
+
kind: s.kind,
|
|
51
|
+
bindings: s.bindings ?? [],
|
|
52
|
+
referenced: s.referenced ?? [],
|
|
53
|
+
opaque: s.opaque === true,
|
|
54
|
+
...(s.installed ? { installed: { ...s.installed, root: relOf(s.installed.root) } } : {})
|
|
55
|
+
}))
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const graph = facts.graph;
|
|
59
|
+
/** @type {DocumentReachedPackage[]} */
|
|
60
|
+
const reached = graph
|
|
61
|
+
? [...graph.reached.values()]
|
|
62
|
+
.map((entry) => ({
|
|
63
|
+
key: relKey(entry.key),
|
|
64
|
+
name: entry.name,
|
|
65
|
+
dirName: entry.dirName,
|
|
66
|
+
version: entry.version,
|
|
67
|
+
root: relOf(entry.root),
|
|
68
|
+
chain: entry.chain.map(relKey),
|
|
69
|
+
dynamic: entry.dynamic,
|
|
70
|
+
incomplete: entry.incomplete,
|
|
71
|
+
importers: entry.importers.map((imp) => ({
|
|
72
|
+
file: relOf(imp.file),
|
|
73
|
+
line: imp.line,
|
|
74
|
+
snippet: imp.snippet,
|
|
75
|
+
kind: imp.kind,
|
|
76
|
+
bindings: imp.bindings ?? [],
|
|
77
|
+
referenced: imp.referenced ?? [],
|
|
78
|
+
opaque: imp.opaque === true,
|
|
79
|
+
fromPackage: imp.fromPackage === undefined ? null : relKey(imp.fromPackage)
|
|
80
|
+
}))
|
|
81
|
+
}))
|
|
82
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
83
|
+
: [];
|
|
84
|
+
const unresolvedByName = graph
|
|
85
|
+
? [...graph.unresolvedByName]
|
|
86
|
+
.map(([pkg, sites]) => ({
|
|
87
|
+
package: pkg,
|
|
88
|
+
sites: sites.map((s) => ({ file: relOf(s.file), reason: s.reason }))
|
|
89
|
+
}))
|
|
90
|
+
.sort((a, b) => (a.package < b.package ? -1 : a.package > b.package ? 1 : 0))
|
|
91
|
+
: [];
|
|
92
|
+
|
|
93
|
+
const moduleGraph = {
|
|
94
|
+
enabled: graph !== undefined,
|
|
95
|
+
filesParsed: graph ? graph.filesParsed : 0,
|
|
96
|
+
filesSkippedForSize: graph ? graph.filesSkippedForSize : 0,
|
|
97
|
+
unresolved: graph ? graph.unresolved : 0,
|
|
98
|
+
truncated: graph ? graph.truncated : false,
|
|
99
|
+
nodeModulesMissing: facts.nodeModulesMissing,
|
|
100
|
+
weakPackages: graph ? graph.weakPackages : [],
|
|
101
|
+
reached,
|
|
102
|
+
unresolvedByName
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const ws = facts.workspace;
|
|
106
|
+
const workspace = {
|
|
107
|
+
firstPartyNames: [...ws.firstPartyNames].sort(),
|
|
108
|
+
depScopes: [...ws.depScopes]
|
|
109
|
+
.map(([name, scope]) => ({ name, scope }))
|
|
110
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
111
|
+
aliasPrefixes: [...ws.aliasPrefixes].sort(),
|
|
112
|
+
// `aliasScope` itself is a closure (`.for(file)`) and cannot be
|
|
113
|
+
// serialized; this projects the same per-config information it answers
|
|
114
|
+
// from, the way `reached`/`unresolvedByName` project the module graph's
|
|
115
|
+
// Maps above -- one entry per tsconfig/jsconfig that declared `paths`,
|
|
116
|
+
// `dir` made target-relative and POSIX like every other path here.
|
|
117
|
+
aliasScope: ws.aliasLayers
|
|
118
|
+
.map((layer) => ({ dir: relOf(layer.dir), prefixes: [...layer.prefixes].sort() }))
|
|
119
|
+
.sort((a, b) => (a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0)),
|
|
120
|
+
devDeclaredBy: [...ws.devDeclaredBy]
|
|
121
|
+
.map(([name, manifests]) => ({ name, manifests: [...manifests].sort() }))
|
|
122
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
123
|
+
sourceFiles: ws.sourceFiles.length,
|
|
124
|
+
diagnostics: ws.diagnostics
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const lockfile = {
|
|
128
|
+
files: facts.lockfile.files.map(relOf),
|
|
129
|
+
packages: facts.lockfile.packages,
|
|
130
|
+
rootDependencies: facts.lockfile.rootDependencies,
|
|
131
|
+
edges: facts.lockfile.edges,
|
|
132
|
+
diagnostics: facts.lockfile.diagnostics
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** @type {FactsSummary} */
|
|
136
|
+
const summary = {
|
|
137
|
+
scanned: ws.sourceFiles.length,
|
|
138
|
+
analyzed: facts.files.length,
|
|
139
|
+
unanalyzable: facts.unanalyzable.length,
|
|
140
|
+
imports: facts.files.reduce((n, f) => n + f.sites.length, 0),
|
|
141
|
+
moduleGraph: {
|
|
142
|
+
filesParsed: moduleGraph.filesParsed,
|
|
143
|
+
reached: reached.length,
|
|
144
|
+
unresolved: moduleGraph.unresolved,
|
|
145
|
+
truncated: moduleGraph.truncated
|
|
146
|
+
},
|
|
147
|
+
exitCode
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return { summary, workspace, imports, moduleGraph, lockfile, unanalyzable: facts.unanalyzable };
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/facts/errors.js
|
|
2
|
+
// The one error type the import-facts modules throw for a condition the caller
|
|
3
|
+
// is expected to handle by code rather than by message: `TYPESCRIPT_MISSING`
|
|
4
|
+
// (the optional `typescript` peer is not installed). Everything else — an
|
|
5
|
+
// unreadable file, an unparseable lockfile — is reported IN the facts, never
|
|
6
|
+
// thrown, because a facts document that aborts on one bad file has told the
|
|
7
|
+
// consumer nothing about the rest of the tree.
|
|
8
|
+
|
|
9
|
+
export class FactsError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} code
|
|
12
|
+
* @param {string} message
|
|
13
|
+
*/
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'FactsError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/facts/index.js
|
|
2
|
+
// Barrel for `@dependably/npm-check/facts` — the npm LANGUAGE-FACTS layer:
|
|
3
|
+
// per-file imports, bindings and referenced names; Node-style resolution;
|
|
4
|
+
// the walk through node_modules; lockfile graphs; workspace discovery.
|
|
5
|
+
// Facts, not findings: nothing exported here carries a severity, a verdict
|
|
6
|
+
// or a package identifier beyond the name and version on disk. The main
|
|
7
|
+
// barrel (`src/index.js`) deliberately does NOT re-export this — the
|
|
8
|
+
// lockfile commands must never load the TypeScript compiler.
|
|
9
|
+
//
|
|
10
|
+
// Precedent: pycheck's `--imports` document (its readme, "Import facts") —
|
|
11
|
+
// report not gate, a `documentType` discriminator, `unanalyzable[]`
|
|
12
|
+
// load-bearing, additive fields only.
|
|
13
|
+
|
|
14
|
+
// The facts document's own schema version — what `buildFactsEnvelope`
|
|
15
|
+
// (src/schema.js) writes as `schemaVersion`, a SEPARATE version line from
|
|
16
|
+
// the findings envelope's `SCHEMA_VERSION`. See version.js for why it is a
|
|
17
|
+
// leaf module rather than living here or in schema.js.
|
|
18
|
+
export { FACTS_SCHEMA_VERSION } from './version.js';
|
|
19
|
+
|
|
20
|
+
export { FactsError } from './errors.js';
|
|
21
|
+
export { loadTypeScript } from './ts.js';
|
|
22
|
+
export { collectImportFacts, makeRelOf } from './collect.js';
|
|
23
|
+
export { factsDocument } from './document.js';
|
|
24
|
+
export { scanSource } from './scan.js';
|
|
25
|
+
export { ModuleResolver, packageRootOf, resolveExports } from './resolve.js';
|
|
26
|
+
export { walkModuleGraph, packageKey, DEFAULT_MAX_FILES, DEFAULT_MAX_FILE_BYTES } from './modulegraph.js';
|
|
27
|
+
export {
|
|
28
|
+
discoverLockfileGraphs,
|
|
29
|
+
mergeDiscovered,
|
|
30
|
+
parsePackageLockJson,
|
|
31
|
+
parsePackageLockJsonGraph,
|
|
32
|
+
parsePnpmLockYaml,
|
|
33
|
+
parsePnpmLockYamlGraph
|
|
34
|
+
} from './lockfile-graph.js';
|
|
35
|
+
export { specifierToPackage, aliasBaseFromPathsKey, fixedAliasScope, asAliasScope } from './specifier.js';
|
|
36
|
+
export { discoverWorkspace, governedByManifest } from './workspace.js';
|
|
37
|
+
export { loadGitignores, isGitignored, filterGitignored, OUTPUT_SHAPED_DIRS, outputDirScannedDiagnostic } from './sourcescan.js';
|