@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.
Files changed (119) hide show
  1. package/.eslintrc.js +10 -0
  2. package/.github/CODEOWNERS +6 -0
  3. package/.github/workflows/pr-checks.yaml +12 -0
  4. package/.github/workflows/release.yaml +36 -0
  5. package/.github/workflows/test-flow.yaml +58 -0
  6. package/.husky/commit-msg +4 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc +1 -0
  9. package/CHANGELOG.md +27 -0
  10. package/README.md +93 -0
  11. package/action.yml +13 -0
  12. package/commitlint.config.js +3 -0
  13. package/jest.config.js +19 -0
  14. package/lib/cjs/clone-drivers/git-clone.d.ts +1 -0
  15. package/lib/cjs/clone-drivers/git-clone.js +14 -0
  16. package/lib/cjs/clone-drivers/index.d.ts +2 -0
  17. package/lib/cjs/clone-drivers/index.js +18 -0
  18. package/lib/cjs/clone-drivers/types.d.ts +5 -0
  19. package/lib/cjs/clone-drivers/types.js +2 -0
  20. package/lib/cjs/diff-drivers/git-diff.d.ts +1 -0
  21. package/lib/cjs/diff-drivers/git-diff.js +13 -0
  22. package/lib/cjs/diff-drivers/index.d.ts +2 -0
  23. package/lib/cjs/diff-drivers/index.js +18 -0
  24. package/lib/cjs/diff-drivers/types.d.ts +5 -0
  25. package/lib/cjs/diff-drivers/types.js +2 -0
  26. package/lib/cjs/formatting/index.d.ts +2 -0
  27. package/lib/cjs/formatting/index.js +18 -0
  28. package/lib/cjs/formatting/infer-json-indent.d.ts +1 -0
  29. package/lib/cjs/formatting/infer-json-indent.js +18 -0
  30. package/lib/cjs/formatting/sync-results-to-md.d.ts +2 -0
  31. package/lib/cjs/formatting/sync-results-to-md.js +40 -0
  32. package/lib/cjs/index.d.ts +3 -0
  33. package/lib/cjs/index.js +19 -0
  34. package/lib/cjs/load-plugin.d.ts +2 -0
  35. package/lib/cjs/load-plugin.js +63 -0
  36. package/lib/cjs/match.d.ts +10 -0
  37. package/lib/cjs/match.js +45 -0
  38. package/lib/cjs/merge-file.d.ts +29 -0
  39. package/lib/cjs/merge-file.js +99 -0
  40. package/lib/cjs/plugins/index.d.ts +4 -0
  41. package/lib/cjs/plugins/index.js +10 -0
  42. package/lib/cjs/plugins/json-merge.d.ts +3 -0
  43. package/lib/cjs/plugins/json-merge.js +185 -0
  44. package/lib/cjs/template-sync.d.ts +40 -0
  45. package/lib/cjs/template-sync.js +56 -0
  46. package/lib/cjs/test-utils/index.d.ts +2 -0
  47. package/lib/cjs/test-utils/index.js +10 -0
  48. package/lib/cjs/types.d.ts +113 -0
  49. package/lib/cjs/types.js +2 -0
  50. package/lib/esm/clone-drivers/git-clone.js +14 -0
  51. package/lib/esm/clone-drivers/index.js +18 -0
  52. package/lib/esm/clone-drivers/types.js +2 -0
  53. package/lib/esm/diff-drivers/git-diff.js +13 -0
  54. package/lib/esm/diff-drivers/index.js +18 -0
  55. package/lib/esm/diff-drivers/types.js +2 -0
  56. package/lib/esm/formatting/index.js +18 -0
  57. package/lib/esm/formatting/infer-json-indent.js +18 -0
  58. package/lib/esm/formatting/sync-results-to-md.js +40 -0
  59. package/lib/esm/index.js +19 -0
  60. package/lib/esm/load-plugin.js +40 -0
  61. package/lib/esm/match.js +45 -0
  62. package/lib/esm/merge-file.js +99 -0
  63. package/lib/esm/plugins/index.js +10 -0
  64. package/lib/esm/plugins/json-merge.js +185 -0
  65. package/lib/esm/template-sync.js +56 -0
  66. package/lib/esm/test-utils/index.js +10 -0
  67. package/lib/esm/types.js +2 -0
  68. package/package.json +60 -0
  69. package/release.config.js +34 -0
  70. package/src/clone-drivers/git-clone.ts +16 -0
  71. package/src/clone-drivers/index.ts +2 -0
  72. package/src/clone-drivers/types.ts +8 -0
  73. package/src/diff-drivers/git-diff.ts +10 -0
  74. package/src/diff-drivers/index.ts +2 -0
  75. package/src/diff-drivers/types.ts +8 -0
  76. package/src/formatting/__snapshots__/sync-results-to-md.spec.ts.snap +22 -0
  77. package/src/formatting/index.ts +2 -0
  78. package/src/formatting/infer-json-indent.spec.ts +49 -0
  79. package/src/formatting/infer-json-indent.ts +16 -0
  80. package/src/formatting/sync-results-to-md.spec.ts +25 -0
  81. package/src/formatting/sync-results-to-md.ts +46 -0
  82. package/src/index.ts +3 -0
  83. package/src/load-plugin.ts +42 -0
  84. package/src/match.spec.ts +68 -0
  85. package/src/match.ts +52 -0
  86. package/src/merge-file.spec.ts +432 -0
  87. package/src/merge-file.ts +150 -0
  88. package/src/plugins/index.ts +11 -0
  89. package/src/plugins/json-merge.spec.ts +350 -0
  90. package/src/plugins/json-merge.ts +205 -0
  91. package/src/template-sync.spec.ts +216 -0
  92. package/src/template-sync.ts +113 -0
  93. package/src/test-utils/index.ts +13 -0
  94. package/src/types.ts +124 -0
  95. package/templatesync.local.json +15 -0
  96. package/test-fixtures/downstream/README.md +3 -0
  97. package/test-fixtures/downstream/package.json +18 -0
  98. package/test-fixtures/downstream/plugins/custom-plugin.js +11 -0
  99. package/test-fixtures/downstream/src/index.js +2 -0
  100. package/test-fixtures/downstream/src/index.ts +1 -0
  101. package/test-fixtures/downstream/src/templated.js +2 -0
  102. package/test-fixtures/downstream/src/templated.ts +1 -0
  103. package/test-fixtures/downstream/templatesync.json +19 -0
  104. package/test-fixtures/downstream/templatesync.local.json +14 -0
  105. package/test-fixtures/dummy-plugin.js +8 -0
  106. package/test-fixtures/glob-test/folder1/something.js +1 -0
  107. package/test-fixtures/glob-test/folder1/something.ts +0 -0
  108. package/test-fixtures/glob-test/toplevel.js +0 -0
  109. package/test-fixtures/glob-test/toplevel.txt +0 -0
  110. package/test-fixtures/template/custom-bin/something.txt +1 -0
  111. package/test-fixtures/template/package.json +17 -0
  112. package/test-fixtures/template/src/index.js +2 -0
  113. package/test-fixtures/template/src/index.ts +1 -0
  114. package/test-fixtures/template/src/templated.js +2 -0
  115. package/test-fixtures/template/src/templated.ts +1 -0
  116. package/test-fixtures/template/templatesync.json +19 -0
  117. package/tsconfig.cjs.json +12 -0
  118. package/tsconfig.esm.json +10 -0
  119. 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
+ }