@hanseltime/template-repo-sync 1.0.1
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/.eslintrc.js +10 -0
- package/.github/CODEOWNERS +6 -0
- package/.github/workflows/pr-checks.yaml +12 -0
- package/.github/workflows/release.yaml +36 -0
- package/.github/workflows/test-flow.yaml +58 -0
- package/.husky/commit-msg +4 -0
- package/.prettierignore +1 -0
- package/.prettierrc +1 -0
- package/CHANGELOG.md +27 -0
- package/README.md +93 -0
- package/action.yml +13 -0
- package/commitlint.config.js +3 -0
- package/jest.config.js +19 -0
- package/lib/cjs/clone-drivers/git-clone.d.ts +1 -0
- package/lib/cjs/clone-drivers/git-clone.js +14 -0
- package/lib/cjs/clone-drivers/index.d.ts +2 -0
- package/lib/cjs/clone-drivers/index.js +18 -0
- package/lib/cjs/clone-drivers/types.d.ts +5 -0
- package/lib/cjs/clone-drivers/types.js +2 -0
- package/lib/cjs/diff-drivers/git-diff.d.ts +1 -0
- package/lib/cjs/diff-drivers/git-diff.js +13 -0
- package/lib/cjs/diff-drivers/index.d.ts +2 -0
- package/lib/cjs/diff-drivers/index.js +18 -0
- package/lib/cjs/diff-drivers/types.d.ts +5 -0
- package/lib/cjs/diff-drivers/types.js +2 -0
- package/lib/cjs/formatting/index.d.ts +2 -0
- package/lib/cjs/formatting/index.js +18 -0
- package/lib/cjs/formatting/infer-json-indent.d.ts +1 -0
- package/lib/cjs/formatting/infer-json-indent.js +18 -0
- package/lib/cjs/formatting/sync-results-to-md.d.ts +2 -0
- package/lib/cjs/formatting/sync-results-to-md.js +40 -0
- package/lib/cjs/index.d.ts +3 -0
- package/lib/cjs/index.js +19 -0
- package/lib/cjs/load-plugin.d.ts +2 -0
- package/lib/cjs/load-plugin.js +63 -0
- package/lib/cjs/match.d.ts +10 -0
- package/lib/cjs/match.js +45 -0
- package/lib/cjs/merge-file.d.ts +29 -0
- package/lib/cjs/merge-file.js +99 -0
- package/lib/cjs/plugins/index.d.ts +4 -0
- package/lib/cjs/plugins/index.js +10 -0
- package/lib/cjs/plugins/json-merge.d.ts +3 -0
- package/lib/cjs/plugins/json-merge.js +185 -0
- package/lib/cjs/template-sync.d.ts +40 -0
- package/lib/cjs/template-sync.js +56 -0
- package/lib/cjs/test-utils/index.d.ts +2 -0
- package/lib/cjs/test-utils/index.js +10 -0
- package/lib/cjs/types.d.ts +113 -0
- package/lib/cjs/types.js +2 -0
- package/lib/esm/clone-drivers/git-clone.js +14 -0
- package/lib/esm/clone-drivers/index.js +18 -0
- package/lib/esm/clone-drivers/types.js +2 -0
- package/lib/esm/diff-drivers/git-diff.js +13 -0
- package/lib/esm/diff-drivers/index.js +18 -0
- package/lib/esm/diff-drivers/types.js +2 -0
- package/lib/esm/formatting/index.js +18 -0
- package/lib/esm/formatting/infer-json-indent.js +18 -0
- package/lib/esm/formatting/sync-results-to-md.js +40 -0
- package/lib/esm/index.js +19 -0
- package/lib/esm/load-plugin.js +40 -0
- package/lib/esm/match.js +45 -0
- package/lib/esm/merge-file.js +99 -0
- package/lib/esm/plugins/index.js +10 -0
- package/lib/esm/plugins/json-merge.js +185 -0
- package/lib/esm/template-sync.js +56 -0
- package/lib/esm/test-utils/index.js +10 -0
- package/lib/esm/types.js +2 -0
- package/package.json +60 -0
- package/release.config.js +34 -0
- package/src/clone-drivers/git-clone.ts +16 -0
- package/src/clone-drivers/index.ts +2 -0
- package/src/clone-drivers/types.ts +8 -0
- package/src/diff-drivers/git-diff.ts +10 -0
- package/src/diff-drivers/index.ts +2 -0
- package/src/diff-drivers/types.ts +8 -0
- package/src/formatting/__snapshots__/sync-results-to-md.spec.ts.snap +22 -0
- package/src/formatting/index.ts +2 -0
- package/src/formatting/infer-json-indent.spec.ts +49 -0
- package/src/formatting/infer-json-indent.ts +16 -0
- package/src/formatting/sync-results-to-md.spec.ts +25 -0
- package/src/formatting/sync-results-to-md.ts +46 -0
- package/src/index.ts +3 -0
- package/src/load-plugin.ts +42 -0
- package/src/match.spec.ts +68 -0
- package/src/match.ts +52 -0
- package/src/merge-file.spec.ts +432 -0
- package/src/merge-file.ts +150 -0
- package/src/plugins/index.ts +11 -0
- package/src/plugins/json-merge.spec.ts +350 -0
- package/src/plugins/json-merge.ts +205 -0
- package/src/template-sync.spec.ts +216 -0
- package/src/template-sync.ts +113 -0
- package/src/test-utils/index.ts +13 -0
- package/src/types.ts +124 -0
- package/templatesync.local.json +15 -0
- package/test-fixtures/downstream/README.md +3 -0
- package/test-fixtures/downstream/package.json +18 -0
- package/test-fixtures/downstream/plugins/custom-plugin.js +11 -0
- package/test-fixtures/downstream/src/index.js +2 -0
- package/test-fixtures/downstream/src/index.ts +1 -0
- package/test-fixtures/downstream/src/templated.js +2 -0
- package/test-fixtures/downstream/src/templated.ts +1 -0
- package/test-fixtures/downstream/templatesync.json +19 -0
- package/test-fixtures/downstream/templatesync.local.json +14 -0
- package/test-fixtures/dummy-plugin.js +8 -0
- package/test-fixtures/glob-test/folder1/something.js +1 -0
- package/test-fixtures/glob-test/folder1/something.ts +0 -0
- package/test-fixtures/glob-test/toplevel.js +0 -0
- package/test-fixtures/glob-test/toplevel.txt +0 -0
- package/test-fixtures/template/custom-bin/something.txt +1 -0
- package/test-fixtures/template/package.json +17 -0
- package/test-fixtures/template/src/index.js +2 -0
- package/test-fixtures/template/src/index.ts +1 -0
- package/test-fixtures/template/src/templated.js +2 -0
- package/test-fixtures/template/src/templated.ts +1 -0
- package/test-fixtures/template/templatesync.json +19 -0
- package/tsconfig.cjs.json +12 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { JsonPathOverrides } from "../types";
|
|
3
|
+
import { merge, validate } from "./json-merge";
|
|
4
|
+
|
|
5
|
+
const testFileJson = {
|
|
6
|
+
here: "here",
|
|
7
|
+
another: 23,
|
|
8
|
+
inner: {
|
|
9
|
+
el1: "el1",
|
|
10
|
+
arr1: ["a1", "a2"],
|
|
11
|
+
nested: {
|
|
12
|
+
final: "final",
|
|
13
|
+
final2: "final2",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const templateFileJson = {
|
|
19
|
+
here: "heretemplate",
|
|
20
|
+
extra: "extra",
|
|
21
|
+
inner: {
|
|
22
|
+
arr1: ["b1", "b2"],
|
|
23
|
+
extra2: "extra2",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("merge", () => {
|
|
28
|
+
it("performs overwrite when specified", async () => {
|
|
29
|
+
const fromTemplateJson = {
|
|
30
|
+
fullOverride: true,
|
|
31
|
+
};
|
|
32
|
+
expect(
|
|
33
|
+
JSON.parse(
|
|
34
|
+
await merge(
|
|
35
|
+
JSON.stringify(testFileJson),
|
|
36
|
+
JSON.stringify(fromTemplateJson),
|
|
37
|
+
{
|
|
38
|
+
relFilePath: "somepath",
|
|
39
|
+
mergeArguments: "overwrite",
|
|
40
|
+
},
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
).toEqual(fromTemplateJson);
|
|
44
|
+
});
|
|
45
|
+
it("performs lodash merge-template when specified", async () => {
|
|
46
|
+
expect(
|
|
47
|
+
JSON.parse(
|
|
48
|
+
await merge(
|
|
49
|
+
JSON.stringify(testFileJson),
|
|
50
|
+
JSON.stringify(templateFileJson),
|
|
51
|
+
{
|
|
52
|
+
relFilePath: "somepath",
|
|
53
|
+
mergeArguments: "merge-template",
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
).toEqual({
|
|
58
|
+
here: "heretemplate",
|
|
59
|
+
extra: "extra",
|
|
60
|
+
another: 23,
|
|
61
|
+
inner: {
|
|
62
|
+
el1: "el1",
|
|
63
|
+
arr1: ["b1", "b2"],
|
|
64
|
+
extra2: "extra2",
|
|
65
|
+
nested: {
|
|
66
|
+
final: "final",
|
|
67
|
+
final2: "final2",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it("performs lodash merge-current when specified", async () => {
|
|
73
|
+
expect(
|
|
74
|
+
JSON.parse(
|
|
75
|
+
await merge(
|
|
76
|
+
JSON.stringify(testFileJson),
|
|
77
|
+
JSON.stringify(templateFileJson),
|
|
78
|
+
{
|
|
79
|
+
relFilePath: "somepath",
|
|
80
|
+
mergeArguments: "merge-current",
|
|
81
|
+
},
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
).toEqual({
|
|
85
|
+
here: "here",
|
|
86
|
+
extra: "extra",
|
|
87
|
+
another: 23,
|
|
88
|
+
inner: {
|
|
89
|
+
el1: "el1",
|
|
90
|
+
arr1: ["a1", "a2"],
|
|
91
|
+
extra2: "extra2",
|
|
92
|
+
nested: {
|
|
93
|
+
final: "final",
|
|
94
|
+
final2: "final2",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("performs pathjson merges with default non-path values", async () => {
|
|
100
|
+
expect(
|
|
101
|
+
JSON.parse(
|
|
102
|
+
await merge(
|
|
103
|
+
JSON.stringify(testFileJson),
|
|
104
|
+
JSON.stringify(templateFileJson),
|
|
105
|
+
{
|
|
106
|
+
relFilePath: "somepath",
|
|
107
|
+
mergeArguments: {
|
|
108
|
+
paths: [
|
|
109
|
+
["$.here", "merge-template"],
|
|
110
|
+
["$.inner.arr1", "merge-template"],
|
|
111
|
+
["$.inner.el1", "overwrite"],
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
).toEqual({
|
|
118
|
+
here: "heretemplate",
|
|
119
|
+
// This is the extra here since we add missing
|
|
120
|
+
extra: "extra",
|
|
121
|
+
another: 23,
|
|
122
|
+
inner: {
|
|
123
|
+
// This will stay here
|
|
124
|
+
el1: "el1",
|
|
125
|
+
arr1: ["b1", "b2"],
|
|
126
|
+
// extra2 doesn't show up since we didn't dictate it
|
|
127
|
+
nested: {
|
|
128
|
+
final: "final",
|
|
129
|
+
final2: "final2",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it("performs pathjson merges with missing deletions", async () => {
|
|
135
|
+
expect(
|
|
136
|
+
JSON.parse(
|
|
137
|
+
await merge(
|
|
138
|
+
JSON.stringify(testFileJson),
|
|
139
|
+
JSON.stringify(templateFileJson),
|
|
140
|
+
{
|
|
141
|
+
relFilePath: "somepath",
|
|
142
|
+
mergeArguments: {
|
|
143
|
+
missingIsDelete: true,
|
|
144
|
+
paths: [
|
|
145
|
+
["$.here", "merge-template"],
|
|
146
|
+
["$.inner.nested", "overwrite"],
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
).toEqual({
|
|
153
|
+
here: "heretemplate",
|
|
154
|
+
extra: "extra",
|
|
155
|
+
another: 23,
|
|
156
|
+
inner: {
|
|
157
|
+
el1: "el1",
|
|
158
|
+
arr1: ["a1", "a2"],
|
|
159
|
+
// nested is gone because it was undefined
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
it("performs pathjson merges with ignore new", async () => {
|
|
164
|
+
expect(
|
|
165
|
+
JSON.parse(
|
|
166
|
+
await merge(
|
|
167
|
+
JSON.stringify(testFileJson),
|
|
168
|
+
JSON.stringify(templateFileJson),
|
|
169
|
+
{
|
|
170
|
+
relFilePath: "somepath",
|
|
171
|
+
mergeArguments: {
|
|
172
|
+
ignoreNewProperties: true,
|
|
173
|
+
paths: [
|
|
174
|
+
["$.here", "merge-template"],
|
|
175
|
+
["$.inner.arr1", "merge-template"],
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
).toEqual({
|
|
182
|
+
here: "heretemplate",
|
|
183
|
+
// extra: 'extra', - not added
|
|
184
|
+
another: 23,
|
|
185
|
+
inner: {
|
|
186
|
+
el1: "el1",
|
|
187
|
+
arr1: ["b1", "b2"],
|
|
188
|
+
nested: {
|
|
189
|
+
final: "final",
|
|
190
|
+
final2: "final2",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
it("performs pathjson merges with no missing delete", async () => {
|
|
196
|
+
expect(
|
|
197
|
+
JSON.parse(
|
|
198
|
+
await merge(
|
|
199
|
+
JSON.stringify(testFileJson),
|
|
200
|
+
JSON.stringify(templateFileJson),
|
|
201
|
+
{
|
|
202
|
+
relFilePath: "somepath",
|
|
203
|
+
mergeArguments: {
|
|
204
|
+
paths: [
|
|
205
|
+
["$.here", "merge-template"],
|
|
206
|
+
["$.inner.nested", "merge-template"],
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
).toEqual({
|
|
213
|
+
here: "heretemplate",
|
|
214
|
+
extra: "extra",
|
|
215
|
+
another: 23,
|
|
216
|
+
inner: {
|
|
217
|
+
el1: "el1",
|
|
218
|
+
arr1: ["a1", "a2"],
|
|
219
|
+
nested: {
|
|
220
|
+
final: "final",
|
|
221
|
+
final2: "final2",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
it("performs pathjson merges on multipath selection", async () => {
|
|
227
|
+
const template = { ...templateFileJson };
|
|
228
|
+
(template.inner as any).nested = {
|
|
229
|
+
final: { something: 44 },
|
|
230
|
+
newThing: "this",
|
|
231
|
+
};
|
|
232
|
+
expect(
|
|
233
|
+
JSON.parse(
|
|
234
|
+
await merge(JSON.stringify(testFileJson), JSON.stringify(template), {
|
|
235
|
+
relFilePath: "somepath",
|
|
236
|
+
mergeArguments: {
|
|
237
|
+
paths: [
|
|
238
|
+
["$.here", "merge-template"],
|
|
239
|
+
["$.inner.nested.*", "merge-template"],
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
),
|
|
244
|
+
).toEqual({
|
|
245
|
+
here: "heretemplate",
|
|
246
|
+
extra: "extra",
|
|
247
|
+
another: 23,
|
|
248
|
+
inner: {
|
|
249
|
+
el1: "el1",
|
|
250
|
+
arr1: ["a1", "a2"],
|
|
251
|
+
nested: {
|
|
252
|
+
final: { something: 44 },
|
|
253
|
+
final2: "final2",
|
|
254
|
+
newThing: "this",
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
it("performs pathjson merges on stacked selections", async () => {
|
|
260
|
+
const template = { ...templateFileJson };
|
|
261
|
+
(template.inner as any).nested = {
|
|
262
|
+
final: { something: 44 },
|
|
263
|
+
newThing: "this",
|
|
264
|
+
};
|
|
265
|
+
expect(
|
|
266
|
+
JSON.parse(
|
|
267
|
+
await merge(JSON.stringify(testFileJson), JSON.stringify(template), {
|
|
268
|
+
relFilePath: "somepath",
|
|
269
|
+
mergeArguments: {
|
|
270
|
+
paths: [
|
|
271
|
+
["$.here", "merge-template"],
|
|
272
|
+
["$.inner.nested.*", "merge-template"],
|
|
273
|
+
["$.inner.nested.final", "merge-current"],
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
),
|
|
278
|
+
).toEqual({
|
|
279
|
+
here: "heretemplate",
|
|
280
|
+
extra: "extra",
|
|
281
|
+
another: 23,
|
|
282
|
+
inner: {
|
|
283
|
+
el1: "el1",
|
|
284
|
+
arr1: ["a1", "a2"],
|
|
285
|
+
nested: {
|
|
286
|
+
final: "final",
|
|
287
|
+
final2: "final2",
|
|
288
|
+
// We only added the newThing
|
|
289
|
+
newThing: "this",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("validate", () => {
|
|
297
|
+
it("passes correct flat mapped values", () => {
|
|
298
|
+
expect(validate("merge-template")).toEqual([]);
|
|
299
|
+
});
|
|
300
|
+
it("passes correct options object", () => {
|
|
301
|
+
expect(
|
|
302
|
+
validate({
|
|
303
|
+
ignoreNewProperties: true,
|
|
304
|
+
missingIsDelete: false,
|
|
305
|
+
paths: [
|
|
306
|
+
["$.something", "merge-current"],
|
|
307
|
+
["$.something[*].values", "merge-current"],
|
|
308
|
+
],
|
|
309
|
+
} as JsonPathOverrides),
|
|
310
|
+
).toEqual([]);
|
|
311
|
+
});
|
|
312
|
+
it("returns an error if a flat map value is not correct", () => {
|
|
313
|
+
expect(validate("bad-merge-options")).toEqual([
|
|
314
|
+
"bad-merge-options must be one of type overwrite, merge-template, or merge-current",
|
|
315
|
+
]);
|
|
316
|
+
});
|
|
317
|
+
it("returns an unknown key exits in options object", () => {
|
|
318
|
+
expect(
|
|
319
|
+
validate({
|
|
320
|
+
unknownKey: "something",
|
|
321
|
+
paths: [
|
|
322
|
+
["$.here", "merge-template"],
|
|
323
|
+
["$.inner.nested.*", "merge-template"],
|
|
324
|
+
["$.inner.nested.final", "merge-current"],
|
|
325
|
+
],
|
|
326
|
+
}),
|
|
327
|
+
).toEqual(["Unrecognized key: unknownKey"]);
|
|
328
|
+
});
|
|
329
|
+
it("returns a type error if the options object is an array", () => {
|
|
330
|
+
expect(validate([])).toEqual(["Options must be an object and not Array"]);
|
|
331
|
+
});
|
|
332
|
+
it("returns a type error if the options object is null", () => {
|
|
333
|
+
expect(validate(null)).toEqual(["Options cannot be null"]);
|
|
334
|
+
});
|
|
335
|
+
it("returns a type error for each path that is invalid", () => {
|
|
336
|
+
expect(
|
|
337
|
+
validate({
|
|
338
|
+
paths: [
|
|
339
|
+
["$.badc/value", "merge-template"],
|
|
340
|
+
["no$", "merge-template"],
|
|
341
|
+
["$.something", "not a value"],
|
|
342
|
+
],
|
|
343
|
+
}),
|
|
344
|
+
).toEqual([
|
|
345
|
+
"Invalid jsonpath key: Error: Lexical error on line 1. Unrecognized text.\n$.badc/value\n------^",
|
|
346
|
+
"Unrecognized jsonpath syntax: no$",
|
|
347
|
+
"jsonpath $.something: not a value must be one of type overwrite, merge-template, or merge-current",
|
|
348
|
+
]);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MergeContext,
|
|
3
|
+
JsonFileMergeOptions,
|
|
4
|
+
JsonPathOverrides,
|
|
5
|
+
} from "../types";
|
|
6
|
+
import lodashMerge from "lodash.merge";
|
|
7
|
+
import locashCloneDeep from "lodash.clonedeep";
|
|
8
|
+
import jp, { PathComponent } from "jsonpath";
|
|
9
|
+
import { inferJSONIndent } from "../formatting/infer-json-indent";
|
|
10
|
+
|
|
11
|
+
function stringOptionError(value: string) {
|
|
12
|
+
if (
|
|
13
|
+
value === "overwrite" ||
|
|
14
|
+
value === "merge-template" ||
|
|
15
|
+
value === "merge-current"
|
|
16
|
+
) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
return `${value} must be one of type overwrite, merge-template, or merge-current`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validate(options: unknown) {
|
|
23
|
+
const errors: string[] = [];
|
|
24
|
+
|
|
25
|
+
// check for flat options
|
|
26
|
+
if (typeof options === "string") {
|
|
27
|
+
const errMsg = stringOptionError(options);
|
|
28
|
+
if (errMsg) {
|
|
29
|
+
errors.push(errMsg);
|
|
30
|
+
}
|
|
31
|
+
return errors;
|
|
32
|
+
} else {
|
|
33
|
+
if (typeof options !== "object") {
|
|
34
|
+
errors.push(`Options must be an object and not ${typeof options}`);
|
|
35
|
+
return errors;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(options)) {
|
|
39
|
+
errors.push(`Options must be an object and not Array`);
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options === null) {
|
|
44
|
+
errors.push("Options cannot be null");
|
|
45
|
+
return errors;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const optionsCast = options as JsonPathOverrides;
|
|
50
|
+
const optionKeys = Object.keys(
|
|
51
|
+
optionsCast,
|
|
52
|
+
) as unknown as (keyof JsonPathOverrides)[];
|
|
53
|
+
optionKeys.forEach((k) => {
|
|
54
|
+
if (k === "paths") {
|
|
55
|
+
optionsCast.paths.forEach((pathObj) => {
|
|
56
|
+
const [jsonPath, options] = pathObj;
|
|
57
|
+
if (jsonPath.startsWith("$.")) {
|
|
58
|
+
try {
|
|
59
|
+
jp.parse(jsonPath);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
errors.push(`Invalid jsonpath key: ${err}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const errMsg = stringOptionError(options);
|
|
65
|
+
if (errMsg) {
|
|
66
|
+
errors.push(`jsonpath ${jsonPath}: ${errMsg}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
errors.push(`Unrecognized jsonpath syntax: ${jsonPath}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
if (k === "ignoreNewProperties" || k === "missingIsDelete") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
errors.push(`Unrecognized key: ${k}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return errors;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function merge(
|
|
83
|
+
current: string,
|
|
84
|
+
fromTemplateRepo: string,
|
|
85
|
+
context: MergeContext<JsonFileMergeOptions>,
|
|
86
|
+
): Promise<string> {
|
|
87
|
+
if (context.mergeArguments === "overwrite") {
|
|
88
|
+
return fromTemplateRepo;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const currentJson = JSON.parse(current);
|
|
92
|
+
const fromTemplateJson = JSON.parse(fromTemplateRepo);
|
|
93
|
+
|
|
94
|
+
if (context.mergeArguments === "merge-current") {
|
|
95
|
+
// Performs Lodash Merge with current as the override
|
|
96
|
+
return JSON.stringify(lodashMerge(fromTemplateJson, currentJson), null, 4);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (context.mergeArguments === "merge-template") {
|
|
100
|
+
// Performs Lodash Merge with current as the override
|
|
101
|
+
return JSON.stringify(lodashMerge(currentJson, fromTemplateJson), null, 4);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { missingIsDelete, ignoreNewProperties, paths } =
|
|
105
|
+
context.mergeArguments as JsonPathOverrides;
|
|
106
|
+
|
|
107
|
+
const returnJson = locashCloneDeep(currentJson);
|
|
108
|
+
paths.forEach((p) => {
|
|
109
|
+
const [jPath, overrideType] = p;
|
|
110
|
+
|
|
111
|
+
const fromTemplatePaths: Map<string, PathComponent[]> = new Map();
|
|
112
|
+
if (overrideType === "merge-template") {
|
|
113
|
+
// We want to make sure there aren't extra paths from the template that didn't get added
|
|
114
|
+
jp.nodes(fromTemplateJson, jPath).forEach((n) => {
|
|
115
|
+
fromTemplatePaths.set(jp.stringify(n.path), n.path);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
jp.nodes(currentJson, jPath).forEach(({ path, value }) => {
|
|
119
|
+
// This solves for wildcard operators
|
|
120
|
+
const fullPath = jp.stringify(path);
|
|
121
|
+
// track the paths in the template we've walked
|
|
122
|
+
fromTemplatePaths.delete(fullPath);
|
|
123
|
+
jp.apply(returnJson, fullPath, () => {
|
|
124
|
+
const templateValue = jp.value(fromTemplateJson, fullPath);
|
|
125
|
+
if (overrideType === "merge-template") {
|
|
126
|
+
if (templateValue === undefined) {
|
|
127
|
+
if (missingIsDelete) {
|
|
128
|
+
return templateValue;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
return applyValueMerge(value, templateValue);
|
|
132
|
+
}
|
|
133
|
+
} else if (overrideType === "merge-current") {
|
|
134
|
+
return applyValueMerge(templateValue, value);
|
|
135
|
+
} else if (overrideType === "overwrite") {
|
|
136
|
+
if (templateValue !== undefined || missingIsDelete) {
|
|
137
|
+
return templateValue;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error(`Unexpected JsonPath merge value ${overrideType}`);
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!ignoreNewProperties) {
|
|
147
|
+
for (const fromTemplatePath of fromTemplatePaths.values()) {
|
|
148
|
+
applyPerPath(fromTemplatePath, returnJson, fromTemplateJson);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!ignoreNewProperties) {
|
|
154
|
+
Object.keys(fromTemplateJson).forEach((key) => {
|
|
155
|
+
if (!returnJson[key]) {
|
|
156
|
+
returnJson[key] = fromTemplateJson[key];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return JSON.stringify(returnJson, null, inferJSONIndent(current));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A merge from a template to template perspective is either melding objects
|
|
166
|
+
* or completely overwriting primitive types
|
|
167
|
+
*/
|
|
168
|
+
function applyValueMerge(base: unknown, toMerge: unknown) {
|
|
169
|
+
if (typeof base === "object" && typeof toMerge === "object") {
|
|
170
|
+
return lodashMerge(base, toMerge);
|
|
171
|
+
} else {
|
|
172
|
+
return toMerge;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Given a nodePath on the "map" tree, this will go to the first missing element
|
|
178
|
+
* and copy that node onto the object
|
|
179
|
+
*
|
|
180
|
+
* @param nodePath
|
|
181
|
+
* @param onto
|
|
182
|
+
* @param map
|
|
183
|
+
* @param forIdx
|
|
184
|
+
*/
|
|
185
|
+
function applyPerPath(
|
|
186
|
+
nodePath: jp.PathComponent[],
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
|
+
onto: any,
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
+
map: any,
|
|
191
|
+
forIdx = 0,
|
|
192
|
+
) {
|
|
193
|
+
if (nodePath[forIdx] === "$") {
|
|
194
|
+
applyPerPath(nodePath, onto, map, forIdx + 1);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const selector = nodePath[forIdx];
|
|
199
|
+
if (onto[selector]) {
|
|
200
|
+
applyPerPath(nodePath, onto[selector], map[selector], forIdx + 1);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
onto[selector] = map[selector];
|
|
205
|
+
}
|