@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/coverage/clover.xml +110 -443
  3. package/coverage/coverage-final.json +3 -9
  4. package/coverage/lcov-report/{src/builder → builder}/allocator.ts.html +10 -10
  5. package/coverage/lcov-report/{src/builder → builder}/buildScopedConditions.ts.html +10 -10
  6. package/coverage/lcov-report/{src/builder → builder}/buildScopedDatafile.ts.html +10 -10
  7. package/coverage/lcov-report/{src/builder → builder}/buildScopedSegments.ts.html +10 -10
  8. package/coverage/lcov-report/{src/builder → builder}/index.html +10 -10
  9. package/coverage/lcov-report/{src/builder → builder}/revision.ts.html +10 -10
  10. package/coverage/lcov-report/{src/builder → builder}/traffic.ts.html +10 -10
  11. package/coverage/lcov-report/index.html +27 -57
  12. package/coverage/lcov-report/{src/list → list}/index.html +10 -10
  13. package/coverage/lcov-report/{src/list → list}/matrix.ts.html +10 -10
  14. package/coverage/lcov-report/{lib/tester → parsers}/index.html +41 -26
  15. package/coverage/lcov-report/parsers/json.ts.html +118 -0
  16. package/coverage/lcov-report/parsers/yml.ts.html +589 -0
  17. package/coverage/lcov-report/{src/tester → tester}/helpers.ts.html +10 -10
  18. package/coverage/lcov-report/{src/tester → tester}/index.html +10 -10
  19. package/coverage/lcov.info +198 -858
  20. package/jest.config.js +1 -0
  21. package/lib/config/index.d.ts +0 -1
  22. package/lib/config/index.js +0 -1
  23. package/lib/config/index.js.map +1 -1
  24. package/lib/config/projectConfig.d.ts +1 -1
  25. package/lib/config/projectConfig.js +1 -1
  26. package/lib/config/projectConfig.js.map +1 -1
  27. package/lib/datasource/datasource.js.map +1 -1
  28. package/lib/datasource/filesystemAdapter.js +1 -1
  29. package/lib/datasource/filesystemAdapter.js.map +1 -1
  30. package/lib/index.d.ts +1 -0
  31. package/lib/index.js +1 -0
  32. package/lib/index.js.map +1 -1
  33. package/lib/linter/conditionSchema.js +45 -6
  34. package/lib/linter/conditionSchema.js.map +1 -1
  35. package/lib/{config/parsers.d.ts → parsers/index.d.ts} +5 -5
  36. package/lib/parsers/index.js +15 -0
  37. package/lib/parsers/index.js.map +1 -0
  38. package/lib/parsers/json.d.ts +2 -0
  39. package/lib/parsers/json.js +13 -0
  40. package/lib/parsers/json.js.map +1 -0
  41. package/lib/parsers/json.spec.d.ts +1 -0
  42. package/lib/parsers/json.spec.js +35 -0
  43. package/lib/parsers/json.spec.js.map +1 -0
  44. package/lib/parsers/yml.d.ts +2 -0
  45. package/lib/parsers/yml.js +154 -0
  46. package/lib/parsers/yml.js.map +1 -0
  47. package/lib/parsers/yml.spec.d.ts +1 -0
  48. package/lib/parsers/yml.spec.js +143 -0
  49. package/lib/parsers/yml.spec.js.map +1 -0
  50. package/lib/utils/git.js.map +1 -1
  51. package/package.json +3 -3
  52. package/src/config/index.ts +0 -1
  53. package/src/config/projectConfig.ts +1 -1
  54. package/src/datasource/datasource.ts +2 -1
  55. package/src/datasource/filesystemAdapter.ts +3 -2
  56. package/src/index.ts +1 -0
  57. package/src/linter/conditionSchema.ts +43 -6
  58. package/src/parsers/index.ts +22 -0
  59. package/src/parsers/json.spec.ts +36 -0
  60. package/src/parsers/json.ts +11 -0
  61. package/src/parsers/yml.spec.ts +174 -0
  62. package/src/parsers/yml.ts +168 -0
  63. package/src/utils/git.ts +2 -1
  64. package/coverage/lcov-report/lib/builder/allocator.js.html +0 -196
  65. package/coverage/lcov-report/lib/builder/buildScopedConditions.js.html +0 -373
  66. package/coverage/lcov-report/lib/builder/buildScopedDatafile.js.html +0 -403
  67. package/coverage/lcov-report/lib/builder/buildScopedSegments.js.html +0 -379
  68. package/coverage/lcov-report/lib/builder/index.html +0 -191
  69. package/coverage/lcov-report/lib/builder/revision.js.html +0 -148
  70. package/coverage/lcov-report/lib/builder/traffic.js.html +0 -493
  71. package/coverage/lcov-report/lib/list/index.html +0 -116
  72. package/coverage/lcov-report/lib/list/matrix.js.html +0 -532
  73. package/coverage/lcov-report/lib/tester/helpers.js.html +0 -295
  74. package/lib/config/parsers.js +0 -32
  75. package/lib/config/parsers.js.map +0 -1
  76. 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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/core",
3
- "version": "2.7.0",
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": "aebf65e5be80d1f8a67453e45b4609af2490103e"
56
+ "gitHead": "8b9d6bf3c8c25d8bd0eb1db05b1a9f7e8493caf5"
57
57
  }
@@ -1,2 +1 @@
1
1
  export * from "./projectConfig";
2
- export * from "./parsers";
@@ -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 "./parsers";
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, CustomParser } from "../config";
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, CustomParser } from "../config";
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
@@ -1,4 +1,5 @@
1
1
  export * from "./config";
2
+ export * from "./parsers";
2
3
  export * from "./linter";
3
4
  export * from "./builder";
4
5
  export * from "./tester";
@@ -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) && !(data.value instanceof Date)) {
118
- context.addIssue({
119
- code: z.ZodIssueCode.custom,
120
- message: `when operator is "${data.operator}", value must be a date`,
121
- path: ["value"],
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
+ });