@featurevisor/core 2.12.0 → 2.13.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/coverage/clover.xml +1184 -466
  3. package/coverage/coverage-final.json +21 -6
  4. package/coverage/lcov-report/builder/allocator.ts.html +1 -1
  5. package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
  6. package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
  7. package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
  8. package/coverage/lcov-report/builder/index.html +1 -1
  9. package/coverage/lcov-report/builder/revision.ts.html +1 -1
  10. package/coverage/lcov-report/builder/traffic.ts.html +1 -1
  11. package/coverage/lcov-report/config/index.html +116 -0
  12. package/coverage/lcov-report/config/projectConfig.ts.html +676 -0
  13. package/coverage/lcov-report/datasource/adapter.ts.html +235 -0
  14. package/coverage/lcov-report/datasource/datasource.ts.html +862 -0
  15. package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +1354 -0
  16. package/coverage/lcov-report/datasource/index.html +161 -0
  17. package/coverage/lcov-report/datasource/index.ts.html +94 -0
  18. package/coverage/lcov-report/index.html +74 -29
  19. package/coverage/lcov-report/linter/attributeSchema.ts.html +175 -0
  20. package/coverage/lcov-report/linter/checkCircularDependency.ts.html +220 -0
  21. package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +268 -0
  22. package/coverage/lcov-report/linter/conditionSchema.ts.html +38 -38
  23. package/coverage/lcov-report/linter/featureSchema.ts.html +363 -363
  24. package/coverage/lcov-report/linter/groupSchema.ts.html +226 -0
  25. package/coverage/lcov-report/linter/index.html +124 -19
  26. package/coverage/lcov-report/linter/lintProject.ts.html +1840 -0
  27. package/coverage/lcov-report/linter/printError.ts.html +238 -0
  28. package/coverage/lcov-report/linter/schema.ts.html +116 -116
  29. package/coverage/lcov-report/linter/segmentSchema.ts.html +5 -5
  30. package/coverage/lcov-report/linter/testSchema.ts.html +550 -0
  31. package/coverage/lcov-report/list/index.html +1 -1
  32. package/coverage/lcov-report/list/matrix.ts.html +1 -1
  33. package/coverage/lcov-report/parsers/index.html +20 -5
  34. package/coverage/lcov-report/parsers/index.ts.html +151 -0
  35. package/coverage/lcov-report/parsers/json.ts.html +2 -2
  36. package/coverage/lcov-report/parsers/yml.ts.html +6 -6
  37. package/coverage/lcov-report/tester/cliFormat.ts.html +103 -0
  38. package/coverage/lcov-report/tester/helpers.ts.html +1 -1
  39. package/coverage/lcov-report/tester/index.html +20 -5
  40. package/coverage/lcov-report/utils/git.ts.html +481 -0
  41. package/coverage/lcov-report/utils/index.html +116 -0
  42. package/coverage/lcov.info +2179 -859
  43. package/lib/linter/lintProject.d.ts +17 -1
  44. package/lib/linter/lintProject.js +191 -198
  45. package/lib/linter/lintProject.js.map +1 -1
  46. package/lib/linter/lintProject.spec.d.ts +1 -0
  47. package/lib/linter/lintProject.spec.js +86 -0
  48. package/lib/linter/lintProject.spec.js.map +1 -0
  49. package/lib/linter/printError.d.ts +7 -0
  50. package/lib/linter/printError.js +30 -15
  51. package/lib/linter/printError.js.map +1 -1
  52. package/package.json +2 -2
  53. package/src/linter/lintProject.spec.ts +115 -0
  54. package/src/linter/lintProject.ts +256 -254
  55. package/src/linter/printError.ts +40 -16
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const projectConfig_1 = require("../config/projectConfig");
7
+ const datasource_1 = require("../datasource");
8
+ const lintProject_1 = require("./lintProject");
9
+ function createTempProjectFromExample1() {
10
+ const fixturePath = path.resolve(__dirname, "../../../../examples/example-1");
11
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-lint-"));
12
+ fs.cpSync(fixturePath, tempRoot, { recursive: true });
13
+ return tempRoot;
14
+ }
15
+ function getDeps(rootDirectoryPath) {
16
+ const projectConfig = (0, projectConfig_1.getProjectConfig)(rootDirectoryPath);
17
+ const datasource = new datasource_1.Datasource(projectConfig, rootDirectoryPath);
18
+ return {
19
+ rootDirectoryPath,
20
+ projectConfig,
21
+ datasource,
22
+ options: {},
23
+ };
24
+ }
25
+ describe("core: lintProject", function () {
26
+ let tempProjectPath;
27
+ beforeEach(() => {
28
+ tempProjectPath = createTempProjectFromExample1();
29
+ });
30
+ afterEach(() => {
31
+ fs.rmSync(tempProjectPath, { recursive: true, force: true });
32
+ jest.restoreAllMocks();
33
+ });
34
+ it("returns JSON-friendly empty errors array for a valid project", async () => {
35
+ const result = await (0, lintProject_1.lintProject)(getDeps(tempProjectPath), { json: true });
36
+ expect(result).toEqual({
37
+ hasError: false,
38
+ errors: [],
39
+ });
40
+ });
41
+ it("returns structured errors in JSON mode", async () => {
42
+ fs.writeFileSync(path.join(tempProjectPath, "attributes", "invalid name.yml"), "description: this has an invalid key name\ntype: string\n", "utf8");
43
+ const result = await (0, lintProject_1.lintProject)(getDeps(tempProjectPath), {
44
+ json: true,
45
+ entityType: "attribute",
46
+ });
47
+ expect(result.hasError).toBe(true);
48
+ expect(result.errors.length).toBeGreaterThan(0);
49
+ expect(result.errors[0]).toMatchObject({
50
+ entityType: "attribute",
51
+ key: "invalid name",
52
+ message: 'Invalid name: "invalid name"',
53
+ code: "invalid_name",
54
+ path: [],
55
+ });
56
+ expect(result.errors[0].filePath).toContain("attributes");
57
+ });
58
+ it("plugin prints pretty JSON only once in --json --pretty mode", async () => {
59
+ const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => { });
60
+ const pluginResult = await lintProject_1.lintPlugin.handler({
61
+ ...getDeps(tempProjectPath),
62
+ parsed: {
63
+ json: true,
64
+ pretty: true,
65
+ },
66
+ });
67
+ expect(pluginResult).toBeUndefined();
68
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1);
69
+ const output = consoleLogSpy.mock.calls[0][0];
70
+ expect(output).toContain('\n "errors": []\n');
71
+ });
72
+ it("does not call process.exit for zod validation errors while linting tests", async () => {
73
+ fs.writeFileSync(path.join(tempProjectPath, "tests", "broken.spec.yml"), "feature: foo\n", "utf8");
74
+ const processExitSpy = jest
75
+ .spyOn(process, "exit")
76
+ .mockImplementation((() => undefined));
77
+ const result = await (0, lintProject_1.lintProject)(getDeps(tempProjectPath), {
78
+ json: true,
79
+ entityType: "test",
80
+ });
81
+ expect(result.hasError).toBe(true);
82
+ expect(result.errors.length).toBeGreaterThan(0);
83
+ expect(processExitSpy).not.toHaveBeenCalled();
84
+ });
85
+ });
86
+ //# sourceMappingURL=lintProject.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lintProject.spec.js","sourceRoot":"","sources":["../../src/linter/lintProject.spec.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,9 @@
1
1
  import { ZodError } from "zod";
2
+ export interface LintIssueFromZod {
3
+ message: string;
4
+ path: (string | number)[];
5
+ code?: string;
6
+ value?: unknown;
7
+ }
8
+ export declare function getLintIssuesFromZodError(e: ZodError): LintIssueFromZod[];
2
9
  export declare function printZodError(e: ZodError): void;
@@ -1,24 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLintIssuesFromZodError = getLintIssuesFromZodError;
3
4
  exports.printZodError = printZodError;
4
5
  const cliFormat_1 = require("../tester/cliFormat");
5
- function printZodError(e) {
6
- const { issues } = e;
7
- issues.forEach((issue) => {
8
- if (issue.code === "invalid_union" && issue.path.length === 0 && issue.unionErrors.length > 0) {
9
- // invalid_union
6
+ function getLintIssuesFromZodError(e) {
7
+ return e.issues
8
+ .map((issue) => {
9
+ if (issue.code === "invalid_union" &&
10
+ issue.path.length === 0 &&
11
+ issue.unionErrors.length > 0) {
10
12
  const lastUnionError = issue.unionErrors[issue.unionErrors.length - 1];
11
- console.error(cliFormat_1.CLI_FORMAT_RED, ` => Error: ${lastUnionError.issues[0].message}`);
12
- console.error(" Path:", lastUnionError.issues[0].path.join("."));
13
+ const nestedIssue = lastUnionError.issues[0];
14
+ return {
15
+ message: nestedIssue.message,
16
+ path: nestedIssue.path,
17
+ code: nestedIssue.code,
18
+ value: nestedIssue.received,
19
+ };
13
20
  }
14
- else {
15
- // others
16
- console.error(cliFormat_1.CLI_FORMAT_RED, ` => Error: ${issue.message}`);
17
- console.error(" Path:", issue.path.join("."));
18
- const receivedValue = issue.received;
19
- if (typeof receivedValue !== "undefined" && receivedValue !== "undefined") {
20
- console.error(" Value:", receivedValue);
21
- }
21
+ return {
22
+ message: issue.message,
23
+ path: issue.path,
24
+ code: issue.code,
25
+ value: issue.received,
26
+ };
27
+ })
28
+ .filter(Boolean);
29
+ }
30
+ function printZodError(e) {
31
+ const issues = getLintIssuesFromZodError(e);
32
+ issues.forEach((issue) => {
33
+ console.error(cliFormat_1.CLI_FORMAT_RED, ` => Error: ${issue.message}`);
34
+ console.error(" Path:", issue.path.join("."));
35
+ if (typeof issue.value !== "undefined" && issue.value !== "undefined") {
36
+ console.error(" Value:", issue.value);
22
37
  }
23
38
  console.error("");
24
39
  });
@@ -1 +1 @@
1
- {"version":3,"file":"printError.js","sourceRoot":"","sources":["../../src/linter/printError.ts"],"names":[],"mappings":";;AAIA,sCAsBC;AAxBD,mDAAqD;AAErD,SAAgB,aAAa,CAAC,CAAW;IACvC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IAErB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9F,gBAAgB;YAChB,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvE,OAAO,CAAC,KAAK,CAAC,0BAAc,EAAE,eAAe,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACjF,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,SAAS;YACT,OAAO,CAAC,KAAK,CAAC,0BAAc,EAAE,eAAe,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9D,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAElD,MAAM,aAAa,GAAI,KAAa,CAAC,QAAQ,CAAC;YAC9C,IAAI,OAAO,aAAa,KAAK,WAAW,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;gBAC1E,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"printError.js","sourceRoot":"","sources":["../../src/linter/printError.ts"],"names":[],"mappings":";;AAWA,8DA2BC;AAED,sCAUC;AAhDD,mDAAqD;AASrD,SAAgB,yBAAyB,CAAC,CAAW;IACnD,OAAO,CAAC,CAAC,MAAM;SACZ,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,IACE,KAAK,CAAC,IAAI,KAAK,eAAe;YAC9B,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YACvB,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAC5B,CAAC;YACD,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvE,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAE7C,OAAO;gBACL,OAAO,EAAE,WAAW,CAAC,OAAO;gBAC5B,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,KAAK,EAAG,WAAmB,CAAC,QAAQ;aACrC,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAG,KAAa,CAAC,QAAQ;SAC/B,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,SAAgB,aAAa,CAAC,CAAW;IACvC,MAAM,MAAM,GAAG,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,0BAAc,EAAE,eAAe,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,WAAW,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YACtE,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/core",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "Core package of Featurevisor for Node.js usage",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -49,5 +49,5 @@
49
49
  "yargs": "^17.7.2",
50
50
  "zod": "^3.22.4"
51
51
  },
52
- "gitHead": "d7542c7da3615c821b031912e4d12ca4c81e5db2"
52
+ "gitHead": "7f6fadd9c233f676f7439613544bef08520a4e36"
53
53
  }
@@ -0,0 +1,115 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+
5
+ import { getProjectConfig } from "../config/projectConfig";
6
+ import { Datasource } from "../datasource";
7
+ import { lintPlugin, lintProject, type LintResult } from "./lintProject";
8
+
9
+ function createTempProjectFromExample1() {
10
+ const fixturePath = path.resolve(__dirname, "../../../../examples/example-1");
11
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-lint-"));
12
+
13
+ fs.cpSync(fixturePath, tempRoot, { recursive: true });
14
+
15
+ return tempRoot;
16
+ }
17
+
18
+ function getDeps(rootDirectoryPath: string) {
19
+ const projectConfig = getProjectConfig(rootDirectoryPath);
20
+ const datasource = new Datasource(projectConfig, rootDirectoryPath);
21
+
22
+ return {
23
+ rootDirectoryPath,
24
+ projectConfig,
25
+ datasource,
26
+ options: {},
27
+ };
28
+ }
29
+
30
+ describe("core: lintProject", function () {
31
+ let tempProjectPath: string;
32
+
33
+ beforeEach(() => {
34
+ tempProjectPath = createTempProjectFromExample1();
35
+ });
36
+
37
+ afterEach(() => {
38
+ fs.rmSync(tempProjectPath, { recursive: true, force: true });
39
+ jest.restoreAllMocks();
40
+ });
41
+
42
+ it("returns JSON-friendly empty errors array for a valid project", async () => {
43
+ const result = await lintProject(getDeps(tempProjectPath) as any, { json: true });
44
+
45
+ expect(result).toEqual({
46
+ hasError: false,
47
+ errors: [],
48
+ });
49
+ });
50
+
51
+ it("returns structured errors in JSON mode", async () => {
52
+ fs.writeFileSync(
53
+ path.join(tempProjectPath, "attributes", "invalid name.yml"),
54
+ "description: this has an invalid key name\ntype: string\n",
55
+ "utf8",
56
+ );
57
+
58
+ const result = await lintProject(getDeps(tempProjectPath) as any, {
59
+ json: true,
60
+ entityType: "attribute",
61
+ });
62
+
63
+ expect(result.hasError).toBe(true);
64
+ expect(result.errors.length).toBeGreaterThan(0);
65
+
66
+ expect(result.errors[0]).toMatchObject({
67
+ entityType: "attribute",
68
+ key: "invalid name",
69
+ message: 'Invalid name: "invalid name"',
70
+ code: "invalid_name",
71
+ path: [],
72
+ });
73
+
74
+ expect(result.errors[0].filePath).toContain("attributes");
75
+ });
76
+
77
+ it("plugin prints pretty JSON only once in --json --pretty mode", async () => {
78
+ const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
79
+
80
+ const pluginResult = await lintPlugin.handler({
81
+ ...getDeps(tempProjectPath),
82
+ parsed: {
83
+ json: true,
84
+ pretty: true,
85
+ },
86
+ } as any);
87
+
88
+ expect(pluginResult).toBeUndefined();
89
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1);
90
+
91
+ const output = consoleLogSpy.mock.calls[0][0] as string;
92
+ expect(output).toContain('\n "errors": []\n');
93
+ });
94
+
95
+ it("does not call process.exit for zod validation errors while linting tests", async () => {
96
+ fs.writeFileSync(
97
+ path.join(tempProjectPath, "tests", "broken.spec.yml"),
98
+ "feature: foo\n",
99
+ "utf8",
100
+ );
101
+
102
+ const processExitSpy = jest
103
+ .spyOn(process, "exit")
104
+ .mockImplementation((() => undefined) as unknown as typeof process.exit);
105
+
106
+ const result: LintResult = await lintProject(getDeps(tempProjectPath) as any, {
107
+ json: true,
108
+ entityType: "test",
109
+ });
110
+
111
+ expect(result.hasError).toBe(true);
112
+ expect(result.errors.length).toBeGreaterThan(0);
113
+ expect(processExitSpy).not.toHaveBeenCalled();
114
+ });
115
+ });