@monorepolint/rules 0.5.0-alpha.1 → 0.5.0-alpha.103

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 (149) hide show
  1. package/.turbo/turbo-clean.log +4 -0
  2. package/.turbo/turbo-compile-typescript.log +4 -0
  3. package/.turbo/turbo-lint.log +107 -0
  4. package/.turbo/turbo-test.log +631 -0
  5. package/.turbo/turbo-transpile-typescript.log +18 -0
  6. package/build/js/index.cjs +1491 -0
  7. package/build/js/index.cjs.map +1 -0
  8. package/build/js/index.js +1433 -0
  9. package/build/js/index.js.map +1 -0
  10. package/build/tsconfig.tsbuildinfo +1 -0
  11. package/{lib/__tests__/utils.d.ts → build/types/__tests__/alphabeticalScripts.spec.d.ts} +2 -2
  12. package/build/types/__tests__/alphabeticalScripts.spec.d.ts.map +1 -0
  13. package/build/types/__tests__/bannedDependencies.spec.d.ts +2 -0
  14. package/build/types/__tests__/bannedDependencies.spec.d.ts.map +1 -0
  15. package/build/types/__tests__/consistentDependencies.spec.d.ts +2 -0
  16. package/build/types/__tests__/consistentDependencies.spec.d.ts.map +1 -0
  17. package/build/types/__tests__/consistentVersions.spec.d.ts +8 -0
  18. package/build/types/__tests__/consistentVersions.spec.d.ts.map +1 -0
  19. package/build/types/__tests__/fileContents.spec.d.ts +8 -0
  20. package/build/types/__tests__/fileContents.spec.d.ts.map +1 -0
  21. package/build/types/__tests__/mustSatisfyPeerDependencies.spec.d.ts +8 -0
  22. package/build/types/__tests__/mustSatisfyPeerDependencies.spec.d.ts.map +1 -0
  23. package/build/types/__tests__/nestedWorkspaces.spec.d.ts +2 -0
  24. package/build/types/__tests__/nestedWorkspaces.spec.d.ts.map +1 -0
  25. package/build/types/__tests__/packageEntry.spec.d.ts +8 -0
  26. package/build/types/__tests__/packageEntry.spec.d.ts.map +1 -0
  27. package/{lib → build/types}/__tests__/packageOrder.spec.d.ts +0 -0
  28. package/build/types/__tests__/packageOrder.spec.d.ts.map +1 -0
  29. package/{lib → build/types}/__tests__/packageScript.spec.d.ts +0 -0
  30. package/build/types/__tests__/packageScript.spec.d.ts.map +1 -0
  31. package/build/types/__tests__/requireDependency.spec.d.ts +2 -0
  32. package/build/types/__tests__/requireDependency.spec.d.ts.map +1 -0
  33. package/build/types/__tests__/utils.d.ts +81 -0
  34. package/build/types/__tests__/utils.d.ts.map +1 -0
  35. package/build/types/alphabeticalDependencies.d.ts +23 -0
  36. package/build/types/alphabeticalDependencies.d.ts.map +1 -0
  37. package/build/types/alphabeticalScripts.d.ts +23 -0
  38. package/build/types/alphabeticalScripts.d.ts.map +1 -0
  39. package/build/types/bannedDependencies.d.ts +134 -0
  40. package/build/types/bannedDependencies.d.ts.map +1 -0
  41. package/build/types/consistentDependencies.d.ts +38 -0
  42. package/build/types/consistentDependencies.d.ts.map +1 -0
  43. package/build/types/consistentVersions.d.ts +47 -0
  44. package/build/types/consistentVersions.d.ts.map +1 -0
  45. package/build/types/fileContents.d.ts +111 -0
  46. package/build/types/fileContents.d.ts.map +1 -0
  47. package/build/types/index.d.ts +20 -0
  48. package/build/types/index.d.ts.map +1 -0
  49. package/build/types/mustSatisfyPeerDependencies.d.ts +721 -0
  50. package/build/types/mustSatisfyPeerDependencies.d.ts.map +1 -0
  51. package/build/types/nestedWorkspaces.d.ts +24 -0
  52. package/build/types/nestedWorkspaces.d.ts.map +1 -0
  53. package/build/types/packageEntry.d.ts +115 -0
  54. package/build/types/packageEntry.d.ts.map +1 -0
  55. package/build/types/packageOrder.d.ts +33 -0
  56. package/build/types/packageOrder.d.ts.map +1 -0
  57. package/build/types/packageScript.d.ts +89 -0
  58. package/build/types/packageScript.d.ts.map +1 -0
  59. package/build/types/requireDependency.d.ts +99 -0
  60. package/build/types/requireDependency.d.ts.map +1 -0
  61. package/build/types/standardTsconfig.d.ts +113 -0
  62. package/build/types/standardTsconfig.d.ts.map +1 -0
  63. package/build/types/util/checkAlpha.d.ts +10 -0
  64. package/build/types/util/checkAlpha.d.ts.map +1 -0
  65. package/build/types/util/createNewRuleConversion.d.ts +30 -0
  66. package/build/types/util/createNewRuleConversion.d.ts.map +1 -0
  67. package/build/types/util/makeDirectory.d.ts +8 -0
  68. package/build/types/util/makeDirectory.d.ts.map +1 -0
  69. package/build/types/util/packageDependencyGraphService.d.ts +37 -0
  70. package/build/types/util/packageDependencyGraphService.d.ts.map +1 -0
  71. package/{jest.config.js → jest.config.cjs} +0 -0
  72. package/package.json +39 -18
  73. package/src/__tests__/alphabeticalScripts.spec.ts +76 -0
  74. package/src/__tests__/bannedDependencies.spec.ts +191 -0
  75. package/src/__tests__/consistentDependencies.spec.ts +142 -0
  76. package/src/__tests__/consistentVersions.spec.ts +224 -0
  77. package/src/__tests__/fileContents.spec.ts +75 -0
  78. package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +1189 -0
  79. package/src/__tests__/nestedWorkspaces.spec.ts +153 -0
  80. package/src/__tests__/packageEntry.spec.ts +201 -0
  81. package/src/__tests__/packageOrder.spec.ts +48 -40
  82. package/src/__tests__/packageScript.spec.ts +149 -65
  83. package/src/__tests__/requireDependency.spec.ts +152 -0
  84. package/src/__tests__/utils.ts +115 -11
  85. package/src/alphabeticalDependencies.ts +6 -48
  86. package/src/alphabeticalScripts.ts +21 -0
  87. package/src/bannedDependencies.ts +135 -44
  88. package/src/consistentDependencies.ts +38 -14
  89. package/src/consistentVersions.ts +142 -0
  90. package/src/fileContents.ts +35 -30
  91. package/src/index.ts +13 -8
  92. package/src/mustSatisfyPeerDependencies.ts +748 -0
  93. package/src/nestedWorkspaces.ts +61 -0
  94. package/src/packageEntry.ts +72 -23
  95. package/src/packageOrder.ts +15 -10
  96. package/src/packageScript.ts +60 -13
  97. package/src/requireDependency.ts +71 -0
  98. package/src/standardTsconfig.ts +50 -21
  99. package/src/util/checkAlpha.ts +59 -0
  100. package/src/util/createNewRuleConversion.ts +38 -0
  101. package/src/util/makeDirectory.ts +24 -0
  102. package/src/util/packageDependencyGraphService.ts +114 -0
  103. package/tsconfig.json +10 -2
  104. package/lib/__tests__/packageOrder.spec.d.ts.map +0 -1
  105. package/lib/__tests__/packageOrder.spec.js +0 -114
  106. package/lib/__tests__/packageOrder.spec.js.map +0 -1
  107. package/lib/__tests__/packageScript.spec.d.ts.map +0 -1
  108. package/lib/__tests__/packageScript.spec.js +0 -108
  109. package/lib/__tests__/packageScript.spec.js.map +0 -1
  110. package/lib/__tests__/utils.d.ts.map +0 -1
  111. package/lib/__tests__/utils.js +0 -23
  112. package/lib/__tests__/utils.js.map +0 -1
  113. package/lib/alphabeticalDependencies.d.ts +0 -10
  114. package/lib/alphabeticalDependencies.d.ts.map +0 -1
  115. package/lib/alphabeticalDependencies.js +0 -56
  116. package/lib/alphabeticalDependencies.js.map +0 -1
  117. package/lib/bannedDependencies.d.ts +0 -15
  118. package/lib/bannedDependencies.d.ts.map +0 -1
  119. package/lib/bannedDependencies.js +0 -57
  120. package/lib/bannedDependencies.js.map +0 -1
  121. package/lib/consistentDependencies.d.ts +0 -10
  122. package/lib/consistentDependencies.d.ts.map +0 -1
  123. package/lib/consistentDependencies.js +0 -57
  124. package/lib/consistentDependencies.js.map +0 -1
  125. package/lib/fileContents.d.ts +0 -25
  126. package/lib/fileContents.d.ts.map +0 -1
  127. package/lib/fileContents.js +0 -77
  128. package/lib/fileContents.js.map +0 -1
  129. package/lib/index.d.ts +0 -15
  130. package/lib/index.d.ts.map +0 -1
  131. package/lib/index.js +0 -25
  132. package/lib/index.js.map +0 -1
  133. package/lib/packageEntry.d.ts +0 -16
  134. package/lib/packageEntry.d.ts.map +0 -1
  135. package/lib/packageEntry.js +0 -38
  136. package/lib/packageEntry.js.map +0 -1
  137. package/lib/packageOrder.d.ts +0 -12
  138. package/lib/packageOrder.d.ts.map +0 -1
  139. package/lib/packageOrder.js +0 -102
  140. package/lib/packageOrder.js.map +0 -1
  141. package/lib/packageScript.d.ts +0 -17
  142. package/lib/packageScript.d.ts.map +0 -1
  143. package/lib/packageScript.js +0 -51
  144. package/lib/packageScript.js.map +0 -1
  145. package/lib/standardTsconfig.d.ts +0 -29
  146. package/lib/standardTsconfig.d.ts.map +0 -1
  147. package/lib/standardTsconfig.js +0 -96
  148. package/lib/standardTsconfig.js.map +0 -1
  149. package/tsconfig.tsbuildinfo +0 -2181
@@ -6,22 +6,16 @@
6
6
  */
7
7
 
8
8
  // tslint:disable:no-console
9
- import { createMockFiles } from "./utils";
9
+ import { describe, expect, it, beforeEach, jest } from "@jest/globals";
10
+ import { Context, Failure } from "@monorepolint/config";
11
+ import { packageScript } from "../packageScript.js";
12
+ import { AddErrorSpy, createTestingWorkspace, HOST_FACTORIES, TestingWorkspace } from "./utils.js";
10
13
 
11
- // done first since this also mocks 'fs'
12
- const mockFiles: Map<string, string> = createMockFiles();
14
+ const json = (a: unknown) => JSON.stringify(a, undefined, 2) + "\n";
13
15
 
14
- import { Failure, PackageContext } from "@monorepolint/core";
15
- import { packageScript } from "../packageScript";
16
-
17
- const PACKAGE_WITHOUT_SCRIPTS =
18
- JSON.stringify(
19
- {
20
- name: "package-without-scripts",
21
- },
22
- undefined,
23
- 2
24
- ) + "\n";
16
+ const PACKAGE_WITHOUT_SCRIPTS = json({
17
+ name: "package-without-scripts",
18
+ });
25
19
 
26
20
  const MISSING_SCRIPT_NAME = "missing";
27
21
  const MISSING_SCRIPT_VALUE = "missing value";
@@ -29,40 +23,31 @@ const MISSING_SCRIPT_VALUE = "missing value";
29
23
  const SCRIPT_NAME = "exists";
30
24
  const SCRIPT_VALUE = "exists value";
31
25
 
32
- const PACKAGE_WITH_SCRIPTS =
33
- JSON.stringify(
34
- {
35
- name: "package-with-scripts",
36
- scripts: {
37
- [SCRIPT_NAME]: SCRIPT_VALUE,
38
- },
39
- },
40
- undefined,
41
- 2
42
- ) + "\n";
43
-
44
- describe("expectPackageScript", () => {
45
- afterEach(() => {
46
- mockFiles.clear();
47
- });
26
+ const PACKAGE_WITH_SCRIPTS = json({
27
+ name: "package-with-scripts",
28
+ scripts: {
29
+ [SCRIPT_NAME]: SCRIPT_VALUE,
30
+ },
31
+ });
48
32
 
33
+ describe.each(HOST_FACTORIES)("expectPackageScript ($name)", (hostFactory) => {
49
34
  describe("fix: false", () => {
50
- const context = new PackageContext(".", {
51
- rules: [],
52
- fix: false,
53
- verbose: false,
54
- });
35
+ let workspace: TestingWorkspace;
36
+ let spy: AddErrorSpy;
55
37
 
56
- const spy = jest.spyOn(context, "addError");
38
+ beforeEach(async () => {
39
+ workspace = await createTestingWorkspace({
40
+ fixFlag: false,
41
+ host: hostFactory.make(),
42
+ });
57
43
 
58
- afterEach(() => {
59
- spy.mockClear();
44
+ spy = jest.spyOn(workspace.context, "addError");
60
45
  });
61
46
 
62
47
  it("handles an empty script section", () => {
63
- mockFiles.set("package.json", PACKAGE_WITHOUT_SCRIPTS);
48
+ workspace.writeFile("package.json", PACKAGE_WITHOUT_SCRIPTS);
64
49
 
65
- packageScript.check(context, {
50
+ packageScript.check(workspace.context, {
66
51
  scripts: {
67
52
  foo: "bar",
68
53
  },
@@ -71,26 +56,33 @@ describe("expectPackageScript", () => {
71
56
  expect(spy).toHaveBeenCalledTimes(1);
72
57
 
73
58
  const failure: Failure = spy.mock.calls[0][0];
74
- expect(failure.file).toBe("package.json");
75
- expect(failure.fixer).not.toBeUndefined();
76
- expect(failure.message).toBe("No scripts block in package.json");
59
+ expect(failure).toMatchObject(
60
+ workspace.failureMatcher({
61
+ file: "package.json",
62
+ hasFixer: true,
63
+ message: "No scripts block in package.json",
64
+ })
65
+ );
77
66
  });
78
67
  });
79
68
 
80
69
  describe("fix: true", () => {
81
- const context = new PackageContext(".", {
82
- rules: [],
83
- fix: true,
84
- verbose: false,
85
- });
86
- const spy = jest.spyOn(context, "addError");
70
+ let workspace: TestingWorkspace;
71
+ let spy: AddErrorSpy;
72
+ let context: Context;
73
+
74
+ beforeEach(async () => {
75
+ workspace = await createTestingWorkspace({
76
+ fixFlag: true,
77
+ host: hostFactory.make(),
78
+ });
87
79
 
88
- afterEach(() => {
89
- spy.mockClear();
80
+ spy = jest.spyOn(workspace.context, "addError");
81
+ context = workspace.context; // minimizing delta
90
82
  });
91
83
 
92
84
  it("fixes an empty script section", () => {
93
- mockFiles.set("package.json", PACKAGE_WITHOUT_SCRIPTS);
85
+ workspace.writeFile("package.json", PACKAGE_WITHOUT_SCRIPTS);
94
86
 
95
87
  packageScript.check(context, {
96
88
  scripts: {
@@ -101,15 +93,19 @@ describe("expectPackageScript", () => {
101
93
  expect(spy).toHaveBeenCalledTimes(1);
102
94
 
103
95
  const failure: Failure = spy.mock.calls[0][0];
104
- expect(failure.file).toBe("package.json");
105
- expect(failure.fixer).not.toBeUndefined();
106
- expect(failure.message).toBe("No scripts block in package.json");
107
-
108
- expect(JSON.parse(mockFiles.get("package.json")!).scripts).toEqual({});
96
+ expect(failure).toMatchObject(
97
+ workspace.failureMatcher({
98
+ file: "package.json",
99
+ hasFixer: true,
100
+ message: "No scripts block in package.json",
101
+ })
102
+ );
103
+
104
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({});
109
105
  });
110
106
 
111
107
  it("adds a script", () => {
112
- mockFiles.set("package.json", PACKAGE_WITH_SCRIPTS);
108
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
113
109
 
114
110
  packageScript.check(context, {
115
111
  scripts: {
@@ -120,15 +116,23 @@ describe("expectPackageScript", () => {
120
116
  expect(spy).toHaveBeenCalledTimes(1);
121
117
 
122
118
  const failure: Failure = spy.mock.calls[0][0];
123
- expect(failure.file).toBe("package.json");
124
- expect(failure.fixer).not.toBeUndefined();
125
- expect(failure.message).toBe(`Expected standardized script entry for '${MISSING_SCRIPT_NAME}'`);
126
-
127
- expect(JSON.parse(mockFiles.get("package.json")!).scripts[MISSING_SCRIPT_NAME]).toEqual(MISSING_SCRIPT_VALUE);
119
+ expect(failure).toMatchObject(
120
+ workspace.failureMatcher({
121
+ file: "package.json",
122
+ hasFixer: true,
123
+ message: expect.stringContaining(
124
+ `Expected standardized script entry for '${MISSING_SCRIPT_NAME}'`
125
+ ) as any as string,
126
+ })
127
+ );
128
+
129
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts[MISSING_SCRIPT_NAME]).toEqual(
130
+ MISSING_SCRIPT_VALUE
131
+ );
128
132
  });
129
133
 
130
134
  it("does nothing if the value exists", () => {
131
- mockFiles.set("package.json", PACKAGE_WITH_SCRIPTS);
135
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
132
136
 
133
137
  packageScript.check(context, {
134
138
  scripts: {
@@ -138,9 +142,89 @@ describe("expectPackageScript", () => {
138
142
 
139
143
  expect(spy).not.toHaveBeenCalled();
140
144
 
141
- expect(JSON.parse(mockFiles.get("package.json")!).scripts).toEqual({
145
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({
142
146
  [SCRIPT_NAME]: SCRIPT_VALUE,
143
147
  });
144
148
  });
149
+
150
+ it("errors if long form is used and no value matches and there is no fixValue", () => {
151
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
152
+
153
+ packageScript.check(context, {
154
+ scripts: {
155
+ foo: {
156
+ options: ["a", "b"],
157
+ },
158
+ },
159
+ });
160
+
161
+ const errors = spy.mock.calls;
162
+
163
+ expect(errors.length).toBe(1);
164
+ expect(errors[0][0].fixer).toBeUndefined();
165
+ });
166
+
167
+ it("uses the fixValue for fixing if provided", () => {
168
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
169
+
170
+ packageScript.check(context, {
171
+ scripts: {
172
+ foo: {
173
+ options: ["a", "b"],
174
+ fixValue: "a",
175
+ },
176
+ },
177
+ });
178
+
179
+ const errors = spy.mock.calls;
180
+
181
+ expect(errors.length).toBe(1);
182
+ expect(errors[0][0].fixer).toBeDefined();
183
+
184
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({
185
+ [SCRIPT_NAME]: SCRIPT_VALUE,
186
+ foo: "a",
187
+ });
188
+ });
189
+
190
+ it("can fix to empty", () => {
191
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
192
+
193
+ packageScript.check(context, {
194
+ scripts: {
195
+ [SCRIPT_NAME]: {
196
+ options: ["a", undefined],
197
+ fixValue: undefined,
198
+ },
199
+ },
200
+ });
201
+
202
+ const errors = spy.mock.calls;
203
+
204
+ expect(errors.length).toBe(1);
205
+ expect(errors[0][0].fixer).toBeDefined();
206
+
207
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({});
208
+ });
209
+
210
+ it("can allow only empty", () => {
211
+ workspace.writeFile("package.json", PACKAGE_WITH_SCRIPTS);
212
+
213
+ packageScript.check(context, {
214
+ scripts: {
215
+ [SCRIPT_NAME]: {
216
+ options: [undefined],
217
+ fixValue: undefined,
218
+ },
219
+ },
220
+ });
221
+
222
+ const errors = spy.mock.calls;
223
+
224
+ expect(errors.length).toBe(1);
225
+ expect(errors[0][0].fixer).toBeDefined();
226
+
227
+ expect(JSON.parse(workspace.readFile("package.json")!).scripts).toEqual({});
228
+ });
145
229
  });
146
230
  });
@@ -0,0 +1,152 @@
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 { WorkspaceContextImpl } from "@monorepolint/core";
8
+ import { SimpleHost } from "@monorepolint/utils";
9
+ import { readFileSync, writeFileSync } from "node:fs";
10
+ import * as path from "node:path";
11
+ import * as tmp from "tmp";
12
+ import { requireDependency } from "../requireDependency.js";
13
+ import { makeDirectoryRecursively } from "../util/makeDirectory.js";
14
+ import { jsonToString } from "./utils.js";
15
+ import { describe, expect, it, afterEach, jest } from "@jest/globals";
16
+
17
+ const PACKAGE_ROOT = jsonToString({
18
+ workspaces: {
19
+ packages: ["packages/*"],
20
+ },
21
+ dependencies: {
22
+ foo: "5",
23
+ },
24
+ });
25
+
26
+ const PACKAGE_WITH_NO_ENTRIES = jsonToString({});
27
+
28
+ const PACKAGE_WITH_ENTRIES_MISSING = jsonToString({
29
+ dependencies: {},
30
+ devDependencies: {},
31
+ });
32
+
33
+ const PACKAGE_WITH_WRONG_ENTRIES = jsonToString({
34
+ dependencies: {
35
+ foo: "0.1.0",
36
+ },
37
+ devDependencies: {
38
+ bar: "1.0.0",
39
+ },
40
+ });
41
+
42
+ const PACKAGE_WITH_RIGHT_ENTRIES = jsonToString({
43
+ dependencies: {
44
+ foo: "1.0.0",
45
+ },
46
+ devDependencies: {
47
+ bar: "^2.0.0",
48
+ },
49
+ });
50
+
51
+ const OPTIONS = {
52
+ dependencies: {
53
+ foo: "1.0.0",
54
+ },
55
+ devDependencies: {
56
+ bar: "^2.0.0",
57
+ },
58
+ } as const;
59
+
60
+ const CORRECT_OUTPUT = jsonToString(OPTIONS);
61
+
62
+ describe("requireDependency", () => {
63
+ tmp.setGracefulCleanup();
64
+
65
+ let cleanupJobs: Array<() => void> = [];
66
+
67
+ afterEach(() => {
68
+ for (const cleanupJob of cleanupJobs) {
69
+ cleanupJob();
70
+ }
71
+ cleanupJobs = [];
72
+ });
73
+
74
+ function makeWorkspace({ fix }: { fix: boolean }) {
75
+ const dir: tmp.DirResult = tmp.dirSync({ unsafeCleanup: true });
76
+ cleanupJobs.push(() => dir.removeCallback());
77
+
78
+ const workspaceContext = new WorkspaceContextImpl(
79
+ dir.name,
80
+ {
81
+ rules: [],
82
+ fix,
83
+ verbose: false,
84
+ silent: true,
85
+ },
86
+ new SimpleHost()
87
+ );
88
+
89
+ function checkAndSpy(q: string) {
90
+ const context = workspaceContext.createChildContext(path.resolve(dir.name, q));
91
+ const addErrorSpy = jest.spyOn(context, "addError");
92
+ requireDependency.check(context, OPTIONS);
93
+ return { context, addErrorSpy };
94
+ }
95
+
96
+ function addFile(filePath: string, content: string) {
97
+ const dirPath = path.resolve(dir.name, path.dirname(filePath));
98
+ const resolvedFilePath = path.resolve(dir.name, filePath);
99
+
100
+ makeDirectoryRecursively(dirPath);
101
+ writeFileSync(resolvedFilePath, content);
102
+ }
103
+
104
+ function readFile(filePath: string) {
105
+ return readFileSync(path.resolve(dir.name, filePath)).toString();
106
+ }
107
+
108
+ return { addFile, readFile, workspaceContext, checkAndSpy };
109
+ }
110
+
111
+ it("checks correctly", () => {
112
+ const { addFile, workspaceContext, checkAndSpy } = makeWorkspace({ fix: false });
113
+ addFile("./package.json", PACKAGE_ROOT);
114
+ addFile("./packages/none/package.json", PACKAGE_WITH_NO_ENTRIES);
115
+ addFile("./packages/missing/package.json", PACKAGE_WITH_ENTRIES_MISSING);
116
+ addFile("./packages/wrong/package.json", PACKAGE_WITH_WRONG_ENTRIES);
117
+ addFile("./packages/right/package.json", PACKAGE_WITH_RIGHT_ENTRIES);
118
+
119
+ requireDependency.check(workspaceContext, OPTIONS);
120
+
121
+ const none = checkAndSpy("./packages/none");
122
+ expect(none.addErrorSpy).toHaveBeenCalledTimes(2);
123
+
124
+ const missing = checkAndSpy("./packages/missing");
125
+ expect(missing.addErrorSpy).toHaveBeenCalledTimes(2);
126
+
127
+ const wrong = checkAndSpy("./packages/wrong");
128
+ expect(wrong.addErrorSpy).toHaveBeenCalledTimes(2);
129
+
130
+ const right = checkAndSpy("./packages/right");
131
+ expect(right.addErrorSpy).toHaveBeenCalledTimes(0);
132
+ });
133
+
134
+ it("fixes correctly", () => {
135
+ const { addFile, readFile, checkAndSpy } = makeWorkspace({ fix: true });
136
+ addFile("./package.json", PACKAGE_ROOT);
137
+ addFile("./packages/missing/package.json", PACKAGE_WITH_ENTRIES_MISSING);
138
+ addFile("./packages/wrong/package.json", PACKAGE_WITH_WRONG_ENTRIES);
139
+
140
+ const missing = checkAndSpy("./packages/missing");
141
+ expect(missing.addErrorSpy).toHaveBeenCalledTimes(2);
142
+
143
+ const wrong = checkAndSpy("./packages/wrong");
144
+ expect(wrong.addErrorSpy).toHaveBeenCalledTimes(2);
145
+
146
+ const missingContents = readFile("./packages/missing/package.json");
147
+ expect(missingContents).toEqual(CORRECT_OUTPUT);
148
+
149
+ const contents = readFile("./packages/wrong/package.json");
150
+ expect(contents).toEqual(CORRECT_OUTPUT);
151
+ });
152
+ });
@@ -5,19 +5,123 @@
5
5
  *
6
6
  */
7
7
 
8
- export function createMockFiles() {
9
- const mockFiles: Map<string, string> = new Map();
8
+ import { AddErrorOptions, WorkspaceContext } from "@monorepolint/config";
9
+ import { WorkspaceContextImpl } from "@monorepolint/core";
10
+ import { CachingHost, Host, SimpleHost } from "@monorepolint/utils";
11
+ import { expect, jest } from "@jest/globals";
12
+ import * as path from "node:path";
13
+ import * as tmp from "tmp";
10
14
 
11
- jest.mock("fs", () => ({
12
- writeFileSync: function writeFileSync(filePath: string, contents: string) {
13
- mockFiles.set(filePath, contents);
14
- },
15
+ export function jsonToString(obj: unknown) {
16
+ return JSON.stringify(obj, undefined, 2) + "\n";
17
+ }
18
+
19
+ interface TestingWorkspaceOpts {
20
+ host: Host;
21
+ rootProjectName?: string;
22
+ fixFlag: boolean;
23
+ }
24
+
25
+ export async function createTestingWorkspace(inboundOpts: TestingWorkspaceOpts) {
26
+ tmp.setGracefulCleanup();
27
+ const tmpdir = tmp.dirSync();
28
+ const opts = {
29
+ ...inboundOpts,
30
+ rootProjectName: inboundOpts.rootProjectName ?? "rootProject",
31
+ };
32
+ const rootPath = tmpdir.name;
33
+ opts.host.mkdir(rootPath, { recursive: true });
15
34
 
16
- // tslint:disable-next-line: variable-name
17
- readFileSync: function readFileSync(filePath: string, _contentType: string) {
18
- return mockFiles.get(filePath);
35
+ opts.host.writeJson(path.join(rootPath, "package.json"), {
36
+ name: opts.rootProjectName,
37
+ workspaces: {
38
+ packages: ["packages/*"],
19
39
  },
20
- }));
40
+ });
41
+
42
+ await opts.host.flush();
21
43
 
22
- return mockFiles;
44
+ return new DefaultTestingWorkspace(
45
+ { ...opts, rootPath },
46
+ new WorkspaceContextImpl(rootPath, { fix: opts.fixFlag, rules: [] }, opts.host)
47
+ );
23
48
  }
49
+
50
+ interface RealTestingWorkspaceOpts extends Required<TestingWorkspaceOpts> {
51
+ rootPath: string;
52
+ }
53
+
54
+ export interface TestingWorkspace {
55
+ /**
56
+ * Adds a utf8 file to `packageName` with `relativePath` to the package directory.
57
+ *
58
+ * @param packageName The child package to add to or undefined for root package
59
+ * @param filePath The path of the file to be written. Will be prepended with package dir
60
+ * @param contents the contents to be written (as utf8)
61
+ */
62
+ writeFile(filePath: string, contents: string): void;
63
+
64
+ writeJsonFile(filePath: string, json: object): void;
65
+ addProject(name: string, fields: object): void;
66
+ getFilePath(filePath: string): string;
67
+ readFile(filePath: string): string;
68
+
69
+ /**
70
+ * Helper method for matching failures via jest `expect().toMatchObject
71
+ *
72
+ */
73
+ failureMatcher(opts: { file: string; message: string; hasFixer: boolean }): any;
74
+
75
+ readonly context: WorkspaceContext;
76
+ }
77
+
78
+ class DefaultTestingWorkspace implements TestingWorkspace {
79
+ constructor(private opts: RealTestingWorkspaceOpts, public readonly context: WorkspaceContext) {}
80
+
81
+ addProject(name: string, fields: object) {
82
+ this.writeJsonFile(path.join("packages", name, "package.json"), {
83
+ name,
84
+ ...fields,
85
+ });
86
+ }
87
+
88
+ writeJsonFile(filePath: string, json: object) {
89
+ this.writeFile(filePath, JSON.stringify(json, undefined, 2));
90
+ }
91
+
92
+ /**
93
+ * Adds a utf8 file to `packageName` with `relativePath` to the package directory.
94
+ *
95
+ * @param packageName The child package to add to or undefined for root package
96
+ * @param filePath The path of the file to be written. Will be prepended with package dir
97
+ * @param contents the contents to be written (as utf8)
98
+ */
99
+ writeFile(filePath: string, contents: string) {
100
+ const fullFilePath = this.getFilePath(filePath);
101
+ this.opts.host.mkdir(path.dirname(fullFilePath), { recursive: true });
102
+ this.opts.host.writeFile(fullFilePath, contents, { encoding: "utf-8" });
103
+ }
104
+
105
+ getFilePath(filePath: string) {
106
+ return path.join(this.opts.rootPath, filePath);
107
+ }
108
+
109
+ readFile(filePath: string) {
110
+ return this.opts.host.readFile(this.getFilePath(filePath), { encoding: "utf-8" });
111
+ }
112
+
113
+ failureMatcher(opts: { file: string; message: string; hasFixer: boolean }) {
114
+ return {
115
+ file: this.getFilePath(opts.file),
116
+ message: opts.message,
117
+ ...(opts.hasFixer ? { fixer: expect.any(Function) } : {}),
118
+ };
119
+ }
120
+ }
121
+
122
+ export type AddErrorSpy = jest.SpiedFunction<(options: AddErrorOptions) => void>;
123
+
124
+ export const HOST_FACTORIES: Array<{ name: string; make: () => Host }> = [
125
+ { name: "SimpleHost", make: () => new SimpleHost() },
126
+ { name: "CachingHost", make: () => new CachingHost() },
127
+ ];
@@ -5,61 +5,19 @@
5
5
  *
6
6
  */
7
7
 
8
- import { Context, RuleModule } from "@monorepolint/core";
9
- import { writeJson } from "@monorepolint/utils";
10
- import diff from "jest-diff";
8
+ import { Context, RuleModule } from "@monorepolint/config";
11
9
  import * as r from "runtypes";
12
-
10
+ import { checkAlpha } from "./util/checkAlpha.js";
11
+ import { createNewRuleConversion } from "./util/createNewRuleConversion.js";
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
- }
21
+ };
63
22
 
64
- return true;
65
- }
23
+ export const AlphabeticalDependencies = createNewRuleConversion("AlphabetialDependencies", alphabeticalDependencies);
@@ -0,0 +1,21 @@
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/config";
9
+ import * as r from "runtypes";
10
+ import { checkAlpha } from "./util/checkAlpha.js";
11
+ import { createNewRuleConversion } from "./util/createNewRuleConversion.js";
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
+ };
20
+
21
+ export const AlphabeticalScripts = createNewRuleConversion("AlphabeticalScripts", alphabeticalScripts);