@monorepolint/rules 0.5.0-alpha.82 → 0.5.0-alpha.85

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 (106) hide show
  1. package/lib/__tests__/alphabeticalScripts.spec.js +22 -24
  2. package/lib/__tests__/alphabeticalScripts.spec.js.map +1 -1
  3. package/lib/__tests__/bannedDependencies.spec.js +67 -9
  4. package/lib/__tests__/bannedDependencies.spec.js.map +1 -1
  5. package/lib/__tests__/consistentDependencies.spec.js +11 -10
  6. package/lib/__tests__/consistentDependencies.spec.js.map +1 -1
  7. package/lib/__tests__/consistentVersions.spec.js +23 -22
  8. package/lib/__tests__/consistentVersions.spec.js.map +1 -1
  9. package/lib/__tests__/fileContents.spec.js +25 -32
  10. package/lib/__tests__/fileContents.spec.js.map +1 -1
  11. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js +49 -48
  12. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js.map +1 -1
  13. package/lib/__tests__/nestedWorkspaces.spec.js +10 -8
  14. package/lib/__tests__/nestedWorkspaces.spec.js.map +1 -1
  15. package/lib/__tests__/packageEntry.spec.js +47 -40
  16. package/lib/__tests__/packageEntry.spec.js.map +1 -1
  17. package/lib/__tests__/packageOrder.spec.js +34 -33
  18. package/lib/__tests__/packageOrder.spec.js.map +1 -1
  19. package/lib/__tests__/packageScript.spec.js +50 -51
  20. package/lib/__tests__/packageScript.spec.js.map +1 -1
  21. package/lib/__tests__/requireDependency.spec.js +12 -11
  22. package/lib/__tests__/requireDependency.spec.js.map +1 -1
  23. package/lib/__tests__/utils.d.ts +68 -1
  24. package/lib/__tests__/utils.d.ts.map +1 -1
  25. package/lib/__tests__/utils.js +70 -21
  26. package/lib/__tests__/utils.js.map +1 -1
  27. package/lib/alphabeticalDependencies.js +3 -3
  28. package/lib/alphabeticalDependencies.js.map +1 -1
  29. package/lib/alphabeticalScripts.js +1 -1
  30. package/lib/alphabeticalScripts.js.map +1 -1
  31. package/lib/bannedDependencies.d.ts +21 -12
  32. package/lib/bannedDependencies.d.ts.map +1 -1
  33. package/lib/bannedDependencies.js +86 -57
  34. package/lib/bannedDependencies.js.map +1 -1
  35. package/lib/consistentDependencies.d.ts +6 -6
  36. package/lib/consistentDependencies.d.ts.map +1 -1
  37. package/lib/consistentDependencies.js +2 -3
  38. package/lib/consistentDependencies.js.map +1 -1
  39. package/lib/consistentVersions.d.ts +1 -1
  40. package/lib/consistentVersions.js +8 -8
  41. package/lib/consistentVersions.js.map +1 -1
  42. package/lib/fileContents.d.ts +8 -8
  43. package/lib/fileContents.d.ts.map +1 -1
  44. package/lib/fileContents.js +32 -31
  45. package/lib/fileContents.js.map +1 -1
  46. package/lib/index.js +1 -0
  47. package/lib/index.js.map +1 -1
  48. package/lib/mustSatisfyPeerDependencies.d.ts +31 -31
  49. package/lib/mustSatisfyPeerDependencies.d.ts.map +1 -1
  50. package/lib/mustSatisfyPeerDependencies.js +18 -16
  51. package/lib/mustSatisfyPeerDependencies.js.map +1 -1
  52. package/lib/packageEntry.d.ts +11 -20
  53. package/lib/packageEntry.d.ts.map +1 -1
  54. package/lib/packageEntry.js +15 -7
  55. package/lib/packageEntry.js.map +1 -1
  56. package/lib/packageOrder.d.ts +3 -3
  57. package/lib/packageOrder.d.ts.map +1 -1
  58. package/lib/packageOrder.js +2 -3
  59. package/lib/packageOrder.js.map +1 -1
  60. package/lib/packageScript.d.ts +10 -10
  61. package/lib/packageScript.js +4 -4
  62. package/lib/packageScript.js.map +1 -1
  63. package/lib/requireDependency.d.ts +6 -6
  64. package/lib/requireDependency.d.ts.map +1 -1
  65. package/lib/requireDependency.js +3 -3
  66. package/lib/requireDependency.js.map +1 -1
  67. package/lib/standardTsconfig.d.ts +10 -10
  68. package/lib/standardTsconfig.d.ts.map +1 -1
  69. package/lib/standardTsconfig.js +26 -21
  70. package/lib/standardTsconfig.js.map +1 -1
  71. package/lib/util/checkAlpha.d.ts +1 -0
  72. package/lib/util/checkAlpha.d.ts.map +1 -1
  73. package/lib/util/checkAlpha.js +8 -5
  74. package/lib/util/checkAlpha.js.map +1 -1
  75. package/lib/util/makeDirectory.js +2 -2
  76. package/lib/util/makeDirectory.js.map +1 -1
  77. package/lib/util/packageDependencyGraphService.d.ts +3 -3
  78. package/lib/util/packageDependencyGraphService.d.ts.map +1 -1
  79. package/lib/util/packageDependencyGraphService.js +3 -3
  80. package/lib/util/packageDependencyGraphService.js.map +1 -1
  81. package/package.json +8 -9
  82. package/src/__tests__/alphabeticalScripts.spec.ts +27 -28
  83. package/src/__tests__/bannedDependencies.spec.ts +80 -6
  84. package/src/__tests__/consistentDependencies.spec.ts +11 -6
  85. package/src/__tests__/consistentVersions.spec.ts +32 -27
  86. package/src/__tests__/fileContents.spec.ts +32 -40
  87. package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +52 -47
  88. package/src/__tests__/nestedWorkspaces.spec.ts +12 -6
  89. package/src/__tests__/packageEntry.spec.ts +64 -45
  90. package/src/__tests__/packageOrder.spec.ts +46 -40
  91. package/src/__tests__/packageScript.spec.ts +62 -54
  92. package/src/__tests__/requireDependency.spec.ts +11 -6
  93. package/src/__tests__/utils.ts +109 -19
  94. package/src/bannedDependencies.ts +106 -74
  95. package/src/consistentDependencies.ts +1 -2
  96. package/src/consistentVersions.ts +2 -2
  97. package/src/fileContents.ts +32 -30
  98. package/src/mustSatisfyPeerDependencies.ts +6 -2
  99. package/src/packageEntry.ts +13 -5
  100. package/src/packageOrder.ts +1 -2
  101. package/src/packageScript.ts +2 -2
  102. package/src/requireDependency.ts +2 -2
  103. package/src/standardTsconfig.ts +22 -19
  104. package/src/util/checkAlpha.ts +6 -3
  105. package/src/util/packageDependencyGraphService.ts +8 -5
  106. package/tsconfig.tsbuildinfo +1 -6233
@@ -6,109 +6,141 @@
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";
12
11
  import path from "path";
13
12
  import * as r from "runtypes";
14
13
  import { IPackageDependencyGraphNode, PackageDependencyGraphService } from "./util/packageDependencyGraphService";
15
14
 
15
+ // FIXME: This rule is messed. bannedTransitiveDependencies doesnt glob
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
+
16
25
  const Options = r.Union(
17
- r
18
- .Record({
19
- bannedDependencies: r.Array(r.String),
20
- })
21
- .And(
22
- r.Partial({
23
- bannedTransitiveDependencies: r.Undefined,
24
- })
25
- ),
26
- r
27
- .Record({
28
- bannedTransitiveDependencies: r.Array(r.String),
29
- })
30
- .And(
31
- r.Partial({
32
- bannedDependencies: r.Undefined,
33
- })
34
- ),
35
26
  r.Record({
36
- bannedDependencies: r.Array(r.String),
27
+ bannedDependencies: bannedDepGlobsField,
28
+ bannedTransitiveDependencies: r.Undefined.optional(),
29
+ }),
30
+
31
+ r.Record({
32
+ bannedDependencies: bannedDepGlobsField.optional(),
37
33
  bannedTransitiveDependencies: r.Array(r.String),
34
+ }),
35
+
36
+ r.Record({
37
+ bannedDependencies: bannedDepGlobsField.optional(),
38
+ bannedTransitiveDependencies: r.Array(r.String).optional(),
38
39
  })
39
40
  );
40
41
 
41
42
  export type Options = r.Static<typeof Options>;
42
43
 
43
- export const bannedDependencies: RuleModule<typeof Options> = {
44
- check: function expectAllowedDependencies(context: Context, opts: Options) {
45
- // tslint:disable-next-line:no-shadowed-variable
46
- const { bannedDependencies, bannedTransitiveDependencies } = opts;
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>>();
47
49
 
48
- if (bannedDependencies) {
49
- checkBanned(context, bannedDependencies, "dependencies");
50
- checkBanned(context, bannedDependencies, "devDependencies");
51
- checkBanned(context, bannedDependencies, "peerDependencies");
52
- }
50
+ const aggregateTiming = new AggregateTiming(":bannedDependencies stats");
53
51
 
54
- if (bannedTransitiveDependencies) {
55
- checkTransitives(context, bannedTransitiveDependencies);
56
- }
57
- },
58
- optionsRuntype: Options,
59
- };
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");
60
57
 
61
- function checkBanned(
62
- context: Context,
63
- // tslint:disable-next-line:no-shadowed-variable
64
- bannedDependencies: ReadonlyArray<string>,
65
- block: "dependencies" | "devDependencies" | "peerDependencies"
66
- ) {
67
- const packageJson = context.getPackageJson();
68
- const packagePath = context.getPackageJsonPath();
58
+ const packageJson = context.getPackageJson();
59
+ const packagePath = context.getPackageJsonPath();
69
60
 
70
- const dependencies = packageJson[block];
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);
71
64
 
72
- if (dependencies === undefined) {
73
- return;
74
- }
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);
75
69
 
76
- const newPackageJson = { ...packageJson };
77
- const violations: string[] = [];
70
+ const violations = new Set<string>();
78
71
 
79
- for (const dependency of Object.keys(dependencies)) {
80
- for (const bannedDependency of bannedDependencies) {
81
- if (minimatch(dependency, bannedDependency)) {
82
- violations.push(dependency);
83
- delete newPackageJson[block]![dependency];
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);
84
83
  }
84
+ if (curDeps) populateProblemsExact(set, curDeps, violations);
85
+ if (curDevDeps) populateProblemsExact(set, curDevDeps, violations);
86
+ if (curPeerDeps) populateProblemsExact(set, curPeerDeps, violations);
85
87
  }
86
- }
87
88
 
88
- if (violations.length > 0) {
89
- context.addError({
90
- file: packagePath,
91
- message:
92
- `Found ${violations.length} banned dependencies in '${block}' block of package.json:\n\t` +
93
- violations.map((v) => `'${v}'`).join(", "),
94
- longMessage: diff(newPackageJson[block], dependencies, { expand: true }),
95
- fixer: () => {
96
- writeJson(packagePath, newPackageJson);
97
- },
98
- });
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();
110
+ },
111
+ optionsRuntype: Options,
112
+ printStats: () => {
113
+ aggregateTiming.printResults();
114
+ },
115
+ };
116
+
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
+ }
99
122
  }
100
123
  }
101
124
 
102
- function checkTransitives(
103
- context: Context,
104
- // tslint:disable-next-line: no-shadowed-variable
105
- bannedDependencies: ReadonlyArray<string>
125
+ function populateProblemsGlobs(
126
+ bannedDependencyGlobs: ReadonlyArray<string>,
127
+ dependencies: ReadonlyArray<string>,
128
+ violations: Set<string>
106
129
  ) {
130
+ for (const dependency of dependencies) {
131
+ if (matchesAnyGlob(dependency, bannedDependencyGlobs)) {
132
+ violations.add(dependency);
133
+ }
134
+ }
135
+ }
136
+
137
+ // This function is slow. God help you if you use this on a big repo
138
+ function checkTransitives(context: Context, banned: Set<string>) {
107
139
  const graphService = new PackageDependencyGraphService();
108
- const root = graphService.buildDependencyGraph(path.resolve(context.getPackageJsonPath()));
140
+ const root = graphService.buildDependencyGraph(path.resolve(context.getPackageJsonPath()), context.host);
109
141
  for (const { dependencies, importPath } of graphService.traverse(root)) {
110
142
  for (const [dependency] of dependencies) {
111
- if (bannedDependencies.includes(dependency)) {
143
+ if (banned.has(dependency)) {
112
144
  // Remove the starting package since it's obvious in CLI output.
113
145
  const [, ...importPathWithoutRoot] = importPath;
114
146
  const pathing = [...importPathWithoutRoot.map(nameOrPackageJsonPath), dependency].join(" -> ");
@@ -6,7 +6,6 @@
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
 
@@ -59,7 +58,7 @@ function checkDeps(context: Context, args: Options, block: "dependencies" | "dev
59
58
  fixer: () => {
60
59
  const newPackageJson = { ...packageJson };
61
60
  newPackageJson[block] = expectedDependencies;
62
- writeJson(packagePath, newPackageJson);
61
+ context.host.writeJson(packagePath, newPackageJson);
63
62
  },
64
63
  });
65
64
  }
@@ -58,7 +58,7 @@ const ensurePackageIsCorrectVersion = (
58
58
  file: packageJsonPath,
59
59
  message: `Expected dependency on ${dependencyPackageName} to match version defined in monorepolint configuration '${expectedPackageDependencyValue}', got '${actualPackageDependencyValue}' instead.`,
60
60
  fixer: () =>
61
- mutateJson<PackageJson>(packageJsonPath, (input) => {
61
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
62
62
  input.dependencies![dependencyPackageName] = expectedPackageDependencyValue;
63
63
  return input;
64
64
  }),
@@ -76,7 +76,7 @@ const ensurePackageIsCorrectVersion = (
76
76
  file: packageJsonPath,
77
77
  message: `Expected devDependency on ${dependencyPackageName} to match version defined in monorepolint configuration '${expectedPackageDependencyValue}', got '${actualPackageDevDependencyValue}' instead`,
78
78
  fixer: () =>
79
- mutateJson<PackageJson>(packageJsonPath, (input) => {
79
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
80
80
  input.devDependencies![dependencyPackageName] = expectedPackageDependencyValue;
81
81
  return input;
82
82
  }),
@@ -7,31 +7,29 @@
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";
14
- import { makeDirectoryRecursively } from "./util/makeDirectory";
15
13
 
16
14
  const Options = r.Union(
17
15
  r.Record({
18
16
  file: r.String,
19
17
  generator: r.Function,
20
- template: r.Undefined,
21
- templateFile: r.Undefined,
18
+ template: r.Undefined.optional(),
19
+ templateFile: r.Undefined.optional(),
22
20
  }),
23
21
 
24
22
  r.Record({
25
23
  file: r.String,
26
- generator: r.Undefined,
24
+ generator: r.Undefined.optional(),
27
25
  template: r.String,
28
- templateFile: r.Undefined,
26
+ templateFile: r.Undefined.optional(),
29
27
  }),
30
28
 
31
29
  r.Record({
32
30
  file: r.String,
33
- generator: r.Undefined,
34
- template: r.Undefined,
31
+ generator: r.Undefined.optional(),
32
+ template: r.Undefined.optional(),
35
33
  templateFile: r.String,
36
34
  })
37
35
  );
@@ -41,22 +39,21 @@ type Options = r.Static<typeof Options>;
41
39
  export const fileContents = {
42
40
  check: function expectFileContents(context: Context, opts: Options) {
43
41
  const fullPath = path.join(context.packageDir, opts.file);
44
- const generator = getGenerator(context, opts);
45
- const expectedContent = generator(context);
42
+ const expectedContent = 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
  });
@@ -65,25 +62,30 @@ export const fileContents = {
65
62
  optionsRuntype: Options,
66
63
  } as RuleModule<typeof Options>;
67
64
 
68
- 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
+
69
77
  if (opts.generator) {
70
- 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
71
80
  } else if (opts.templateFile) {
72
81
  const { packageDir: workspacePackageDir } = context.getWorkspaceContext();
73
82
  const fullPath = path.resolve(workspacePackageDir, opts.templateFile);
74
- const template = readFileSync(fullPath, "utf-8");
83
+ const template = context.host.readFile(fullPath, { encoding: "utf-8" });
75
84
 
76
- return makeGenerator(template);
77
- } else if (opts.template) {
78
- return makeGenerator(opts.template);
85
+ optionsCache.set(opts, template);
86
+ return template;
79
87
  } else {
80
- throw new Error("Unable to make generator");
88
+ optionsCache.set(opts, opts.template);
89
+ return opts.template;
81
90
  }
82
91
  }
83
-
84
- function makeGenerator(template: string) {
85
- // tslint:disable-next-line:variable-name
86
- return function generator(_context: Context) {
87
- return template;
88
- };
89
- }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { Context, RuleModule } from "@monorepolint/core";
9
- import { mutateJson, PackageJson } from "@monorepolint/utils";
9
+ import { Host, mutateJson, PackageJson } from "@monorepolint/utils";
10
10
  import path from "path";
11
11
  import resolvePackagePath from "resolve-package-path";
12
12
  import * as r from "runtypes";
@@ -398,6 +398,7 @@ function checkSatisfyPeerDependencies(context: Context, opts: Options) {
398
398
  dependencyType: "peerDependencies",
399
399
  dependencyName: peerDependencyName,
400
400
  version: mostStrictPeerRequirement.range,
401
+ host: context.host,
401
402
  }),
402
403
  });
403
404
  }
@@ -423,6 +424,7 @@ function checkSatisfyPeerDependencies(context: Context, opts: Options) {
423
424
  dependencyType: "peerDependencies",
424
425
  dependencyName: peerDependencyName,
425
426
  version: mostStrictPeerRequirement.range,
427
+ host: context.host,
426
428
  }),
427
429
  });
428
430
  }
@@ -717,14 +719,16 @@ function getAddDependencyTypeFixer({
717
719
  dependencyType,
718
720
  dependencyName,
719
721
  version,
722
+ host,
720
723
  }: {
721
724
  packageJsonPath: string;
722
725
  dependencyType: IDependencyType;
723
726
  dependencyName: string;
724
727
  version: string;
728
+ host: Host;
725
729
  }) {
726
730
  return () => {
727
- mutateJson<PackageJson>(packageJsonPath, (packageJson) => {
731
+ mutateJson<PackageJson>(packageJsonPath, host, (packageJson) => {
728
732
  if (packageJson[dependencyType] == null) {
729
733
  packageJson[dependencyType] = {};
730
734
  }
@@ -37,7 +37,7 @@ export const Options = r.Union(
37
37
 
38
38
  export type Options = r.Static<typeof Options>;
39
39
 
40
- export const packageEntry = {
40
+ export const packageEntry: RuleModule<typeof Options> = {
41
41
  check: function expectPackageEntry(context: Context, options: Options) {
42
42
  const packageJson = context.getPackageJson();
43
43
 
@@ -53,10 +53,10 @@ export const packageEntry = {
53
53
  ) {
54
54
  context.addError({
55
55
  file: context.getPackageJsonPath(),
56
- message: `Expected standardized entry for '${key}'`,
56
+ message: createStandardizedEntryErrorMessage(key),
57
57
  longMessage: entryDiff,
58
58
  fixer: () => {
59
- mutateJson<PackageJson>(context.getPackageJsonPath(), (input) => {
59
+ mutateJson<PackageJson>(context.getPackageJsonPath(), context.host, (input) => {
60
60
  input[key] = value;
61
61
  return input;
62
62
  });
@@ -71,11 +71,19 @@ export const packageEntry = {
71
71
  if (packageJson[key] === undefined) {
72
72
  context.addError({
73
73
  file: context.getPackageJsonPath(),
74
- message: `Expected entry for '${key}' to exist`,
74
+ message: createExpectedEntryErrorMessage(key),
75
75
  });
76
76
  }
77
77
  }
78
78
  }
79
79
  },
80
80
  optionsRuntype: Options,
81
- } as RuleModule<typeof Options>;
81
+ };
82
+
83
+ export function createStandardizedEntryErrorMessage(key: string) {
84
+ return `Expected standardized entry for '${key}'`;
85
+ }
86
+
87
+ export function createExpectedEntryErrorMessage(key: string) {
88
+ return `Expected entry for '${key}' to exist`;
89
+ }
@@ -6,7 +6,6 @@
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
 
@@ -73,7 +72,7 @@ export const packageOrder = {
73
72
  expectedPackageJson[key] = packageJson[key];
74
73
  });
75
74
 
76
- writeJson(packagePath, expectedPackageJson);
75
+ context.host.writeJson(packagePath, expectedPackageJson);
77
76
  },
78
77
  });
79
78
  }
@@ -39,7 +39,7 @@ export const packageScript = {
39
39
  file: context.getPackageJsonPath(),
40
40
  message: MSG_NO_SCRIPTS_BLOCK,
41
41
  fixer: () => {
42
- mutateJson<PackageJson>(context.getPackageJsonPath(), (input) => {
42
+ mutateJson<PackageJson>(context.getPackageJsonPath(), context.host, (input) => {
43
43
  input.scripts = {};
44
44
  return input;
45
45
  });
@@ -75,7 +75,7 @@ export const packageScript = {
75
75
  if (fixValue !== false && (fixValue !== undefined || fixToEmpty === true)) {
76
76
  const q = fixValue;
77
77
  fixer = () => {
78
- mutateJson<PackageJson>(context.getPackageJsonPath(), (input) => {
78
+ mutateJson<PackageJson>(context.getPackageJsonPath(), context.host, (input) => {
79
79
  if (fixToEmpty && q === undefined) {
80
80
  delete input.scripts![name];
81
81
  } else {
@@ -39,7 +39,7 @@ export const requireDependency = {
39
39
  file: packageJsonPath,
40
40
  message: `No ${type} block, cannot add required ${type}.`,
41
41
  fixer: () => {
42
- mutateJson<PackageJson>(packageJsonPath, (input) => {
42
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
43
43
  input[type] = options[type];
44
44
  return input;
45
45
  });
@@ -55,7 +55,7 @@ export const requireDependency = {
55
55
  message: `Expected dependency ${dep}@${version}`,
56
56
  longMessage: diff(`${dep}@${version}\n`, `${dep}@${packageJson[type][dep] || "missing"}\n`)!,
57
57
  fixer: () => {
58
- mutateJson<PackageJson>(packageJsonPath, (input) => {
58
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
59
59
  input[type] = { ...input[type], [dep]: version };
60
60
  return input;
61
61
  });
@@ -6,9 +6,8 @@
6
6
  */
7
7
 
8
8
  import { Context, RuleModule } from "@monorepolint/core";
9
- import { existsSync, readFileSync, writeFileSync } from "fs";
9
+ import { matchesAnyGlob } from "@monorepolint/utils";
10
10
  import diff from "jest-diff";
11
- import minimatch from "minimatch";
12
11
  import * as path from "path";
13
12
  import * as r from "runtypes";
14
13
 
@@ -48,7 +47,9 @@ export const standardTsconfig = {
48
47
  const generator = getGenerator(context, opts);
49
48
  const expectedContent = generator(context);
50
49
 
51
- const actualContent = existsSync(fullPath) ? readFileSync(fullPath, "utf-8") : undefined;
50
+ const actualContent = context.host.exists(fullPath)
51
+ ? context.host.readFile(fullPath, { encoding: "utf-8" })
52
+ : undefined;
52
53
 
53
54
  if (expectedContent === undefined) {
54
55
  context.addWarning({
@@ -64,7 +65,9 @@ export const standardTsconfig = {
64
65
  message: "Expect file contents to match",
65
66
  longMessage: diff(expectedContent, actualContent, { expand: true }),
66
67
  fixer: () => {
67
- writeFileSync(fullPath, expectedContent);
68
+ context.host.writeFile(fullPath, expectedContent, {
69
+ encoding: "utf-8",
70
+ });
68
71
  },
69
72
  });
70
73
  }
@@ -78,7 +81,7 @@ function getGenerator(context: Context, opts: Options) {
78
81
  } else if (opts.templateFile) {
79
82
  const { packageDir: workspacePackageDir } = context.getWorkspaceContext();
80
83
  const fullPath = path.resolve(workspacePackageDir, opts.templateFile);
81
- const template = JSON.parse(readFileSync(fullPath, "utf-8"));
84
+ const template = JSON.parse(context.host.readFile(fullPath, { encoding: "utf-8" }));
82
85
 
83
86
  return makeGenerator(template, opts.excludedReferences, opts.additionalReferences, opts.tsconfigReferenceFile);
84
87
  } else if (opts.template) {
@@ -90,8 +93,8 @@ function getGenerator(context: Context, opts: Options) {
90
93
 
91
94
  function makeGenerator(
92
95
  template: any,
93
- excludedReferences: ReadonlyArray<string> = [],
94
- additionalReferences: ReadonlyArray<string> = [],
96
+ excludedReferences: ReadonlyArray<string> | undefined,
97
+ additionalReferences: ReadonlyArray<string> | undefined,
95
98
  tsconfigReferenceFile?: string
96
99
  ) {
97
100
  return function generator(context: Context) {
@@ -105,24 +108,24 @@ function makeGenerator(
105
108
  const packageJson = context.getPackageJson();
106
109
  const deps = [...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.devDependencies || {})];
107
110
 
108
- deps
109
- .filter(
110
- (name) => nameToDirectory.has(name) && !excludedReferences.some((excludedRef) => minimatch(name, excludedRef))
111
- )
112
- .forEach((packageName) => {
113
- const packageDir = nameToDirectory.get(packageName)!;
111
+ for (const dep of deps) {
112
+ const packageDir = nameToDirectory.get(dep);
113
+ if (packageDir !== undefined && (excludedReferences === undefined || matchesAnyGlob(dep, excludedReferences))) {
114
114
  const absoluteReferencePath =
115
115
  tsconfigReferenceFile !== undefined ? path.join(packageDir, tsconfigReferenceFile) : packageDir;
116
116
  template.references.push({
117
117
  path: path.relative(context.packageDir, absoluteReferencePath),
118
118
  });
119
- });
119
+ }
120
+ }
120
121
 
121
- additionalReferences.forEach((additionalReference) => {
122
- template.references.push({
123
- path: additionalReference,
124
- });
125
- });
122
+ if (additionalReferences) {
123
+ for (const additionalReference of additionalReferences) {
124
+ template.references.push({
125
+ path: additionalReference,
126
+ });
127
+ }
128
+ }
126
129
 
127
130
  return JSON.stringify(template, undefined, 2) + "\n";
128
131
  };
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import { Context } from "@monorepolint/core";
9
- import { writeJson } from "@monorepolint/utils";
10
9
  import diff from "jest-diff";
11
10
 
12
11
  export function checkAlpha(
@@ -28,7 +27,7 @@ export function checkAlpha(
28
27
  if (!arrayOrderCompare(actualOrder, expectedOrder)) {
29
28
  context.addError({
30
29
  file: packagePath,
31
- message: `Incorrect order of ${block} in ${packageJson.name}'s package.json`,
30
+ message: createIncorrectOrderErrorMessage(block, packageJson.name!),
32
31
  longMessage: diff(expectedOrder, actualOrder, { expand: true }),
33
32
  fixer: () => {
34
33
  const expectedDependencies: Record<string, string> = {};
@@ -39,7 +38,7 @@ export function checkAlpha(
39
38
 
40
39
  const newPackageJson = { ...packageJson };
41
40
  newPackageJson[block] = expectedDependencies;
42
- writeJson(packagePath, newPackageJson);
41
+ context.host.writeJson(packagePath, newPackageJson);
43
42
  },
44
43
  });
45
44
  }
@@ -54,3 +53,7 @@ function arrayOrderCompare(a: ReadonlyArray<string>, b: ReadonlyArray<string>) {
54
53
 
55
54
  return true;
56
55
  }
56
+
57
+ export function createIncorrectOrderErrorMessage(block: string, packageName: string) {
58
+ return `Incorrect order of ${block} in ${packageName}'s package.json`;
59
+ }
@@ -2,7 +2,7 @@
2
2
  * @license Copyright 2019 Palantir Technologies, Inc. All rights reserved.
3
3
  */
4
4
 
5
- import { PackageJson } from "@monorepolint/utils";
5
+ import { Host, PackageJson } from "@monorepolint/utils";
6
6
  import path from "path";
7
7
  import resolvePackagePath from "resolve-package-path";
8
8
 
@@ -19,7 +19,7 @@ export interface IPackageDependencyGraphNode {
19
19
  /** Service abstraction for constructing and traversing package dependency graphs. */
20
20
  export interface IPackageDependencyGraphService {
21
21
  /** Construct a graph of package dependencies. */
22
- buildDependencyGraph(packageJsonPath: string, maxDepth?: number): IPackageDependencyGraphNode;
22
+ buildDependencyGraph(packageJsonPath: string, host: Host, maxDepth?: number): IPackageDependencyGraphNode;
23
23
 
24
24
  /** Traverse a package dependency graph. */
25
25
  traverse(
@@ -34,15 +34,18 @@ export interface IPackageDependencyGraphService {
34
34
  /** Default implementation of the package dependency graph service. */
35
35
  export class PackageDependencyGraphService implements IPackageDependencyGraphService {
36
36
  /** Construct a graph of package dependencies and return the root node. */
37
- public buildDependencyGraph(startPackageJsonPath: string, maxDepth?: number): IPackageDependencyGraphNode {
37
+ public buildDependencyGraph(
38
+ startPackageJsonPath: string,
39
+ host: Host,
40
+ maxDepth?: number
41
+ ): IPackageDependencyGraphNode {
38
42
  const nodes = new Map<string, IPackageDependencyGraphNode>();
39
-
40
43
  const visit = (packageJsonPath: string, currentDepth: number): IPackageDependencyGraphNode => {
41
44
  if (nodes.has(packageJsonPath)) {
42
45
  return nodes.get(packageJsonPath)!;
43
46
  }
44
47
 
45
- const packageJson: PackageJson = require(packageJsonPath);
48
+ const packageJson: PackageJson = host.readJson(packageJsonPath);
46
49
  const node: IPackageDependencyGraphNode = {
47
50
  packageJson,
48
51
  dependencies: new Map<string, IPackageDependencyGraphNode>(),