@monorepolint/rules 0.5.0-alpha.9 → 0.5.0-alpha.95
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/lib/__tests__/alphabeticalScripts.spec.d.ts +8 -0
- package/lib/__tests__/alphabeticalScripts.spec.d.ts.map +1 -0
- package/lib/__tests__/alphabeticalScripts.spec.js +61 -0
- package/lib/__tests__/alphabeticalScripts.spec.js.map +1 -0
- package/lib/__tests__/bannedDependencies.spec.d.ts +2 -0
- package/lib/__tests__/bannedDependencies.spec.d.ts.map +1 -0
- package/lib/__tests__/bannedDependencies.spec.js +161 -0
- package/lib/__tests__/bannedDependencies.spec.js.map +1 -0
- package/lib/__tests__/consistentDependencies.spec.js +30 -23
- package/lib/__tests__/consistentDependencies.spec.js.map +1 -1
- package/lib/__tests__/consistentVersions.spec.d.ts +8 -0
- package/lib/__tests__/consistentVersions.spec.d.ts.map +1 -0
- package/lib/__tests__/consistentVersions.spec.js +183 -0
- package/lib/__tests__/consistentVersions.spec.js.map +1 -0
- package/lib/__tests__/fileContents.spec.d.ts +8 -0
- package/lib/__tests__/fileContents.spec.d.ts.map +1 -0
- package/lib/__tests__/fileContents.spec.js +59 -0
- package/lib/__tests__/fileContents.spec.js.map +1 -0
- package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts +8 -0
- package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts.map +1 -0
- package/lib/__tests__/mustSatisfyPeerDependencies.spec.js +1063 -0
- package/lib/__tests__/mustSatisfyPeerDependencies.spec.js.map +1 -0
- package/lib/__tests__/nestedWorkspaces.spec.d.ts +2 -0
- package/lib/__tests__/nestedWorkspaces.spec.d.ts.map +1 -0
- package/lib/__tests__/nestedWorkspaces.spec.js +124 -0
- package/lib/__tests__/nestedWorkspaces.spec.js.map +1 -0
- package/lib/__tests__/packageEntry.spec.js +72 -27
- package/lib/__tests__/packageEntry.spec.js.map +1 -1
- package/lib/__tests__/packageOrder.spec.js +34 -33
- package/lib/__tests__/packageOrder.spec.js.map +1 -1
- package/lib/__tests__/packageScript.spec.js +50 -51
- package/lib/__tests__/packageScript.spec.js.map +1 -1
- package/lib/__tests__/requireDependency.spec.d.ts +2 -0
- package/lib/__tests__/requireDependency.spec.d.ts.map +1 -0
- package/lib/__tests__/requireDependency.spec.js +123 -0
- package/lib/__tests__/requireDependency.spec.js.map +1 -0
- package/lib/__tests__/utils.d.ts +69 -1
- package/lib/__tests__/utils.d.ts.map +1 -1
- package/lib/__tests__/utils.js +72 -12
- package/lib/__tests__/utils.js.map +1 -1
- package/lib/alphabeticalDependencies.d.ts +3 -1
- package/lib/alphabeticalDependencies.d.ts.map +1 -1
- package/lib/alphabeticalDependencies.js +5 -39
- package/lib/alphabeticalDependencies.js.map +1 -1
- package/lib/alphabeticalScripts.d.ts +12 -0
- package/lib/alphabeticalScripts.d.ts.map +1 -0
- package/lib/alphabeticalScripts.js +20 -0
- package/lib/alphabeticalScripts.js.map +1 -0
- package/lib/bannedDependencies.d.ts +23 -5
- package/lib/bannedDependencies.d.ts.map +1 -1
- package/lib/bannedDependencies.js +110 -36
- package/lib/bannedDependencies.js.map +1 -1
- package/lib/consistentDependencies.d.ts +8 -1
- package/lib/consistentDependencies.d.ts.map +1 -1
- package/lib/consistentDependencies.js +30 -12
- package/lib/consistentDependencies.js.map +1 -1
- package/lib/consistentVersions.d.ts +14 -0
- package/lib/consistentVersions.d.ts.map +1 -0
- package/lib/consistentVersions.js +94 -0
- package/lib/consistentVersions.js.map +1 -0
- package/lib/fileContents.d.ts +8 -8
- package/lib/fileContents.d.ts.map +1 -1
- package/lib/fileContents.js +32 -27
- package/lib/fileContents.js.map +1 -1
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +19 -8
- package/lib/index.js.map +1 -1
- package/lib/mustSatisfyPeerDependencies.d.ts +240 -0
- package/lib/mustSatisfyPeerDependencies.d.ts.map +1 -0
- package/lib/mustSatisfyPeerDependencies.js +636 -0
- package/lib/mustSatisfyPeerDependencies.js.map +1 -0
- package/lib/nestedWorkspaces.d.ts +13 -0
- package/lib/nestedWorkspaces.d.ts.map +1 -0
- package/lib/nestedWorkspaces.js +50 -0
- package/lib/nestedWorkspaces.js.map +1 -0
- package/lib/packageEntry.d.ts +15 -6
- package/lib/packageEntry.d.ts.map +1 -1
- package/lib/packageEntry.js +53 -18
- package/lib/packageEntry.js.map +1 -1
- package/lib/packageOrder.d.ts +3 -3
- package/lib/packageOrder.d.ts.map +1 -1
- package/lib/packageOrder.js +8 -7
- package/lib/packageOrder.js.map +1 -1
- package/lib/packageScript.d.ts +10 -10
- package/lib/packageScript.js +6 -5
- package/lib/packageScript.js.map +1 -1
- package/lib/requireDependency.d.ts +15 -0
- package/lib/requireDependency.d.ts.map +1 -0
- package/lib/requireDependency.js +65 -0
- package/lib/requireDependency.js.map +1 -0
- package/lib/standardTsconfig.d.ts +20 -8
- package/lib/standardTsconfig.d.ts.map +1 -1
- package/lib/standardTsconfig.js +36 -18
- package/lib/standardTsconfig.js.map +1 -1
- package/lib/util/checkAlpha.d.ts +10 -0
- package/lib/util/checkAlpha.d.ts.map +1 -0
- package/lib/util/checkAlpha.js +51 -0
- package/lib/util/checkAlpha.js.map +1 -0
- package/lib/util/makeDirectory.d.ts +8 -0
- package/lib/util/makeDirectory.d.ts.map +1 -0
- package/lib/util/makeDirectory.js +28 -0
- package/lib/util/makeDirectory.js.map +1 -0
- package/lib/util/packageDependencyGraphService.d.ts +37 -0
- package/lib/util/packageDependencyGraphService.d.ts.map +1 -0
- package/lib/util/packageDependencyGraphService.js +70 -0
- package/lib/util/packageDependencyGraphService.js.map +1 -0
- package/package.json +18 -15
- package/src/__tests__/alphabeticalScripts.spec.ts +75 -0
- package/src/__tests__/bannedDependencies.spec.ts +191 -0
- package/src/__tests__/consistentDependencies.spec.ts +39 -26
- package/src/__tests__/consistentVersions.spec.ts +223 -0
- package/src/__tests__/fileContents.spec.ts +74 -0
- package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +1188 -0
- package/src/__tests__/nestedWorkspaces.spec.ts +152 -0
- package/src/__tests__/packageEntry.spec.ts +98 -31
- package/src/__tests__/packageOrder.spec.ts +46 -40
- package/src/__tests__/packageScript.spec.ts +62 -54
- package/src/__tests__/requireDependency.spec.ts +151 -0
- package/src/__tests__/utils.ts +113 -11
- package/src/alphabeticalDependencies.ts +3 -47
- package/src/alphabeticalScripts.ts +19 -0
- package/src/bannedDependencies.ts +133 -43
- package/src/consistentDependencies.ts +34 -12
- package/src/consistentVersions.ts +140 -0
- package/src/fileContents.ts +31 -27
- package/src/index.ts +5 -0
- package/src/mustSatisfyPeerDependencies.ts +739 -0
- package/src/nestedWorkspaces.ts +59 -0
- package/src/packageEntry.ts +67 -24
- package/src/packageOrder.ts +6 -6
- package/src/packageScript.ts +3 -3
- package/src/requireDependency.ts +69 -0
- package/src/standardTsconfig.ts +40 -18
- package/src/util/checkAlpha.ts +59 -0
- package/src/util/makeDirectory.ts +24 -0
- package/src/util/packageDependencyGraphService.ts +114 -0
- package/tsconfig.tsbuildinfo +1 -2439
|
@@ -6,64 +6,154 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Context, RuleModule } from "@monorepolint/core";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import
|
|
9
|
+
import { matchesAnyGlob } from "@monorepolint/utils";
|
|
10
|
+
import { AggregateTiming } from "@monorepolint/utils";
|
|
11
|
+
import path from "path";
|
|
12
12
|
import * as r from "runtypes";
|
|
13
|
+
import { IPackageDependencyGraphNode, PackageDependencyGraphService } from "./util/packageDependencyGraphService";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
bannedDependencies: r.Array(r.String),
|
|
16
|
-
});
|
|
15
|
+
// FIXME: This rule is messed. bannedTransitiveDependencies doesnt glob
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
const bannedDepGlobsField = r.Union(
|
|
18
|
+
r.Array(r.String),
|
|
19
|
+
r.Record({
|
|
20
|
+
glob: r.Array(r.String).optional(),
|
|
21
|
+
exact: r.Array(r.String).optional(),
|
|
22
|
+
})
|
|
23
|
+
);
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
const Options = r.Union(
|
|
26
|
+
r.Record({
|
|
27
|
+
bannedDependencies: bannedDepGlobsField,
|
|
28
|
+
bannedTransitiveDependencies: r.Undefined.optional(),
|
|
29
|
+
}),
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
r.Record({
|
|
32
|
+
bannedDependencies: bannedDepGlobsField.optional(),
|
|
33
|
+
bannedTransitiveDependencies: r.Array(r.String),
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
r.Record({
|
|
37
|
+
bannedDependencies: bannedDepGlobsField.optional(),
|
|
38
|
+
bannedTransitiveDependencies: r.Array(r.String).optional(),
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export type Options = r.Static<typeof Options>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* We use this locally to avoid making a billion sets. Because check is called once per package
|
|
46
|
+
* (with the exact same config object reference) we can save quite a bit of time by reusing this cache.
|
|
47
|
+
*/
|
|
48
|
+
const setCache = new Map<ReadonlyArray<string>, Set<string>>();
|
|
49
|
+
|
|
50
|
+
const aggregateTiming = new AggregateTiming(":bannedDependencies stats");
|
|
51
|
+
|
|
52
|
+
export const bannedDependencies: RuleModule<typeof Options> & {
|
|
53
|
+
printStats: () => void;
|
|
54
|
+
} = {
|
|
55
|
+
check: function expectAllowedDependencies(context, opts, extra) {
|
|
56
|
+
aggregateTiming.start(extra?.id ?? "unknown id");
|
|
57
|
+
|
|
58
|
+
const packageJson = context.getPackageJson();
|
|
59
|
+
const packagePath = context.getPackageJsonPath();
|
|
60
|
+
|
|
61
|
+
const curDeps = packageJson.dependencies && Object.keys(packageJson.dependencies);
|
|
62
|
+
const curDevDeps = packageJson.devDependencies && Object.keys(packageJson.devDependencies);
|
|
63
|
+
const curPeerDeps = packageJson.peerDependencies && Object.keys(packageJson.peerDependencies);
|
|
64
|
+
|
|
65
|
+
const { bannedDependencies: banned, bannedTransitiveDependencies: transitives } = opts;
|
|
66
|
+
|
|
67
|
+
const globs = banned && (Array.isArray(banned) ? banned : banned.glob);
|
|
68
|
+
const exacts = banned && (Array.isArray(banned) ? undefined : banned.exact);
|
|
69
|
+
|
|
70
|
+
const violations = new Set<string>();
|
|
71
|
+
|
|
72
|
+
if (globs) {
|
|
73
|
+
if (curDeps) populateProblemsGlobs(globs, curDeps, violations);
|
|
74
|
+
if (curDevDeps) populateProblemsGlobs(globs, curDevDeps, violations);
|
|
75
|
+
if (curPeerDeps) populateProblemsGlobs(globs, curPeerDeps, violations);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (exacts) {
|
|
79
|
+
let set = setCache.get(exacts);
|
|
80
|
+
if (set === undefined) {
|
|
81
|
+
set = new Set(exacts);
|
|
82
|
+
setCache.set(exacts, set);
|
|
83
|
+
}
|
|
84
|
+
if (curDeps) populateProblemsExact(set, curDeps, violations);
|
|
85
|
+
if (curDevDeps) populateProblemsExact(set, curDevDeps, violations);
|
|
86
|
+
if (curPeerDeps) populateProblemsExact(set, curPeerDeps, violations);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (violations.size > 0) {
|
|
90
|
+
context.addError({
|
|
91
|
+
file: packagePath,
|
|
92
|
+
message:
|
|
93
|
+
`Found ${violations.size} banned dependencies of package.json:\n\t` +
|
|
94
|
+
Array.from(violations)
|
|
95
|
+
.map((v) => `'${v}'`)
|
|
96
|
+
.join(", "),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (transitives) {
|
|
101
|
+
let set = setCache.get(transitives);
|
|
102
|
+
if (set === undefined) {
|
|
103
|
+
set = new Set(transitives);
|
|
104
|
+
setCache.set(transitives, set);
|
|
105
|
+
}
|
|
106
|
+
checkTransitives(context, set);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
aggregateTiming.stop();
|
|
28
110
|
},
|
|
29
111
|
optionsRuntype: Options,
|
|
112
|
+
printStats: () => {
|
|
113
|
+
aggregateTiming.printResults();
|
|
114
|
+
},
|
|
30
115
|
};
|
|
31
116
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const packagePath = context.getPackageJsonPath();
|
|
40
|
-
|
|
41
|
-
const dependencies = packageJson[block];
|
|
117
|
+
function populateProblemsExact(banned: Set<string>, dependencies: ReadonlyArray<string>, violations: Set<string>) {
|
|
118
|
+
for (const dependency of dependencies) {
|
|
119
|
+
if (banned.has(dependency)) {
|
|
120
|
+
violations.add(dependency);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
42
124
|
|
|
43
|
-
|
|
44
|
-
|
|
125
|
+
function populateProblemsGlobs(
|
|
126
|
+
bannedDependencyGlobs: ReadonlyArray<string>,
|
|
127
|
+
dependencies: ReadonlyArray<string>,
|
|
128
|
+
violations: Set<string>
|
|
129
|
+
) {
|
|
130
|
+
for (const dependency of dependencies) {
|
|
131
|
+
if (matchesAnyGlob(dependency, bannedDependencyGlobs)) {
|
|
132
|
+
violations.add(dependency);
|
|
133
|
+
}
|
|
45
134
|
}
|
|
135
|
+
}
|
|
46
136
|
|
|
47
|
-
|
|
137
|
+
// This function is slow. God help you if you use this on a big repo
|
|
138
|
+
function checkTransitives(context: Context, banned: Set<string>) {
|
|
139
|
+
const graphService = new PackageDependencyGraphService();
|
|
140
|
+
const root = graphService.buildDependencyGraph(path.resolve(context.getPackageJsonPath()), context.host);
|
|
141
|
+
for (const { dependencies, importPath } of graphService.traverse(root)) {
|
|
142
|
+
for (const [dependency] of dependencies) {
|
|
143
|
+
if (banned.has(dependency)) {
|
|
144
|
+
// Remove the starting package since it's obvious in CLI output.
|
|
145
|
+
const [, ...importPathWithoutRoot] = importPath;
|
|
146
|
+
const pathing = [...importPathWithoutRoot.map(nameOrPackageJsonPath), dependency].join(" -> ");
|
|
48
147
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
148
|
+
context.addError({
|
|
149
|
+
file: root.paths.packageJsonPath,
|
|
150
|
+
message: `Banned transitive dependencies in repo: ${pathing}`,
|
|
151
|
+
});
|
|
53
152
|
}
|
|
54
153
|
}
|
|
55
154
|
}
|
|
155
|
+
}
|
|
56
156
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
file: packagePath,
|
|
60
|
-
message: `Banned depdendencies in ${block} in package.json`,
|
|
61
|
-
longMessage: diff(expectedDependencies, dependencies, { expand: true }),
|
|
62
|
-
fixer: () => {
|
|
63
|
-
const newPackageJson = { ...packageJson };
|
|
64
|
-
newPackageJson[block] = expectedDependencies;
|
|
65
|
-
writeJson(packagePath, newPackageJson);
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
157
|
+
function nameOrPackageJsonPath(node: IPackageDependencyGraphNode): string {
|
|
158
|
+
return node.packageJson.name ?? node.paths.packageJsonPath;
|
|
69
159
|
}
|
|
@@ -6,23 +6,28 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Context, RuleModule } from "@monorepolint/core";
|
|
9
|
-
import { writeJson } from "@monorepolint/utils";
|
|
10
9
|
import diff from "jest-diff";
|
|
11
10
|
import * as r from "runtypes";
|
|
12
11
|
|
|
13
|
-
const Options = r
|
|
14
|
-
|
|
12
|
+
const Options = r
|
|
13
|
+
.Record({
|
|
14
|
+
ignoredDependencies: r.Array(r.String).Or(r.Undefined),
|
|
15
|
+
})
|
|
16
|
+
.Or(r.Undefined);
|
|
17
|
+
export type Options = r.Static<typeof Options>;
|
|
18
|
+
|
|
19
|
+
const skippedVersions = ["*", "latest"];
|
|
15
20
|
|
|
16
21
|
export const consistentDependencies = {
|
|
17
|
-
check: function expectConsistentDependencies(context: Context) {
|
|
18
|
-
checkDeps(context, "dependencies");
|
|
19
|
-
checkDeps(context, "devDependencies");
|
|
22
|
+
check: function expectConsistentDependencies(context: Context, args: Options) {
|
|
23
|
+
checkDeps(context, args, "dependencies");
|
|
24
|
+
checkDeps(context, args, "devDependencies");
|
|
20
25
|
// we don't check peer deps since they can be more lenient
|
|
21
26
|
},
|
|
22
27
|
optionsRuntype: Options,
|
|
23
28
|
} as RuleModule<typeof Options>;
|
|
24
29
|
|
|
25
|
-
function checkDeps(context: Context, block: "dependencies" | "devDependencies" | "peerDependencies") {
|
|
30
|
+
function checkDeps(context: Context, args: Options, block: "dependencies" | "devDependencies" | "peerDependencies") {
|
|
26
31
|
const packageJson = context.getPackageJson();
|
|
27
32
|
const packagePath = context.getPackageJsonPath();
|
|
28
33
|
const dependencies = packageJson[block];
|
|
@@ -30,24 +35,30 @@ function checkDeps(context: Context, block: "dependencies" | "devDependencies" |
|
|
|
30
35
|
const workspacePackageJson = context.getWorkspaceContext().getPackageJson();
|
|
31
36
|
const workspaceDependencies = workspacePackageJson[block];
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
const ignoredDeps = args?.ignoredDependencies ?? [];
|
|
39
|
+
const depsToCheck =
|
|
40
|
+
workspaceDependencies == null || ignoredDeps.length === 0
|
|
41
|
+
? workspaceDependencies
|
|
42
|
+
: omit(workspaceDependencies, ignoredDeps);
|
|
43
|
+
|
|
44
|
+
if (dependencies === undefined || depsToCheck === undefined) {
|
|
34
45
|
return;
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
const expectedDependencies = {
|
|
38
49
|
...dependencies,
|
|
39
|
-
...filterKeys(
|
|
50
|
+
...filterKeys(depsToCheck, dependencies),
|
|
40
51
|
};
|
|
41
52
|
|
|
42
53
|
if (JSON.stringify(dependencies) !== JSON.stringify(expectedDependencies)) {
|
|
43
54
|
context.addError({
|
|
44
55
|
file: packagePath,
|
|
45
|
-
message: `
|
|
56
|
+
message: `Inconsistent ${block} with root in package.json`,
|
|
46
57
|
longMessage: diff(expectedDependencies, dependencies, { expand: true }),
|
|
47
58
|
fixer: () => {
|
|
48
59
|
const newPackageJson = { ...packageJson };
|
|
49
60
|
newPackageJson[block] = expectedDependencies;
|
|
50
|
-
writeJson(packagePath, newPackageJson);
|
|
61
|
+
context.host.writeJson(packagePath, newPackageJson);
|
|
51
62
|
},
|
|
52
63
|
});
|
|
53
64
|
}
|
|
@@ -57,10 +68,21 @@ function filterKeys(ob: Record<string, string>, filterOb: Record<string, string>
|
|
|
57
68
|
const newOb: Record<string, any> = {};
|
|
58
69
|
|
|
59
70
|
for (const key of Object.keys(filterOb)) {
|
|
60
|
-
if (ob[key] !== undefined && filterOb[key]
|
|
71
|
+
if (ob[key] !== undefined && skippedVersions.indexOf(filterOb[key]) === -1) {
|
|
61
72
|
newOb[key] = ob[key];
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
return newOb;
|
|
66
77
|
}
|
|
78
|
+
|
|
79
|
+
function omit<T>(obj: Record<string, T>, keysToOmit: readonly string[]): Record<string, T> {
|
|
80
|
+
const newObj: Record<string, T> = {};
|
|
81
|
+
|
|
82
|
+
const filtered = Object.entries(obj).filter(([key]) => !keysToOmit.includes(key));
|
|
83
|
+
for (const [key, value] of filtered) {
|
|
84
|
+
newObj[key] = value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return newObj;
|
|
88
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright 2020 Palantir Technologies, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the MIT license. See LICENSE file in the project root for details.
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Context, RuleModule } from "@monorepolint/core";
|
|
9
|
+
import { mutateJson, PackageJson } from "@monorepolint/utils";
|
|
10
|
+
import * as r from "runtypes";
|
|
11
|
+
import { coerce, SemVer } from "semver";
|
|
12
|
+
|
|
13
|
+
export const Options = r.Record({
|
|
14
|
+
matchDependencyVersions: r.Dictionary(r.Union(r.String, r.Array(r.String))),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type Options = r.Static<typeof Options>;
|
|
18
|
+
|
|
19
|
+
export const consistentVersions: RuleModule<typeof Options> = {
|
|
20
|
+
check: checkConsistentVersions,
|
|
21
|
+
optionsRuntype: Options,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function checkConsistentVersions(context: Context, options: Options) {
|
|
25
|
+
for (const [dependencyPackageName, expectedPackageDependencyValue] of Object.entries(
|
|
26
|
+
options.matchDependencyVersions
|
|
27
|
+
)) {
|
|
28
|
+
if (Array.isArray(expectedPackageDependencyValue)) {
|
|
29
|
+
ensurePackageMatchesSomeVersion(context, dependencyPackageName, expectedPackageDependencyValue);
|
|
30
|
+
} else {
|
|
31
|
+
ensurePackageIsCorrectVersion(context, dependencyPackageName, expectedPackageDependencyValue);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ensurePackageIsCorrectVersion = (
|
|
37
|
+
context: Context,
|
|
38
|
+
dependencyPackageName: string,
|
|
39
|
+
expectedPackageDependencyValue: string
|
|
40
|
+
) => {
|
|
41
|
+
const packageJson = context.getPackageJson();
|
|
42
|
+
const packageJsonPath = context.getPackageJsonPath();
|
|
43
|
+
|
|
44
|
+
const expectedPackageDependencyVersion = coerce(expectedPackageDependencyValue);
|
|
45
|
+
if (expectedPackageDependencyVersion == null) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Malformed expected package dependency version defined in monorepolint configuration: ${dependencyPackageName} @ '${expectedPackageDependencyValue}'`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const actualPackageDependencyValue = packageJson.dependencies && packageJson.dependencies[dependencyPackageName];
|
|
52
|
+
const actualPackageDependencyVersion = coerce(actualPackageDependencyValue);
|
|
53
|
+
if (
|
|
54
|
+
actualPackageDependencyVersion != null &&
|
|
55
|
+
actualPackageDependencyVersion.raw !== expectedPackageDependencyVersion.raw
|
|
56
|
+
) {
|
|
57
|
+
context.addError({
|
|
58
|
+
file: packageJsonPath,
|
|
59
|
+
message: `Expected dependency on ${dependencyPackageName} to match version defined in monorepolint configuration '${expectedPackageDependencyValue}', got '${actualPackageDependencyValue}' instead.`,
|
|
60
|
+
fixer: () =>
|
|
61
|
+
mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
|
|
62
|
+
input.dependencies![dependencyPackageName] = expectedPackageDependencyValue;
|
|
63
|
+
return input;
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const actualPackageDevDependencyValue =
|
|
69
|
+
packageJson.devDependencies && packageJson.devDependencies[dependencyPackageName];
|
|
70
|
+
const actualPackageDevDependencyVersion = coerce(actualPackageDevDependencyValue);
|
|
71
|
+
if (
|
|
72
|
+
actualPackageDevDependencyVersion != null &&
|
|
73
|
+
actualPackageDevDependencyVersion.raw !== expectedPackageDependencyVersion.raw
|
|
74
|
+
) {
|
|
75
|
+
context.addError({
|
|
76
|
+
file: packageJsonPath,
|
|
77
|
+
message: `Expected devDependency on ${dependencyPackageName} to match version defined in monorepolint configuration '${expectedPackageDependencyValue}', got '${actualPackageDevDependencyValue}' instead`,
|
|
78
|
+
fixer: () =>
|
|
79
|
+
mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
|
|
80
|
+
input.devDependencies![dependencyPackageName] = expectedPackageDependencyValue;
|
|
81
|
+
return input;
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const ensurePackageMatchesSomeVersion = (
|
|
88
|
+
context: Context,
|
|
89
|
+
dependencyPackageName: string,
|
|
90
|
+
acceptedPackageDependencyValues: string[]
|
|
91
|
+
) => {
|
|
92
|
+
const packageJson = context.getPackageJson();
|
|
93
|
+
const packageJsonPath = context.getPackageJsonPath();
|
|
94
|
+
|
|
95
|
+
const acceptedPackageDependencyVersions: SemVer[] = acceptedPackageDependencyValues.map(
|
|
96
|
+
(acceptedPackageDependencyValue) => {
|
|
97
|
+
const acceptedPackageDependencyVersion = coerce(acceptedPackageDependencyValue);
|
|
98
|
+
if (acceptedPackageDependencyVersion == null) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Malformed accepted package dependency version defined in monorepolint configuration: ${dependencyPackageName} @ '${acceptedPackageDependencyValue}'`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return acceptedPackageDependencyVersion;
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const actualPackageDependencyValue = packageJson.dependencies && packageJson.dependencies[dependencyPackageName];
|
|
108
|
+
const actualPackageDependencyVersion = coerce(actualPackageDependencyValue);
|
|
109
|
+
if (
|
|
110
|
+
actualPackageDependencyVersion != null &&
|
|
111
|
+
acceptedPackageDependencyVersions.every(
|
|
112
|
+
(acceptedPackageDependencyVersion) => actualPackageDependencyVersion.raw !== acceptedPackageDependencyVersion.raw
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
context.addError({
|
|
116
|
+
file: packageJsonPath,
|
|
117
|
+
message: `Expected dependency on ${dependencyPackageName} to match one of '${JSON.stringify(
|
|
118
|
+
acceptedPackageDependencyValues
|
|
119
|
+
)}', got '${actualPackageDependencyValue}' instead.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const actualPackageDevDependencyValue =
|
|
124
|
+
packageJson.devDependencies && packageJson.devDependencies[dependencyPackageName];
|
|
125
|
+
const actualPackageDevDependencyVersion = coerce(actualPackageDevDependencyValue);
|
|
126
|
+
if (
|
|
127
|
+
actualPackageDevDependencyVersion != null &&
|
|
128
|
+
acceptedPackageDependencyVersions.every(
|
|
129
|
+
(acceptedPackageDependencyVersion) =>
|
|
130
|
+
actualPackageDevDependencyVersion.raw !== acceptedPackageDependencyVersion.raw
|
|
131
|
+
)
|
|
132
|
+
) {
|
|
133
|
+
context.addError({
|
|
134
|
+
file: packageJsonPath,
|
|
135
|
+
message: `Expected devDependency on ${dependencyPackageName} to match one of '${JSON.stringify(
|
|
136
|
+
acceptedPackageDependencyValues
|
|
137
|
+
)}', got '${actualPackageDevDependencyValue}' instead.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
package/src/fileContents.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import { Context } from "@monorepolint/core";
|
|
9
9
|
import { RuleModule } from "@monorepolint/core";
|
|
10
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
11
10
|
import diff from "jest-diff";
|
|
12
11
|
import * as path from "path";
|
|
13
12
|
import * as r from "runtypes";
|
|
@@ -16,21 +15,21 @@ const Options = r.Union(
|
|
|
16
15
|
r.Record({
|
|
17
16
|
file: r.String,
|
|
18
17
|
generator: r.Function,
|
|
19
|
-
template: r.Undefined,
|
|
20
|
-
templateFile: r.Undefined,
|
|
18
|
+
template: r.Undefined.optional(),
|
|
19
|
+
templateFile: r.Undefined.optional(),
|
|
21
20
|
}),
|
|
22
21
|
|
|
23
22
|
r.Record({
|
|
24
23
|
file: r.String,
|
|
25
|
-
generator: r.Undefined,
|
|
24
|
+
generator: r.Undefined.optional(),
|
|
26
25
|
template: r.String,
|
|
27
|
-
templateFile: r.Undefined,
|
|
26
|
+
templateFile: r.Undefined.optional(),
|
|
28
27
|
}),
|
|
29
28
|
|
|
30
29
|
r.Record({
|
|
31
30
|
file: r.String,
|
|
32
|
-
generator: r.Undefined,
|
|
33
|
-
template: r.Undefined,
|
|
31
|
+
generator: r.Undefined.optional(),
|
|
32
|
+
template: r.Undefined.optional(),
|
|
34
33
|
templateFile: r.String,
|
|
35
34
|
})
|
|
36
35
|
);
|
|
@@ -40,11 +39,10 @@ type Options = r.Static<typeof Options>;
|
|
|
40
39
|
export const fileContents = {
|
|
41
40
|
check: function expectFileContents(context: Context, opts: Options) {
|
|
42
41
|
const fullPath = path.join(context.packageDir, opts.file);
|
|
43
|
-
const
|
|
44
|
-
const expectedContent = generator(context);
|
|
45
|
-
|
|
46
|
-
const actualContent = existsSync(fullPath) ? readFileSync(fullPath, "utf-8") : undefined;
|
|
42
|
+
const expectedContent = getExpectedContents(context, opts);
|
|
47
43
|
|
|
44
|
+
const pathExists = context.host.exists(fullPath);
|
|
45
|
+
const actualContent = pathExists ? context.host.readFile(fullPath, { encoding: "utf-8" }) : undefined;
|
|
48
46
|
if (actualContent !== expectedContent) {
|
|
49
47
|
context.addError({
|
|
50
48
|
file: fullPath,
|
|
@@ -52,9 +50,10 @@ export const fileContents = {
|
|
|
52
50
|
longMessage: diff(expectedContent, actualContent, { expand: true }),
|
|
53
51
|
fixer: () => {
|
|
54
52
|
if (expectedContent === undefined) {
|
|
55
|
-
|
|
53
|
+
if (pathExists) context.host.deleteFile(fullPath);
|
|
56
54
|
} else {
|
|
57
|
-
|
|
55
|
+
context.host.mkdir(path.dirname(fullPath), { recursive: true });
|
|
56
|
+
context.host.writeFile(fullPath, expectedContent, { encoding: "utf-8" });
|
|
58
57
|
}
|
|
59
58
|
},
|
|
60
59
|
});
|
|
@@ -63,25 +62,30 @@ export const fileContents = {
|
|
|
63
62
|
optionsRuntype: Options,
|
|
64
63
|
} as RuleModule<typeof Options>;
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
const optionsCache = new Map<Options, ((context: Context) => string | undefined) | string | undefined>();
|
|
66
|
+
|
|
67
|
+
function getExpectedContents(context: Context, opts: Options) {
|
|
68
|
+
// we need to use has because undefined is a valid value in the cache
|
|
69
|
+
if (optionsCache.has(opts)) {
|
|
70
|
+
const cachedEntry = optionsCache.get(opts);
|
|
71
|
+
if (cachedEntry && typeof cachedEntry === "function") {
|
|
72
|
+
return cachedEntry(context);
|
|
73
|
+
}
|
|
74
|
+
return cachedEntry;
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
if (opts.generator) {
|
|
68
|
-
|
|
78
|
+
optionsCache.set(opts, opts.generator);
|
|
79
|
+
return opts.generator(context) as string | undefined; // we have no guarentee its the right kind of function
|
|
69
80
|
} else if (opts.templateFile) {
|
|
70
81
|
const { packageDir: workspacePackageDir } = context.getWorkspaceContext();
|
|
71
82
|
const fullPath = path.resolve(workspacePackageDir, opts.templateFile);
|
|
72
|
-
const template =
|
|
83
|
+
const template = context.host.readFile(fullPath, { encoding: "utf-8" });
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return makeGenerator(opts.template);
|
|
85
|
+
optionsCache.set(opts, template);
|
|
86
|
+
return template;
|
|
77
87
|
} else {
|
|
78
|
-
|
|
88
|
+
optionsCache.set(opts, opts.template);
|
|
89
|
+
return opts.template;
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
|
-
|
|
82
|
-
function makeGenerator(template: string) {
|
|
83
|
-
// tslint:disable-next-line:variable-name
|
|
84
|
-
return function generator(_context: Context) {
|
|
85
|
-
return template;
|
|
86
|
-
};
|
|
87
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export { alphabeticalDependencies } from "./alphabeticalDependencies";
|
|
9
|
+
export { alphabeticalScripts } from "./alphabeticalScripts";
|
|
9
10
|
export { bannedDependencies } from "./bannedDependencies";
|
|
10
11
|
export { consistentDependencies } from "./consistentDependencies";
|
|
12
|
+
export { consistentVersions } from "./consistentVersions";
|
|
11
13
|
export { fileContents } from "./fileContents";
|
|
14
|
+
export { mustSatisfyPeerDependencies } from "./mustSatisfyPeerDependencies";
|
|
12
15
|
export { packageOrder } from "./packageOrder";
|
|
13
16
|
export { packageEntry } from "./packageEntry";
|
|
14
17
|
export { packageScript } from "./packageScript";
|
|
15
18
|
export { standardTsconfig } from "./standardTsconfig";
|
|
19
|
+
export { nestedWorkspaces } from "./nestedWorkspaces";
|
|
20
|
+
export { requireDependency } from "./requireDependency";
|