@hanseltime/template-repo-sync 2.1.2 → 2.2.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.2.1](https://github.com/HanseltimeIndustries/template-repo-sync/compare/v2.2.0...v2.2.1) (2026-02-22)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * template sync afterRef updates ([21dd335](https://github.com/HanseltimeIndustries/template-repo-sync/commit/21dd335045034d132ad5f6ab7944a81c82b96981))
7
+
8
+ # [2.2.0](https://github.com/HanseltimeIndustries/template-repo-sync/compare/v2.1.2...v2.2.0) (2026-02-21)
9
+
10
+
11
+ ### Features
12
+
13
+ * add modified files output to track actual sync changes ([050ffd0](https://github.com/HanseltimeIndustries/template-repo-sync/commit/050ffd0ff74195943e60fc8dbf4354191959c0be))
14
+
1
15
  ## [2.1.2](https://github.com/HanseltimeIndustries/template-repo-sync/compare/v2.1.1...v2.1.2) (2026-02-08)
2
16
 
3
17
 
@@ -21,6 +21,23 @@ ${result.localFileChanges[file].reduce((diffS, change) => {
21
21
  }, "")}
22
22
  \`\`\``;
23
23
  }, "")}
24
+
25
+ ## Files Modified (${result.modifiedFiles.total})
26
+
27
+ Added:
28
+ ${result.modifiedFiles.added.reduce((s, f) => {
29
+ return `${s}\n- ${f}`;
30
+ }, "")}
31
+
32
+ Modified:
33
+ ${result.modifiedFiles.modified.reduce((s, f) => {
34
+ return `${s}\n- ${f}`;
35
+ }, "")}
36
+
37
+ Deleted:
38
+ ${result.modifiedFiles.deleted.reduce((s, f) => {
39
+ return `${s}\n- ${f}`;
40
+ }, "")}
24
41
  `;
25
42
  }
26
43
  exports.syncResultsToMd = syncResultsToMd;
@@ -14,7 +14,8 @@ interface MergeFileReturn {
14
14
  ignoredDueToLocal: boolean;
15
15
  /**
16
16
  * Only available if the file wasn't ignored, this is a list of lineDiffs
17
- * from the the diff library that were applied to what would've been removed
17
+ * from the diff library that were applied to what would've been changed by
18
+ * the base templatesync repo
18
19
  */
19
20
  localChanges?: Change[];
20
21
  }
@@ -1,6 +1,6 @@
1
1
  import { Change } from "diff";
2
2
  import { TemplateCloneDriverFn } from "./clone-drivers";
3
- import { TemplateDiffDriverFn } from "./diff-drivers";
3
+ import { DiffResult, TemplateDiffDriverFn } from "./diff-drivers";
4
4
  import { TemplateRefDriverFn } from "./ref-drivers/types";
5
5
  import { TemplateCheckoutDriverFn } from "./checkout-drivers";
6
6
  export interface TemplateSyncOptions {
@@ -57,6 +57,16 @@ export interface TemplateSyncReturn {
57
57
  localFileChanges: {
58
58
  [filePath: string]: Change[];
59
59
  };
60
+ /**
61
+ * A list of all files that are modified by this operation. You can use total to quickly check if
62
+ * there was an actual meaningful change.
63
+ *
64
+ * Please note, ther may be localSkipfiles and total: 0, which indicates there were changes BUT the local template
65
+ * sync file rendered them meaningless.
66
+ */
67
+ modifiedFiles: DiffResult & {
68
+ total: number;
69
+ };
60
70
  }
61
71
  export declare const TEMPLATE_SYNC_CONFIG = "templatesync";
62
72
  export declare const TEMPLATE_SYNC_LOCAL_CONFIG = "templatesync.local";
@@ -61,12 +61,29 @@ async function templateSync(options) {
61
61
  const templateSyncConfig = (0, fs_1.existsSync)(cloneConfigPath)
62
62
  ? commentJSON.parse((0, fs_1.readFileSync)(cloneConfigPath).toString())
63
63
  : { ignore: [] };
64
- const localConfigPath = (0, path_1.join)(options.repoDir, `${exports.TEMPLATE_SYNC_LOCAL_CONFIG}.json`);
64
+ const localConfigFile = `${exports.TEMPLATE_SYNC_LOCAL_CONFIG}.json`;
65
+ const localConfigPath = (0, path_1.join)(options.repoDir, localConfigFile);
65
66
  const localTemplateSyncConfig = (0, fs_1.existsSync)(localConfigPath)
66
67
  ? commentJSON.parse((0, fs_1.readFileSync)(localConfigPath).toString())
67
68
  : { ignore: [] };
68
69
  let filesToSync;
70
+ const ref = await currentRefDriver({
71
+ rootDir: tempCloneDir,
72
+ });
69
73
  if (localTemplateSyncConfig.afterRef) {
74
+ if (ref === localTemplateSyncConfig.afterRef) {
75
+ // short circuit if the refs match
76
+ return {
77
+ localSkipFiles: [],
78
+ localFileChanges: {},
79
+ modifiedFiles: {
80
+ added: [],
81
+ modified: [],
82
+ deleted: [],
83
+ total: 0,
84
+ },
85
+ };
86
+ }
70
87
  filesToSync = await diffDriver(tempCloneDir, localTemplateSyncConfig.afterRef);
71
88
  }
72
89
  else {
@@ -83,7 +100,7 @@ async function templateSync(options) {
83
100
  filesToSync.added = filesToSync.added.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
84
101
  filesToSync.modified = filesToSync.modified.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
85
102
  filesToSync.deleted = filesToSync.deleted.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
86
- const localSkipFiles = [];
103
+ const localSkipFiles = new Set();
87
104
  const localFileChanges = {};
88
105
  const fileSyncFactory = (op) => {
89
106
  return async (f) => {
@@ -95,7 +112,7 @@ async function templateSync(options) {
95
112
  fileOperation: op,
96
113
  });
97
114
  if (result.ignoredDueToLocal) {
98
- localSkipFiles.push(f);
115
+ localSkipFiles.add(f);
99
116
  }
100
117
  else if (result?.localChanges && result.localChanges.length > 0) {
101
118
  localFileChanges[f] = result.localChanges;
@@ -106,6 +123,15 @@ async function templateSync(options) {
106
123
  await Promise.all(filesToSync.added.map(fileSyncFactory("added")));
107
124
  await Promise.all(filesToSync.modified.map(fileSyncFactory("modified")));
108
125
  await Promise.all(filesToSync.deleted.map(fileSyncFactory("deleted")));
126
+ // Report the files that changed in general
127
+ const actualAdded = filesToSync.added.filter((f) => !localSkipFiles.has(f));
128
+ const actualModified = filesToSync.modified.filter((f) => !localSkipFiles.has(f));
129
+ const actualDeleted = filesToSync.deleted.filter((f) => !localSkipFiles.has(f));
130
+ const modifiedFiles = {
131
+ added: actualAdded,
132
+ modified: actualModified,
133
+ deleted: actualDeleted,
134
+ };
109
135
  // apply after ref
110
136
  if (options.updateAfterRef) {
111
137
  const ref = await currentRefDriver({
@@ -116,14 +142,22 @@ async function templateSync(options) {
116
142
  const config = commentJSON.parse(configStr);
117
143
  config.afterRef = ref;
118
144
  (0, fs_1.writeFileSync)(localConfigPath, commentJSON.stringify(config, null, (0, formatting_1.inferJSONIndent)(configStr)));
145
+ modifiedFiles.modified.push(localConfigFile);
119
146
  }
120
147
  else {
121
148
  (0, fs_1.writeFileSync)(localConfigPath, commentJSON.stringify({ afterRef: ref }, null, 4));
149
+ modifiedFiles.added.push(localConfigFile);
122
150
  }
123
151
  }
124
152
  return {
125
- localSkipFiles,
153
+ localSkipFiles: Array.from(localSkipFiles),
126
154
  localFileChanges,
155
+ modifiedFiles: {
156
+ ...modifiedFiles,
157
+ total: modifiedFiles.added.length +
158
+ modifiedFiles.deleted.length +
159
+ modifiedFiles.modified.length,
160
+ },
127
161
  };
128
162
  }
129
163
  exports.templateSync = templateSync;
@@ -21,6 +21,23 @@ ${result.localFileChanges[file].reduce((diffS, change) => {
21
21
  }, "")}
22
22
  \`\`\``;
23
23
  }, "")}
24
+
25
+ ## Files Modified (${result.modifiedFiles.total})
26
+
27
+ Added:
28
+ ${result.modifiedFiles.added.reduce((s, f) => {
29
+ return `${s}\n- ${f}`;
30
+ }, "")}
31
+
32
+ Modified:
33
+ ${result.modifiedFiles.modified.reduce((s, f) => {
34
+ return `${s}\n- ${f}`;
35
+ }, "")}
36
+
37
+ Deleted:
38
+ ${result.modifiedFiles.deleted.reduce((s, f) => {
39
+ return `${s}\n- ${f}`;
40
+ }, "")}
24
41
  `;
25
42
  }
26
43
  exports.syncResultsToMd = syncResultsToMd;
@@ -61,12 +61,29 @@ async function templateSync(options) {
61
61
  const templateSyncConfig = (0, fs_1.existsSync)(cloneConfigPath)
62
62
  ? commentJSON.parse((0, fs_1.readFileSync)(cloneConfigPath).toString())
63
63
  : { ignore: [] };
64
- const localConfigPath = (0, path_1.join)(options.repoDir, `${exports.TEMPLATE_SYNC_LOCAL_CONFIG}.json`);
64
+ const localConfigFile = `${exports.TEMPLATE_SYNC_LOCAL_CONFIG}.json`;
65
+ const localConfigPath = (0, path_1.join)(options.repoDir, localConfigFile);
65
66
  const localTemplateSyncConfig = (0, fs_1.existsSync)(localConfigPath)
66
67
  ? commentJSON.parse((0, fs_1.readFileSync)(localConfigPath).toString())
67
68
  : { ignore: [] };
68
69
  let filesToSync;
70
+ const ref = await currentRefDriver({
71
+ rootDir: tempCloneDir,
72
+ });
69
73
  if (localTemplateSyncConfig.afterRef) {
74
+ if (ref === localTemplateSyncConfig.afterRef) {
75
+ // short circuit if the refs match
76
+ return {
77
+ localSkipFiles: [],
78
+ localFileChanges: {},
79
+ modifiedFiles: {
80
+ added: [],
81
+ modified: [],
82
+ deleted: [],
83
+ total: 0,
84
+ },
85
+ };
86
+ }
70
87
  filesToSync = await diffDriver(tempCloneDir, localTemplateSyncConfig.afterRef);
71
88
  }
72
89
  else {
@@ -83,7 +100,7 @@ async function templateSync(options) {
83
100
  filesToSync.added = filesToSync.added.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
84
101
  filesToSync.modified = filesToSync.modified.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
85
102
  filesToSync.deleted = filesToSync.deleted.filter((f) => !(0, micromatch_1.some)(f, templateSyncConfig.ignore));
86
- const localSkipFiles = [];
103
+ const localSkipFiles = new Set();
87
104
  const localFileChanges = {};
88
105
  const fileSyncFactory = (op) => {
89
106
  return async (f) => {
@@ -95,7 +112,7 @@ async function templateSync(options) {
95
112
  fileOperation: op,
96
113
  });
97
114
  if (result.ignoredDueToLocal) {
98
- localSkipFiles.push(f);
115
+ localSkipFiles.add(f);
99
116
  }
100
117
  else if (result?.localChanges && result.localChanges.length > 0) {
101
118
  localFileChanges[f] = result.localChanges;
@@ -106,6 +123,15 @@ async function templateSync(options) {
106
123
  await Promise.all(filesToSync.added.map(fileSyncFactory("added")));
107
124
  await Promise.all(filesToSync.modified.map(fileSyncFactory("modified")));
108
125
  await Promise.all(filesToSync.deleted.map(fileSyncFactory("deleted")));
126
+ // Report the files that changed in general
127
+ const actualAdded = filesToSync.added.filter((f) => !localSkipFiles.has(f));
128
+ const actualModified = filesToSync.modified.filter((f) => !localSkipFiles.has(f));
129
+ const actualDeleted = filesToSync.deleted.filter((f) => !localSkipFiles.has(f));
130
+ const modifiedFiles = {
131
+ added: actualAdded,
132
+ modified: actualModified,
133
+ deleted: actualDeleted,
134
+ };
109
135
  // apply after ref
110
136
  if (options.updateAfterRef) {
111
137
  const ref = await currentRefDriver({
@@ -116,14 +142,22 @@ async function templateSync(options) {
116
142
  const config = commentJSON.parse(configStr);
117
143
  config.afterRef = ref;
118
144
  (0, fs_1.writeFileSync)(localConfigPath, commentJSON.stringify(config, null, (0, formatting_1.inferJSONIndent)(configStr)));
145
+ modifiedFiles.modified.push(localConfigFile);
119
146
  }
120
147
  else {
121
148
  (0, fs_1.writeFileSync)(localConfigPath, commentJSON.stringify({ afterRef: ref }, null, 4));
149
+ modifiedFiles.added.push(localConfigFile);
122
150
  }
123
151
  }
124
152
  return {
125
- localSkipFiles,
153
+ localSkipFiles: Array.from(localSkipFiles),
126
154
  localFileChanges,
155
+ modifiedFiles: {
156
+ ...modifiedFiles,
157
+ total: modifiedFiles.added.length +
158
+ modifiedFiles.deleted.length +
159
+ modifiedFiles.modified.length,
160
+ },
127
161
  };
128
162
  }
129
163
  exports.templateSync = templateSync;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanseltime/template-repo-sync",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "An npm library that enables pluggable, customizable synchronization between template repos",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/cjs/index.d.ts",
@@ -18,5 +18,22 @@ package.json
18
18
  -your thang
19
19
 
20
20
  \`\`\`
21
+
22
+ ## Files Modified (6)
23
+
24
+ Added:
25
+
26
+ - src/something.ts
27
+ - new_settings.toml
28
+
29
+ Modified:
30
+
31
+ - something.txt
32
+ - .env
33
+
34
+ Deleted:
35
+
36
+ - TODO.md
37
+ - throwaway.ts
21
38
  "
22
39
  `;
@@ -19,6 +19,12 @@ describe("syncResultsToMd", () => {
19
19
  },
20
20
  ],
21
21
  },
22
+ modifiedFiles: {
23
+ added: ["src/something.ts", "new_settings.toml"],
24
+ deleted: ["TODO.md", "throwaway.ts"],
25
+ modified: ["something.txt", ".env"],
26
+ total: 6,
27
+ },
22
28
  }),
23
29
  ).toMatchSnapshot();
24
30
  });
@@ -23,6 +23,23 @@ ${result.localFileChanges[file].reduce((diffS, change) => {
23
23
  }, "")}
24
24
  \`\`\``;
25
25
  }, "")}
26
+
27
+ ## Files Modified (${result.modifiedFiles.total})
28
+
29
+ Added:
30
+ ${result.modifiedFiles.added.reduce((s, f) => {
31
+ return `${s}\n- ${f}`;
32
+ }, "")}
33
+
34
+ Modified:
35
+ ${result.modifiedFiles.modified.reduce((s, f) => {
36
+ return `${s}\n- ${f}`;
37
+ }, "")}
38
+
39
+ Deleted:
40
+ ${result.modifiedFiles.deleted.reduce((s, f) => {
41
+ return `${s}\n- ${f}`;
42
+ }, "")}
26
43
  `;
27
44
  }
28
45
 
package/src/merge-file.ts CHANGED
@@ -22,7 +22,8 @@ interface MergeFileReturn {
22
22
  ignoredDueToLocal: boolean;
23
23
  /**
24
24
  * Only available if the file wasn't ignored, this is a list of lineDiffs
25
- * from the the diff library that were applied to what would've been removed
25
+ * from the diff library that were applied to what would've been changed by
26
+ * the base templatesync repo
26
27
  */
27
28
  localChanges?: Change[];
28
29
  }
@@ -44,6 +44,18 @@ describe("templateSync", () => {
44
44
  // Expect no changes since there was no local sync file
45
45
  localSkipFiles: [],
46
46
  localFileChanges: {},
47
+ modifiedFiles: {
48
+ added: [
49
+ "package.json",
50
+ "src/index.js",
51
+ "src/templated.js",
52
+ "src/templated.ts",
53
+ "templatesync.json",
54
+ ],
55
+ deleted: [],
56
+ modified: [],
57
+ total: 5,
58
+ },
47
59
  });
48
60
 
49
61
  // Verify the files
@@ -72,6 +84,18 @@ describe("templateSync", () => {
72
84
  // Expect no changes since there was no local sync file
73
85
  localSkipFiles: [],
74
86
  localFileChanges: {},
87
+ modifiedFiles: {
88
+ added: [
89
+ "package.json",
90
+ "src/index.js",
91
+ "src/templated.js",
92
+ "src/templated.ts",
93
+ "templatesync.json",
94
+ ],
95
+ deleted: [],
96
+ modified: [],
97
+ total: 5,
98
+ },
75
99
  });
76
100
 
77
101
  // Verify the files
@@ -175,6 +199,18 @@ describe("templateSync", () => {
175
199
  "package.json": expect.arrayContaining([]),
176
200
  }),
177
201
  );
202
+ // Make sure the result captures the changes
203
+ expect(result.modifiedFiles).toEqual({
204
+ added: [
205
+ "package.json",
206
+ "src/index.js",
207
+ "src/templated.js",
208
+ "templatesync.json",
209
+ ],
210
+ deleted: [],
211
+ modified: [],
212
+ total: 4,
213
+ });
178
214
 
179
215
  // Verify the files
180
216
  await fileMatchTemplate(tmpDir, "templatesync.json");
@@ -256,7 +292,7 @@ describe("templateSync", () => {
256
292
  // We will only update the templated.ts
257
293
  const mockDiffDriver = jest.fn().mockImplementation(async () => ({
258
294
  added: ["src/templated.ts"],
259
- modified: ["src/index.ts"], // Add index.ts so we make sure it is still ignored - due to a bug
295
+ modified: ["src/index.ts"], // Add index.ts so we make sure it is still ignored - see test-fixtures/template/templatesync.json ignores
260
296
  deleted: [],
261
297
  }));
262
298
  const mockCurrentRefDriver = jest
@@ -273,8 +309,14 @@ describe("templateSync", () => {
273
309
  checkoutDriver: dummyCheckoutDriver,
274
310
  });
275
311
 
276
- // since there was no override for this file, not changes from the local file
312
+ // since there was no override for this file, no changes from the local file
277
313
  expect(result.localFileChanges).toEqual(expect.objectContaining({}));
314
+ expect(result.modifiedFiles).toEqual({
315
+ added: ["src/templated.ts"],
316
+ modified: ["templatesync.local.json"], // Add index.ts so we make sure it is still ignored - due to a bug
317
+ deleted: [],
318
+ total: 2,
319
+ });
278
320
 
279
321
  // Verify the files
280
322
  await fileMatchTemplate(tmpDir, "templatesync.json");
@@ -296,6 +338,73 @@ describe("templateSync", () => {
296
338
  });
297
339
  expect(dummyCheckoutDriver).not.toHaveBeenCalled();
298
340
  });
341
+ it("Does not update the local templatesync if updateAfterRef is true and the ref is the same", async () => {
342
+ // Remove the local sync overrides
343
+ await rm(join(tmpDir, "templatesync.local.json"));
344
+
345
+ const mockLocalConfig = {
346
+ afterRef: "dummySha",
347
+ ignore: [
348
+ // We don't have a need for this in here, but it's an example of keeping things cleaner for our custom plugins
349
+ "plugins/**",
350
+ ],
351
+ };
352
+
353
+ writeFileSync(
354
+ join(tmpDir, "templatesync.local.json"),
355
+ JSON.stringify(mockLocalConfig),
356
+ );
357
+
358
+ // We will only update the templated.ts
359
+ const mockDiffDriver = jest.fn().mockImplementation(async () => ({
360
+ added: ["src/templated.ts"],
361
+ modified: ["src/index.ts"], // Add index.ts so we make sure it is still ignored - see test-fixtures/template/templatesync.json ignores
362
+ deleted: [],
363
+ }));
364
+ const mockCurrentRefDriver = jest
365
+ .fn()
366
+ .mockImplementation(async () => "dummySha");
367
+ const result = await templateSync({
368
+ tmpCloneDir: "stubbed-by-driver",
369
+ cloneDriver: dummyCloneDriver,
370
+ repoUrl: "not-important",
371
+ repoDir: tmpDir,
372
+ updateAfterRef: true,
373
+ diffDriver: mockDiffDriver,
374
+ currentRefDriver: mockCurrentRefDriver,
375
+ checkoutDriver: dummyCheckoutDriver,
376
+ });
377
+
378
+ // Nothing shoudl be reported as changing
379
+ expect(result).toEqual({
380
+ localFileChanges: {},
381
+ localSkipFiles: [],
382
+ modifiedFiles: {
383
+ added: [],
384
+ modified: [],
385
+ deleted: [],
386
+ total: 0,
387
+ },
388
+ });
389
+ // Verify the files
390
+ await fileMatchDownstream(tmpDir, "templatesync.json");
391
+ await fileMatchDownstream(tmpDir, "src/templated.ts");
392
+
393
+ // Expect the none of the diff files to work
394
+ await fileMatchDownstream(tmpDir, "src/index.ts");
395
+ await fileMatchDownstream(tmpDir, "plugins/custom-plugin.js");
396
+ await fileMatchDownstream(tmpDir, "package.json");
397
+
398
+ // Ensure we have updated the local template field
399
+ expect(
400
+ JSON.parse(
401
+ (await readFile(join(tmpDir, "templatesync.local.json"))).toString(),
402
+ ),
403
+ ).toEqual({
404
+ ...mockLocalConfig,
405
+ });
406
+ expect(dummyCheckoutDriver).not.toHaveBeenCalled();
407
+ });
299
408
  it("creates the local templatesync with the current ref if updateAfterRef is true and no local template exists", async () => {
300
409
  // Remove the local sync overrides
301
410
  await rm(join(tmpDir, "templatesync.local.json"));
@@ -318,8 +427,21 @@ describe("templateSync", () => {
318
427
  checkoutDriver: dummyCheckoutDriver,
319
428
  });
320
429
 
321
- // since there was no override for this file, not changes from the local file
430
+ // since there was no override for this file, no changes from the local file
322
431
  expect(result.localFileChanges).toEqual(expect.objectContaining({}));
432
+ expect(result.modifiedFiles).toEqual({
433
+ added: [
434
+ "package.json",
435
+ "src/index.js",
436
+ "src/templated.js",
437
+ "src/templated.ts",
438
+ "templatesync.json",
439
+ "templatesync.local.json",
440
+ ],
441
+ deleted: [],
442
+ modified: [],
443
+ total: 6,
444
+ });
323
445
 
324
446
  // Verify the files
325
447
  await fileMatchTemplate(tmpDir, "templatesync.json");
@@ -77,6 +77,16 @@ export interface TemplateSyncReturn {
77
77
  localFileChanges: {
78
78
  [filePath: string]: Change[];
79
79
  };
80
+ /**
81
+ * A list of all files that are modified by this operation. You can use total to quickly check if
82
+ * there was an actual meaningful change.
83
+ *
84
+ * Please note, ther may be localSkipfiles and total: 0, which indicates there were changes BUT the local template
85
+ * sync file rendered them meaningless.
86
+ */
87
+ modifiedFiles: DiffResult & {
88
+ total: number;
89
+ };
80
90
  }
81
91
 
82
92
  export const TEMPLATE_SYNC_CONFIG = "templatesync";
@@ -115,10 +125,8 @@ export async function templateSync(
115
125
  ) as unknown as Config)
116
126
  : { ignore: [] };
117
127
 
118
- const localConfigPath = join(
119
- options.repoDir,
120
- `${TEMPLATE_SYNC_LOCAL_CONFIG}.json`,
121
- );
128
+ const localConfigFile = `${TEMPLATE_SYNC_LOCAL_CONFIG}.json`;
129
+ const localConfigPath = join(options.repoDir, localConfigFile);
122
130
  const localTemplateSyncConfig: LocalConfig = existsSync(localConfigPath)
123
131
  ? (commentJSON.parse(
124
132
  readFileSync(localConfigPath).toString(),
@@ -126,7 +134,23 @@ export async function templateSync(
126
134
  : { ignore: [] };
127
135
 
128
136
  let filesToSync: DiffResult;
137
+ const ref = await currentRefDriver({
138
+ rootDir: tempCloneDir,
139
+ });
129
140
  if (localTemplateSyncConfig.afterRef) {
141
+ if (ref === localTemplateSyncConfig.afterRef) {
142
+ // short circuit if the refs match
143
+ return {
144
+ localSkipFiles: [],
145
+ localFileChanges: {},
146
+ modifiedFiles: {
147
+ added: [],
148
+ modified: [],
149
+ deleted: [],
150
+ total: 0,
151
+ },
152
+ };
153
+ }
130
154
  filesToSync = await diffDriver(
131
155
  tempCloneDir,
132
156
  localTemplateSyncConfig.afterRef,
@@ -153,7 +177,7 @@ export async function templateSync(
153
177
  (f) => !some(f, templateSyncConfig.ignore),
154
178
  );
155
179
 
156
- const localSkipFiles: string[] = [];
180
+ const localSkipFiles: Set<string> = new Set();
157
181
  const localFileChanges: {
158
182
  [filePath: string]: Change[];
159
183
  } = {};
@@ -168,7 +192,7 @@ export async function templateSync(
168
192
  fileOperation: op,
169
193
  });
170
194
  if (result.ignoredDueToLocal) {
171
- localSkipFiles.push(f);
195
+ localSkipFiles.add(f);
172
196
  } else if (result?.localChanges && result.localChanges.length > 0) {
173
197
  localFileChanges[f] = result.localChanges;
174
198
  }
@@ -180,6 +204,20 @@ export async function templateSync(
180
204
  await Promise.all(filesToSync.modified.map(fileSyncFactory("modified")));
181
205
  await Promise.all(filesToSync.deleted.map(fileSyncFactory("deleted")));
182
206
 
207
+ // Report the files that changed in general
208
+ const actualAdded = filesToSync.added.filter((f) => !localSkipFiles.has(f));
209
+ const actualModified = filesToSync.modified.filter(
210
+ (f) => !localSkipFiles.has(f),
211
+ );
212
+ const actualDeleted = filesToSync.deleted.filter(
213
+ (f) => !localSkipFiles.has(f),
214
+ );
215
+ const modifiedFiles = {
216
+ added: actualAdded,
217
+ modified: actualModified,
218
+ deleted: actualDeleted,
219
+ };
220
+
183
221
  // apply after ref
184
222
  if (options.updateAfterRef) {
185
223
  const ref = await currentRefDriver({
@@ -194,16 +232,25 @@ export async function templateSync(
194
232
  localConfigPath,
195
233
  commentJSON.stringify(config, null, inferJSONIndent(configStr)),
196
234
  );
235
+ modifiedFiles.modified.push(localConfigFile);
197
236
  } else {
198
237
  writeFileSync(
199
238
  localConfigPath,
200
239
  commentJSON.stringify({ afterRef: ref }, null, 4),
201
240
  );
241
+ modifiedFiles.added.push(localConfigFile);
202
242
  }
203
243
  }
204
244
 
205
245
  return {
206
- localSkipFiles,
246
+ localSkipFiles: Array.from(localSkipFiles),
207
247
  localFileChanges,
248
+ modifiedFiles: {
249
+ ...modifiedFiles,
250
+ total:
251
+ modifiedFiles.added.length +
252
+ modifiedFiles.deleted.length +
253
+ modifiedFiles.modified.length,
254
+ },
208
255
  };
209
256
  }