@monorepolint/rules 0.5.0-alpha.9 → 0.5.0-alpha.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/lib/__tests__/alphabeticalScripts.spec.d.ts +8 -0
  2. package/lib/__tests__/alphabeticalScripts.spec.d.ts.map +1 -0
  3. package/lib/__tests__/alphabeticalScripts.spec.js +61 -0
  4. package/lib/__tests__/alphabeticalScripts.spec.js.map +1 -0
  5. package/lib/__tests__/bannedDependencies.spec.d.ts +2 -0
  6. package/lib/__tests__/bannedDependencies.spec.d.ts.map +1 -0
  7. package/lib/__tests__/bannedDependencies.spec.js +161 -0
  8. package/lib/__tests__/bannedDependencies.spec.js.map +1 -0
  9. package/lib/__tests__/consistentDependencies.spec.js +30 -23
  10. package/lib/__tests__/consistentDependencies.spec.js.map +1 -1
  11. package/lib/__tests__/consistentVersions.spec.d.ts +8 -0
  12. package/lib/__tests__/consistentVersions.spec.d.ts.map +1 -0
  13. package/lib/__tests__/consistentVersions.spec.js +183 -0
  14. package/lib/__tests__/consistentVersions.spec.js.map +1 -0
  15. package/lib/__tests__/fileContents.spec.d.ts +8 -0
  16. package/lib/__tests__/fileContents.spec.d.ts.map +1 -0
  17. package/lib/__tests__/fileContents.spec.js +59 -0
  18. package/lib/__tests__/fileContents.spec.js.map +1 -0
  19. package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts +8 -0
  20. package/lib/__tests__/mustSatisfyPeerDependencies.spec.d.ts.map +1 -0
  21. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js +1063 -0
  22. package/lib/__tests__/mustSatisfyPeerDependencies.spec.js.map +1 -0
  23. package/lib/__tests__/nestedWorkspaces.spec.d.ts +2 -0
  24. package/lib/__tests__/nestedWorkspaces.spec.d.ts.map +1 -0
  25. package/lib/__tests__/nestedWorkspaces.spec.js +124 -0
  26. package/lib/__tests__/nestedWorkspaces.spec.js.map +1 -0
  27. package/lib/__tests__/packageEntry.spec.js +72 -27
  28. package/lib/__tests__/packageEntry.spec.js.map +1 -1
  29. package/lib/__tests__/packageOrder.spec.js +34 -33
  30. package/lib/__tests__/packageOrder.spec.js.map +1 -1
  31. package/lib/__tests__/packageScript.spec.js +50 -51
  32. package/lib/__tests__/packageScript.spec.js.map +1 -1
  33. package/lib/__tests__/requireDependency.spec.d.ts +2 -0
  34. package/lib/__tests__/requireDependency.spec.d.ts.map +1 -0
  35. package/lib/__tests__/requireDependency.spec.js +123 -0
  36. package/lib/__tests__/requireDependency.spec.js.map +1 -0
  37. package/lib/__tests__/utils.d.ts +69 -1
  38. package/lib/__tests__/utils.d.ts.map +1 -1
  39. package/lib/__tests__/utils.js +72 -12
  40. package/lib/__tests__/utils.js.map +1 -1
  41. package/lib/alphabeticalDependencies.d.ts +3 -1
  42. package/lib/alphabeticalDependencies.d.ts.map +1 -1
  43. package/lib/alphabeticalDependencies.js +5 -39
  44. package/lib/alphabeticalDependencies.js.map +1 -1
  45. package/lib/alphabeticalScripts.d.ts +12 -0
  46. package/lib/alphabeticalScripts.d.ts.map +1 -0
  47. package/lib/alphabeticalScripts.js +20 -0
  48. package/lib/alphabeticalScripts.js.map +1 -0
  49. package/lib/bannedDependencies.d.ts +23 -5
  50. package/lib/bannedDependencies.d.ts.map +1 -1
  51. package/lib/bannedDependencies.js +110 -36
  52. package/lib/bannedDependencies.js.map +1 -1
  53. package/lib/consistentDependencies.d.ts +8 -1
  54. package/lib/consistentDependencies.d.ts.map +1 -1
  55. package/lib/consistentDependencies.js +30 -12
  56. package/lib/consistentDependencies.js.map +1 -1
  57. package/lib/consistentVersions.d.ts +14 -0
  58. package/lib/consistentVersions.d.ts.map +1 -0
  59. package/lib/consistentVersions.js +94 -0
  60. package/lib/consistentVersions.js.map +1 -0
  61. package/lib/fileContents.d.ts +8 -8
  62. package/lib/fileContents.d.ts.map +1 -1
  63. package/lib/fileContents.js +32 -27
  64. package/lib/fileContents.js.map +1 -1
  65. package/lib/index.d.ts +5 -0
  66. package/lib/index.d.ts.map +1 -1
  67. package/lib/index.js +19 -8
  68. package/lib/index.js.map +1 -1
  69. package/lib/mustSatisfyPeerDependencies.d.ts +240 -0
  70. package/lib/mustSatisfyPeerDependencies.d.ts.map +1 -0
  71. package/lib/mustSatisfyPeerDependencies.js +636 -0
  72. package/lib/mustSatisfyPeerDependencies.js.map +1 -0
  73. package/lib/nestedWorkspaces.d.ts +13 -0
  74. package/lib/nestedWorkspaces.d.ts.map +1 -0
  75. package/lib/nestedWorkspaces.js +50 -0
  76. package/lib/nestedWorkspaces.js.map +1 -0
  77. package/lib/packageEntry.d.ts +15 -6
  78. package/lib/packageEntry.d.ts.map +1 -1
  79. package/lib/packageEntry.js +53 -18
  80. package/lib/packageEntry.js.map +1 -1
  81. package/lib/packageOrder.d.ts +3 -3
  82. package/lib/packageOrder.d.ts.map +1 -1
  83. package/lib/packageOrder.js +8 -7
  84. package/lib/packageOrder.js.map +1 -1
  85. package/lib/packageScript.d.ts +10 -10
  86. package/lib/packageScript.js +6 -5
  87. package/lib/packageScript.js.map +1 -1
  88. package/lib/requireDependency.d.ts +15 -0
  89. package/lib/requireDependency.d.ts.map +1 -0
  90. package/lib/requireDependency.js +65 -0
  91. package/lib/requireDependency.js.map +1 -0
  92. package/lib/standardTsconfig.d.ts +20 -8
  93. package/lib/standardTsconfig.d.ts.map +1 -1
  94. package/lib/standardTsconfig.js +36 -18
  95. package/lib/standardTsconfig.js.map +1 -1
  96. package/lib/util/checkAlpha.d.ts +10 -0
  97. package/lib/util/checkAlpha.d.ts.map +1 -0
  98. package/lib/util/checkAlpha.js +51 -0
  99. package/lib/util/checkAlpha.js.map +1 -0
  100. package/lib/util/makeDirectory.d.ts +8 -0
  101. package/lib/util/makeDirectory.d.ts.map +1 -0
  102. package/lib/util/makeDirectory.js +28 -0
  103. package/lib/util/makeDirectory.js.map +1 -0
  104. package/lib/util/packageDependencyGraphService.d.ts +37 -0
  105. package/lib/util/packageDependencyGraphService.d.ts.map +1 -0
  106. package/lib/util/packageDependencyGraphService.js +70 -0
  107. package/lib/util/packageDependencyGraphService.js.map +1 -0
  108. package/package.json +18 -15
  109. package/src/__tests__/alphabeticalScripts.spec.ts +75 -0
  110. package/src/__tests__/bannedDependencies.spec.ts +191 -0
  111. package/src/__tests__/consistentDependencies.spec.ts +39 -26
  112. package/src/__tests__/consistentVersions.spec.ts +223 -0
  113. package/src/__tests__/fileContents.spec.ts +74 -0
  114. package/src/__tests__/mustSatisfyPeerDependencies.spec.ts +1188 -0
  115. package/src/__tests__/nestedWorkspaces.spec.ts +152 -0
  116. package/src/__tests__/packageEntry.spec.ts +98 -31
  117. package/src/__tests__/packageOrder.spec.ts +46 -40
  118. package/src/__tests__/packageScript.spec.ts +62 -54
  119. package/src/__tests__/requireDependency.spec.ts +151 -0
  120. package/src/__tests__/utils.ts +113 -11
  121. package/src/alphabeticalDependencies.ts +3 -47
  122. package/src/alphabeticalScripts.ts +19 -0
  123. package/src/bannedDependencies.ts +133 -43
  124. package/src/consistentDependencies.ts +34 -12
  125. package/src/consistentVersions.ts +140 -0
  126. package/src/fileContents.ts +31 -27
  127. package/src/index.ts +5 -0
  128. package/src/mustSatisfyPeerDependencies.ts +739 -0
  129. package/src/nestedWorkspaces.ts +59 -0
  130. package/src/packageEntry.ts +67 -24
  131. package/src/packageOrder.ts +6 -6
  132. package/src/packageScript.ts +3 -3
  133. package/src/requireDependency.ts +69 -0
  134. package/src/standardTsconfig.ts +40 -18
  135. package/src/util/checkAlpha.ts +59 -0
  136. package/src/util/makeDirectory.ts +24 -0
  137. package/src/util/packageDependencyGraphService.ts +114 -0
  138. package/tsconfig.tsbuildinfo +1 -2439
@@ -0,0 +1,59 @@
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 globby from "globby";
10
+ import path from "path";
11
+ import * as r from "runtypes";
12
+
13
+ export const Options = r.Undefined;
14
+
15
+ type Options = r.Static<typeof Options>;
16
+
17
+ // Enforce that the root package.json contains all of the workspaces in the repo (including nested packages)
18
+ export const nestedWorkspaces: RuleModule<typeof Options> = {
19
+ check: (context: Context) => {
20
+ const rootPackageJson = context.getWorkspaceContext().getPackageJson();
21
+
22
+ // Expand a set of globs covering all package.json files in the entire repo (except the root)
23
+ const packageJsonPaths = globby.sync(["*/**/package.json", "!**/node_modules/**"]);
24
+
25
+ const workspaces = Array.isArray(rootPackageJson.workspaces)
26
+ ? rootPackageJson.workspaces
27
+ : rootPackageJson.workspaces !== undefined
28
+ ? rootPackageJson.workspaces.packages
29
+ : undefined;
30
+
31
+ if (workspaces === undefined && packageJsonPaths.length > 0) {
32
+ context.addError({
33
+ file: context.getPackageJsonPath(),
34
+ message: 'The "workspace" field is missing, even though there are workspaces in the repository.',
35
+ });
36
+ return;
37
+ }
38
+
39
+ // Build a set of globs for each package.json that exists in packages specified by a workspace.
40
+ const workspacePackageJsons = (workspaces || []).map((item) => `${item}/package.json`);
41
+
42
+ // Expand the globs to get an array of all package.json files that are in packages specified by a workspace.
43
+ const expandedWorkspacesGlobs = globby.sync([...workspacePackageJsons, "!**/node_modules/**"]);
44
+
45
+ // Ensure there are no package.jsons which are not included in the globbed workspaces set
46
+ const difference = packageJsonPaths.filter((packageJsonPath) => !expandedWorkspacesGlobs.includes(packageJsonPath));
47
+
48
+ if (difference.length !== 0) {
49
+ const differencesList = difference.map((packageJsonPath) => path.dirname(packageJsonPath)).join(", ");
50
+ context.addError({
51
+ file: context.getPackageJsonPath(),
52
+ message:
53
+ `The "workspace" field is missing one or more values: ${differencesList}. ` +
54
+ 'You may be able to use a glob to avoid listing each workspace individually, e.g. "packages/nested-workspace/*".',
55
+ });
56
+ }
57
+ },
58
+ optionsRuntype: Options,
59
+ };
@@ -10,37 +10,80 @@ import { mutateJson, PackageJson } from "@monorepolint/utils";
10
10
  import diff from "jest-diff";
11
11
  import * as r from "runtypes";
12
12
 
13
- export const Options = r.Record({
14
- entries: r.Dictionary(r.Unknown), // string => unknown
15
- });
13
+ export const Options = r.Union(
14
+ r
15
+ .Record({
16
+ entries: r.Dictionary(r.Unknown), // string => unknown, enforces existence of keys and their values
17
+ })
18
+ .And(
19
+ r.Partial({
20
+ entriesExist: r.Undefined,
21
+ })
22
+ ),
23
+ r
24
+ .Record({
25
+ entriesExist: r.Array(r.String), // enforces existence of keys, but not values
26
+ })
27
+ .And(
28
+ r.Partial({
29
+ entries: r.Undefined,
30
+ })
31
+ ),
32
+ r.Record({
33
+ entries: r.Dictionary(r.Unknown), // string => unknown, enforces existence of keys and their values
34
+ entriesExist: r.Array(r.String),
35
+ })
36
+ );
16
37
 
17
38
  export type Options = r.Static<typeof Options>;
18
39
 
19
- export const packageEntry = {
40
+ export const packageEntry: RuleModule<typeof Options> = {
20
41
  check: function expectPackageEntry(context: Context, options: Options) {
21
42
  const packageJson = context.getPackageJson();
22
43
 
23
- for (const key of Object.keys(options.entries)) {
24
- const value = options.entries[key];
25
-
26
- const entryDiff = diff(JSON.stringify(value) + "\n", (JSON.stringify(packageJson[key]) || "") + "\n");
27
- if (
28
- (typeof value !== "object" && value !== packageJson[key]) ||
29
- (entryDiff == null || !entryDiff.includes("Compared values have no visual difference"))
30
- ) {
31
- context.addError({
32
- file: context.getPackageJsonPath(),
33
- message: `Expected standardized entry for '${key}'`,
34
- longMessage: entryDiff,
35
- fixer: () => {
36
- mutateJson<PackageJson>(context.getPackageJsonPath(), input => {
37
- input[key] = value;
38
- return input;
39
- });
40
- },
41
- });
44
+ if (options.entries) {
45
+ for (const key of Object.keys(options.entries)) {
46
+ const value = options.entries[key];
47
+
48
+ const entryDiff = diff(JSON.stringify(value) + "\n", (JSON.stringify(packageJson[key]) || "") + "\n");
49
+ if (
50
+ (typeof value !== "object" && value !== packageJson[key]) ||
51
+ entryDiff == null ||
52
+ !entryDiff.includes("Compared values have no visual difference")
53
+ ) {
54
+ context.addError({
55
+ file: context.getPackageJsonPath(),
56
+ message: createStandardizedEntryErrorMessage(key),
57
+ longMessage: entryDiff,
58
+ fixer: () => {
59
+ mutateJson<PackageJson>(context.getPackageJsonPath(), context.host, (input) => {
60
+ input[key] = value;
61
+ return input;
62
+ });
63
+ },
64
+ });
65
+ }
66
+ }
67
+ }
68
+
69
+ if (options.entriesExist) {
70
+ for (const key of options.entriesExist) {
71
+ if (packageJson[key] === undefined) {
72
+ context.addError({
73
+ file: context.getPackageJsonPath(),
74
+ message: createExpectedEntryErrorMessage(key),
75
+ });
76
+ }
42
77
  }
43
78
  }
44
79
  },
45
80
  optionsRuntype: Options,
46
- } 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
 
@@ -23,6 +22,7 @@ type Options = r.Static<typeof Options>;
23
22
  const defaultKeyOrder = [
24
23
  "name",
25
24
  "version",
25
+ "description",
26
26
  "author",
27
27
  "contributors",
28
28
  "url",
@@ -55,10 +55,10 @@ export const packageOrder = {
55
55
 
56
56
  const order: string[] | OrderFunction = opts === undefined ? defaultKeyOrder : opts.order;
57
57
 
58
- const compartor = isOrderFunction(order) ? order(context) : createCompartor(order);
58
+ const comparator = isOrderFunction(order) ? order(context) : createComparator(order);
59
59
 
60
60
  const actualOrder = Object.keys(packageJson);
61
- const expectedOrder = actualOrder.slice().sort(compartor); // sort mutates, so we need to copy the previous result
61
+ const expectedOrder = actualOrder.slice().sort(comparator); // sort mutates, so we need to copy the previous result
62
62
 
63
63
  if (!arrayOrderCompare(actualOrder, expectedOrder)) {
64
64
  context.addError({
@@ -68,11 +68,11 @@ export const packageOrder = {
68
68
  fixer: () => {
69
69
  const expectedPackageJson: Record<string, any> = {};
70
70
 
71
- expectedOrder.forEach(key => {
71
+ expectedOrder.forEach((key) => {
72
72
  expectedPackageJson[key] = packageJson[key];
73
73
  });
74
74
 
75
- writeJson(packagePath, expectedPackageJson);
75
+ context.host.writeJson(packagePath, expectedPackageJson);
76
76
  },
77
77
  });
78
78
  }
@@ -90,7 +90,7 @@ function arrayOrderCompare(a: ReadonlyArray<string>, b: ReadonlyArray<string>) {
90
90
  return true;
91
91
  }
92
92
 
93
- function createCompartor(order: ReadonlyArray<string>) {
93
+ function createComparator(order: ReadonlyArray<string>) {
94
94
  return (a: string, b: string) => {
95
95
  const aIndex = order.indexOf(a);
96
96
  const bIndex = order.indexOf(b);
@@ -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 {
@@ -87,7 +87,7 @@ export const packageScript = {
87
87
  }
88
88
 
89
89
  const validOptionsString = Array.from(allowedValues.values())
90
- .map(a => (a === undefined ? "(empty)" : `'${a}'`))
90
+ .map((a) => (a === undefined ? "(empty)" : `'${a}'`))
91
91
  .join(", ");
92
92
 
93
93
  context.addError({
@@ -0,0 +1,69 @@
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 { mutateJson, PackageJson } from "@monorepolint/utils";
10
+ import diff from "jest-diff";
11
+ import * as r from "runtypes";
12
+
13
+ const Options = r.Partial({
14
+ dependencies: r.Dictionary(r.String),
15
+ devDependencies: r.Dictionary(r.String),
16
+ peerDependencies: r.Dictionary(r.String),
17
+ optionalDependencies: r.Dictionary(r.String),
18
+ });
19
+
20
+ type Options = r.Static<typeof Options>;
21
+
22
+ export const requireDependency = {
23
+ check: function expectPackageEntry(context: Context, options: Options) {
24
+ const packageJson = context.getPackageJson();
25
+ const packageJsonPath = context.getPackageJsonPath();
26
+
27
+ [
28
+ "dependencies" as const,
29
+ "devDependencies" as const,
30
+ "peerDependencies" as const,
31
+ "optionalDependencies" as const,
32
+ ].forEach((type) => {
33
+ if (!options[type]) {
34
+ return;
35
+ }
36
+
37
+ if (packageJson[type] === undefined) {
38
+ context.addError({
39
+ file: packageJsonPath,
40
+ message: `No ${type} block, cannot add required ${type}.`,
41
+ fixer: () => {
42
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
43
+ input[type] = options[type];
44
+ return input;
45
+ });
46
+ },
47
+ });
48
+ return;
49
+ }
50
+
51
+ for (const [dep, version] of Object.entries(options[type]!)) {
52
+ if (packageJson[type][dep] !== version) {
53
+ context.addError({
54
+ file: packageJsonPath,
55
+ message: `Expected dependency ${dep}@${version}`,
56
+ longMessage: diff(`${dep}@${version}\n`, `${dep}@${packageJson[type][dep] || "missing"}\n`)!,
57
+ fixer: () => {
58
+ mutateJson<PackageJson>(packageJsonPath, context.host, (input) => {
59
+ input[type] = { ...input[type], [dep]: version };
60
+ return input;
61
+ });
62
+ },
63
+ });
64
+ }
65
+ }
66
+ });
67
+ },
68
+ optionsRuntype: Options,
69
+ } as RuleModule<typeof Options>;
@@ -6,19 +6,22 @@
6
6
  */
7
7
 
8
8
  import { Context, RuleModule } from "@monorepolint/core";
9
- import { getPackageNameToDir } from "@monorepolint/utils";
10
- import { existsSync, readFileSync, writeFileSync } from "fs";
9
+ import { matchesAnyGlob } from "@monorepolint/utils";
11
10
  import diff from "jest-diff";
12
- import minimatch from "minimatch";
13
11
  import * as path from "path";
14
12
  import * as r from "runtypes";
15
13
 
14
+ const DEFAULT_TSCONFIG_FILENAME = "tsconfig.json";
15
+
16
16
  const Options = r
17
17
  .Partial({
18
+ file: r.String,
18
19
  generator: r.Function,
20
+ tsconfigReferenceFile: r.String,
19
21
  template: r.Record({}).Or(r.String),
20
22
  templateFile: r.String,
21
23
  excludedReferences: r.Array(r.String).Or(r.Undefined),
24
+ additionalReferences: r.Array(r.String).Or(r.Undefined),
22
25
  })
23
26
  .withConstraint(({ generator, template, templateFile }) => {
24
27
  let count = 0;
@@ -39,11 +42,14 @@ export type Options = r.Static<typeof Options>;
39
42
 
40
43
  export const standardTsconfig = {
41
44
  check: function expectStandardTsconfig(context: Context, opts: Options) {
42
- const fullPath = path.resolve(context.packageDir, "tsconfig.json");
45
+ const tsconfigFileName = opts.file ?? DEFAULT_TSCONFIG_FILENAME;
46
+ const fullPath = path.resolve(context.packageDir, tsconfigFileName);
43
47
  const generator = getGenerator(context, opts);
44
48
  const expectedContent = generator(context);
45
49
 
46
- 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;
47
53
 
48
54
  if (expectedContent === undefined) {
49
55
  context.addWarning({
@@ -59,7 +65,9 @@ export const standardTsconfig = {
59
65
  message: "Expect file contents to match",
60
66
  longMessage: diff(expectedContent, actualContent, { expand: true }),
61
67
  fixer: () => {
62
- writeFileSync(fullPath, expectedContent);
68
+ context.host.writeFile(fullPath, expectedContent, {
69
+ encoding: "utf-8",
70
+ });
63
71
  },
64
72
  });
65
73
  }
@@ -73,37 +81,51 @@ function getGenerator(context: Context, opts: Options) {
73
81
  } else if (opts.templateFile) {
74
82
  const { packageDir: workspacePackageDir } = context.getWorkspaceContext();
75
83
  const fullPath = path.resolve(workspacePackageDir, opts.templateFile);
76
- const template = JSON.parse(readFileSync(fullPath, "utf-8"));
84
+ const template = JSON.parse(context.host.readFile(fullPath, { encoding: "utf-8" }));
77
85
 
78
- return makeGenerator(template, opts.excludedReferences);
86
+ return makeGenerator(template, opts.excludedReferences, opts.additionalReferences, opts.tsconfigReferenceFile);
79
87
  } else if (opts.template) {
80
- return makeGenerator(opts.template, opts.excludedReferences);
88
+ return makeGenerator(opts.template, opts.excludedReferences, opts.additionalReferences, opts.tsconfigReferenceFile);
81
89
  } else {
82
90
  throw new Error("Unable to make generator");
83
91
  }
84
92
  }
85
93
 
86
- function makeGenerator(template: any, excludedReferences: ReadonlyArray<string> = []) {
94
+ function makeGenerator(
95
+ template: any,
96
+ excludedReferences: ReadonlyArray<string> | undefined,
97
+ additionalReferences: ReadonlyArray<string> | undefined,
98
+ tsconfigReferenceFile?: string
99
+ ) {
87
100
  return function generator(context: Context) {
88
101
  template = {
89
102
  ...template,
90
103
  references: [],
91
104
  }; // make a copy and ensure we have a references array
92
105
 
93
- const nameToDirectory = getPackageNameToDir(context.getWorkspaceContext().packageDir);
106
+ const nameToDirectory = context.getWorkspaceContext().getPackageNameToDir();
94
107
 
95
108
  const packageJson = context.getPackageJson();
96
109
  const deps = [...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.devDependencies || {})];
97
110
 
98
- deps
99
- .filter(
100
- name => nameToDirectory.has(name) && !excludedReferences.some(excludedRef => minimatch(name, excludedRef))
101
- )
102
- .forEach(packageName => {
111
+ for (const dep of deps) {
112
+ const packageDir = nameToDirectory.get(dep);
113
+ if (packageDir !== undefined && (excludedReferences === undefined || matchesAnyGlob(dep, excludedReferences))) {
114
+ const absoluteReferencePath =
115
+ tsconfigReferenceFile !== undefined ? path.join(packageDir, tsconfigReferenceFile) : packageDir;
103
116
  template.references.push({
104
- path: path.relative(context.packageDir, nameToDirectory.get(packageName)!),
117
+ path: path.relative(context.packageDir, absoluteReferencePath),
105
118
  });
106
- });
119
+ }
120
+ }
121
+
122
+ if (additionalReferences) {
123
+ for (const additionalReference of additionalReferences) {
124
+ template.references.push({
125
+ path: additionalReference,
126
+ });
127
+ }
128
+ }
107
129
 
108
130
  return JSON.stringify(template, undefined, 2) + "\n";
109
131
  };
@@ -0,0 +1,59 @@
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 } from "@monorepolint/core";
9
+ import diff from "jest-diff";
10
+
11
+ export function checkAlpha(
12
+ context: Context,
13
+ block: "dependencies" | "devDependencies" | "peerDependencies" | "scripts"
14
+ ) {
15
+ const packageJson = context.getPackageJson();
16
+ const packagePath = context.getPackageJsonPath();
17
+
18
+ const blockToSort = packageJson[block];
19
+
20
+ if (blockToSort === undefined) {
21
+ return;
22
+ }
23
+
24
+ const actualOrder = Object.keys(blockToSort);
25
+ const expectedOrder = actualOrder.slice().sort(); // sort mutates, so we need to copy the previous result
26
+
27
+ if (!arrayOrderCompare(actualOrder, expectedOrder)) {
28
+ context.addError({
29
+ file: packagePath,
30
+ message: createIncorrectOrderErrorMessage(block, packageJson.name!),
31
+ longMessage: diff(expectedOrder, actualOrder, { expand: true }),
32
+ fixer: () => {
33
+ const expectedDependencies: Record<string, string> = {};
34
+
35
+ expectedOrder.forEach((dep) => {
36
+ expectedDependencies[dep] = blockToSort[dep];
37
+ });
38
+
39
+ const newPackageJson = { ...packageJson };
40
+ newPackageJson[block] = expectedDependencies;
41
+ context.host.writeJson(packagePath, newPackageJson);
42
+ },
43
+ });
44
+ }
45
+ }
46
+
47
+ function arrayOrderCompare(a: ReadonlyArray<string>, b: ReadonlyArray<string>) {
48
+ for (let index = 0; index < a.length; index++) {
49
+ if (a[index] !== b[index]) {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ return true;
55
+ }
56
+
57
+ export function createIncorrectOrderErrorMessage(block: string, packageName: string) {
58
+ return `Incorrect order of ${block} in ${packageName}'s package.json`;
59
+ }
@@ -0,0 +1,24 @@
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 { existsSync, mkdirSync } from "fs";
9
+ import * as path from "path";
10
+
11
+ export function makeDirectoryRecursively(directoryPath: string) {
12
+ // node < 10 doesn't support mkdirSync w/ recursive: true
13
+ // so we manually do it instead
14
+ const dirSegments = directoryPath.split(path.sep);
15
+ for (let i = 0; i < dirSegments.length; i++) {
16
+ if (dirSegments[i].length > 0) {
17
+ // we skip the empty segment
18
+ const curDirPath = dirSegments.slice(0, i + 1).join(path.sep);
19
+ if (!existsSync(curDirPath)) {
20
+ mkdirSync(curDirPath);
21
+ }
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @license Copyright 2019 Palantir Technologies, Inc. All rights reserved.
3
+ */
4
+
5
+ import { Host, PackageJson } from "@monorepolint/utils";
6
+ import path from "path";
7
+ import resolvePackagePath from "resolve-package-path";
8
+
9
+ /** Interface for a node in a package dependency graph. */
10
+ export interface IPackageDependencyGraphNode {
11
+ packageJson: PackageJson;
12
+ dependencies: Map<string, IPackageDependencyGraphNode>;
13
+ paths: {
14
+ packageJsonPath: string;
15
+ rootDirectory: string;
16
+ };
17
+ }
18
+
19
+ /** Service abstraction for constructing and traversing package dependency graphs. */
20
+ export interface IPackageDependencyGraphService {
21
+ /** Construct a graph of package dependencies. */
22
+ buildDependencyGraph(packageJsonPath: string, host: Host, maxDepth?: number): IPackageDependencyGraphNode;
23
+
24
+ /** Traverse a package dependency graph. */
25
+ traverse(
26
+ root: IPackageDependencyGraphNode,
27
+ opts?: {
28
+ /** Traverse each unique path to a given package (potentially slow). */
29
+ traverseAllPaths?: boolean;
30
+ }
31
+ ): IterableIterator<IPackageDependencyGraphNode & { importPath: IPackageDependencyGraphNode[] }>;
32
+ }
33
+
34
+ /** Default implementation of the package dependency graph service. */
35
+ export class PackageDependencyGraphService implements IPackageDependencyGraphService {
36
+ /** Construct a graph of package dependencies and return the root node. */
37
+ public buildDependencyGraph(
38
+ startPackageJsonPath: string,
39
+ host: Host,
40
+ maxDepth?: number
41
+ ): IPackageDependencyGraphNode {
42
+ const nodes = new Map<string, IPackageDependencyGraphNode>();
43
+ const visit = (packageJsonPath: string, currentDepth: number): IPackageDependencyGraphNode => {
44
+ if (nodes.has(packageJsonPath)) {
45
+ return nodes.get(packageJsonPath)!;
46
+ }
47
+
48
+ const packageJson: PackageJson = host.readJson(packageJsonPath);
49
+ const node: IPackageDependencyGraphNode = {
50
+ packageJson,
51
+ dependencies: new Map<string, IPackageDependencyGraphNode>(),
52
+ paths: {
53
+ packageJsonPath,
54
+ rootDirectory: path.dirname(packageJsonPath),
55
+ },
56
+ };
57
+
58
+ // It's important that we register the node before visiting its dependencies to avoid cycles
59
+ nodes.set(packageJsonPath, node);
60
+
61
+ const nextDepth = currentDepth + 1;
62
+ if (maxDepth == null || nextDepth <= maxDepth) {
63
+ const dependencies = packageJson.dependencies != null ? Object.keys(packageJson.dependencies) : [];
64
+ for (const dependency of dependencies) {
65
+ const dependencyPackageJsonPath = resolvePackagePath(dependency, node.paths.rootDirectory);
66
+ if (dependencyPackageJsonPath == null) {
67
+ throw new Error(`Could not resolve ${dependency} from ${node.paths.rootDirectory}`);
68
+ }
69
+
70
+ node.dependencies.set(dependency, visit(dependencyPackageJsonPath, nextDepth));
71
+ }
72
+ }
73
+
74
+ return node;
75
+ };
76
+
77
+ return visit(startPackageJsonPath, 0);
78
+ }
79
+
80
+ /** Traverse a package dependency graph with an iterator. */
81
+ public *traverse(
82
+ root: IPackageDependencyGraphNode,
83
+ opts = { traverseAllPaths: false }
84
+ ): IterableIterator<IPackageDependencyGraphNode & { importPath: IPackageDependencyGraphNode[] }> {
85
+ const visited = new Set<IPackageDependencyGraphNode>();
86
+
87
+ function* visit(
88
+ node: IPackageDependencyGraphNode,
89
+ importPath: IPackageDependencyGraphNode[] = []
90
+ ): IterableIterator<IPackageDependencyGraphNode & { importPath: IPackageDependencyGraphNode[] }> {
91
+ // Don't visit a package more than once unless explicitly asked to traverse all paths
92
+ if (!opts.traverseAllPaths && visited.has(node)) {
93
+ return;
94
+ }
95
+
96
+ // Break cycles when traversing all paths
97
+ if (importPath.indexOf(node) !== -1) {
98
+ return;
99
+ }
100
+
101
+ // Visit the node
102
+ visited.add(node);
103
+ importPath = [...importPath, node];
104
+ yield { ...node, importPath };
105
+
106
+ // Recursively visit the node's dependencies
107
+ for (const dependency of node.dependencies.values()) {
108
+ yield* visit(dependency, importPath);
109
+ }
110
+ }
111
+
112
+ yield* visit(root);
113
+ }
114
+ }