@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,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Peer propagation across dependency edges.
|
|
3
|
+
*
|
|
4
|
+
* This module applies peer requirements from local libraries and external
|
|
5
|
+
* packages to the current workspace. It consumes already collected peer
|
|
6
|
+
* metadata and does not inspect `node_modules` itself.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
addExpectedMirrorDependency,
|
|
10
|
+
addReason,
|
|
11
|
+
addRuleReason,
|
|
12
|
+
getExpectedRange,
|
|
13
|
+
getExpectedSection,
|
|
14
|
+
setExpectedDependency,
|
|
15
|
+
} from './expected';
|
|
16
|
+
import { getEffectiveRule } from './rules';
|
|
17
|
+
import type {
|
|
18
|
+
DependencyRule,
|
|
19
|
+
ExpectedWorkspace,
|
|
20
|
+
WorkspaceFacts,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
function providePeer(
|
|
24
|
+
expected: ExpectedWorkspace,
|
|
25
|
+
rootExpected: ExpectedWorkspace,
|
|
26
|
+
workspaceNames: Set<string>,
|
|
27
|
+
rules: DependencyRule[],
|
|
28
|
+
depIdent: string,
|
|
29
|
+
range: string,
|
|
30
|
+
provider: string,
|
|
31
|
+
): boolean {
|
|
32
|
+
if (workspaceNames.has(depIdent)) return false;
|
|
33
|
+
if (expected.sections.dependencies.has(depIdent)) return false;
|
|
34
|
+
|
|
35
|
+
const beforeSection = getExpectedSection(expected, depIdent);
|
|
36
|
+
const beforeRange = getExpectedRange(expected, depIdent);
|
|
37
|
+
const rule = getEffectiveRule(
|
|
38
|
+
rules,
|
|
39
|
+
depIdent,
|
|
40
|
+
expected.workspace.name,
|
|
41
|
+
expected.workspace.location,
|
|
42
|
+
);
|
|
43
|
+
const resolvedRange = rule.version ?? range;
|
|
44
|
+
|
|
45
|
+
addRuleReason(expected, depIdent, rule);
|
|
46
|
+
|
|
47
|
+
if (rule.rootOnly) {
|
|
48
|
+
setExpectedDependency(
|
|
49
|
+
rootExpected,
|
|
50
|
+
'devDependencies',
|
|
51
|
+
depIdent,
|
|
52
|
+
resolvedRange,
|
|
53
|
+
{
|
|
54
|
+
kind: 'root-only',
|
|
55
|
+
detail: `${depIdent} is required by ${provider} but marked rootOnly`,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
addReason(expected, depIdent, {
|
|
59
|
+
kind: 'root-only',
|
|
60
|
+
detail: `${depIdent} is required by ${provider} but marked rootOnly`,
|
|
61
|
+
});
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
expected.workspace.role === 'library' &&
|
|
67
|
+
rule.section !== 'dependencies'
|
|
68
|
+
) {
|
|
69
|
+
setExpectedDependency(
|
|
70
|
+
expected,
|
|
71
|
+
'peerDependencies',
|
|
72
|
+
depIdent,
|
|
73
|
+
resolvedRange,
|
|
74
|
+
{
|
|
75
|
+
kind: 'peer-propagation',
|
|
76
|
+
detail: `${depIdent} is required peer of ${provider}`,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
addExpectedMirrorDependency(expected, depIdent, resolvedRange, {
|
|
80
|
+
kind: 'mirror',
|
|
81
|
+
detail: `${depIdent} mirrors propagated peer`,
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
setExpectedDependency(expected, 'dependencies', depIdent, resolvedRange, {
|
|
85
|
+
kind: 'peer-propagation',
|
|
86
|
+
detail: `${depIdent} is required peer of ${provider}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
beforeSection !== getExpectedSection(expected, depIdent) ||
|
|
92
|
+
beforeRange !== getExpectedRange(expected, depIdent)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function propagatePeers(
|
|
97
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
98
|
+
workspacesByName: Map<string, WorkspaceFacts>,
|
|
99
|
+
workspaceNames: Set<string>,
|
|
100
|
+
externalPeerMetadata: Map<string, Map<string, string> | null>,
|
|
101
|
+
rules: DependencyRule[],
|
|
102
|
+
): void {
|
|
103
|
+
const rootExpected = expectedByLocation.get('.')!;
|
|
104
|
+
let changed = true;
|
|
105
|
+
let passes = 0;
|
|
106
|
+
|
|
107
|
+
while (changed && passes < 10) {
|
|
108
|
+
changed = false;
|
|
109
|
+
passes++;
|
|
110
|
+
|
|
111
|
+
for (const expected of expectedByLocation.values()) {
|
|
112
|
+
const workspace = expected.workspace;
|
|
113
|
+
const providerDeps = new Map<string, string>();
|
|
114
|
+
|
|
115
|
+
for (const [depIdent, range] of expected.sections.dependencies) {
|
|
116
|
+
providerDeps.set(depIdent, range);
|
|
117
|
+
}
|
|
118
|
+
for (const [depIdent, range] of expected.sections.peerDependencies) {
|
|
119
|
+
providerDeps.set(depIdent, range);
|
|
120
|
+
}
|
|
121
|
+
if (workspace.isRoot) {
|
|
122
|
+
for (const [depIdent, range] of expected.sections.devDependencies) {
|
|
123
|
+
providerDeps.set(depIdent, range);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [providerIdent] of providerDeps) {
|
|
128
|
+
if (workspaceNames.has(providerIdent)) {
|
|
129
|
+
const provider = workspacesByName.get(providerIdent);
|
|
130
|
+
if (!provider || provider.role !== 'library') continue;
|
|
131
|
+
const providerExpected = expectedByLocation.get(provider.location);
|
|
132
|
+
if (!providerExpected) continue;
|
|
133
|
+
|
|
134
|
+
for (const [peerIdent, peerRange] of providerExpected.sections
|
|
135
|
+
.peerDependencies) {
|
|
136
|
+
if (provider.pkg.peerDependenciesMeta?.[peerIdent]?.optional) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (workspaceNames.has(peerIdent)) continue;
|
|
140
|
+
if (workspace.isRoot) {
|
|
141
|
+
setExpectedDependency(
|
|
142
|
+
rootExpected,
|
|
143
|
+
'devDependencies',
|
|
144
|
+
peerIdent,
|
|
145
|
+
peerRange,
|
|
146
|
+
{
|
|
147
|
+
kind: 'peer-propagation',
|
|
148
|
+
detail: `${peerIdent} is required by ${providerIdent}`,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
changed =
|
|
153
|
+
providePeer(
|
|
154
|
+
expected,
|
|
155
|
+
rootExpected,
|
|
156
|
+
workspaceNames,
|
|
157
|
+
rules,
|
|
158
|
+
peerIdent,
|
|
159
|
+
peerRange,
|
|
160
|
+
providerIdent,
|
|
161
|
+
) || changed;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const externalPeers = externalPeerMetadata.get(providerIdent) ?? null;
|
|
168
|
+
if (!externalPeers) continue;
|
|
169
|
+
|
|
170
|
+
for (const [peerIdent, peerRange] of externalPeers) {
|
|
171
|
+
if (workspaceNames.has(peerIdent)) continue;
|
|
172
|
+
if (workspace.isRoot) {
|
|
173
|
+
setExpectedDependency(
|
|
174
|
+
rootExpected,
|
|
175
|
+
'devDependencies',
|
|
176
|
+
peerIdent,
|
|
177
|
+
peerRange,
|
|
178
|
+
{
|
|
179
|
+
kind: 'peer-propagation',
|
|
180
|
+
detail: `${peerIdent} is required by ${providerIdent}`,
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
changed =
|
|
185
|
+
providePeer(
|
|
186
|
+
expected,
|
|
187
|
+
rootExpected,
|
|
188
|
+
workspaceNames,
|
|
189
|
+
rules,
|
|
190
|
+
peerIdent,
|
|
191
|
+
peerRange,
|
|
192
|
+
providerIdent,
|
|
193
|
+
) || changed;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency placement rules.
|
|
3
|
+
*
|
|
4
|
+
* This module converts source and `.d.ts` usage facts into expected manifest
|
|
5
|
+
* sections. It is pure: it receives workspace facts, rules, type metadata, and
|
|
6
|
+
* local workspace names, then mutates only the in-memory expected graph.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
addExpectedMirrorDependency,
|
|
10
|
+
addReason,
|
|
11
|
+
addRuleReason,
|
|
12
|
+
getDeclaredRange,
|
|
13
|
+
getRangeSource,
|
|
14
|
+
setExpectedDependency,
|
|
15
|
+
} from './expected';
|
|
16
|
+
import { getEffectiveRule } from './rules';
|
|
17
|
+
import type {
|
|
18
|
+
DependencyRule,
|
|
19
|
+
ExpectedWorkspace,
|
|
20
|
+
Reason,
|
|
21
|
+
WorkspaceFacts,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
function isTestFile(file: string): boolean {
|
|
25
|
+
return /(^|[/.])(__test__|__tests__|test|tests|spec|e2e)([/.]|$)|\.(spec|test|e2e)\.[cm]?[tj]sx?$/.test(
|
|
26
|
+
file,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isExternal(
|
|
31
|
+
depIdent: string,
|
|
32
|
+
workspaceNames: Set<string>,
|
|
33
|
+
): boolean {
|
|
34
|
+
return !workspaceNames.has(depIdent);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getDefinitelyTypedProvider(depIdent: string): string | null {
|
|
38
|
+
if (depIdent.startsWith('@types/')) return null;
|
|
39
|
+
if (depIdent.startsWith('@')) {
|
|
40
|
+
const [scope, name] = depIdent.slice(1).split('/');
|
|
41
|
+
if (!scope || !name) return null;
|
|
42
|
+
return `@types/${scope}__${name}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return `@types/${depIdent}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shouldAddDefinitelyTypedProvider(
|
|
49
|
+
depIdent: string,
|
|
50
|
+
externalTypeMetadata: Map<string, { hasBundledTypes: boolean }>,
|
|
51
|
+
typeProviderPackages: Set<string>,
|
|
52
|
+
): string | null {
|
|
53
|
+
const provider = getDefinitelyTypedProvider(depIdent);
|
|
54
|
+
if (!provider || !typeProviderPackages.has(provider)) return null;
|
|
55
|
+
if (externalTypeMetadata.get(depIdent)?.hasBundledTypes) return null;
|
|
56
|
+
|
|
57
|
+
return provider;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getConcreteRuleMatches(rule: DependencyRule): string[] {
|
|
61
|
+
const matches = Array.isArray(rule.match) ? rule.match : [rule.match];
|
|
62
|
+
|
|
63
|
+
return matches.filter((match) => !match.includes('*'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function addPublicDependency(
|
|
67
|
+
expected: ExpectedWorkspace,
|
|
68
|
+
rootExpected: ExpectedWorkspace,
|
|
69
|
+
root: WorkspaceFacts,
|
|
70
|
+
workspaceNames: Set<string>,
|
|
71
|
+
rules: DependencyRule[],
|
|
72
|
+
depIdent: string,
|
|
73
|
+
reason: Reason,
|
|
74
|
+
options: { allowRootOnly?: boolean; rootOnlyRequired?: boolean } = {},
|
|
75
|
+
): void {
|
|
76
|
+
const workspace = expected.workspace;
|
|
77
|
+
const rule = getEffectiveRule(
|
|
78
|
+
rules,
|
|
79
|
+
depIdent,
|
|
80
|
+
workspace.name,
|
|
81
|
+
workspace.location,
|
|
82
|
+
);
|
|
83
|
+
const isLocal = workspaceNames.has(depIdent);
|
|
84
|
+
const range = isLocal
|
|
85
|
+
? 'workspace:*'
|
|
86
|
+
: getRangeSource(root, workspace, depIdent, rules);
|
|
87
|
+
|
|
88
|
+
addRuleReason(expected, depIdent, rule);
|
|
89
|
+
|
|
90
|
+
if (rule.rootOnly && !isLocal && !options.allowRootOnly) {
|
|
91
|
+
addRuleReason(rootExpected, depIdent, rule);
|
|
92
|
+
setExpectedDependency(rootExpected, 'devDependencies', depIdent, range, {
|
|
93
|
+
kind: options.rootOnlyRequired ? reason.kind : 'root-only',
|
|
94
|
+
detail: options.rootOnlyRequired
|
|
95
|
+
? `${reason.detail} for ${workspace.name}; rootOnly keeps it in root devDependencies`
|
|
96
|
+
: `${depIdent} is marked rootOnly`,
|
|
97
|
+
});
|
|
98
|
+
addReason(expected, depIdent, reason);
|
|
99
|
+
if (!options.rootOnlyRequired) {
|
|
100
|
+
addReason(expected, depIdent, {
|
|
101
|
+
kind: 'root-only',
|
|
102
|
+
detail: `${depIdent} is marked rootOnly and cannot be used by ${workspace.name}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isLocal) {
|
|
109
|
+
setExpectedDependency(expected, 'dependencies', depIdent, 'workspace:*', {
|
|
110
|
+
kind: reason.kind,
|
|
111
|
+
detail: `${reason.detail}; local workspace edge`,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
workspace.role === 'library' &&
|
|
118
|
+
rule.section !== 'dependencies' &&
|
|
119
|
+
rule.section !== 'devDependencies'
|
|
120
|
+
) {
|
|
121
|
+
setExpectedDependency(
|
|
122
|
+
expected,
|
|
123
|
+
'peerDependencies',
|
|
124
|
+
depIdent,
|
|
125
|
+
range,
|
|
126
|
+
reason,
|
|
127
|
+
);
|
|
128
|
+
addExpectedMirrorDependency(expected, depIdent, range, {
|
|
129
|
+
kind: 'mirror',
|
|
130
|
+
detail: `${depIdent} mirrors peerDependencies in devDependencies`,
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setExpectedDependency(expected, 'dependencies', depIdent, range, reason);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addPrivateDependency(
|
|
139
|
+
rootExpected: ExpectedWorkspace,
|
|
140
|
+
root: WorkspaceFacts,
|
|
141
|
+
requester: ExpectedWorkspace,
|
|
142
|
+
workspaceNames: Set<string>,
|
|
143
|
+
rules: DependencyRule[],
|
|
144
|
+
depIdent: string,
|
|
145
|
+
reason: Reason,
|
|
146
|
+
): void {
|
|
147
|
+
const workspace = requester.workspace;
|
|
148
|
+
if (workspaceNames.has(depIdent)) return;
|
|
149
|
+
const rule = getEffectiveRule(
|
|
150
|
+
rules,
|
|
151
|
+
depIdent,
|
|
152
|
+
workspace.name,
|
|
153
|
+
workspace.location,
|
|
154
|
+
);
|
|
155
|
+
const range = getRangeSource(root, workspace, depIdent, rules);
|
|
156
|
+
|
|
157
|
+
addRuleReason(rootExpected, depIdent, rule);
|
|
158
|
+
addRuleReason(requester, depIdent, rule);
|
|
159
|
+
|
|
160
|
+
if (rule.rootOnly) {
|
|
161
|
+
addReason(requester, depIdent, {
|
|
162
|
+
kind: 'root-only',
|
|
163
|
+
detail: `${depIdent} is marked rootOnly and cannot be used by ${workspace.name}`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setExpectedDependency(
|
|
168
|
+
rootExpected,
|
|
169
|
+
'devDependencies',
|
|
170
|
+
depIdent,
|
|
171
|
+
range,
|
|
172
|
+
reason,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function seedRootDevDependencies(
|
|
177
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
178
|
+
workspaceNames: Set<string>,
|
|
179
|
+
rules: DependencyRule[],
|
|
180
|
+
): void {
|
|
181
|
+
const rootExpected = expectedByLocation.get('.')!;
|
|
182
|
+
|
|
183
|
+
for (const [depIdent, declaredRange] of Object.entries(
|
|
184
|
+
rootExpected.workspace.pkg.devDependencies ?? {},
|
|
185
|
+
)) {
|
|
186
|
+
const rule = getEffectiveRule(
|
|
187
|
+
rules,
|
|
188
|
+
depIdent,
|
|
189
|
+
rootExpected.workspace.name,
|
|
190
|
+
rootExpected.workspace.location,
|
|
191
|
+
);
|
|
192
|
+
const range = workspaceNames.has(depIdent)
|
|
193
|
+
? 'workspace:*'
|
|
194
|
+
: (rule.version ?? declaredRange);
|
|
195
|
+
|
|
196
|
+
setExpectedDependency(rootExpected, 'devDependencies', depIdent, range, {
|
|
197
|
+
kind: 'declared',
|
|
198
|
+
detail: `${depIdent} is declared in root devDependencies baseline`,
|
|
199
|
+
});
|
|
200
|
+
addRuleReason(rootExpected, depIdent, rule);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function addUsageDependencies(
|
|
205
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
206
|
+
root: WorkspaceFacts,
|
|
207
|
+
workspaceNames: Set<string>,
|
|
208
|
+
rules: DependencyRule[],
|
|
209
|
+
externalTypeMetadata: Map<string, { hasBundledTypes: boolean }>,
|
|
210
|
+
typeProviderPackages: Set<string>,
|
|
211
|
+
): void {
|
|
212
|
+
const rootExpected = expectedByLocation.get('.')!;
|
|
213
|
+
|
|
214
|
+
for (const expected of expectedByLocation.values()) {
|
|
215
|
+
const workspace = expected.workspace;
|
|
216
|
+
if (workspace.isRoot) continue;
|
|
217
|
+
|
|
218
|
+
for (const [depIdent, usage] of workspace.sourceUsage) {
|
|
219
|
+
const publicType = workspace.dtsImports.has(depIdent);
|
|
220
|
+
const runtime = [...usage.runtimeFiles].some((file) => !isTestFile(file));
|
|
221
|
+
const allTestOnly = [...usage.files].every(isTestFile);
|
|
222
|
+
|
|
223
|
+
if (runtime || publicType) {
|
|
224
|
+
addPublicDependency(
|
|
225
|
+
expected,
|
|
226
|
+
rootExpected,
|
|
227
|
+
root,
|
|
228
|
+
workspaceNames,
|
|
229
|
+
rules,
|
|
230
|
+
depIdent,
|
|
231
|
+
{
|
|
232
|
+
kind: publicType ? 'source-public-type' : 'source-runtime',
|
|
233
|
+
detail: publicType
|
|
234
|
+
? `${depIdent} appears in emitted .d.ts`
|
|
235
|
+
: `${depIdent} is imported at runtime`,
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
} else {
|
|
239
|
+
addPrivateDependency(
|
|
240
|
+
rootExpected,
|
|
241
|
+
root,
|
|
242
|
+
expected,
|
|
243
|
+
workspaceNames,
|
|
244
|
+
rules,
|
|
245
|
+
depIdent,
|
|
246
|
+
{
|
|
247
|
+
kind: 'source-private-dev',
|
|
248
|
+
detail: allTestOnly
|
|
249
|
+
? `${depIdent} is only used by test files in ${workspace.name}`
|
|
250
|
+
: `${depIdent} is only used as private type/tooling dependency in ${workspace.name}`,
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
for (const depIdent of workspace.dtsImports) {
|
|
257
|
+
addPublicDependency(
|
|
258
|
+
expected,
|
|
259
|
+
rootExpected,
|
|
260
|
+
root,
|
|
261
|
+
workspaceNames,
|
|
262
|
+
rules,
|
|
263
|
+
depIdent,
|
|
264
|
+
{
|
|
265
|
+
kind: 'source-public-type',
|
|
266
|
+
detail: `${depIdent} appears in emitted .d.ts`,
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const typeProvider = shouldAddDefinitelyTypedProvider(
|
|
271
|
+
depIdent,
|
|
272
|
+
externalTypeMetadata,
|
|
273
|
+
typeProviderPackages,
|
|
274
|
+
);
|
|
275
|
+
if (!typeProvider) continue;
|
|
276
|
+
|
|
277
|
+
addPublicDependency(
|
|
278
|
+
expected,
|
|
279
|
+
rootExpected,
|
|
280
|
+
root,
|
|
281
|
+
workspaceNames,
|
|
282
|
+
rules,
|
|
283
|
+
typeProvider,
|
|
284
|
+
{
|
|
285
|
+
kind: 'dts-provider',
|
|
286
|
+
detail: `${typeProvider} provides public types for ${depIdent}`,
|
|
287
|
+
},
|
|
288
|
+
{ allowRootOnly: true },
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function addRequiredRuleDependencies(
|
|
295
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
296
|
+
root: WorkspaceFacts,
|
|
297
|
+
workspaceNames: Set<string>,
|
|
298
|
+
rules: DependencyRule[],
|
|
299
|
+
): void {
|
|
300
|
+
const rootExpected = expectedByLocation.get('.')!;
|
|
301
|
+
const candidates = new Set<string>();
|
|
302
|
+
|
|
303
|
+
for (const rule of rules) {
|
|
304
|
+
if (rule.required === undefined) continue;
|
|
305
|
+
for (const depIdent of getConcreteRuleMatches(rule)) {
|
|
306
|
+
candidates.add(depIdent);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const expected of expectedByLocation.values()) {
|
|
311
|
+
const workspace = expected.workspace;
|
|
312
|
+
if (workspace.isRoot) continue;
|
|
313
|
+
|
|
314
|
+
for (const depIdent of candidates) {
|
|
315
|
+
const rule = getEffectiveRule(
|
|
316
|
+
rules,
|
|
317
|
+
depIdent,
|
|
318
|
+
workspace.name,
|
|
319
|
+
workspace.location,
|
|
320
|
+
);
|
|
321
|
+
if (!rule.required) continue;
|
|
322
|
+
|
|
323
|
+
addPublicDependency(
|
|
324
|
+
expected,
|
|
325
|
+
rootExpected,
|
|
326
|
+
root,
|
|
327
|
+
workspaceNames,
|
|
328
|
+
rules,
|
|
329
|
+
depIdent,
|
|
330
|
+
{
|
|
331
|
+
kind: 'required-rule',
|
|
332
|
+
detail: `${depIdent} is marked required by dependency rule`,
|
|
333
|
+
},
|
|
334
|
+
{ rootOnlyRequired: rule.rootOnly },
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function rebuildLibraryMirrors(
|
|
341
|
+
expectedByLocation: Map<string, ExpectedWorkspace>,
|
|
342
|
+
): void {
|
|
343
|
+
for (const expected of expectedByLocation.values()) {
|
|
344
|
+
if (expected.workspace.role !== 'library') continue;
|
|
345
|
+
expected.sections.devDependencies = new Map(
|
|
346
|
+
expected.sections.peerDependencies,
|
|
347
|
+
);
|
|
348
|
+
for (const [depIdent] of expected.sections.devDependencies) {
|
|
349
|
+
addReason(expected, depIdent, {
|
|
350
|
+
kind: 'mirror',
|
|
351
|
+
detail: `${depIdent} mirrors peerDependencies in devDependencies`,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function resolveExpectedRangeAfterRulePass(
|
|
358
|
+
root: WorkspaceFacts,
|
|
359
|
+
expected: ExpectedWorkspace,
|
|
360
|
+
workspaceNames: Set<string>,
|
|
361
|
+
rules: DependencyRule[],
|
|
362
|
+
depIdent: string,
|
|
363
|
+
currentRange: string,
|
|
364
|
+
): string {
|
|
365
|
+
const rule = getEffectiveRule(
|
|
366
|
+
rules,
|
|
367
|
+
depIdent,
|
|
368
|
+
expected.workspace.name,
|
|
369
|
+
expected.workspace.location,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return workspaceNames.has(depIdent)
|
|
373
|
+
? 'workspace:*'
|
|
374
|
+
: (rule.version ??
|
|
375
|
+
getDeclaredRange(expected.workspace.pkg, depIdent) ??
|
|
376
|
+
getDeclaredRange(root.pkg, depIdent) ??
|
|
377
|
+
currentRange);
|
|
378
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule matching and precedence.
|
|
3
|
+
*
|
|
4
|
+
* Dependency rules are the explicit override surface for the model: versions,
|
|
5
|
+
* section overrides, root-only constraints, and workspace-specific exceptions.
|
|
6
|
+
* The helpers here are pure and apply the documented "last matching rule wins"
|
|
7
|
+
* semantics.
|
|
8
|
+
*/
|
|
9
|
+
import type { DependencyRule, EffectiveRule, WorkspaceRole } from './types';
|
|
10
|
+
|
|
11
|
+
export function matchesGlob(value: string, pattern: string): boolean {
|
|
12
|
+
if (!pattern.includes('*')) return value === pattern;
|
|
13
|
+
const escaped = pattern.replaceAll(
|
|
14
|
+
/[-[\]{}()+?.,\\^$|#\s]/g,
|
|
15
|
+
String.raw`\$&`,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return new RegExp(
|
|
19
|
+
'^' + escaped.replaceAll('**', '.*').replaceAll('*', '[^/]*') + '$',
|
|
20
|
+
).test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function matchesPatterns(
|
|
24
|
+
value: string,
|
|
25
|
+
pattern: string | string[],
|
|
26
|
+
): boolean {
|
|
27
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
28
|
+
|
|
29
|
+
return patterns.some((p) => matchesGlob(value, p));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getEffectiveRule(
|
|
33
|
+
rules: DependencyRule[],
|
|
34
|
+
depIdent: string,
|
|
35
|
+
wsIdent: string,
|
|
36
|
+
wsCwd: string,
|
|
37
|
+
): EffectiveRule {
|
|
38
|
+
const effective: EffectiveRule = { rootOnly: false };
|
|
39
|
+
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
if (!matchesPatterns(depIdent, rule.match)) continue;
|
|
42
|
+
if (
|
|
43
|
+
rule.workspace &&
|
|
44
|
+
!matchesPatterns(wsIdent, rule.workspace) &&
|
|
45
|
+
!matchesPatterns(wsCwd, rule.workspace)
|
|
46
|
+
)
|
|
47
|
+
continue;
|
|
48
|
+
if (rule.section) effective.section = rule.section;
|
|
49
|
+
if (rule.version !== undefined) effective.version = rule.version;
|
|
50
|
+
if (rule.rootOnly !== undefined) effective.rootOnly = rule.rootOnly;
|
|
51
|
+
if (rule.required !== undefined) effective.required = rule.required;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return effective;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasGlobalVersionRule(
|
|
58
|
+
rules: DependencyRule[],
|
|
59
|
+
depIdent: string,
|
|
60
|
+
): boolean {
|
|
61
|
+
return rules.some(
|
|
62
|
+
(rule) =>
|
|
63
|
+
!rule.workspace &&
|
|
64
|
+
rule.version !== undefined &&
|
|
65
|
+
matchesPatterns(depIdent, rule.match),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function hasWorkspaceVersionRule(
|
|
70
|
+
rules: DependencyRule[],
|
|
71
|
+
depIdent: string,
|
|
72
|
+
): boolean {
|
|
73
|
+
return rules.some(
|
|
74
|
+
(rule) =>
|
|
75
|
+
rule.workspace &&
|
|
76
|
+
rule.version !== undefined &&
|
|
77
|
+
matchesPatterns(depIdent, rule.match),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isWorkspaceRole(
|
|
82
|
+
value: string | undefined,
|
|
83
|
+
): value is WorkspaceRole {
|
|
84
|
+
return value === 'library' || value === 'service' || value === 'cli';
|
|
85
|
+
}
|