@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,432 @@
|
|
|
1
|
+
import { join, resolve } from "path";
|
|
2
|
+
import { mergeFile } from "./merge-file";
|
|
3
|
+
import { tempDir, TEST_FIXTURES_DIR } from "./test-utils";
|
|
4
|
+
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
5
|
+
import { copySync } from "fs-extra";
|
|
6
|
+
import { JsonFileMergeOptions } from "./types";
|
|
7
|
+
|
|
8
|
+
const testTemplateDir = resolve(TEST_FIXTURES_DIR, "template");
|
|
9
|
+
const testDownstreamDir = resolve(TEST_FIXTURES_DIR, "downstream");
|
|
10
|
+
|
|
11
|
+
describe("mergeFile", () => {
|
|
12
|
+
let tmpDir: string;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
tmpDir = await mkdtemp(tempDir());
|
|
15
|
+
copySync(testDownstreamDir, tmpDir);
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await rm(tmpDir, {
|
|
19
|
+
force: true,
|
|
20
|
+
recursive: true,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
// Note: we use the "ignore" from the templateSync to constrain files we iterate over so it doeesn't happen here
|
|
24
|
+
// it('skips the file if it is part of template config ignroe', async () => {
|
|
25
|
+
// expect(await mergeFile('package.json', {
|
|
26
|
+
// cwd: tmpDir,
|
|
27
|
+
// tempCloneDir: testTemplateDir,
|
|
28
|
+
// localTemplateSyncConfig: {
|
|
29
|
+
// ignore: [],
|
|
30
|
+
// merge: {
|
|
31
|
+
// }
|
|
32
|
+
// },
|
|
33
|
+
// templateSyncConfig: {
|
|
34
|
+
// ignore: ['**/package.json'],
|
|
35
|
+
// }
|
|
36
|
+
// })).toBe(false)
|
|
37
|
+
// })
|
|
38
|
+
it("skips the file if it is part of local config ignore", async () => {
|
|
39
|
+
expect(
|
|
40
|
+
await mergeFile("package.json", {
|
|
41
|
+
cwd: tmpDir,
|
|
42
|
+
tempCloneDir: testTemplateDir,
|
|
43
|
+
localTemplateSyncConfig: {
|
|
44
|
+
ignore: ["**/package.json"],
|
|
45
|
+
merge: {},
|
|
46
|
+
},
|
|
47
|
+
templateSyncConfig: {
|
|
48
|
+
ignore: ["**/*.txt"],
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
).toEqual({
|
|
52
|
+
ignoredDueToLocal: true,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it("overwrites if no merge config for files", async () => {
|
|
56
|
+
expect(
|
|
57
|
+
await mergeFile("package.json", {
|
|
58
|
+
cwd: tmpDir,
|
|
59
|
+
tempCloneDir: testTemplateDir,
|
|
60
|
+
localTemplateSyncConfig: {
|
|
61
|
+
ignore: [],
|
|
62
|
+
},
|
|
63
|
+
templateSyncConfig: {
|
|
64
|
+
ignore: ["**/*.txt"],
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
).toEqual({
|
|
68
|
+
ignoredDueToLocal: false,
|
|
69
|
+
localChanges: [],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Ensure we overwrote
|
|
73
|
+
expect(await readFile(join(tmpDir, "package.json"))).toEqual(
|
|
74
|
+
await readFile(join(testTemplateDir, "package.json")),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
// Yea... these are more integration tests but I'm kinda untrusting of mocks for this
|
|
78
|
+
it("applies default [.json] merge with first rules if merge applies to file", async () => {
|
|
79
|
+
expect(
|
|
80
|
+
await mergeFile("package.json", {
|
|
81
|
+
cwd: tmpDir,
|
|
82
|
+
tempCloneDir: testTemplateDir,
|
|
83
|
+
localTemplateSyncConfig: {
|
|
84
|
+
ignore: [],
|
|
85
|
+
},
|
|
86
|
+
templateSyncConfig: {
|
|
87
|
+
ignore: ["**/*.txt"],
|
|
88
|
+
merge: {
|
|
89
|
+
".json": {
|
|
90
|
+
// no plugins
|
|
91
|
+
rules: [
|
|
92
|
+
{
|
|
93
|
+
glob: "**/package.json",
|
|
94
|
+
options: "merge-current",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
glob: "**/package.json",
|
|
98
|
+
options: "merge-template",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
).toEqual({
|
|
106
|
+
ignoredDueToLocal: false,
|
|
107
|
+
localChanges: [],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Ensure we overwrote
|
|
111
|
+
expect(
|
|
112
|
+
JSON.parse((await readFile(join(tmpDir, "package.json"))).toString()),
|
|
113
|
+
).toEqual({
|
|
114
|
+
name: "mypkg",
|
|
115
|
+
description: "my description",
|
|
116
|
+
dependencies: {
|
|
117
|
+
mypackage: "^1.2.0",
|
|
118
|
+
newpacakge: "^22.2.2",
|
|
119
|
+
package2: "3.22.1",
|
|
120
|
+
huh: "^2.30.0",
|
|
121
|
+
},
|
|
122
|
+
engines: {
|
|
123
|
+
node: ">=20",
|
|
124
|
+
},
|
|
125
|
+
scripts: {
|
|
126
|
+
build: "build",
|
|
127
|
+
test: "jest",
|
|
128
|
+
myscript: "somescript",
|
|
129
|
+
},
|
|
130
|
+
version: "new-version",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it("[inverse] applies default [.json] merge with first rules if merge applies to file", async () => {
|
|
134
|
+
expect(
|
|
135
|
+
await mergeFile("package.json", {
|
|
136
|
+
cwd: tmpDir,
|
|
137
|
+
tempCloneDir: testTemplateDir,
|
|
138
|
+
localTemplateSyncConfig: {
|
|
139
|
+
ignore: [],
|
|
140
|
+
},
|
|
141
|
+
templateSyncConfig: {
|
|
142
|
+
ignore: ["**/*.txt"],
|
|
143
|
+
merge: {
|
|
144
|
+
".json": {
|
|
145
|
+
// no plugins
|
|
146
|
+
rules: [
|
|
147
|
+
{
|
|
148
|
+
glob: "**/package.json",
|
|
149
|
+
options: "merge-template",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
glob: "**/package.json",
|
|
153
|
+
options: "merge-current",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
).toEqual({
|
|
161
|
+
ignoredDueToLocal: false,
|
|
162
|
+
localChanges: [],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Ensure we overwrote
|
|
166
|
+
expect(
|
|
167
|
+
JSON.parse((await readFile(join(tmpDir, "package.json"))).toString()),
|
|
168
|
+
).toEqual({
|
|
169
|
+
name: "some-stub-name",
|
|
170
|
+
description: "some-stub-description",
|
|
171
|
+
dependencies: {
|
|
172
|
+
mypackage: "^1.2.0",
|
|
173
|
+
newpacakge: "^22.2.2",
|
|
174
|
+
package2: "3.22.1",
|
|
175
|
+
huh: "~1.0.0",
|
|
176
|
+
},
|
|
177
|
+
engines: {
|
|
178
|
+
node: ">=15",
|
|
179
|
+
},
|
|
180
|
+
scripts: {
|
|
181
|
+
build: "build",
|
|
182
|
+
test: "fill this in yourself",
|
|
183
|
+
myscript: "somescript",
|
|
184
|
+
},
|
|
185
|
+
version: "new-version",
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
it("[inverse] applies default [.json] merge with sync and then local override", async () => {
|
|
189
|
+
expect(
|
|
190
|
+
await mergeFile("package.json", {
|
|
191
|
+
cwd: tmpDir,
|
|
192
|
+
tempCloneDir: testTemplateDir,
|
|
193
|
+
localTemplateSyncConfig: {
|
|
194
|
+
ignore: [],
|
|
195
|
+
merge: {
|
|
196
|
+
".json": {
|
|
197
|
+
rules: [
|
|
198
|
+
{
|
|
199
|
+
glob: "**/package.json",
|
|
200
|
+
options: {
|
|
201
|
+
paths: [
|
|
202
|
+
// Do not touch huh
|
|
203
|
+
["$.dependencies.huh", "merge-current"],
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
templateSyncConfig: {
|
|
212
|
+
ignore: ["**/*.txt"],
|
|
213
|
+
merge: {
|
|
214
|
+
".json": {
|
|
215
|
+
// no plugins
|
|
216
|
+
rules: [
|
|
217
|
+
{
|
|
218
|
+
glob: "**/package.json",
|
|
219
|
+
options: {
|
|
220
|
+
missingIsDelete: true,
|
|
221
|
+
paths: [
|
|
222
|
+
// Merge all template dependencies
|
|
223
|
+
["$.dependencies", "merge-template"],
|
|
224
|
+
],
|
|
225
|
+
} as JsonFileMergeOptions,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
glob: "**/package.json",
|
|
229
|
+
options: "merge-current",
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
).toEqual({
|
|
237
|
+
ignoredDueToLocal: false,
|
|
238
|
+
localChanges: expect.arrayContaining([
|
|
239
|
+
{
|
|
240
|
+
added: undefined,
|
|
241
|
+
count: 1,
|
|
242
|
+
removed: true,
|
|
243
|
+
value: ' "huh": "~1.0.0"\n',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
added: true,
|
|
247
|
+
count: 1,
|
|
248
|
+
removed: undefined,
|
|
249
|
+
value: ' "huh": "^2.30.0"\n',
|
|
250
|
+
},
|
|
251
|
+
]),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Ensure we overwrote
|
|
255
|
+
expect(
|
|
256
|
+
JSON.parse((await readFile(join(tmpDir, "package.json"))).toString()),
|
|
257
|
+
).toEqual({
|
|
258
|
+
name: "mypkg",
|
|
259
|
+
description: "my description",
|
|
260
|
+
dependencies: {
|
|
261
|
+
mypackage: "^1.2.0",
|
|
262
|
+
newpacakge: "^22.2.2",
|
|
263
|
+
package2: "3.22.1",
|
|
264
|
+
huh: "^2.30.0",
|
|
265
|
+
},
|
|
266
|
+
engines: {
|
|
267
|
+
node: ">=20",
|
|
268
|
+
},
|
|
269
|
+
scripts: {
|
|
270
|
+
build: "build",
|
|
271
|
+
test: "jest",
|
|
272
|
+
myscript: "somescript",
|
|
273
|
+
},
|
|
274
|
+
version: "new-version",
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
it("[inverse] applies default [.json] and custom merge for local with sync and then local override", async () => {
|
|
278
|
+
expect(
|
|
279
|
+
await mergeFile("package.json", {
|
|
280
|
+
cwd: tmpDir,
|
|
281
|
+
tempCloneDir: testTemplateDir,
|
|
282
|
+
localTemplateSyncConfig: {
|
|
283
|
+
ignore: [],
|
|
284
|
+
merge: {
|
|
285
|
+
".json": {
|
|
286
|
+
// simulates a "node" plugin since we're pulling from the current context
|
|
287
|
+
plugin: "../test-fixtures/dummy-plugin.js",
|
|
288
|
+
rules: [
|
|
289
|
+
{
|
|
290
|
+
glob: "**/package.json",
|
|
291
|
+
options: {
|
|
292
|
+
paths: [
|
|
293
|
+
// Do not touch huh
|
|
294
|
+
["$.dependencies.huh", "merge-current"],
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
templateSyncConfig: {
|
|
303
|
+
ignore: ["**/*.txt"],
|
|
304
|
+
merge: {
|
|
305
|
+
".json": {
|
|
306
|
+
// no plugins
|
|
307
|
+
rules: [
|
|
308
|
+
{
|
|
309
|
+
glob: "**/package.json",
|
|
310
|
+
options: {
|
|
311
|
+
missingIsDelete: true,
|
|
312
|
+
paths: [
|
|
313
|
+
// Merge all template dependencies
|
|
314
|
+
["$.dependencies", "merge-template"],
|
|
315
|
+
],
|
|
316
|
+
} as JsonFileMergeOptions,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
glob: "**/package.json",
|
|
320
|
+
options: "merge-current",
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
).toEqual({
|
|
328
|
+
ignoredDueToLocal: false,
|
|
329
|
+
// TODO: I don't
|
|
330
|
+
localChanges: expect.arrayContaining([
|
|
331
|
+
{
|
|
332
|
+
added: undefined,
|
|
333
|
+
count: 17,
|
|
334
|
+
removed: true,
|
|
335
|
+
value: ` "name": "mypkg",
|
|
336
|
+
"description": "my description",
|
|
337
|
+
"dependencies": {
|
|
338
|
+
"mypackage": "^1.2.0",
|
|
339
|
+
"newpacakge": "^22.2.2",
|
|
340
|
+
"package2": "3.22.1",
|
|
341
|
+
"huh": "~1.0.0"
|
|
342
|
+
},
|
|
343
|
+
"engines": {
|
|
344
|
+
"node": ">=20"
|
|
345
|
+
},
|
|
346
|
+
"scripts": {
|
|
347
|
+
"build": "build",
|
|
348
|
+
"test": "jest",
|
|
349
|
+
"myscript": "somescript"
|
|
350
|
+
},
|
|
351
|
+
"version": "new-version"\n`,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
added: true,
|
|
355
|
+
count: 1,
|
|
356
|
+
removed: undefined,
|
|
357
|
+
value: ` "tested": true\n`,
|
|
358
|
+
},
|
|
359
|
+
]),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Ensure we overwrote
|
|
363
|
+
expect(
|
|
364
|
+
JSON.parse((await readFile(join(tmpDir, "package.json"))).toString()),
|
|
365
|
+
).toEqual({
|
|
366
|
+
tested: true,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
it("[inverse] applies default [.json] and custom merge for local with sync plugin and then local override", async () => {
|
|
370
|
+
expect(
|
|
371
|
+
await mergeFile("package.json", {
|
|
372
|
+
cwd: tmpDir,
|
|
373
|
+
tempCloneDir: testTemplateDir,
|
|
374
|
+
localTemplateSyncConfig: {
|
|
375
|
+
ignore: [],
|
|
376
|
+
merge: {
|
|
377
|
+
".json": {
|
|
378
|
+
// This is a relative path to ht
|
|
379
|
+
plugin: "plugins/custom-plugin.js",
|
|
380
|
+
rules: [
|
|
381
|
+
{
|
|
382
|
+
glob: "**/package.json",
|
|
383
|
+
options: {
|
|
384
|
+
paths: [
|
|
385
|
+
// Do not touch huh
|
|
386
|
+
["$.dependencies.huh", "merge-current"],
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
templateSyncConfig: {
|
|
395
|
+
ignore: ["**/*.txt"],
|
|
396
|
+
merge: {
|
|
397
|
+
".json": {
|
|
398
|
+
// no plugins
|
|
399
|
+
rules: [
|
|
400
|
+
{
|
|
401
|
+
glob: "**/package.json",
|
|
402
|
+
options: {
|
|
403
|
+
missingIsDelete: true,
|
|
404
|
+
paths: [
|
|
405
|
+
// Merge all template dependencies
|
|
406
|
+
["$.dependencies", "merge-template"],
|
|
407
|
+
],
|
|
408
|
+
} as JsonFileMergeOptions,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
glob: "**/package.json",
|
|
412
|
+
options: "merge-current",
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
}),
|
|
419
|
+
).toEqual({
|
|
420
|
+
ignoredDueToLocal: false,
|
|
421
|
+
// We are just making sure plugin look up happens here
|
|
422
|
+
localChanges: expect.arrayContaining([]),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Ensure we overwrote
|
|
426
|
+
expect(
|
|
427
|
+
JSON.parse((await readFile(join(tmpDir, "package.json"))).toString()),
|
|
428
|
+
).toEqual({
|
|
429
|
+
downstream: true,
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { isMatch, some } from "micromatch";
|
|
2
|
+
import { Config, MergeContext, MergePlugin } from "./types";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { loadPlugin } from "./load-plugin";
|
|
7
|
+
import { Change, diffLines } from "diff";
|
|
8
|
+
import { outputFile } from "fs-extra";
|
|
9
|
+
|
|
10
|
+
interface MergeFileOptions {
|
|
11
|
+
localTemplateSyncConfig: Config;
|
|
12
|
+
templateSyncConfig: Config;
|
|
13
|
+
tempCloneDir: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MergeFileReturn {
|
|
18
|
+
/**
|
|
19
|
+
* If the file was ignored due to the local config
|
|
20
|
+
*/
|
|
21
|
+
ignoredDueToLocal: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Only available if the file wasn't ignored, this is a list of lineDiffs
|
|
24
|
+
* from the the diff library that were applied to what would've been removed
|
|
25
|
+
*/
|
|
26
|
+
localChanges?: Change[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Applies the merge to a file according to the context information.
|
|
31
|
+
*
|
|
32
|
+
* Returns true if merged and false if skipped
|
|
33
|
+
* @param relPath
|
|
34
|
+
* @param context
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
export async function mergeFile(
|
|
38
|
+
relPath: string,
|
|
39
|
+
context: MergeFileOptions,
|
|
40
|
+
): Promise<MergeFileReturn> {
|
|
41
|
+
const { localTemplateSyncConfig, templateSyncConfig, tempCloneDir, cwd } =
|
|
42
|
+
context;
|
|
43
|
+
|
|
44
|
+
if (some(relPath, localTemplateSyncConfig.ignore)) {
|
|
45
|
+
return {
|
|
46
|
+
ignoredDueToLocal: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ext = extname(relPath);
|
|
51
|
+
const filePath = join(cwd, relPath);
|
|
52
|
+
const templatePath = join(tempCloneDir, relPath);
|
|
53
|
+
|
|
54
|
+
const mergeConfig = templateSyncConfig.merge?.[ext];
|
|
55
|
+
const localMergeConfig = localTemplateSyncConfig.merge?.[ext];
|
|
56
|
+
|
|
57
|
+
// Either write the merge or write
|
|
58
|
+
let fileContents: string;
|
|
59
|
+
const localChanges: Change[] = [];
|
|
60
|
+
if (existsSync(filePath) && (mergeConfig || localMergeConfig)) {
|
|
61
|
+
const originalCurrentFile = (await readFile(filePath)).toString();
|
|
62
|
+
if (mergeConfig) {
|
|
63
|
+
// Apply the template's most recent merges
|
|
64
|
+
const handler = await loadPlugin(mergeConfig, ext, tempCloneDir);
|
|
65
|
+
|
|
66
|
+
const mergeOptions = mergeConfig.rules.find((rule) => {
|
|
67
|
+
return isMatch(relPath, rule.glob);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (mergeOptions) {
|
|
71
|
+
fileContents = await safeMerge(
|
|
72
|
+
handler,
|
|
73
|
+
mergeConfig.plugin ?? `default for ${ext}`,
|
|
74
|
+
originalCurrentFile,
|
|
75
|
+
(await readFile(templatePath)).toString(),
|
|
76
|
+
{
|
|
77
|
+
relFilePath: relPath,
|
|
78
|
+
mergeArguments: mergeOptions.options,
|
|
79
|
+
isLocalOptions: true,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
// Apply overwrite if we didn't set up merge
|
|
84
|
+
fileContents = (await readFile(templatePath)).toString();
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// Apply overwrite if we didn't set up merge
|
|
88
|
+
fileContents = (await readFile(templatePath)).toString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// We apply the localMerge Config to the fileContent output by the template merge
|
|
92
|
+
if (localMergeConfig) {
|
|
93
|
+
const handler = await loadPlugin(localMergeConfig, ext, cwd);
|
|
94
|
+
|
|
95
|
+
const mergeOptions = localMergeConfig.rules.find((rule) => {
|
|
96
|
+
return isMatch(relPath, rule.glob);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (mergeOptions) {
|
|
100
|
+
const localContents = await safeMerge(
|
|
101
|
+
handler,
|
|
102
|
+
localMergeConfig.plugin ?? `default for ${ext}`,
|
|
103
|
+
originalCurrentFile,
|
|
104
|
+
fileContents,
|
|
105
|
+
{
|
|
106
|
+
relFilePath: relPath,
|
|
107
|
+
mergeArguments: mergeOptions.options,
|
|
108
|
+
isLocalOptions: true,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
localChanges.push(...diffLines(fileContents, localContents));
|
|
112
|
+
fileContents = localContents;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// Just perform simple overwrite
|
|
117
|
+
fileContents = (await readFile(templatePath)).toString();
|
|
118
|
+
}
|
|
119
|
+
await outputFile(filePath, fileContents);
|
|
120
|
+
return {
|
|
121
|
+
ignoredDueToLocal: false,
|
|
122
|
+
localChanges,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Simple helper function to ensure that we don't let bad plugins corrupt the call flow
|
|
128
|
+
* @param plugin
|
|
129
|
+
*/
|
|
130
|
+
async function safeMerge(
|
|
131
|
+
plugin: MergePlugin<unknown>,
|
|
132
|
+
pluginPath: string,
|
|
133
|
+
current: string,
|
|
134
|
+
fromTemplate: string,
|
|
135
|
+
context: MergeContext,
|
|
136
|
+
) {
|
|
137
|
+
const ret = await plugin.merge(current, fromTemplate, context);
|
|
138
|
+
if (typeof ret !== "string") {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Plugin ${pluginPath} did not return string for merge function! This is not allowed!`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!ret) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Plugin ${pluginPath} should not make a merge be an empty string!`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return ret as string;
|
|
150
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JsonFileMergeOptions, MergePlugin } from "../types";
|
|
2
|
+
import { merge as jsonMerge, validate as jsonValidate } from "./json-merge";
|
|
3
|
+
|
|
4
|
+
export const defaultExtensionMap = {
|
|
5
|
+
".json": {
|
|
6
|
+
merge: jsonMerge,
|
|
7
|
+
validate: jsonValidate,
|
|
8
|
+
},
|
|
9
|
+
} as {
|
|
10
|
+
[ext: string]: MergePlugin<JsonFileMergeOptions>;
|
|
11
|
+
};
|