@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.
Files changed (138) hide show
  1. package/lib/__tests__/alphabeticalScripts.spec.d.ts +8 -0
  2. package/lib/__tests__/alphabeticalScripts.spec.d.ts.map +1 -0
  3. package/lib/__tests__/alphabeticalScripts.spec.js +61 -0
  4. package/lib/__tests__/alphabeticalScripts.spec.js.map +1 -0
  5. package/lib/__tests__/bannedDependencies.spec.d.ts +2 -0
  6. package/lib/__tests__/bannedDependencies.spec.d.ts.map +1 -0
  7. package/lib/__tests__/bannedDependencies.spec.js +161 -0
  8. package/lib/__tests__/bannedDependencies.spec.js.map +1 -0
  9. package/lib/__tests__/consistentDependencies.spec.js +30 -23
  10. package/lib/__tests__/consistentDependencies.spec.js.map +1 -1
  11. package/lib/__tests__/consistentVersions.spec.d.ts +8 -0
  12. package/lib/__tests__/consistentVersions.spec.d.ts.map +1 -0
  13. package/lib/__tests__/consistentVersions.spec.js +183 -0
  14. package/lib/__tests__/consistentVersions.spec.js.map +1 -0
  15. package/lib/__tests__/fileContents.spec.d.ts +8 -0
  16. package/lib/__tests__/fileContents.spec.d.ts.map +1 -0
  17. package/lib/__tests__/fileContents.spec.js +59 -0
  18. package/lib/__tests__/fileContents.spec.js.map +1 -0
  19. package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts +8 -0
  20. package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts.map +1 -0
  21. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js +1063 -0
  22. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js.map +1 -0
  23. package/lib/__tests__/nestedWorkspaces.spec.d.ts +2 -0
  24. package/lib/__tests__/nestedWorkspaces.spec.d.ts.map +1 -0
  25. package/lib/__tests__/nestedWorkspaces.spec.js +124 -0
  26. package/lib/__tests__/nestedWorkspaces.spec.js.map +1 -0
  27. package/lib/__tests__/packageEntry.spec.js +72 -27
  28. package/lib/__tests__/packageEntry.spec.js.map +1 -1
  29. package/lib/__tests__/packageOrder.spec.js +34 -33
  30. package/lib/__tests__/packageOrder.spec.js.map +1 -1
  31. package/lib/__tests__/packageScript.spec.js +50 -51
  32. package/lib/__tests__/packageScript.spec.js.map +1 -1
  33. package/lib/__tests__/requireDependency.spec.d.ts +2 -0
  34. package/lib/__tests__/requireDependency.spec.d.ts.map +1 -0
  35. package/lib/__tests__/requireDependency.spec.js +123 -0
  36. package/lib/__tests__/requireDependency.spec.js.map +1 -0
  37. package/lib/__tests__/utils.d.ts +69 -1
  38. package/lib/__tests__/utils.d.ts.map +1 -1
  39. package/lib/__tests__/utils.js +72 -12
  40. package/lib/__tests__/utils.js.map +1 -1
  41. package/lib/alphabeticalDependencies.d.ts +3 -1
  42. package/lib/alphabeticalDependencies.d.ts.map +1 -1
  43. package/lib/alphabeticalDependencies.js +5 -39
  44. package/lib/alphabeticalDependencies.js.map +1 -1
  45. package/lib/alphabeticalScripts.d.ts +12 -0
  46. package/lib/alphabeticalScripts.d.ts.map +1 -0
  47. package/lib/alphabeticalScripts.js +20 -0
  48. package/lib/alphabeticalScripts.js.map +1 -0
  49. package/lib/bannedDependencies.d.ts +23 -5
  50. package/lib/bannedDependencies.d.ts.map +1 -1
  51. package/lib/bannedDependencies.js +110 -36
  52. package/lib/bannedDependencies.js.map +1 -1
  53. package/lib/consistentDependencies.d.ts +8 -1
  54. package/lib/consistentDependencies.d.ts.map +1 -1
  55. package/lib/consistentDependencies.js +30 -12
  56. package/lib/consistentDependencies.js.map +1 -1
  57. package/lib/consistentVersions.d.ts +14 -0
  58. package/lib/consistentVersions.d.ts.map +1 -0
  59. package/lib/consistentVersions.js +94 -0
  60. package/lib/consistentVersions.js.map +1 -0
  61. package/lib/fileContents.d.ts +8 -8
  62. package/lib/fileContents.d.ts.map +1 -1
  63. package/lib/fileContents.js +32 -27
  64. package/lib/fileContents.js.map +1 -1
  65. package/lib/index.d.ts +5 -0
  66. package/lib/index.d.ts.map +1 -1
  67. package/lib/index.js +19 -8
  68. package/lib/index.js.map +1 -1
  69. package/lib/mustSatisfyPeerDependencies.d.ts +240 -0
  70. package/lib/mustSatisfyPeerDependencies.d.ts.map +1 -0
  71. package/lib/mustSatisfyPeerDependencies.js +636 -0
  72. package/lib/mustSatisfyPeerDependencies.js.map +1 -0
  73. package/lib/nestedWorkspaces.d.ts +13 -0
  74. package/lib/nestedWorkspaces.d.ts.map +1 -0
  75. package/lib/nestedWorkspaces.js +50 -0
  76. package/lib/nestedWorkspaces.js.map +1 -0
  77. package/lib/packageEntry.d.ts +15 -6
  78. package/lib/packageEntry.d.ts.map +1 -1
  79. package/lib/packageEntry.js +53 -18
  80. package/lib/packageEntry.js.map +1 -1
  81. package/lib/packageOrder.d.ts +3 -3
  82. package/lib/packageOrder.d.ts.map +1 -1
  83. package/lib/packageOrder.js +8 -7
  84. package/lib/packageOrder.js.map +1 -1
  85. package/lib/packageScript.d.ts +10 -10
  86. package/lib/packageScript.js +6 -5
  87. package/lib/packageScript.js.map +1 -1
  88. package/lib/requireDependency.d.ts +15 -0
  89. package/lib/requireDependency.d.ts.map +1 -0
  90. package/lib/requireDependency.js +65 -0
  91. package/lib/requireDependency.js.map +1 -0
  92. package/lib/standardTsconfig.d.ts +20 -8
  93. package/lib/standardTsconfig.d.ts.map +1 -1
  94. package/lib/standardTsconfig.js +36 -18
  95. package/lib/standardTsconfig.js.map +1 -1
  96. package/lib/util/checkAlpha.d.ts +10 -0
  97. package/lib/util/checkAlpha.d.ts.map +1 -0
  98. package/lib/util/checkAlpha.js +51 -0
  99. package/lib/util/checkAlpha.js.map +1 -0
  100. package/lib/util/makeDirectory.d.ts +8 -0
  101. package/lib/util/makeDirectory.d.ts.map +1 -0
  102. package/lib/util/makeDirectory.js +28 -0
  103. package/lib/util/makeDirectory.js.map +1 -0
  104. package/lib/util/packageDependencyGraphService.d.ts +37 -0
  105. package/lib/util/packageDependencyGraphService.d.ts.map +1 -0
  106. package/lib/util/packageDependencyGraphService.js +70 -0
  107. package/lib/util/packageDependencyGraphService.js.map +1 -0
  108. package/package.json +18 -15
  109. package/src/__tests__/alphabeticalScripts.spec.ts +75 -0
  110. package/src/__tests__/bannedDependencies.spec.ts +191 -0
  111. package/src/__tests__/consistentDependencies.spec.ts +39 -26
  112. package/src/__tests__/consistentVersions.spec.ts +223 -0
  113. package/src/__tests__/fileContents.spec.ts +74 -0
  114. package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +1188 -0
  115. package/src/__tests__/nestedWorkspaces.spec.ts +152 -0
  116. package/src/__tests__/packageEntry.spec.ts +98 -31
  117. package/src/__tests__/packageOrder.spec.ts +46 -40
  118. package/src/__tests__/packageScript.spec.ts +62 -54
  119. package/src/__tests__/requireDependency.spec.ts +151 -0
  120. package/src/__tests__/utils.ts +113 -11
  121. package/src/alphabeticalDependencies.ts +3 -47
  122. package/src/alphabeticalScripts.ts +19 -0
  123. package/src/bannedDependencies.ts +133 -43
  124. package/src/consistentDependencies.ts +34 -12
  125. package/src/consistentVersions.ts +140 -0
  126. package/src/fileContents.ts +31 -27
  127. package/src/index.ts +5 -0
  128. package/src/mustSatisfyPeerDependencies.ts +739 -0
  129. package/src/nestedWorkspaces.ts +59 -0
  130. package/src/packageEntry.ts +67 -24
  131. package/src/packageOrder.ts +6 -6
  132. package/src/packageScript.ts +3 -3
  133. package/src/requireDependency.ts +69 -0
  134. package/src/standardTsconfig.ts +40 -18
  135. package/src/util/checkAlpha.ts +59 -0
  136. package/src/util/makeDirectory.ts +24 -0
  137. package/src/util/packageDependencyGraphService.ts +114 -0
  138. package/tsconfig.tsbuildinfo +1 -2439
@@ -6,64 +6,154 @@
6
6
  */
7
7
 
8
8
  import { Context, RuleModule } from "@monorepolint/core";
9
- import { writeJson } from "@monorepolint/utils";
10
- import diff from "jest-diff";
11
- import minimatch from "minimatch";
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
- const Options = r.Record({
15
- bannedDependencies: r.Array(r.String),
16
- });
15
+ // FIXME: This rule is messed. bannedTransitiveDependencies doesnt glob
17
16
 
18
- type Options = r.Static<typeof Options>;
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
- 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;
25
+ const Options = r.Union(
26
+ r.Record({
27
+ bannedDependencies: bannedDepGlobsField,
28
+ bannedTransitiveDependencies: r.Undefined.optional(),
29
+ }),
24
30
 
25
- checkBanned(context, bannedDependencies, "dependencies");
26
- checkBanned(context, bannedDependencies, "devDependencies");
27
- checkBanned(context, bannedDependencies, "peerDependencies");
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 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();
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
- if (dependencies === undefined) {
44
- return;
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
- const expectedDependencies: Record<string, string> = {};
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
- for (const dependency of Object.keys(dependencies)) {
50
- for (const bannedDependency of bannedDependencies) {
51
- if (!minimatch(dependency, bannedDependency)) {
52
- expectedDependencies[dependency] = dependencies[dependency];
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
- 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
- }
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.Undefined;
14
- type Options = r.Static<typeof Options>;
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
- if (dependencies === undefined || workspaceDependencies === undefined) {
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(workspaceDependencies, dependencies),
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: `Inconsitent ${block} with root in package.json`,
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
+ };
@@ -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 generator = getGenerator(context, opts);
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
- unlinkSync(fullPath);
53
+ if (pathExists) context.host.deleteFile(fullPath);
56
54
  } else {
57
- writeFileSync(fullPath, expectedContent);
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
- function getGenerator(context: Context, opts: Options) {
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
- return opts.generator;
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 = readFileSync(fullPath, "utf-8");
83
+ const template = context.host.readFile(fullPath, { encoding: "utf-8" });
73
84
 
74
- return makeGenerator(template);
75
- } else if (opts.template) {
76
- return makeGenerator(opts.template);
85
+ optionsCache.set(opts, template);
86
+ return template;
77
87
  } else {
78
- throw new Error("Unable to make generator");
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";