@code-pushup/eslint-plugin 0.1.1 → 0.2.0

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.
package/README.md CHANGED
@@ -35,6 +35,8 @@ Detected ESLint rules are mapped to Code PushUp audits. Audit reports are calcul
35
35
  Remember that Code PushUp only collects and uploads the results, it doesn't fail if errors are found.
36
36
  So you can be more strict than in most linter setups, the idea is to set aspirational goals and track your progress.
37
37
 
38
+ > 💡 We recommend extending our own [`@code-pushup/eslint-config`](https://www.npmjs.com/package/@code-pushup/eslint-config). 😇
39
+
38
40
  4. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`).
39
41
 
40
42
  Pass in the path to your ESLint config file, along with glob patterns for which files you wish to target (relative to `process.cwd()`).
@@ -51,6 +53,34 @@ Detected ESLint rules are mapped to Code PushUp audits. Audit reports are calcul
51
53
  };
52
54
  ```
53
55
 
56
+ If you're using an Nx monorepo, additional helper functions are provided to simplify your configuration:
57
+
58
+ - If you wish to combine all projects in your workspace into one report, use the `eslintConfigFromNxProjects` helper:
59
+
60
+ ```js
61
+ import eslintPlugin, { eslintConfigFromNxProjects } from '@code-pushup/eslint-plugin';
62
+
63
+ export default {
64
+ plugins: [
65
+ // ...
66
+ await eslintPlugin(await eslintConfigFromNxProjects()),
67
+ ],
68
+ };
69
+ ```
70
+
71
+ - If you wish to target a specific project along with other projects it depends on, use the `eslintConfigFromNxProject` helper and pass in in your project name:
72
+
73
+ ```js
74
+ import eslintPlugin, { eslintConfigFromNxProject } from '@code-pushup/eslint-plugin';
75
+
76
+ export default {
77
+ plugins: [
78
+ // ...
79
+ await eslintPlugin(await eslintConfigFromNxProject('<PROJECT-NAME>')),
80
+ ],
81
+ };
82
+ ```
83
+
54
84
  5. (Optional) Reference audits (or groups) which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups).
55
85
 
56
86
  Assign weights based on what influence each ESLint rule should have on the overall category score (assign weight 0 to only include as extra info, without influencing category score).
package/bin.js CHANGED
@@ -550,13 +550,6 @@ function pluralizeToken(token, times = 0) {
550
550
  }
551
551
 
552
552
  // packages/utils/src/lib/file-system.ts
553
- function toUnixPath(path, options) {
554
- const unixPath = path.replace(/\\/g, "/");
555
- if (options?.toRelative) {
556
- return unixPath.replace(process.cwd().replace(/\\/g, "/") + "/", "");
557
- }
558
- return unixPath;
559
- }
560
553
  async function readTextFile(path) {
561
554
  const buffer = await readFile(path);
562
555
  return buffer.toString();
@@ -565,6 +558,13 @@ async function readJsonFile(path) {
565
558
  const text = await readTextFile(path);
566
559
  return JSON.parse(text);
567
560
  }
561
+ function toUnixPath(path, options) {
562
+ const unixPath = path.replace(/\\/g, "/");
563
+ if (options?.toRelative) {
564
+ return unixPath.replace(process.cwd().replace(/\\/g, "/") + "/", "");
565
+ }
566
+ return unixPath;
567
+ }
568
568
  function pluginWorkDir(slug) {
569
569
  return join("node_modules", ".code-pushup", slug);
570
570
  }
package/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // packages/plugin-eslint/src/lib/eslint-plugin.ts
2
- import { mkdir, writeFile } from "fs/promises";
2
+ import { mkdir as mkdir2, writeFile } from "fs/promises";
3
3
  import { dirname as dirname2, join as join3 } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
 
6
6
  // packages/plugin-eslint/package.json
7
7
  var name = "@code-pushup/eslint-plugin";
8
- var version = "0.1.1";
8
+ var version = "0.2.0";
9
9
 
10
10
  // packages/plugin-eslint/src/lib/config.ts
11
11
  import { z } from "zod";
@@ -551,6 +551,7 @@ var reportSchema = packageVersionSchema({
551
551
  // packages/utils/src/lib/file-system.ts
552
552
  import { bundleRequire } from "bundle-require";
553
553
  import chalk from "chalk";
554
+ import { mkdir, readFile, readdir, stat } from "fs/promises";
554
555
  import { join } from "path";
555
556
 
556
557
  // packages/utils/src/lib/formatting.ts
@@ -559,6 +560,14 @@ function slugify(text) {
559
560
  }
560
561
 
561
562
  // packages/utils/src/lib/file-system.ts
563
+ async function fileExists(path) {
564
+ try {
565
+ const stats = await stat(path);
566
+ return stats.isFile();
567
+ } catch {
568
+ return false;
569
+ }
570
+ }
562
571
  function pluginWorkDir(slug) {
563
572
  return join("node_modules", ".code-pushup", slug);
564
573
  }
@@ -840,7 +849,7 @@ async function eslintPlugin(config) {
840
849
  const eslint = setupESLint(eslintrc);
841
850
  const { audits, groups } = await listAuditsAndGroups(eslint, patterns);
842
851
  if (typeof eslintrc !== "string") {
843
- await mkdir(dirname2(ESLINTRC_PATH), { recursive: true });
852
+ await mkdir2(dirname2(ESLINTRC_PATH), { recursive: true });
844
853
  await writeFile(ESLINTRC_PATH, JSON.stringify(eslintrc));
845
854
  }
846
855
  const eslintrcPath = typeof eslintrc === "string" ? eslintrc : ESLINTRC_PATH;
@@ -853,7 +862,7 @@ async function eslintPlugin(config) {
853
862
  title: "ESLint",
854
863
  icon: "eslint",
855
864
  description: "Official Code PushUp ESLint plugin",
856
- // TODO: docsUrl (package README, once published)
865
+ docsUrl: "https://www.npmjs.com/package/@code-pushup/eslint-plugin",
857
866
  packageName: name,
858
867
  version,
859
868
  audits,
@@ -867,8 +876,101 @@ async function eslintPlugin(config) {
867
876
  };
868
877
  }
869
878
 
879
+ // packages/plugin-eslint/src/lib/nx/utils.ts
880
+ import { join as join4 } from "node:path";
881
+ async function findCodePushupEslintrc(project) {
882
+ const name2 = "code-pushup.eslintrc";
883
+ const extensions = ["json", "js", "cjs", "yml", "yaml"];
884
+ for (const ext of extensions) {
885
+ const filename = `./${project.root}/${name2}.${ext}`;
886
+ if (await fileExists(join4(process.cwd(), filename))) {
887
+ return filename;
888
+ }
889
+ }
890
+ return null;
891
+ }
892
+ function getLintFilePatterns(project) {
893
+ const options = project.targets?.["lint"]?.options;
894
+ return options?.lintFilePatterns == null ? [] : toArray(options.lintFilePatterns);
895
+ }
896
+ function getEslintConfig(project) {
897
+ const options = project.targets?.["lint"]?.options;
898
+ return options?.eslintConfig ?? `./${project.root}/.eslintrc.json`;
899
+ }
900
+
901
+ // packages/plugin-eslint/src/lib/nx/projects-to-config.ts
902
+ async function nxProjectsToConfig(projectGraph, predicate = () => true) {
903
+ const { readProjectsConfigurationFromProjectGraph } = await import("@nx/devkit");
904
+ const projectsConfiguration = readProjectsConfigurationFromProjectGraph(projectGraph);
905
+ const projects = Object.values(projectsConfiguration.projects).filter((project) => "lint" in (project.targets ?? {})).filter(predicate).sort((a, b) => a.root.localeCompare(b.root));
906
+ const eslintConfig = {
907
+ root: true,
908
+ overrides: await Promise.all(
909
+ projects.map(async (project) => ({
910
+ files: getLintFilePatterns(project),
911
+ extends: await findCodePushupEslintrc(project) ?? getEslintConfig(project)
912
+ }))
913
+ )
914
+ };
915
+ const patterns = projects.flatMap((project) => [
916
+ ...getLintFilePatterns(project),
917
+ // HACK: ESLint.calculateConfigForFile won't find rules included only for subsets of *.ts when globs used
918
+ // so we explicitly provide additional patterns used by @code-pushup/eslint-config to ensure those rules are included
919
+ // this workaround won't be necessary once flat configs are stable (much easier to find all rules)
920
+ `${project.sourceRoot}/*.spec.ts`,
921
+ // jest/* and vitest/* rules
922
+ `${project.sourceRoot}/*.cy.ts`,
923
+ // cypress/* rules
924
+ `${project.sourceRoot}/*.stories.ts`,
925
+ // storybook/* rules
926
+ `${project.sourceRoot}/.storybook/main.ts`
927
+ // storybook/no-uninstalled-addons rule
928
+ ]);
929
+ return {
930
+ eslintrc: eslintConfig,
931
+ patterns
932
+ };
933
+ }
934
+
935
+ // packages/plugin-eslint/src/lib/nx/find-all-projects.ts
936
+ async function eslintConfigFromNxProjects() {
937
+ const { createProjectGraphAsync } = await import("@nx/devkit");
938
+ const projectGraph = await createProjectGraphAsync({ exitOnError: false });
939
+ return nxProjectsToConfig(projectGraph);
940
+ }
941
+
942
+ // packages/plugin-eslint/src/lib/nx/traverse-graph.ts
943
+ function findAllDependencies(entry, projectGraph) {
944
+ const results = /* @__PURE__ */ new Set();
945
+ const queue = [entry];
946
+ while (queue.length > 0) {
947
+ const source = queue.shift();
948
+ const dependencies = projectGraph.dependencies[source];
949
+ for (const { target } of dependencies ?? []) {
950
+ if (!results.has(target) && target !== entry) {
951
+ results.add(target);
952
+ queue.push(target);
953
+ }
954
+ }
955
+ }
956
+ return results;
957
+ }
958
+
959
+ // packages/plugin-eslint/src/lib/nx/find-project-with-deps.ts
960
+ async function eslintConfigFromNxProject(projectName) {
961
+ const { createProjectGraphAsync } = await import("@nx/devkit");
962
+ const projectGraph = await createProjectGraphAsync({ exitOnError: false });
963
+ const dependencies = findAllDependencies(projectName, projectGraph);
964
+ return nxProjectsToConfig(
965
+ projectGraph,
966
+ (project) => !!project.name && (project.name === projectName || dependencies.has(project.name))
967
+ );
968
+ }
969
+
870
970
  // packages/plugin-eslint/src/index.ts
871
971
  var src_default = eslintPlugin;
872
972
  export {
873
- src_default as default
973
+ src_default as default,
974
+ eslintConfigFromNxProject,
975
+ eslintConfigFromNxProjects
874
976
  };
package/package.json CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "@code-pushup/eslint-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "dependencies": {
5
5
  "@code-pushup/utils": "*",
6
+ "@code-pushup/models": "*",
6
7
  "eslint": "~8.46.0",
7
- "zod": "^3.22.4",
8
- "@code-pushup/models": "*"
8
+ "zod": "^3.22.4"
9
+ },
10
+ "peerDependencies": {
11
+ "@nx/devkit": "^17.0.0"
12
+ },
13
+ "peerDependenciesMeta": {
14
+ "@nx/devkit": {
15
+ "optional": true
16
+ }
9
17
  },
10
18
  "type": "module",
11
19
  "main": "./index.js",
package/src/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { eslintPlugin } from './lib/eslint-plugin';
2
2
  export default eslintPlugin;
3
3
  export type { ESLintPluginConfig } from './lib/config';
4
+ export { eslintConfigFromNxProject, eslintConfigFromNxProjects, } from './lib/nx';
@@ -0,0 +1,23 @@
1
+ import type { ESLintPluginConfig } from '../config';
2
+ /**
3
+ * Finds all Nx projects in workspace and converts their lint configurations to Code PushUp ESLint plugin parameters.
4
+ *
5
+ * Use when you wish to automatically include every Nx project in a single Code PushUp project.
6
+ * If you prefer to only include a subset of your Nx monorepo, refer to {@link eslintConfigFromNxProject} instead.
7
+ *
8
+ * @example
9
+ * import eslintPlugin, {
10
+ * eslintConfigFromNxProjects,
11
+ * } from '@code-pushup/eslint-plugin';
12
+ *
13
+ * export default {
14
+ * plugins: [
15
+ * await eslintPlugin(
16
+ * await eslintConfigFromNxProjects()
17
+ * )
18
+ * ]
19
+ * }
20
+ *
21
+ * @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin}
22
+ */
23
+ export declare function eslintConfigFromNxProjects(): Promise<ESLintPluginConfig>;
@@ -0,0 +1,26 @@
1
+ import type { ESLintPluginConfig } from '../config';
2
+ /**
3
+ * Accepts a target Nx projects, finds projects it depends on, and converts lint configurations to Code PushUp ESLint plugin parameters.
4
+ *
5
+ * Use when you wish to include a targetted subset of your Nx monorepo in your Code PushUp project.
6
+ * If you prefer to include all Nx projects, refer to {@link eslintConfigFromNxProjects} instead.
7
+ *
8
+ * @example
9
+ * import eslintPlugin, {
10
+ * eslintConfigFromNxProject,
11
+ * } from '@code-pushup/eslint-plugin';
12
+ *
13
+ * const projectName = 'backoffice'; // <-- name from project.json
14
+ *
15
+ * export default {
16
+ * plugins: [
17
+ * await eslintPlugin(
18
+ * await eslintConfigFromNxProject(projectName)
19
+ * )
20
+ * ]
21
+ * }
22
+ *
23
+ * @param projectName Nx project serving as main entry point
24
+ * @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin}
25
+ */
26
+ export declare function eslintConfigFromNxProject(projectName: string): Promise<ESLintPluginConfig>;
@@ -0,0 +1,2 @@
1
+ export { eslintConfigFromNxProjects } from './find-all-projects';
2
+ export { eslintConfigFromNxProject } from './find-project-with-deps';
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfiguration, ProjectGraph } from '@nx/devkit';
2
+ import type { ESLintPluginConfig } from '../config';
3
+ export declare function nxProjectsToConfig(projectGraph: ProjectGraph, predicate?: (project: ProjectConfiguration) => boolean): Promise<ESLintPluginConfig>;
@@ -0,0 +1,2 @@
1
+ import type { ProjectGraph } from '@nx/devkit';
2
+ export declare function findAllDependencies(entry: string, projectGraph: ProjectGraph): ReadonlySet<string>;
@@ -0,0 +1,4 @@
1
+ import type { ProjectConfiguration } from '@nx/devkit';
2
+ export declare function findCodePushupEslintrc(project: ProjectConfiguration): Promise<string | null>;
3
+ export declare function getLintFilePatterns(project: ProjectConfiguration): string[];
4
+ export declare function getEslintConfig(project: ProjectConfiguration): string;