@monorepolint/rules 0.5.0-alpha.13 → 0.5.0-alpha.130

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