@featurevisor/core 2.7.0 → 2.8.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/CHANGELOG.md +11 -0
- package/coverage/clover.xml +110 -443
- package/coverage/coverage-final.json +3 -9
- package/coverage/lcov-report/{src/builder → builder}/allocator.ts.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/buildScopedConditions.ts.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/buildScopedDatafile.ts.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/buildScopedSegments.ts.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/index.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/revision.ts.html +10 -10
- package/coverage/lcov-report/{src/builder → builder}/traffic.ts.html +10 -10
- package/coverage/lcov-report/index.html +27 -57
- package/coverage/lcov-report/{src/list → list}/index.html +10 -10
- package/coverage/lcov-report/{src/list → list}/matrix.ts.html +10 -10
- package/coverage/lcov-report/{lib/tester → parsers}/index.html +41 -26
- package/coverage/lcov-report/parsers/json.ts.html +118 -0
- package/coverage/lcov-report/parsers/yml.ts.html +589 -0
- package/coverage/lcov-report/{src/tester → tester}/helpers.ts.html +10 -10
- package/coverage/lcov-report/{src/tester → tester}/index.html +10 -10
- package/coverage/lcov.info +198 -858
- package/jest.config.js +1 -0
- package/lib/config/index.d.ts +0 -1
- package/lib/config/index.js +0 -1
- package/lib/config/index.js.map +1 -1
- package/lib/config/projectConfig.d.ts +1 -1
- package/lib/config/projectConfig.js +1 -1
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.js +1 -1
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/linter/conditionSchema.js +45 -6
- package/lib/linter/conditionSchema.js.map +1 -1
- package/lib/{config/parsers.d.ts → parsers/index.d.ts} +5 -5
- package/lib/parsers/index.js +15 -0
- package/lib/parsers/index.js.map +1 -0
- package/lib/parsers/json.d.ts +2 -0
- package/lib/parsers/json.js +13 -0
- package/lib/parsers/json.js.map +1 -0
- package/lib/parsers/json.spec.d.ts +1 -0
- package/lib/parsers/json.spec.js +35 -0
- package/lib/parsers/json.spec.js.map +1 -0
- package/lib/parsers/yml.d.ts +2 -0
- package/lib/parsers/yml.js +154 -0
- package/lib/parsers/yml.js.map +1 -0
- package/lib/parsers/yml.spec.d.ts +1 -0
- package/lib/parsers/yml.spec.js +143 -0
- package/lib/parsers/yml.spec.js.map +1 -0
- package/lib/utils/git.js.map +1 -1
- package/package.json +3 -3
- package/src/config/index.ts +0 -1
- package/src/config/projectConfig.ts +1 -1
- package/src/datasource/datasource.ts +2 -1
- package/src/datasource/filesystemAdapter.ts +3 -2
- package/src/index.ts +1 -0
- package/src/linter/conditionSchema.ts +43 -6
- package/src/parsers/index.ts +22 -0
- package/src/parsers/json.spec.ts +36 -0
- package/src/parsers/json.ts +11 -0
- package/src/parsers/yml.spec.ts +174 -0
- package/src/parsers/yml.ts +168 -0
- package/src/utils/git.ts +2 -1
- package/coverage/lcov-report/lib/builder/allocator.js.html +0 -196
- package/coverage/lcov-report/lib/builder/buildScopedConditions.js.html +0 -373
- package/coverage/lcov-report/lib/builder/buildScopedDatafile.js.html +0 -403
- package/coverage/lcov-report/lib/builder/buildScopedSegments.js.html +0 -379
- package/coverage/lcov-report/lib/builder/index.html +0 -191
- package/coverage/lcov-report/lib/builder/revision.js.html +0 -148
- package/coverage/lcov-report/lib/builder/traffic.js.html +0 -493
- package/coverage/lcov-report/lib/list/index.html +0 -116
- package/coverage/lcov-report/lib/list/matrix.js.html +0 -532
- package/coverage/lcov-report/lib/tester/helpers.js.html +0 -295
- package/lib/config/parsers.js +0 -32
- package/lib/config/parsers.js.map +0 -1
- package/src/config/parsers.ts +0 -40
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const yml_1 = require("./yml");
|
|
4
|
+
describe("core :: parser :: ymlParser", () => {
|
|
5
|
+
it("should parse YAML string into object", () => {
|
|
6
|
+
const yamlString = `
|
|
7
|
+
foo: 1
|
|
8
|
+
bar: baz
|
|
9
|
+
arr:
|
|
10
|
+
- 1
|
|
11
|
+
- 2
|
|
12
|
+
`;
|
|
13
|
+
const result = yml_1.ymlParser.parse(yamlString);
|
|
14
|
+
expect(result).toEqual({ foo: 1, bar: "baz", arr: [1, 2] });
|
|
15
|
+
});
|
|
16
|
+
it("should stringify object into YAML string", () => {
|
|
17
|
+
const obj = { foo: 1, bar: "baz", arr: [1, 2] };
|
|
18
|
+
const yamlString = yml_1.ymlParser.stringify(obj);
|
|
19
|
+
expect(yamlString.trim()).toBe(`foo: 1
|
|
20
|
+
bar: baz
|
|
21
|
+
arr:
|
|
22
|
+
- 1
|
|
23
|
+
- 2`);
|
|
24
|
+
});
|
|
25
|
+
it("should parse and then stringify to preserve YAML content semantically", () => {
|
|
26
|
+
const original = {
|
|
27
|
+
alpha: "a",
|
|
28
|
+
num: 42,
|
|
29
|
+
bool: true,
|
|
30
|
+
nullish: null,
|
|
31
|
+
nested: { z: "x", y: [1, 2] },
|
|
32
|
+
};
|
|
33
|
+
const str = yml_1.ymlParser.stringify(original);
|
|
34
|
+
const reparsed = yml_1.ymlParser.parse(str);
|
|
35
|
+
expect(reparsed).toEqual(original);
|
|
36
|
+
});
|
|
37
|
+
it("should throw if invalid YAML string is provided to parse", () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
yml_1.ymlParser.parse("foo: : bar");
|
|
40
|
+
}).toThrow();
|
|
41
|
+
});
|
|
42
|
+
describe("stringify() with filePath argument (existing file for ordering/comments only)", () => {
|
|
43
|
+
const fs = require("fs");
|
|
44
|
+
const path = require("path");
|
|
45
|
+
const os = require("os");
|
|
46
|
+
let tempFile;
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
tempFile = path.join(os.tmpdir(), `test_${Math.random()}.yml`);
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
if (fs.existsSync(tempFile)) {
|
|
52
|
+
fs.unlinkSync(tempFile);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
it("should save exactly the new object; existing file is only for ordering/comments", () => {
|
|
56
|
+
const beforeYaml = `
|
|
57
|
+
foo: 1 # foo comment here
|
|
58
|
+
extra: blah
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Comment above nested
|
|
62
|
+
#
|
|
63
|
+
nested:
|
|
64
|
+
a: x
|
|
65
|
+
b: y # b comment
|
|
66
|
+
array:
|
|
67
|
+
- one # one
|
|
68
|
+
- two # two
|
|
69
|
+
- three # three
|
|
70
|
+
`.trim() + "\n";
|
|
71
|
+
const newContent = {
|
|
72
|
+
foo: 42,
|
|
73
|
+
bar: "new",
|
|
74
|
+
nested: { b: "updated", c: "added" },
|
|
75
|
+
array: ["two", "three", "four"],
|
|
76
|
+
};
|
|
77
|
+
const afterYaml = `
|
|
78
|
+
foo: 42 # foo comment here
|
|
79
|
+
bar: new
|
|
80
|
+
##
|
|
81
|
+
# Comment above nested
|
|
82
|
+
#
|
|
83
|
+
nested:
|
|
84
|
+
b: updated # b comment
|
|
85
|
+
c: added
|
|
86
|
+
array:
|
|
87
|
+
- two # two
|
|
88
|
+
- three # three
|
|
89
|
+
- four
|
|
90
|
+
`.trim() + "\n";
|
|
91
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
92
|
+
const output = yml_1.ymlParser.stringify(newContent, tempFile);
|
|
93
|
+
expect(output).toBe(afterYaml);
|
|
94
|
+
});
|
|
95
|
+
it("should replace the root if YAML file is empty", () => {
|
|
96
|
+
const beforeYaml = "";
|
|
97
|
+
const afterYaml = `
|
|
98
|
+
hello: world
|
|
99
|
+
test:
|
|
100
|
+
- 1
|
|
101
|
+
- 2
|
|
102
|
+
- 3
|
|
103
|
+
`.trim() + "\n";
|
|
104
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
105
|
+
const obj = { hello: "world", test: [1, 2, 3] };
|
|
106
|
+
const output = yml_1.ymlParser.stringify(obj, tempFile);
|
|
107
|
+
expect(output).toBe(afterYaml);
|
|
108
|
+
});
|
|
109
|
+
it("should throw if trying to set YAML document root to a primitive", () => {
|
|
110
|
+
const beforeYaml = `
|
|
111
|
+
foo: bar
|
|
112
|
+
`.trim() + "\n";
|
|
113
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
114
|
+
// Root must be an object when using filePath; primitives throw
|
|
115
|
+
expect(() => {
|
|
116
|
+
yml_1.ymlParser.stringify("primitive", tempFile);
|
|
117
|
+
}).toThrow(/Cannot set root document to a primitive value/);
|
|
118
|
+
});
|
|
119
|
+
it("should fall back to simple stringify if filePath does not exist", () => {
|
|
120
|
+
const fakeFilePath = path.join(os.tmpdir(), `notfound_${Math.random()}.yml`);
|
|
121
|
+
const afterYaml = `
|
|
122
|
+
only: in-memory
|
|
123
|
+
`.trim() + "\n";
|
|
124
|
+
const obj = { only: "in-memory" };
|
|
125
|
+
const output = yml_1.ymlParser.stringify(obj, fakeFilePath);
|
|
126
|
+
expect(output).toBe(afterYaml);
|
|
127
|
+
expect(fs.existsSync(fakeFilePath)).toBe(false); // should not create the file
|
|
128
|
+
});
|
|
129
|
+
it("should not mutate the original YAML file", () => {
|
|
130
|
+
const beforeYaml = `
|
|
131
|
+
foo: unchanged
|
|
132
|
+
bar: before
|
|
133
|
+
`.trim() + "\n";
|
|
134
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
135
|
+
const onDiskBefore = fs.readFileSync(tempFile, "utf8");
|
|
136
|
+
const obj = { bar: "after" };
|
|
137
|
+
yml_1.ymlParser.stringify(obj, tempFile);
|
|
138
|
+
const onDiskAfter = fs.readFileSync(tempFile, "utf8");
|
|
139
|
+
expect(onDiskAfter).toBe(onDiskBefore); // file on disk unchanged
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=yml.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yml.spec.js","sourceRoot":"","sources":["../../src/parsers/yml.spec.ts"],"names":[],"mappings":"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"}
|
package/lib/utils/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":";;AA8DA,8BAmEC;AAjID,6BAA6B;AAO7B,SAAS,wBAAwB,CAAC,aAAqB;IACrD,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;KACV,CAAC;IAEF,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACrB,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACnD,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACnD,cAAc,GAAG,KAAK,CAAC;QACzB,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/C,cAAc,GAAG,IAAI,CAAC,CAAC,+BAA+B;QACxD,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACzC,IAAI,WAAW,EAAE,CAAC;gBAChB,4CAA4C;gBAC5C,YAAY,GAAG,KAAK,CAAC;YACvB,CAAC;YACD,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,8BAA8B;YACvE,YAAY,GAAG,IAAI,CAAC;YACpB,cAAc,GAAG,KAAK,CAAC,CAAC,8BAA8B;QACxD,CAAC;aAAM,IAAI,YAAY,EAAE,CAAC;YACxB,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,IAAI,GAAG,IAAI,CAAC;QAC3C,CAAC;aAAM,IAAI,cAAc,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAChD,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,yBAAyB;QACjE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAI;IAC7B,IAAI,MAAM,GAAG,SAAS,CAAC,CAAC,uBAAuB;IAE/C,sCAAsC;IACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC9C,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAgB,SAAS,CACvB,aAAqB,EACrB,OAAoE;IAEpE,MAAM,MAAM,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;IACvD,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,MAAM,GAAW;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,MAAM,CAAC,IAAI;QACtB,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAEvC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QACxD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE/C,kBAAkB;QAClB,IAAI,IAAI,GAAe,WAAW,CAAC;QACnC,IAAI,WAAW,KAAK,aAAa,CAAC,uBAAuB,EAAE,CAAC;YAC1D,IAAI,GAAG,WAAW,CAAC;QACrB,CAAC;aAAM,IAAI,WAAW,KAAK,aAAa,CAAC,qBAAqB,EAAE,CAAC;YAC/D,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;aAAM,IAAI,WAAW,KAAK,aAAa,CAAC,qBAAqB,EAAE,CAAC;YAC/D,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;aAAM,IAAI,WAAW,KAAK,aAAa,CAAC,mBAAmB,EAAE,CAAC;YAC7D,IAAI,GAAG,OAAO,CAAC;QACjB,CAAC;aAAM,IAAI,WAAW,KAAK,aAAa,CAAC,kBAAkB,EAAE,CAAC;YAC5D,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,eAAe;YACf,OAAO;QACT,CAAC;QAED,iBAAiB;QACjB,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAY,CAAC;QAC9D,MAAM,gBAAgB,GAAG,GAAG,GAAI,aAAa,CAAC,MAAuB,CAAC,SAAS,CAAC;QAEhF,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACzC,oBAAoB;YACpB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QAEnD,MAAM,UAAU,GAAe;YAC7B,IAAI;YACJ,GAAG;YACH,OAAO,EAAE,IAAI;SACd,CAAC;QAEF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;aAAM,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;QAED,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@featurevisor/site": "2.7.0",
|
|
45
45
|
"@featurevisor/types": "2.7.0",
|
|
46
46
|
"axios": "^1.3.4",
|
|
47
|
-
"js-yaml": "^4.1.0",
|
|
48
47
|
"tar": "^6.1.13",
|
|
48
|
+
"yaml": "^1.10.2",
|
|
49
49
|
"yargs": "^17.7.2",
|
|
50
50
|
"zod": "^3.22.4"
|
|
51
51
|
},
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"@types/js-yaml": "^4.0.5",
|
|
54
54
|
"@types/tar": "^6.1.4"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "8b9d6bf3c8c25d8bd0eb1db05b1a9f7e8493caf5"
|
|
57
57
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as path from "path";
|
|
|
2
2
|
|
|
3
3
|
import type { BucketBy, Context, Tag } from "@featurevisor/types";
|
|
4
4
|
|
|
5
|
-
import { Parser, parsers } from "
|
|
5
|
+
import { Parser, parsers } from "../parsers";
|
|
6
6
|
import { FilesystemAdapter } from "../datasource/filesystemAdapter";
|
|
7
7
|
import type { Plugin } from "../cli";
|
|
8
8
|
import type { BuildTags } from "../builder/buildDatafile";
|
|
@@ -13,7 +13,8 @@ import type {
|
|
|
13
13
|
EntityType,
|
|
14
14
|
} from "@featurevisor/types";
|
|
15
15
|
|
|
16
|
-
import { ProjectConfig
|
|
16
|
+
import { ProjectConfig } from "../config";
|
|
17
|
+
import { CustomParser } from "../parsers";
|
|
17
18
|
|
|
18
19
|
import { Adapter, DatafileOptions } from "./adapter";
|
|
19
20
|
|
|
@@ -14,7 +14,8 @@ import type {
|
|
|
14
14
|
} from "@featurevisor/types";
|
|
15
15
|
|
|
16
16
|
import { Adapter, DatafileOptions } from "./adapter";
|
|
17
|
-
import { ProjectConfig
|
|
17
|
+
import { ProjectConfig } from "../config";
|
|
18
|
+
import { CustomParser } from "../parsers";
|
|
18
19
|
import { getCommit } from "../utils/git";
|
|
19
20
|
|
|
20
21
|
export function getExistingStateFilePath(
|
|
@@ -128,7 +129,7 @@ export class FilesystemAdapter extends Adapter {
|
|
|
128
129
|
fs.mkdirSync(this.getEntityDirectoryPath(entityType), { recursive: true });
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
fs.writeFileSync(filePath, this.parser.stringify(entity));
|
|
132
|
+
fs.writeFileSync(filePath, this.parser.stringify(entity, filePath));
|
|
132
133
|
|
|
133
134
|
return entity;
|
|
134
135
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,12 @@ const arrayOperators = ["in", "notIn"];
|
|
|
25
25
|
const regexOperators = ["matches", "notMatches"];
|
|
26
26
|
const operatorsWithoutValue = ["exists", "notExists"];
|
|
27
27
|
|
|
28
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+\-]\d{2}:\d{2})))?$/;
|
|
29
|
+
|
|
30
|
+
function isIsoDateString(value: string): boolean {
|
|
31
|
+
return isoDateRegex.test(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
export function getConditionsZodSchema(
|
|
29
35
|
projectConfig: ProjectConfig,
|
|
30
36
|
availableAttributeKeys: [string, ...string[]],
|
|
@@ -114,12 +120,43 @@ export function getConditionsZodSchema(
|
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
// date
|
|
117
|
-
if (dateOperators.includes(data.operator)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
if (dateOperators.includes(data.operator)) {
|
|
124
|
+
// Value is required and must not be undefined or null
|
|
125
|
+
if (typeof data.value === "undefined" || data.value === null || data.value === "") {
|
|
126
|
+
context.addIssue({
|
|
127
|
+
code: z.ZodIssueCode.custom,
|
|
128
|
+
message: `when operator is "${data.operator}", value must be provided`,
|
|
129
|
+
path: ["value"],
|
|
130
|
+
});
|
|
131
|
+
} else if (typeof data.value === "string") {
|
|
132
|
+
// If it's a string, must be valid ISO 8601
|
|
133
|
+
if (!isIsoDateString(data.value)) {
|
|
134
|
+
context.addIssue({
|
|
135
|
+
code: z.ZodIssueCode.custom,
|
|
136
|
+
message: `when operator is "${data.operator}", value must be a stringified date in ISO 8601 format`,
|
|
137
|
+
path: ["value"],
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
// valid ISO 8601 string -- no issue
|
|
141
|
+
}
|
|
142
|
+
} else if (data.value instanceof Date) {
|
|
143
|
+
// If it's a Date, must be valid
|
|
144
|
+
if (isNaN(data.value.getTime())) {
|
|
145
|
+
context.addIssue({
|
|
146
|
+
code: z.ZodIssueCode.custom,
|
|
147
|
+
message: `when operator is "${data.operator}", value is a Date but invalid`,
|
|
148
|
+
path: ["value"],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Valid JS Date -- no issue
|
|
152
|
+
} else {
|
|
153
|
+
// NOT a string, NOT a Date
|
|
154
|
+
context.addIssue({
|
|
155
|
+
code: z.ZodIssueCode.custom,
|
|
156
|
+
message: `when operator is "${data.operator}", value must be a stringified date in ISO 8601 format or a valid Date object`,
|
|
157
|
+
path: ["value"],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
123
160
|
}
|
|
124
161
|
|
|
125
162
|
// array
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsonParser } from "./json";
|
|
2
|
+
import { ymlParser } from "./yml";
|
|
3
|
+
|
|
4
|
+
export interface CustomParser {
|
|
5
|
+
extension: string;
|
|
6
|
+
parse: <T>(content: string, filePath?: string) => T;
|
|
7
|
+
stringify: (content: any, filePath?: string) => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* If we want to add more built-in parsers,
|
|
12
|
+
* add them to this object with new file extension as the key,
|
|
13
|
+
* and a function that takes file content as string and returns parsed object as the value.
|
|
14
|
+
*/
|
|
15
|
+
export const parsers: { [key: string]: CustomParser } = {
|
|
16
|
+
yml: ymlParser,
|
|
17
|
+
json: jsonParser,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type BuiltInParser = keyof typeof parsers; // keys of parsers object
|
|
21
|
+
|
|
22
|
+
export type Parser = BuiltInParser | CustomParser;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsonParser } from "./json";
|
|
2
|
+
|
|
3
|
+
describe("core :: parser :: jsonParser", () => {
|
|
4
|
+
it("should parse JSON string into object", () => {
|
|
5
|
+
const jsonString = '{"foo":1,"bar":"baz","arr":[1,2]}';
|
|
6
|
+
const result = jsonParser.parse<{ foo: number; bar: string; arr: number[] }>(jsonString);
|
|
7
|
+
expect(result).toEqual({ foo: 1, bar: "baz", arr: [1, 2] });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should stringify object into JSON string (pretty-printed)", () => {
|
|
11
|
+
const obj = { foo: 1, bar: "baz", arr: [1, 2] };
|
|
12
|
+
const jsonString = jsonParser.stringify(obj);
|
|
13
|
+
const expectedString = `{
|
|
14
|
+
"foo": 1,
|
|
15
|
+
"bar": "baz",
|
|
16
|
+
"arr": [
|
|
17
|
+
1,
|
|
18
|
+
2
|
|
19
|
+
]
|
|
20
|
+
}`;
|
|
21
|
+
expect(jsonString).toBe(expectedString);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should parse and then stringify to preserve content", () => {
|
|
25
|
+
const original = { alpha: "a", num: 42, bool: true, nullish: null, nested: { z: "x" } };
|
|
26
|
+
const str = jsonParser.stringify(original);
|
|
27
|
+
const reparsed = jsonParser.parse<typeof original>(str);
|
|
28
|
+
expect(reparsed).toEqual(original);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should throw if invalid JSON string is provided to parse", () => {
|
|
32
|
+
expect(() => {
|
|
33
|
+
jsonParser.parse("this is not json");
|
|
34
|
+
}).toThrow();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CustomParser } from "./index";
|
|
2
|
+
|
|
3
|
+
export const jsonParser: CustomParser = {
|
|
4
|
+
extension: "json",
|
|
5
|
+
parse: function <T>(content: string): T {
|
|
6
|
+
return JSON.parse(content) as T;
|
|
7
|
+
},
|
|
8
|
+
stringify: function (content: any) {
|
|
9
|
+
return JSON.stringify(content, null, 2);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ymlParser } from "./yml";
|
|
2
|
+
|
|
3
|
+
describe("core :: parser :: ymlParser", () => {
|
|
4
|
+
it("should parse YAML string into object", () => {
|
|
5
|
+
const yamlString = `
|
|
6
|
+
foo: 1
|
|
7
|
+
bar: baz
|
|
8
|
+
arr:
|
|
9
|
+
- 1
|
|
10
|
+
- 2
|
|
11
|
+
`;
|
|
12
|
+
const result = ymlParser.parse<{ foo: number; bar: string; arr: number[] }>(yamlString);
|
|
13
|
+
expect(result).toEqual({ foo: 1, bar: "baz", arr: [1, 2] });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should stringify object into YAML string", () => {
|
|
17
|
+
const obj = { foo: 1, bar: "baz", arr: [1, 2] };
|
|
18
|
+
const yamlString = ymlParser.stringify(obj);
|
|
19
|
+
expect(yamlString.trim()).toBe(`foo: 1
|
|
20
|
+
bar: baz
|
|
21
|
+
arr:
|
|
22
|
+
- 1
|
|
23
|
+
- 2`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should parse and then stringify to preserve YAML content semantically", () => {
|
|
27
|
+
const original = {
|
|
28
|
+
alpha: "a",
|
|
29
|
+
num: 42,
|
|
30
|
+
bool: true,
|
|
31
|
+
nullish: null,
|
|
32
|
+
nested: { z: "x", y: [1, 2] },
|
|
33
|
+
};
|
|
34
|
+
const str = ymlParser.stringify(original);
|
|
35
|
+
const reparsed = ymlParser.parse<typeof original>(str);
|
|
36
|
+
expect(reparsed).toEqual(original);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should throw if invalid YAML string is provided to parse", () => {
|
|
40
|
+
expect(() => {
|
|
41
|
+
ymlParser.parse("foo: : bar");
|
|
42
|
+
}).toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("stringify() with filePath argument (existing file for ordering/comments only)", () => {
|
|
46
|
+
const fs = require("fs");
|
|
47
|
+
const path = require("path");
|
|
48
|
+
const os = require("os");
|
|
49
|
+
|
|
50
|
+
let tempFile: string;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
tempFile = path.join(os.tmpdir(), `test_${Math.random()}.yml`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
if (fs.existsSync(tempFile)) {
|
|
58
|
+
fs.unlinkSync(tempFile);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should save exactly the new object; existing file is only for ordering/comments", () => {
|
|
63
|
+
const beforeYaml =
|
|
64
|
+
`
|
|
65
|
+
foo: 1 # foo comment here
|
|
66
|
+
extra: blah
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# Comment above nested
|
|
70
|
+
#
|
|
71
|
+
nested:
|
|
72
|
+
a: x
|
|
73
|
+
b: y # b comment
|
|
74
|
+
array:
|
|
75
|
+
- one # one
|
|
76
|
+
- two # two
|
|
77
|
+
- three # three
|
|
78
|
+
`.trim() + "\n";
|
|
79
|
+
|
|
80
|
+
const newContent = {
|
|
81
|
+
foo: 42,
|
|
82
|
+
bar: "new",
|
|
83
|
+
nested: { b: "updated", c: "added" },
|
|
84
|
+
array: ["two", "three", "four"],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const afterYaml =
|
|
88
|
+
`
|
|
89
|
+
foo: 42 # foo comment here
|
|
90
|
+
bar: new
|
|
91
|
+
##
|
|
92
|
+
# Comment above nested
|
|
93
|
+
#
|
|
94
|
+
nested:
|
|
95
|
+
b: updated # b comment
|
|
96
|
+
c: added
|
|
97
|
+
array:
|
|
98
|
+
- two # two
|
|
99
|
+
- three # three
|
|
100
|
+
- four
|
|
101
|
+
`.trim() + "\n";
|
|
102
|
+
|
|
103
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
104
|
+
|
|
105
|
+
const output = ymlParser.stringify(newContent, tempFile);
|
|
106
|
+
|
|
107
|
+
expect(output).toBe(afterYaml);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should replace the root if YAML file is empty", () => {
|
|
111
|
+
const beforeYaml = "";
|
|
112
|
+
const afterYaml =
|
|
113
|
+
`
|
|
114
|
+
hello: world
|
|
115
|
+
test:
|
|
116
|
+
- 1
|
|
117
|
+
- 2
|
|
118
|
+
- 3
|
|
119
|
+
`.trim() + "\n";
|
|
120
|
+
|
|
121
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
122
|
+
|
|
123
|
+
const obj = { hello: "world", test: [1, 2, 3] };
|
|
124
|
+
const output = ymlParser.stringify(obj, tempFile);
|
|
125
|
+
|
|
126
|
+
expect(output).toBe(afterYaml);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should throw if trying to set YAML document root to a primitive", () => {
|
|
130
|
+
const beforeYaml =
|
|
131
|
+
`
|
|
132
|
+
foo: bar
|
|
133
|
+
`.trim() + "\n";
|
|
134
|
+
|
|
135
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
136
|
+
|
|
137
|
+
// Root must be an object when using filePath; primitives throw
|
|
138
|
+
expect(() => {
|
|
139
|
+
ymlParser.stringify("primitive", tempFile);
|
|
140
|
+
}).toThrow(/Cannot set root document to a primitive value/);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should fall back to simple stringify if filePath does not exist", () => {
|
|
144
|
+
const fakeFilePath = path.join(os.tmpdir(), `notfound_${Math.random()}.yml`);
|
|
145
|
+
const afterYaml =
|
|
146
|
+
`
|
|
147
|
+
only: in-memory
|
|
148
|
+
`.trim() + "\n";
|
|
149
|
+
|
|
150
|
+
const obj = { only: "in-memory" };
|
|
151
|
+
const output = ymlParser.stringify(obj, fakeFilePath);
|
|
152
|
+
|
|
153
|
+
expect(output).toBe(afterYaml);
|
|
154
|
+
expect(fs.existsSync(fakeFilePath)).toBe(false); // should not create the file
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should not mutate the original YAML file", () => {
|
|
158
|
+
const beforeYaml =
|
|
159
|
+
`
|
|
160
|
+
foo: unchanged
|
|
161
|
+
bar: before
|
|
162
|
+
`.trim() + "\n";
|
|
163
|
+
|
|
164
|
+
fs.writeFileSync(tempFile, beforeYaml);
|
|
165
|
+
const onDiskBefore = fs.readFileSync(tempFile, "utf8");
|
|
166
|
+
|
|
167
|
+
const obj = { bar: "after" };
|
|
168
|
+
ymlParser.stringify(obj, tempFile);
|
|
169
|
+
|
|
170
|
+
const onDiskAfter = fs.readFileSync(tempFile, "utf8");
|
|
171
|
+
expect(onDiskAfter).toBe(onDiskBefore); // file on disk unchanged
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|