@rsdk/yarn.constraints 6.0.0-next.4 → 6.0.0-next.41
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/DEPENDENCY_MODEL.md +460 -0
- package/README.MD +85 -21
- package/__tests__/compatibility.test.ts +321 -0
- package/__tests__/config-validation.test.ts +42 -0
- package/__tests__/engine.test.ts +1107 -0
- package/__tests__/fixtures/imports/bin.js +4 -0
- package/__tests__/fixtures/imports/export-entry.mjs +1 -0
- package/__tests__/fixtures/imports/lib/lib-entry.js +3 -0
- package/__tests__/fixtures/imports/root-entry.js +4 -0
- package/__tests__/fixtures/imports/rules/transitive.js +3 -0
- package/__tests__/fixtures/imports/src/common.cjs +3 -0
- package/__tests__/fixtures/imports/src/common.cts +3 -0
- package/__tests__/fixtures/imports/src/component.tsx +4 -0
- package/__tests__/fixtures/imports/src/index.ts +13 -0
- package/__tests__/fixtures/imports/src/module.mjs +3 -0
- package/__tests__/fixtures/imports/src/module.mts +3 -0
- package/__tests__/fixtures/imports/src/plain.js +3 -0
- package/__tests__/fixtures/imports/src/test-only-usage.ts +1 -0
- package/__tests__/fixtures/imports/test/outside.ts +3 -0
- package/__tests__/imports.test.ts +218 -0
- package/__tests__/manifest-writer.test.ts +157 -0
- package/dist/ansi.d.ts +9 -0
- package/dist/ansi.js +24 -0
- package/dist/ansi.js.map +1 -0
- package/dist/bin/depdoc.d.ts +2 -0
- package/dist/bin/depdoc.js +157 -0
- package/dist/bin/depdoc.js.map +1 -0
- package/dist/collectors/config.d.ts +2 -0
- package/dist/collectors/config.js +28 -0
- package/dist/collectors/config.js.map +1 -0
- package/dist/collectors/external-metadata.d.ts +5 -0
- package/dist/collectors/external-metadata.js +110 -0
- package/dist/collectors/external-metadata.js.map +1 -0
- package/dist/collectors/package-extensions.d.ts +3 -0
- package/dist/collectors/package-extensions.js +43 -0
- package/dist/collectors/package-extensions.js.map +1 -0
- package/dist/collectors/type-providers.d.ts +3 -0
- package/dist/collectors/type-providers.js +46 -0
- package/dist/collectors/type-providers.js.map +1 -0
- package/dist/collectors/workspaces.d.ts +2 -0
- package/dist/collectors/workspaces.js +90 -0
- package/dist/collectors/workspaces.js.map +1 -0
- package/dist/dependency-model.d.ts +11 -0
- package/dist/dependency-model.js +18 -0
- package/dist/dependency-model.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +13 -33
- package/dist/index.js.map +1 -1
- package/dist/lib/imports.d.ts +11 -0
- package/dist/lib/imports.js +342 -0
- package/dist/lib/imports.js.map +1 -0
- package/dist/lib/package-json.d.ts +21 -0
- package/dist/lib/package-json.js +32 -0
- package/dist/lib/package-json.js.map +1 -0
- package/dist/model/config-validation.d.ts +6 -0
- package/dist/model/config-validation.js +31 -0
- package/dist/model/config-validation.js.map +1 -0
- package/dist/model/diagnostics.d.ts +4 -0
- package/dist/model/diagnostics.js +295 -0
- package/dist/model/diagnostics.js.map +1 -0
- package/dist/model/engine.d.ts +5 -0
- package/dist/model/engine.js +52 -0
- package/dist/model/engine.js.map +1 -0
- package/dist/model/expected.d.ts +20 -0
- package/dist/model/expected.js +89 -0
- package/dist/model/expected.js.map +1 -0
- package/dist/model/peer-propagation.d.ts +2 -0
- package/dist/model/peer-propagation.js +124 -0
- package/dist/model/peer-propagation.js.map +1 -0
- package/dist/model/placement.d.ts +9 -0
- package/dist/model/placement.js +210 -0
- package/dist/model/placement.js.map +1 -0
- package/dist/model/rules.d.ts +15 -0
- package/dist/model/rules.js +52 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/types.d.ts +118 -0
- package/dist/model/types.js +9 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/versions.d.ts +3 -0
- package/dist/model/versions.js +77 -0
- package/dist/model/versions.js.map +1 -0
- package/dist/reporting.d.ts +3 -0
- package/dist/reporting.js +80 -0
- package/dist/reporting.js.map +1 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +70 -0
- package/dist/runner.js.map +1 -0
- package/dist/writer/manifest-writer.d.ts +2 -0
- package/dist/writer/manifest-writer.js +72 -0
- package/dist/writer/manifest-writer.js.map +1 -0
- package/eslint.config.cjs +3 -0
- package/jest.config.js +1 -0
- package/package.json +7 -3
- package/src/ansi.ts +23 -0
- package/src/bin/depdoc.ts +213 -0
- package/src/collectors/config.ts +33 -0
- package/src/collectors/external-metadata.ts +148 -0
- package/src/collectors/package-extensions.ts +52 -0
- package/src/collectors/type-providers.ts +51 -0
- package/src/collectors/workspaces.ts +107 -0
- package/src/dependency-model.ts +26 -0
- package/src/index.ts +28 -45
- package/src/lib/imports.ts +435 -0
- package/src/lib/package-json.ts +46 -0
- package/src/model/config-validation.ts +49 -0
- package/src/model/diagnostics.ts +358 -0
- package/src/model/engine.ts +120 -0
- package/src/model/expected.ts +141 -0
- package/src/model/peer-propagation.ts +199 -0
- package/src/model/placement.ts +378 -0
- package/src/model/rules.ts +85 -0
- package/src/model/types.ts +165 -0
- package/src/model/versions.ts +114 -0
- package/src/reporting.ts +117 -0
- package/src/runner.ts +102 -0
- package/src/writer/manifest-writer.ts +111 -0
- package/tsconfig.build.json +1 -0
- package/tsconfig.json +6 -1
- package/dist/constraint-schema.d.ts +0 -1
- package/dist/constraint-schema.js +0 -17
- package/dist/constraint-schema.js.map +0 -1
- package/dist/dependency-checker.d.ts +0 -8
- package/dist/dependency-checker.js +0 -40
- package/dist/dependency-checker.js.map +0 -1
- package/src/constraint-schema.ts +0 -20
- package/src/dependency-checker.ts +0 -41
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DefinitelyTyped provider collector.
|
|
3
|
+
*
|
|
4
|
+
* The engine needs to know which `@types/*` packages are available for public
|
|
5
|
+
* `.d.ts` promotion. This module gathers that set from installed packages, root
|
|
6
|
+
* devDependencies, and explicit non-wildcard rules.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type { PackageJson } from '../lib/package-json';
|
|
12
|
+
import type { DependencyRule } from '../model/types';
|
|
13
|
+
|
|
14
|
+
function collectInstalledTypeProviders(rootDir: string): Set<string> {
|
|
15
|
+
const result = new Set<string>();
|
|
16
|
+
const typesDir = path.join(rootDir, 'node_modules', '@types');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
for (const entry of readdirSync(typesDir, { withFileTypes: true })) {
|
|
20
|
+
if (entry.isDirectory()) result.add(`@types/${entry.name}`);
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// node_modules may not exist yet; root devDeps and rules still contribute.
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function collectTypeProviderPackages(
|
|
30
|
+
rootDir: string,
|
|
31
|
+
rootPkg: PackageJson,
|
|
32
|
+
rules: DependencyRule[],
|
|
33
|
+
): Set<string> {
|
|
34
|
+
const result = collectInstalledTypeProviders(rootDir);
|
|
35
|
+
|
|
36
|
+
for (const depIdent of Object.keys(rootPkg.devDependencies ?? {})) {
|
|
37
|
+
if (depIdent.startsWith('@types/')) result.add(depIdent);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
const patterns = Array.isArray(rule.match) ? rule.match : [rule.match];
|
|
42
|
+
|
|
43
|
+
for (const pattern of patterns) {
|
|
44
|
+
if (pattern.startsWith('@types/') && !pattern.includes('*')) {
|
|
45
|
+
result.add(pattern);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace and usage collector.
|
|
3
|
+
*
|
|
4
|
+
* This module discovers Yarn workspaces, reads their manifests, and attaches
|
|
5
|
+
* source/`.d.ts` import usage facts. It is deliberately outside the engine
|
|
6
|
+
* because it executes Yarn and reads the filesystem.
|
|
7
|
+
*/
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
collectDtsImports,
|
|
14
|
+
collectSourceFiles,
|
|
15
|
+
collectSourceImportsFromFiles,
|
|
16
|
+
} from '../lib/imports';
|
|
17
|
+
import { readPackageJson } from '../lib/package-json';
|
|
18
|
+
import { isWorkspaceRole } from '../model/rules';
|
|
19
|
+
import type { WorkspaceContext } from '../model/types';
|
|
20
|
+
|
|
21
|
+
interface YarnWorkspaceInfo {
|
|
22
|
+
location: string;
|
|
23
|
+
name: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function collectUsage(workspace: WorkspaceContext, withDts: boolean): void {
|
|
27
|
+
const sourceFiles = collectSourceFiles(workspace.dir, workspace.pkg);
|
|
28
|
+
|
|
29
|
+
workspace.sourceFileCount = sourceFiles.length;
|
|
30
|
+
|
|
31
|
+
for (const entry of collectSourceImportsFromFiles(sourceFiles)) {
|
|
32
|
+
if (!workspace.sourceUsage.has(entry.packageName)) {
|
|
33
|
+
workspace.sourceUsage.set(entry.packageName, {
|
|
34
|
+
files: new Set(),
|
|
35
|
+
runtimeFiles: new Set(),
|
|
36
|
+
typeOnlyFiles: new Set(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const usage = workspace.sourceUsage.get(entry.packageName)!;
|
|
40
|
+
|
|
41
|
+
usage.files.add(entry.file);
|
|
42
|
+
if (entry.isTypeOnly) {
|
|
43
|
+
usage.typeOnlyFiles.add(entry.file);
|
|
44
|
+
} else {
|
|
45
|
+
usage.runtimeFiles.add(entry.file);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const distDir = path.join(workspace.dir, 'dist');
|
|
50
|
+
if (withDts || existsSync(distDir)) {
|
|
51
|
+
workspace.dtsImports = collectDtsImports(distDir);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function loadWorkspaces(
|
|
56
|
+
rootDir: string,
|
|
57
|
+
withDts: boolean,
|
|
58
|
+
): WorkspaceContext[] {
|
|
59
|
+
const rootPkg = readPackageJson(rootDir);
|
|
60
|
+
const raw = execSync('yarn workspaces list --json', {
|
|
61
|
+
cwd: rootDir,
|
|
62
|
+
}).toString();
|
|
63
|
+
const listed = raw
|
|
64
|
+
.trim()
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map((line) => JSON.parse(line) as YarnWorkspaceInfo)
|
|
68
|
+
.filter((ws) => ws.location !== '.');
|
|
69
|
+
|
|
70
|
+
const root: WorkspaceContext = {
|
|
71
|
+
name: rootPkg.name,
|
|
72
|
+
location: '.',
|
|
73
|
+
dir: rootDir,
|
|
74
|
+
pkg: rootPkg,
|
|
75
|
+
role: undefined,
|
|
76
|
+
isRoot: true,
|
|
77
|
+
sourceUsage: new Map(),
|
|
78
|
+
dtsImports: new Set(),
|
|
79
|
+
hasSrc: false,
|
|
80
|
+
hasDist: false,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const workspaces: WorkspaceContext[] = [root];
|
|
84
|
+
|
|
85
|
+
for (const ws of listed) {
|
|
86
|
+
const dir = path.join(rootDir, ws.location);
|
|
87
|
+
const pkg = readPackageJson(dir);
|
|
88
|
+
const role = isWorkspaceRole(pkg.role) ? pkg.role : undefined;
|
|
89
|
+
const ctx: WorkspaceContext = {
|
|
90
|
+
name: ws.name ?? pkg.name,
|
|
91
|
+
location: ws.location,
|
|
92
|
+
dir,
|
|
93
|
+
pkg,
|
|
94
|
+
role,
|
|
95
|
+
isRoot: false,
|
|
96
|
+
sourceUsage: new Map(),
|
|
97
|
+
dtsImports: new Set(),
|
|
98
|
+
hasSrc: existsSync(path.join(dir, 'src')),
|
|
99
|
+
hasDist: existsSync(path.join(dir, 'dist')),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
collectUsage(ctx, withDts);
|
|
103
|
+
workspaces.push(ctx);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return workspaces;
|
|
107
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public dependency-model facade.
|
|
3
|
+
*
|
|
4
|
+
* Historically this file contained the whole implementation. It now keeps the
|
|
5
|
+
* public import path stable while delegating to focused modules: the pure model
|
|
6
|
+
* engine, the filesystem/Yarn runner, and CLI reporting helpers.
|
|
7
|
+
*/
|
|
8
|
+
export { deriveDependencyModel } from './model/engine';
|
|
9
|
+
export { runDependencyModel } from './runner';
|
|
10
|
+
export { explainDependency, formatDependencyModelResult } from './reporting';
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
DependencyModelConfig,
|
|
14
|
+
DependencyModelFacts,
|
|
15
|
+
DependencyModelOptions,
|
|
16
|
+
DependencyModelOutput,
|
|
17
|
+
DependencyModelResult,
|
|
18
|
+
DependencyRule,
|
|
19
|
+
DependencyViolation,
|
|
20
|
+
DependencyWarning,
|
|
21
|
+
ExpectedWorkspace,
|
|
22
|
+
SectionType,
|
|
23
|
+
UsageSummary,
|
|
24
|
+
WorkspaceFacts,
|
|
25
|
+
WorkspaceRole,
|
|
26
|
+
} from './model/types';
|
package/src/index.ts
CHANGED
|
@@ -1,46 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Package public API.
|
|
3
|
+
*
|
|
4
|
+
* Consumers should import from this barrel instead of reaching into the model,
|
|
5
|
+
* runner, or collector internals. The split modules remain implementation
|
|
6
|
+
* details unless explicitly exported here.
|
|
7
|
+
*/
|
|
8
|
+
export {
|
|
9
|
+
deriveDependencyModel,
|
|
10
|
+
runDependencyModel,
|
|
11
|
+
formatDependencyModelResult,
|
|
12
|
+
explainDependency,
|
|
13
|
+
} from './dependency-model';
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const isValid = Value.Check(ConstraintSchema, constraints);
|
|
23
|
-
if (!isValid) {
|
|
24
|
-
const cause = [...Value.Errors(ConstraintSchema, constraints)];
|
|
25
|
-
|
|
26
|
-
throw new Error(
|
|
27
|
-
'Invalid constraints.yml\n' + JSON.stringify(cause, null, 2),
|
|
28
|
-
{
|
|
29
|
-
cause: cause,
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
new DependencyChecker(Yarn).enforceConsistentDependenciesAcrossTheProject(
|
|
34
|
-
constraints,
|
|
35
|
-
);
|
|
36
|
-
overrideConfig?.constraints?.(ctx);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const config: Yarn.Config = {
|
|
40
|
-
constraints: createConstraints(),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const override = (config: Yarn.Config): Yarn.Config => ({
|
|
44
|
-
...config,
|
|
45
|
-
constraints: createConstraints(config),
|
|
46
|
-
});
|
|
15
|
+
export type {
|
|
16
|
+
DependencyRule,
|
|
17
|
+
DependencyModelConfig,
|
|
18
|
+
DependencyModelOptions,
|
|
19
|
+
DependencyModelFacts,
|
|
20
|
+
DependencyModelOutput,
|
|
21
|
+
DependencyModelResult,
|
|
22
|
+
DependencyViolation,
|
|
23
|
+
DependencyWarning,
|
|
24
|
+
WorkspaceFacts,
|
|
25
|
+
WorkspaceRole,
|
|
26
|
+
SectionType,
|
|
27
|
+
UsageSummary,
|
|
28
|
+
ExpectedWorkspace,
|
|
29
|
+
} from './dependency-model';
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/JavaScript import collectors.
|
|
3
|
+
*
|
|
4
|
+
* These helpers parse source and emitted declaration files into package-level
|
|
5
|
+
* import facts. They do not decide dependency placement; they only normalize
|
|
6
|
+
* specifiers and classify imports as runtime or type-only.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { builtinModules } from 'node:module';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import ts = require('typescript');
|
|
12
|
+
|
|
13
|
+
import type { PackageJson } from './package-json';
|
|
14
|
+
|
|
15
|
+
export interface ImportEntry {
|
|
16
|
+
packageName: string;
|
|
17
|
+
isTypeOnly: boolean;
|
|
18
|
+
file: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ModuleReference {
|
|
22
|
+
specifier: string;
|
|
23
|
+
isTypeOnly: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const BUILTINS = new Set([
|
|
27
|
+
...builtinModules,
|
|
28
|
+
...builtinModules.map((m) => `node:${m}`),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
32
|
+
'.ts',
|
|
33
|
+
'.tsx',
|
|
34
|
+
'.mts',
|
|
35
|
+
'.cts',
|
|
36
|
+
'.js',
|
|
37
|
+
'.jsx',
|
|
38
|
+
'.mjs',
|
|
39
|
+
'.cjs',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const DTS_RE = /\.d\.(?:c|m)?ts$/;
|
|
43
|
+
const DEFAULT_SOURCE_ROOTS = ['src', 'test', 'tests', '__tests__'];
|
|
44
|
+
const IGNORED_ENTRYPOINT_DIRS = new Set([
|
|
45
|
+
'node_modules',
|
|
46
|
+
'dist',
|
|
47
|
+
'build',
|
|
48
|
+
'coverage',
|
|
49
|
+
]);
|
|
50
|
+
const IGNORED_WALK_DIRS = new Set([
|
|
51
|
+
'node_modules',
|
|
52
|
+
'fixtures',
|
|
53
|
+
'__fixtures__',
|
|
54
|
+
'__mocks__',
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export function getPackageName(specifier: string): string | null {
|
|
58
|
+
if (
|
|
59
|
+
specifier.startsWith('.') ||
|
|
60
|
+
specifier.startsWith('/') ||
|
|
61
|
+
specifier.includes('*')
|
|
62
|
+
)
|
|
63
|
+
return null;
|
|
64
|
+
if (BUILTINS.has(specifier) || specifier.startsWith('node:')) return null;
|
|
65
|
+
if (specifier.startsWith('@')) {
|
|
66
|
+
const scopeSlash = specifier.indexOf('/', 1);
|
|
67
|
+
if (scopeSlash === -1) return null;
|
|
68
|
+
const subpathSlash = specifier.indexOf('/', scopeSlash + 1);
|
|
69
|
+
|
|
70
|
+
return subpathSlash === -1 ? specifier : specifier.slice(0, subpathSlash);
|
|
71
|
+
}
|
|
72
|
+
const slash = specifier.indexOf('/');
|
|
73
|
+
|
|
74
|
+
return slash === -1 ? specifier : specifier.slice(0, slash);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function walkFiles(dir: string, filter: (name: string) => boolean): string[] {
|
|
78
|
+
const results: string[] = [];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
82
|
+
const full = path.join(dir, entry.name);
|
|
83
|
+
if (entry.isDirectory() && !IGNORED_WALK_DIRS.has(entry.name)) {
|
|
84
|
+
results.push(...walkFiles(full, filter));
|
|
85
|
+
} else if (entry.isFile() && filter(entry.name)) {
|
|
86
|
+
results.push(full);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// dir doesn't exist - return empty
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isInside(parent: string, child: string): boolean {
|
|
96
|
+
const relative = path.relative(parent, child);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
relative !== '' &&
|
|
100
|
+
!relative.startsWith('..') &&
|
|
101
|
+
!path.isAbsolute(relative)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function startsWithIgnoredEntrypointDir(relative: string): boolean {
|
|
106
|
+
const [first] = relative.split('/');
|
|
107
|
+
|
|
108
|
+
return first !== undefined && IGNORED_ENTRYPOINT_DIRS.has(first);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isSourceFile(name: string): boolean {
|
|
112
|
+
return SOURCE_EXTENSIONS.has(path.extname(name)) && !DTS_RE.test(name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getScriptKind(file: string): ts.ScriptKind {
|
|
116
|
+
if (file.endsWith('.tsx')) return ts.ScriptKind.TSX;
|
|
117
|
+
if (file.endsWith('.jsx')) return ts.ScriptKind.JSX;
|
|
118
|
+
if (
|
|
119
|
+
file.endsWith('.js') ||
|
|
120
|
+
file.endsWith('.mjs') ||
|
|
121
|
+
file.endsWith('.cjs')
|
|
122
|
+
)
|
|
123
|
+
return ts.ScriptKind.JS;
|
|
124
|
+
return ts.ScriptKind.TS;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function stringLiteralText(node: ts.Node | undefined): string | null {
|
|
128
|
+
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function addImport(
|
|
132
|
+
results: ImportEntry[],
|
|
133
|
+
file: string,
|
|
134
|
+
specifier: string,
|
|
135
|
+
isTypeOnly: boolean,
|
|
136
|
+
): void {
|
|
137
|
+
const packageName = getPackageName(specifier);
|
|
138
|
+
if (!packageName) return;
|
|
139
|
+
results.push({ packageName, isTypeOnly, file });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addModuleReference(
|
|
143
|
+
results: ModuleReference[],
|
|
144
|
+
specifier: string | null,
|
|
145
|
+
isTypeOnly: boolean,
|
|
146
|
+
): void {
|
|
147
|
+
if (!specifier) return;
|
|
148
|
+
results.push({ specifier, isTypeOnly });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isImportDeclarationTypeOnly(node: ts.ImportDeclaration): boolean {
|
|
152
|
+
const clause = node.importClause;
|
|
153
|
+
if (!clause) return false;
|
|
154
|
+
if (clause.isTypeOnly) return true;
|
|
155
|
+
if (clause.name) return false;
|
|
156
|
+
if (!clause.namedBindings) return true;
|
|
157
|
+
if (ts.isNamespaceImport(clause.namedBindings)) return false;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
clause.namedBindings.elements.length > 0 &&
|
|
161
|
+
clause.namedBindings.elements.every((element) => element.isTypeOnly)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isExportDeclarationTypeOnly(node: ts.ExportDeclaration): boolean {
|
|
166
|
+
if (node.isTypeOnly) return true;
|
|
167
|
+
const clause = node.exportClause;
|
|
168
|
+
if (!clause || !ts.isNamedExports(clause)) return false;
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
clause.elements.length > 0 &&
|
|
172
|
+
clause.elements.every((element) => element.isTypeOnly)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function collectExternalModuleReference(
|
|
177
|
+
node: ts.ExternalModuleReference,
|
|
178
|
+
): string | null {
|
|
179
|
+
return stringLiteralText(node.expression);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function extractModuleReferences(
|
|
183
|
+
file: string,
|
|
184
|
+
content: string,
|
|
185
|
+
options: { includeDtsForms?: boolean } = {},
|
|
186
|
+
): ModuleReference[] {
|
|
187
|
+
const results: ModuleReference[] = [];
|
|
188
|
+
const sourceFile = ts.createSourceFile(
|
|
189
|
+
file,
|
|
190
|
+
content,
|
|
191
|
+
ts.ScriptTarget.Latest,
|
|
192
|
+
true,
|
|
193
|
+
getScriptKind(file),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (options.includeDtsForms) {
|
|
197
|
+
for (const reference of sourceFile.typeReferenceDirectives) {
|
|
198
|
+
addModuleReference(results, reference.fileName, true);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const visit = (node: ts.Node): void => {
|
|
203
|
+
if (ts.isImportDeclaration(node)) {
|
|
204
|
+
const specifier = stringLiteralText(node.moduleSpecifier);
|
|
205
|
+
addModuleReference(results, specifier, isImportDeclarationTypeOnly(node));
|
|
206
|
+
} else if (ts.isExportDeclaration(node)) {
|
|
207
|
+
const specifier = stringLiteralText(node.moduleSpecifier);
|
|
208
|
+
addModuleReference(results, specifier, isExportDeclarationTypeOnly(node));
|
|
209
|
+
} else if (ts.isImportEqualsDeclaration(node)) {
|
|
210
|
+
if (ts.isExternalModuleReference(node.moduleReference)) {
|
|
211
|
+
const specifier = collectExternalModuleReference(node.moduleReference);
|
|
212
|
+
addModuleReference(results, specifier, node.isTypeOnly);
|
|
213
|
+
}
|
|
214
|
+
} else if (ts.isCallExpression(node)) {
|
|
215
|
+
if (
|
|
216
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
217
|
+
node.arguments.length === 1
|
|
218
|
+
) {
|
|
219
|
+
const specifier = stringLiteralText(node.arguments[0]);
|
|
220
|
+
addModuleReference(results, specifier, false);
|
|
221
|
+
} else if (
|
|
222
|
+
ts.isIdentifier(node.expression) &&
|
|
223
|
+
node.expression.text === 'require' &&
|
|
224
|
+
node.arguments.length === 1
|
|
225
|
+
) {
|
|
226
|
+
const specifier = stringLiteralText(node.arguments[0]);
|
|
227
|
+
addModuleReference(results, specifier, false);
|
|
228
|
+
}
|
|
229
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
230
|
+
const argument = node.argument;
|
|
231
|
+
if (ts.isLiteralTypeNode(argument)) {
|
|
232
|
+
const specifier = stringLiteralText(argument.literal);
|
|
233
|
+
addModuleReference(results, specifier, true);
|
|
234
|
+
}
|
|
235
|
+
} else if (
|
|
236
|
+
options.includeDtsForms &&
|
|
237
|
+
ts.isModuleDeclaration(node) &&
|
|
238
|
+
ts.isStringLiteral(node.name)
|
|
239
|
+
) {
|
|
240
|
+
addModuleReference(results, node.name.text, true);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
ts.forEachChild(node, visit);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
visit(sourceFile);
|
|
247
|
+
return results;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function extractImports(
|
|
251
|
+
file: string,
|
|
252
|
+
content: string,
|
|
253
|
+
options: { includeDtsForms?: boolean } = {},
|
|
254
|
+
): ImportEntry[] {
|
|
255
|
+
const results: ImportEntry[] = [];
|
|
256
|
+
|
|
257
|
+
for (const reference of extractModuleReferences(file, content, options)) {
|
|
258
|
+
addImport(results, file, reference.specifier, reference.isTypeOnly);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return results;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function collectEntrypointStrings(value: unknown, result: Set<string>): void {
|
|
265
|
+
if (typeof value === 'string') {
|
|
266
|
+
result.add(value);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!value || typeof value !== 'object') return;
|
|
270
|
+
if (Array.isArray(value)) {
|
|
271
|
+
for (const item of value) collectEntrypointStrings(item, result);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
for (const item of Object.values(value as Record<string, unknown>)) {
|
|
275
|
+
collectEntrypointStrings(item, result);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getPackageEntrypointFiles(
|
|
280
|
+
workspaceDir: string,
|
|
281
|
+
pkg: PackageJson,
|
|
282
|
+
): string[] {
|
|
283
|
+
const candidates = new Set<string>();
|
|
284
|
+
if (pkg.main) candidates.add(pkg.main);
|
|
285
|
+
if (typeof pkg.bin === 'string') {
|
|
286
|
+
candidates.add(pkg.bin);
|
|
287
|
+
} else if (pkg.bin) {
|
|
288
|
+
for (const value of Object.values(pkg.bin)) candidates.add(value);
|
|
289
|
+
}
|
|
290
|
+
collectEntrypointStrings(pkg.exports, candidates);
|
|
291
|
+
|
|
292
|
+
const result = new Set<string>();
|
|
293
|
+
|
|
294
|
+
for (const candidate of candidates) {
|
|
295
|
+
if (!candidate || candidate.includes('*')) continue;
|
|
296
|
+
const full = path.resolve(workspaceDir, candidate);
|
|
297
|
+
const relative = path.relative(workspaceDir, full);
|
|
298
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) continue;
|
|
299
|
+
const normalized = relative.split(path.sep).join('/');
|
|
300
|
+
if (startsWithIgnoredEntrypointDir(normalized)) continue;
|
|
301
|
+
if (!existsSync(full) || !isSourceFile(full)) continue;
|
|
302
|
+
|
|
303
|
+
result.add(full);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return [...result];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function isRelativeSpecifier(specifier: string): boolean {
|
|
310
|
+
return specifier.startsWith('./') || specifier.startsWith('../');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getJsToTsCandidates(file: string): string[] {
|
|
314
|
+
const ext = path.extname(file);
|
|
315
|
+
const withoutExt = file.slice(0, -ext.length);
|
|
316
|
+
|
|
317
|
+
if (ext === '.js') return [`${withoutExt}.ts`, `${withoutExt}.tsx`, file];
|
|
318
|
+
if (ext === '.jsx') return [`${withoutExt}.tsx`, file];
|
|
319
|
+
if (ext === '.mjs') return [`${withoutExt}.mts`, file];
|
|
320
|
+
if (ext === '.cjs') return [`${withoutExt}.cts`, file];
|
|
321
|
+
|
|
322
|
+
return [file];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function resolveRelativeSourceFile(
|
|
326
|
+
workspaceDir: string,
|
|
327
|
+
importer: string,
|
|
328
|
+
specifier: string,
|
|
329
|
+
): string | null {
|
|
330
|
+
const target = path.resolve(path.dirname(importer), specifier);
|
|
331
|
+
const candidates: string[] = [];
|
|
332
|
+
const ext = path.extname(target);
|
|
333
|
+
|
|
334
|
+
if (ext) {
|
|
335
|
+
candidates.push(...getJsToTsCandidates(target));
|
|
336
|
+
} else {
|
|
337
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
338
|
+
candidates.push(`${target}${sourceExt}`);
|
|
339
|
+
}
|
|
340
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
341
|
+
candidates.push(path.join(target, `index${sourceExt}`));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const candidate of candidates) {
|
|
346
|
+
if (!isInside(workspaceDir, candidate)) continue;
|
|
347
|
+
if (existsSync(candidate) && isSourceFile(candidate)) return candidate;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function collectEntrypointGraphFiles(
|
|
354
|
+
workspaceDir: string,
|
|
355
|
+
pkg: PackageJson,
|
|
356
|
+
): string[] {
|
|
357
|
+
const result = new Set<string>();
|
|
358
|
+
const stack = getPackageEntrypointFiles(workspaceDir, pkg);
|
|
359
|
+
|
|
360
|
+
while (stack.length > 0) {
|
|
361
|
+
const file = stack.pop()!;
|
|
362
|
+
if (result.has(file)) continue;
|
|
363
|
+
result.add(file);
|
|
364
|
+
|
|
365
|
+
for (const reference of extractModuleReferences(
|
|
366
|
+
file,
|
|
367
|
+
readFileSync(file, 'utf8'),
|
|
368
|
+
)) {
|
|
369
|
+
if (!isRelativeSpecifier(reference.specifier)) continue;
|
|
370
|
+
const resolved = resolveRelativeSourceFile(
|
|
371
|
+
workspaceDir,
|
|
372
|
+
file,
|
|
373
|
+
reference.specifier,
|
|
374
|
+
);
|
|
375
|
+
if (resolved && !result.has(resolved)) stack.push(resolved);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return [...result];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function collectSourceFiles(
|
|
383
|
+
srcDirOrWorkspaceDir: string,
|
|
384
|
+
pkg?: PackageJson,
|
|
385
|
+
): string[] {
|
|
386
|
+
if (!pkg) return walkFiles(srcDirOrWorkspaceDir, (name) => isSourceFile(name));
|
|
387
|
+
|
|
388
|
+
const files = new Set<string>();
|
|
389
|
+
|
|
390
|
+
for (const sourceRoot of DEFAULT_SOURCE_ROOTS) {
|
|
391
|
+
for (const file of walkFiles(
|
|
392
|
+
path.join(srcDirOrWorkspaceDir, sourceRoot),
|
|
393
|
+
(name) => isSourceFile(name),
|
|
394
|
+
)) {
|
|
395
|
+
files.add(file);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const file of collectEntrypointGraphFiles(srcDirOrWorkspaceDir, pkg)) {
|
|
400
|
+
files.add(file);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return [...files].sort();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function collectSourceImportsFromFiles(files: string[]): ImportEntry[] {
|
|
407
|
+
return files.flatMap((file) =>
|
|
408
|
+
extractImports(file, readFileSync(file, 'utf8')),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function collectSourceImports(
|
|
413
|
+
srcDirOrWorkspaceDir: string,
|
|
414
|
+
pkg?: PackageJson,
|
|
415
|
+
): ImportEntry[] {
|
|
416
|
+
return collectSourceImportsFromFiles(
|
|
417
|
+
collectSourceFiles(srcDirOrWorkspaceDir, pkg),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function collectDtsImports(distDir: string): Set<string> {
|
|
422
|
+
const files = walkFiles(distDir, (name) => DTS_RE.test(name));
|
|
423
|
+
const result = new Set<string>();
|
|
424
|
+
|
|
425
|
+
for (const file of files) {
|
|
426
|
+
for (const { packageName } of extractImports(
|
|
427
|
+
file,
|
|
428
|
+
readFileSync(file, 'utf8'),
|
|
429
|
+
{ includeDtsForms: true },
|
|
430
|
+
)) {
|
|
431
|
+
result.add(packageName);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|