@monorepolint/rules 0.5.0-alpha.7 → 0.5.0-alpha.73

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 (131) 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 +63 -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 +103 -0
  8. package/lib/__tests__/bannedDependencies.spec.js.map +1 -0
  9. package/lib/__tests__/consistentDependencies.spec.js +26 -11
  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 +182 -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 +66 -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 +1062 -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 +122 -0
  26. package/lib/__tests__/nestedWorkspaces.spec.js.map +1 -0
  27. package/lib/__tests__/packageEntry.spec.d.ts +8 -0
  28. package/lib/__tests__/packageEntry.spec.d.ts.map +1 -0
  29. package/lib/__tests__/packageEntry.spec.js +137 -0
  30. package/lib/__tests__/packageEntry.spec.js.map +1 -0
  31. package/lib/__tests__/requireDependency.spec.d.ts +2 -0
  32. package/lib/__tests__/requireDependency.spec.d.ts.map +1 -0
  33. package/lib/__tests__/requireDependency.spec.js +122 -0
  34. package/lib/__tests__/requireDependency.spec.js.map +1 -0
  35. package/lib/__tests__/utils.d.ts +1 -0
  36. package/lib/__tests__/utils.d.ts.map +1 -1
  37. package/lib/__tests__/utils.js +11 -0
  38. package/lib/__tests__/utils.js.map +1 -1
  39. package/lib/alphabeticalDependencies.d.ts +3 -1
  40. package/lib/alphabeticalDependencies.d.ts.map +1 -1
  41. package/lib/alphabeticalDependencies.js +5 -39
  42. package/lib/alphabeticalDependencies.js.map +1 -1
  43. package/lib/alphabeticalScripts.d.ts +12 -0
  44. package/lib/alphabeticalScripts.d.ts.map +1 -0
  45. package/lib/alphabeticalScripts.js +20 -0
  46. package/lib/alphabeticalScripts.js.map +1 -0
  47. package/lib/bannedDependencies.d.ts +12 -3
  48. package/lib/bannedDependencies.d.ts.map +1 -1
  49. package/lib/bannedDependencies.js +59 -14
  50. package/lib/bannedDependencies.js.map +1 -1
  51. package/lib/consistentDependencies.d.ts +8 -1
  52. package/lib/consistentDependencies.d.ts.map +1 -1
  53. package/lib/consistentDependencies.js +28 -9
  54. package/lib/consistentDependencies.js.map +1 -1
  55. package/lib/consistentVersions.d.ts +14 -0
  56. package/lib/consistentVersions.d.ts.map +1 -0
  57. package/lib/consistentVersions.js +94 -0
  58. package/lib/consistentVersions.js.map +1 -0
  59. package/lib/fileContents.d.ts.map +1 -1
  60. package/lib/fileContents.js +6 -2
  61. package/lib/fileContents.js.map +1 -1
  62. package/lib/index.d.ts +5 -0
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +18 -8
  65. package/lib/index.js.map +1 -1
  66. package/lib/mustSatisfyPeerDependencies.d.ts +240 -0
  67. package/lib/mustSatisfyPeerDependencies.d.ts.map +1 -0
  68. package/lib/mustSatisfyPeerDependencies.js +634 -0
  69. package/lib/mustSatisfyPeerDependencies.js.map +1 -0
  70. package/lib/nestedWorkspaces.d.ts +13 -0
  71. package/lib/nestedWorkspaces.d.ts.map +1 -0
  72. package/lib/nestedWorkspaces.js +50 -0
  73. package/lib/nestedWorkspaces.js.map +1 -0
  74. package/lib/packageEntry.d.ts +22 -4
  75. package/lib/packageEntry.d.ts.map +1 -1
  76. package/lib/packageEntry.js +45 -16
  77. package/lib/packageEntry.js.map +1 -1
  78. package/lib/packageOrder.d.ts.map +1 -1
  79. package/lib/packageOrder.js +6 -4
  80. package/lib/packageOrder.js.map +1 -1
  81. package/lib/packageScript.js +4 -3
  82. package/lib/packageScript.js.map +1 -1
  83. package/lib/requireDependency.d.ts +15 -0
  84. package/lib/requireDependency.d.ts.map +1 -0
  85. package/lib/requireDependency.js +65 -0
  86. package/lib/requireDependency.js.map +1 -0
  87. package/lib/standardTsconfig.d.ts +12 -0
  88. package/lib/standardTsconfig.d.ts.map +1 -1
  89. package/lib/standardTsconfig.js +17 -7
  90. package/lib/standardTsconfig.js.map +1 -1
  91. package/lib/util/checkAlpha.d.ts +9 -0
  92. package/lib/util/checkAlpha.d.ts.map +1 -0
  93. package/lib/util/checkAlpha.js +48 -0
  94. package/lib/util/checkAlpha.js.map +1 -0
  95. package/lib/util/makeDirectory.d.ts +8 -0
  96. package/lib/util/makeDirectory.d.ts.map +1 -0
  97. package/lib/util/makeDirectory.js +28 -0
  98. package/lib/util/makeDirectory.js.map +1 -0
  99. package/lib/util/packageDependencyGraphService.d.ts +37 -0
  100. package/lib/util/packageDependencyGraphService.d.ts.map +1 -0
  101. package/lib/util/packageDependencyGraphService.js +70 -0
  102. package/lib/util/packageDependencyGraphService.js.map +1 -0
  103. package/package.json +17 -13
  104. package/src/__tests__/alphabeticalScripts.spec.ts +76 -0
  105. package/src/__tests__/bannedDependencies.spec.ts +117 -0
  106. package/src/__tests__/consistentDependencies.spec.ts +29 -10
  107. package/src/__tests__/consistentVersions.spec.ts +218 -0
  108. package/src/__tests__/fileContents.spec.ts +82 -0
  109. package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +1183 -0
  110. package/src/__tests__/nestedWorkspaces.spec.ts +146 -0
  111. package/src/__tests__/packageEntry.spec.ts +181 -0
  112. package/src/__tests__/requireDependency.spec.ts +146 -0
  113. package/src/__tests__/utils.ts +12 -0
  114. package/src/alphabeticalDependencies.ts +3 -47
  115. package/src/alphabeticalScripts.ts +19 -0
  116. package/src/bannedDependencies.ts +74 -16
  117. package/src/consistentDependencies.ts +33 -10
  118. package/src/consistentVersions.ts +140 -0
  119. package/src/fileContents.ts +5 -3
  120. package/src/index.ts +5 -0
  121. package/src/mustSatisfyPeerDependencies.ts +735 -0
  122. package/src/nestedWorkspaces.ts +59 -0
  123. package/src/packageEntry.ts +56 -17
  124. package/src/packageOrder.ts +5 -4
  125. package/src/packageScript.ts +3 -3
  126. package/src/requireDependency.ts +69 -0
  127. package/src/standardTsconfig.ts +19 -7
  128. package/src/util/checkAlpha.ts +56 -0
  129. package/src/util/makeDirectory.ts +24 -0
  130. package/src/util/packageDependencyGraphService.ts +111 -0
  131. package/tsconfig.tsbuildinfo +5399 -1591
@@ -0,0 +1,146 @@
1
+ /*!
2
+ * Copyright 2019 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+ import { WorkspaceContext } from "@monorepolint/core";
8
+ import { writeFileSync } from "fs";
9
+ import * as path from "path";
10
+ import * as tmp from "tmp";
11
+ import { nestedWorkspaces } from "../nestedWorkspaces";
12
+ import { makeDirectoryRecursively } from "../util/makeDirectory";
13
+ import { jsonToString } from "./utils";
14
+
15
+ const EMPTY_PACKAGE = jsonToString({});
16
+
17
+ const PACKAGE_ROOT_WITH_PACKAGES_STAR = jsonToString({
18
+ workspaces: {
19
+ packages: ["packages/*"],
20
+ },
21
+ });
22
+
23
+ const PACKAGE_ROOT_WITH_TWO_LEVEL = jsonToString({
24
+ workspaces: {
25
+ packages: ["packages/*", "packages/deep/*"],
26
+ },
27
+ });
28
+
29
+ const PACKAGE_ROOT_WITH_THREE_LEVEL = jsonToString({
30
+ workspaces: {
31
+ packages: ["packages/*", "packages/deep/*", "packages/very/deep/*"],
32
+ },
33
+ });
34
+
35
+ describe("nestedWorkspaces", () => {
36
+ tmp.setGracefulCleanup();
37
+
38
+ let cleanupJobs: Array<() => void> = [];
39
+ let cwd: string | undefined;
40
+
41
+ beforeEach(() => {
42
+ const dir = tmp.dirSync({ unsafeCleanup: true });
43
+ cleanupJobs.push(() => dir.removeCallback());
44
+ cwd = dir.name;
45
+
46
+ const spy = jest.spyOn(process, "cwd");
47
+ spy.mockReturnValue(cwd);
48
+ });
49
+
50
+ afterEach(() => {
51
+ for (const cleanupJob of cleanupJobs) {
52
+ cleanupJob();
53
+ }
54
+ cleanupJobs = [];
55
+ });
56
+
57
+ function makeWorkspace() {
58
+ const workspaceContext = new WorkspaceContext(cwd!, {
59
+ rules: [],
60
+ fix: false,
61
+ verbose: false,
62
+ silent: true,
63
+ });
64
+
65
+ async function checkAndSpy() {
66
+ const addErrorSpy = jest.spyOn(workspaceContext, "addError");
67
+
68
+ await nestedWorkspaces.check(workspaceContext, undefined);
69
+ return { addErrorSpy };
70
+ }
71
+
72
+ function addFile(filePath: string, content: string) {
73
+ const dirPath = path.resolve(cwd!, path.dirname(filePath));
74
+ const resolvedFilePath = path.resolve(cwd!, filePath);
75
+
76
+ makeDirectoryRecursively(dirPath);
77
+ writeFileSync(resolvedFilePath, content);
78
+ return resolvedFilePath;
79
+ }
80
+
81
+ return { addFile, workspaceContext, checkAndSpy };
82
+ }
83
+
84
+ it("checks correctly when no packages", async () => {
85
+ const { addFile, checkAndSpy } = makeWorkspace();
86
+ addFile("./package.json", EMPTY_PACKAGE);
87
+
88
+ expect((await checkAndSpy()).addErrorSpy).toHaveBeenCalledTimes(0);
89
+ });
90
+
91
+ it("checks correctly when one level packages", async () => {
92
+ const { addFile, checkAndSpy } = makeWorkspace();
93
+ addFile("./package.json", PACKAGE_ROOT_WITH_PACKAGES_STAR);
94
+ addFile("./packages/star/package.json", EMPTY_PACKAGE);
95
+
96
+ expect((await checkAndSpy()).addErrorSpy).toHaveBeenCalledTimes(0);
97
+ });
98
+
99
+ it("checks fail when one level packages with no workspaces field", async () => {
100
+ const { addFile, checkAndSpy } = makeWorkspace();
101
+ const packageJsonPath = addFile("./package.json", EMPTY_PACKAGE);
102
+ addFile("./packages/star/package.json", EMPTY_PACKAGE);
103
+
104
+ const spy = (await checkAndSpy()).addErrorSpy;
105
+ expect(spy).toHaveBeenCalledTimes(1);
106
+ expect(spy).toHaveBeenCalledWith({
107
+ file: packageJsonPath,
108
+ message: 'The "workspace" field is missing, even though there are workspaces in the repository.',
109
+ });
110
+ });
111
+
112
+ it("checks correctly when two level packages with two level workspaces field", async () => {
113
+ const { addFile, checkAndSpy } = makeWorkspace();
114
+ addFile("./package.json", PACKAGE_ROOT_WITH_TWO_LEVEL);
115
+ addFile("./packages/star/package.json", EMPTY_PACKAGE);
116
+ addFile("./packages/deep/star/package.json", EMPTY_PACKAGE);
117
+
118
+ expect((await checkAndSpy()).addErrorSpy).toHaveBeenCalledTimes(0);
119
+ });
120
+
121
+ it("checks fail when two level packages with one level workspaces field", async () => {
122
+ const { addFile, checkAndSpy } = makeWorkspace();
123
+ const packageJsonPath = addFile("./package.json", PACKAGE_ROOT_WITH_PACKAGES_STAR);
124
+ addFile("./packages/star/package.json", EMPTY_PACKAGE);
125
+ addFile("./packages/deep/star/package.json", EMPTY_PACKAGE);
126
+
127
+ const spy = (await checkAndSpy()).addErrorSpy;
128
+ expect(spy).toHaveBeenCalledTimes(1);
129
+ expect(spy).toHaveBeenCalledWith({
130
+ file: packageJsonPath,
131
+ message:
132
+ 'The "workspace" field is missing one or more values: packages/deep/star. ' +
133
+ 'You may be able to use a glob to avoid listing each workspace individually, e.g. "packages/nested-workspace/*".',
134
+ });
135
+ });
136
+
137
+ it("checks correctly when three level packages with three level workspaces field", async () => {
138
+ const { addFile, checkAndSpy } = makeWorkspace();
139
+ addFile("./package.json", PACKAGE_ROOT_WITH_THREE_LEVEL);
140
+ addFile("./packages/star/package.json", EMPTY_PACKAGE);
141
+ addFile("./packages/deep/star/package.json", EMPTY_PACKAGE);
142
+ addFile("./packages/very/deep/star/package.json", EMPTY_PACKAGE);
143
+
144
+ expect((await checkAndSpy()).addErrorSpy).toHaveBeenCalledTimes(0);
145
+ });
146
+ });
@@ -0,0 +1,181 @@
1
+ /*!
2
+ * Copyright 2019 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+
8
+ // tslint:disable:no-console
9
+ import { createMockFiles } from "./utils";
10
+
11
+ // done first since this also mocks 'fs'
12
+ const mockFiles: Map<string, string> = createMockFiles();
13
+
14
+ import { Failure, PackageContext } from "@monorepolint/core";
15
+ import { packageEntry } from "../packageEntry";
16
+
17
+ const PACKAGE_MISSING_ENTRY =
18
+ JSON.stringify(
19
+ {
20
+ name: "package",
21
+ version: {},
22
+ scripts: {},
23
+ dependencies: {},
24
+ },
25
+ undefined,
26
+ 2
27
+ ) + "\n";
28
+
29
+ const PACKAGE_LICENSE =
30
+ JSON.stringify(
31
+ {
32
+ name: "package",
33
+ version: {},
34
+ scripts: {},
35
+ dependencies: {},
36
+ license: "UNLICENSED",
37
+ },
38
+ undefined,
39
+ 2
40
+ ) + "\n";
41
+
42
+ const PACKAGE_REPOSITORY =
43
+ JSON.stringify(
44
+ {
45
+ name: "package",
46
+ version: {},
47
+ scripts: {},
48
+ dependencies: {},
49
+ repository: {
50
+ type: "git",
51
+ url: "https://github.com:foo/foo",
52
+ },
53
+ },
54
+ undefined,
55
+ 2
56
+ ) + "\n";
57
+
58
+ describe("expectPackageEntries", () => {
59
+ afterEach(() => {
60
+ mockFiles.clear();
61
+ });
62
+
63
+ describe("fix: true", () => {
64
+ const context = new PackageContext(".", {
65
+ rules: [],
66
+ fix: true,
67
+ verbose: false,
68
+ silent: true,
69
+ });
70
+ const spy = jest.spyOn(context, "addError");
71
+
72
+ afterEach(() => {
73
+ spy.mockClear();
74
+ });
75
+
76
+ it("fixes missing entries", () => {
77
+ mockFiles.set("package.json", PACKAGE_MISSING_ENTRY);
78
+
79
+ packageEntry.check(context, {
80
+ entries: {
81
+ license: "UNLICENSED",
82
+ },
83
+ entriesExist: undefined,
84
+ });
85
+
86
+ expect(spy).toHaveBeenCalledTimes(1);
87
+
88
+ const failure: Failure = spy.mock.calls[0][0];
89
+ expect(failure.file).toBe("package.json");
90
+ expect(failure.fixer).not.toBeUndefined();
91
+ expect(failure.message).toBe("Expected standardized entry for 'license'");
92
+
93
+ expect(mockFiles.get("package.json")).toEqual(PACKAGE_LICENSE);
94
+ });
95
+
96
+ it("fixes missing nested entries", () => {
97
+ mockFiles.set("package.json", PACKAGE_MISSING_ENTRY);
98
+
99
+ packageEntry.check(context, {
100
+ entries: {
101
+ repository: {
102
+ type: "git",
103
+ url: "https://github.com:foo/foo",
104
+ },
105
+ },
106
+ entriesExist: undefined,
107
+ });
108
+
109
+ expect(spy).toHaveBeenCalledTimes(1);
110
+
111
+ const failure: Failure = spy.mock.calls[0][0];
112
+ expect(failure.file).toBe("package.json");
113
+ expect(failure.fixer).not.toBeUndefined();
114
+ expect(failure.message).toBe("Expected standardized entry for 'repository'");
115
+
116
+ expect(mockFiles.get("package.json")).toEqual(PACKAGE_REPOSITORY);
117
+ });
118
+
119
+ it("doesn't error for nested entries that are defined", () => {
120
+ mockFiles.set("package.json", PACKAGE_REPOSITORY);
121
+
122
+ packageEntry.check(context, {
123
+ entries: {
124
+ repository: {
125
+ type: "git",
126
+ url: "https://github.com:foo/foo",
127
+ },
128
+ },
129
+ entriesExist: undefined,
130
+ });
131
+
132
+ expect(spy).toHaveBeenCalledTimes(0);
133
+ expect(mockFiles.get("package.json")).toEqual(PACKAGE_REPOSITORY);
134
+ });
135
+
136
+ it("errors for keys that are missing", () => {
137
+ mockFiles.set("package.json", PACKAGE_REPOSITORY);
138
+
139
+ packageEntry.check(context, {
140
+ entries: undefined,
141
+ entriesExist: ["bugs"],
142
+ });
143
+
144
+ expect(spy).toHaveBeenCalledTimes(1);
145
+
146
+ const failure: Failure = spy.mock.calls[0][0];
147
+ expect(failure.file).toBe("package.json");
148
+ expect(failure.fixer).toBeUndefined();
149
+ expect(failure.message).toBe("Expected entry for 'bugs' to exist");
150
+ expect(mockFiles.get("package.json")).toEqual(PACKAGE_REPOSITORY);
151
+ });
152
+
153
+ it("handles both entries and entriesExist", () => {
154
+ mockFiles.set("package.json", PACKAGE_MISSING_ENTRY);
155
+
156
+ packageEntry.check(context, {
157
+ entries: {
158
+ repository: {
159
+ type: "git",
160
+ url: "https://github.com:foo/foo",
161
+ },
162
+ },
163
+ entriesExist: ["bugs"],
164
+ });
165
+
166
+ expect(spy).toHaveBeenCalledTimes(2);
167
+
168
+ const failure: Failure = spy.mock.calls[0][0];
169
+ expect(failure.file).toBe("package.json");
170
+ expect(failure.fixer).not.toBeUndefined();
171
+ expect(failure.message).toBe("Expected standardized entry for 'repository'");
172
+
173
+ const failure2: Failure = spy.mock.calls[1][0];
174
+ expect(failure2.file).toBe("package.json");
175
+ expect(failure2.fixer).toBeUndefined();
176
+ expect(failure2.message).toBe("Expected entry for 'bugs' to exist");
177
+
178
+ expect(mockFiles.get("package.json")).toEqual(PACKAGE_REPOSITORY);
179
+ });
180
+ });
181
+ });
@@ -0,0 +1,146 @@
1
+ /*!
2
+ * Copyright 2019 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+ import { WorkspaceContext } from "@monorepolint/core";
8
+ import { readFileSync, writeFileSync } from "fs";
9
+ import * as path from "path";
10
+ import * as tmp from "tmp";
11
+ import { requireDependency } from "../requireDependency";
12
+ import { makeDirectoryRecursively } from "../util/makeDirectory";
13
+ import { jsonToString } from "./utils";
14
+
15
+ const PACKAGE_ROOT = jsonToString({
16
+ workspaces: {
17
+ packages: ["packages/*"],
18
+ },
19
+ dependencies: {
20
+ foo: "5",
21
+ },
22
+ });
23
+
24
+ const PACKAGE_WITH_NO_ENTRIES = jsonToString({});
25
+
26
+ const PACKAGE_WITH_ENTRIES_MISSING = jsonToString({
27
+ dependencies: {},
28
+ devDependencies: {},
29
+ });
30
+
31
+ const PACKAGE_WITH_WRONG_ENTRIES = jsonToString({
32
+ dependencies: {
33
+ foo: "0.1.0",
34
+ },
35
+ devDependencies: {
36
+ bar: "1.0.0",
37
+ },
38
+ });
39
+
40
+ const PACKAGE_WITH_RIGHT_ENTRIES = jsonToString({
41
+ dependencies: {
42
+ foo: "1.0.0",
43
+ },
44
+ devDependencies: {
45
+ bar: "^2.0.0",
46
+ },
47
+ });
48
+
49
+ const OPTIONS = {
50
+ dependencies: {
51
+ foo: "1.0.0",
52
+ },
53
+ devDependencies: {
54
+ bar: "^2.0.0",
55
+ },
56
+ } as const;
57
+
58
+ const CORRECT_OUTPUT = jsonToString(OPTIONS);
59
+
60
+ describe("requireDependency", () => {
61
+ tmp.setGracefulCleanup();
62
+
63
+ let cleanupJobs: Array<() => void> = [];
64
+
65
+ afterEach(() => {
66
+ for (const cleanupJob of cleanupJobs) {
67
+ cleanupJob();
68
+ }
69
+ cleanupJobs = [];
70
+ });
71
+
72
+ function makeWorkspace({ fix }: { fix: boolean }) {
73
+ const dir: tmp.DirResult = tmp.dirSync({ unsafeCleanup: true });
74
+ cleanupJobs.push(() => dir.removeCallback());
75
+
76
+ const workspaceContext = new WorkspaceContext(dir.name, {
77
+ rules: [],
78
+ fix,
79
+ verbose: false,
80
+ silent: true,
81
+ });
82
+
83
+ function checkAndSpy(q: string) {
84
+ const context = workspaceContext.createChildContext(path.resolve(dir.name, q));
85
+ const addErrorSpy = jest.spyOn(context, "addError");
86
+ requireDependency.check(context, OPTIONS);
87
+ return { context, addErrorSpy };
88
+ }
89
+
90
+ function addFile(filePath: string, content: string) {
91
+ const dirPath = path.resolve(dir.name, path.dirname(filePath));
92
+ const resolvedFilePath = path.resolve(dir.name, filePath);
93
+
94
+ makeDirectoryRecursively(dirPath);
95
+ writeFileSync(resolvedFilePath, content);
96
+ }
97
+
98
+ function readFile(filePath: string) {
99
+ return readFileSync(path.resolve(dir.name, filePath)).toString();
100
+ }
101
+
102
+ return { addFile, readFile, workspaceContext, checkAndSpy };
103
+ }
104
+
105
+ it("checks correctly", () => {
106
+ const { addFile, workspaceContext, checkAndSpy } = makeWorkspace({ fix: false });
107
+ addFile("./package.json", PACKAGE_ROOT);
108
+ addFile("./packages/none/package.json", PACKAGE_WITH_NO_ENTRIES);
109
+ addFile("./packages/missing/package.json", PACKAGE_WITH_ENTRIES_MISSING);
110
+ addFile("./packages/wrong/package.json", PACKAGE_WITH_WRONG_ENTRIES);
111
+ addFile("./packages/right/package.json", PACKAGE_WITH_RIGHT_ENTRIES);
112
+
113
+ requireDependency.check(workspaceContext, OPTIONS);
114
+
115
+ const none = checkAndSpy("./packages/none");
116
+ expect(none.addErrorSpy).toHaveBeenCalledTimes(2);
117
+
118
+ const missing = checkAndSpy("./packages/missing");
119
+ expect(missing.addErrorSpy).toHaveBeenCalledTimes(2);
120
+
121
+ const wrong = checkAndSpy("./packages/wrong");
122
+ expect(wrong.addErrorSpy).toHaveBeenCalledTimes(2);
123
+
124
+ const right = checkAndSpy("./packages/right");
125
+ expect(right.addErrorSpy).toHaveBeenCalledTimes(0);
126
+ });
127
+
128
+ it("fixes correctly", () => {
129
+ const { addFile, readFile, checkAndSpy } = makeWorkspace({ fix: true });
130
+ addFile("./package.json", PACKAGE_ROOT);
131
+ addFile("./packages/missing/package.json", PACKAGE_WITH_ENTRIES_MISSING);
132
+ addFile("./packages/wrong/package.json", PACKAGE_WITH_WRONG_ENTRIES);
133
+
134
+ const missing = checkAndSpy("./packages/missing");
135
+ expect(missing.addErrorSpy).toHaveBeenCalledTimes(2);
136
+
137
+ const wrong = checkAndSpy("./packages/wrong");
138
+ expect(wrong.addErrorSpy).toHaveBeenCalledTimes(2);
139
+
140
+ const missingContents = readFile("./packages/missing/package.json");
141
+ expect(missingContents).toEqual(CORRECT_OUTPUT);
142
+
143
+ const contents = readFile("./packages/wrong/package.json");
144
+ expect(contents).toEqual(CORRECT_OUTPUT);
145
+ });
146
+ });
@@ -17,7 +17,19 @@ export function createMockFiles() {
17
17
  readFileSync: function readFileSync(filePath: string, _contentType: string) {
18
18
  return mockFiles.get(filePath);
19
19
  },
20
+
21
+ existsSync: function existsSync(filePath: string) {
22
+ return mockFiles.has(filePath);
23
+ },
24
+
25
+ mkdirSync: function mkdirSync(directoryPath: string) {
26
+ return mockFiles.set(directoryPath, "");
27
+ },
20
28
  }));
21
29
 
22
30
  return mockFiles;
23
31
  }
32
+
33
+ export function jsonToString(obj: {}) {
34
+ return JSON.stringify(obj, undefined, 2) + "\n";
35
+ }
@@ -6,60 +6,16 @@
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
9
  import * as r from "runtypes";
10
+ import { checkAlpha } from "./util/checkAlpha";
12
11
 
13
12
  const Options = r.Undefined;
14
13
 
15
- export const alphabeticalDependencies = {
14
+ export const alphabeticalDependencies: RuleModule<typeof Options> = {
16
15
  check: function expectAlphabeticalDependencies(context: Context) {
17
16
  checkAlpha(context, "dependencies");
18
17
  checkAlpha(context, "devDependencies");
19
18
  checkAlpha(context, "peerDependencies");
20
19
  },
21
20
  optionsRuntype: Options,
22
- } as RuleModule<typeof Options>;
23
-
24
- function checkAlpha(context: Context, block: "dependencies" | "devDependencies" | "peerDependencies") {
25
- const packageJson = context.getPackageJson();
26
- const packagePath = context.getPackageJsonPath();
27
-
28
- const dependencies = packageJson[block];
29
-
30
- if (dependencies === undefined) {
31
- return;
32
- }
33
-
34
- const actualOrder = Object.keys(dependencies);
35
- const expectedOrder = actualOrder.slice().sort(); // sort mutates, so we need to copy the previous result
36
-
37
- if (!arrayOrderCompare(actualOrder, expectedOrder)) {
38
- context.addError({
39
- file: packagePath,
40
- message: `Incorrect order of ${block} in package.json`,
41
- longMessage: diff(expectedOrder, actualOrder, { expand: true }),
42
- fixer: () => {
43
- const expectedDependencies: Record<string, string> = {};
44
-
45
- expectedOrder.forEach(dep => {
46
- expectedDependencies[dep] = dependencies[dep];
47
- });
48
-
49
- const newPackageJson = { ...packageJson };
50
- newPackageJson[block] = expectedDependencies;
51
- writeJson(packagePath, newPackageJson);
52
- },
53
- });
54
- }
55
- }
56
-
57
- function arrayOrderCompare(a: ReadonlyArray<string>, b: ReadonlyArray<string>) {
58
- for (let index = 0; index < a.length; index++) {
59
- if (a[index] !== b[index]) {
60
- return false;
61
- }
62
- }
63
-
64
- return true;
65
- }
21
+ };
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * Copyright 2019 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 * as r from "runtypes";
10
+ import { checkAlpha } from "./util/checkAlpha";
11
+
12
+ const Options = r.Undefined;
13
+
14
+ export const alphabeticalScripts: RuleModule<typeof Options> = {
15
+ check: function expectAlphabeticalScripts(context: Context) {
16
+ checkAlpha(context, "scripts");
17
+ },
18
+ optionsRuntype: Options,
19
+ };
@@ -9,22 +9,51 @@ import { Context, RuleModule } from "@monorepolint/core";
9
9
  import { writeJson } from "@monorepolint/utils";
10
10
  import diff from "jest-diff";
11
11
  import minimatch from "minimatch";
12
+ import path from "path";
12
13
  import * as r from "runtypes";
14
+ import { IPackageDependencyGraphNode, PackageDependencyGraphService } from "./util/packageDependencyGraphService";
13
15
 
14
- const Options = r.Record({
15
- bannedDependencies: r.Array(r.String),
16
- });
16
+ 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
+ r.Record({
36
+ bannedDependencies: r.Array(r.String),
37
+ bannedTransitiveDependencies: r.Array(r.String),
38
+ })
39
+ );
17
40
 
18
- type Options = r.Static<typeof Options>;
41
+ export type Options = r.Static<typeof Options>;
19
42
 
20
43
  export const bannedDependencies: RuleModule<typeof Options> = {
21
44
  check: function expectAllowedDependencies(context: Context, opts: Options) {
22
45
  // tslint:disable-next-line:no-shadowed-variable
23
- const { bannedDependencies } = opts;
46
+ const { bannedDependencies, bannedTransitiveDependencies } = opts;
24
47
 
25
- checkBanned(context, bannedDependencies, "dependencies");
26
- checkBanned(context, bannedDependencies, "devDependencies");
27
- checkBanned(context, bannedDependencies, "peerDependencies");
48
+ if (bannedDependencies) {
49
+ checkBanned(context, bannedDependencies, "dependencies");
50
+ checkBanned(context, bannedDependencies, "devDependencies");
51
+ checkBanned(context, bannedDependencies, "peerDependencies");
52
+ }
53
+
54
+ if (bannedTransitiveDependencies) {
55
+ checkTransitives(context, bannedTransitiveDependencies);
56
+ }
28
57
  },
29
58
  optionsRuntype: Options,
30
59
  };
@@ -44,26 +73,55 @@ function checkBanned(
44
73
  return;
45
74
  }
46
75
 
47
- const expectedDependencies: Record<string, string> = {};
76
+ const newPackageJson = { ...packageJson };
77
+ const violations: string[] = [];
48
78
 
49
79
  for (const dependency of Object.keys(dependencies)) {
50
80
  for (const bannedDependency of bannedDependencies) {
51
- if (!minimatch(dependency, bannedDependency)) {
52
- expectedDependencies[dependency] = dependencies[dependency];
81
+ if (minimatch(dependency, bannedDependency)) {
82
+ violations.push(dependency);
83
+ delete newPackageJson[block]![dependency];
53
84
  }
54
85
  }
55
86
  }
56
87
 
57
- if (Object.keys(expectedDependencies).length !== Object.keys(dependencies).length) {
88
+ if (violations.length > 0) {
58
89
  context.addError({
59
90
  file: packagePath,
60
- message: `Banned depdendencies in ${block} in package.json`,
61
- longMessage: diff(expectedDependencies, dependencies, { expand: true }),
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 }),
62
95
  fixer: () => {
63
- const newPackageJson = { ...packageJson };
64
- newPackageJson[block] = expectedDependencies;
65
96
  writeJson(packagePath, newPackageJson);
66
97
  },
67
98
  });
68
99
  }
69
100
  }
101
+
102
+ function checkTransitives(
103
+ context: Context,
104
+ // tslint:disable-next-line: no-shadowed-variable
105
+ bannedDependencies: ReadonlyArray<string>
106
+ ) {
107
+ const graphService = new PackageDependencyGraphService();
108
+ const root = graphService.buildDependencyGraph(path.resolve(context.getPackageJsonPath()));
109
+ for (const { dependencies, importPath } of graphService.traverse(root)) {
110
+ for (const [dependency] of dependencies) {
111
+ if (bannedDependencies.includes(dependency)) {
112
+ // Remove the starting package since it's obvious in CLI output.
113
+ const [, ...importPathWithoutRoot] = importPath;
114
+ const pathing = [...importPathWithoutRoot.map(nameOrPackageJsonPath), dependency].join(" -> ");
115
+
116
+ context.addError({
117
+ file: root.paths.packageJsonPath,
118
+ message: `Banned transitive dependencies in repo: ${pathing}`,
119
+ });
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ function nameOrPackageJsonPath(node: IPackageDependencyGraphNode): string {
126
+ return node.packageJson.name ?? node.paths.packageJsonPath;
127
+ }