@hanseltime/template-repo-sync 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test-flow.yaml +5 -0
- package/CHANGELOG.md +7 -0
- package/lib/cjs/diff-drivers/git-diff.d.ts +12 -1
- package/lib/cjs/diff-drivers/git-diff.js +31 -3
- package/lib/cjs/diff-drivers/types.d.ts +6 -1
- package/lib/cjs/merge-file.d.ts +2 -1
- package/lib/cjs/merge-file.js +10 -0
- package/lib/cjs/template-sync.js +29 -18
- package/lib/cjs/types.d.ts +1 -0
- package/lib/esm/diff-drivers/git-diff.js +31 -3
- package/lib/esm/merge-file.js +10 -0
- package/lib/esm/template-sync.js +29 -18
- package/package.json +1 -1
- package/src/diff-drivers/git-diff.spec.ts +73 -0
- package/src/diff-drivers/git-diff.ts +32 -3
- package/src/diff-drivers/types.ts +7 -1
- package/src/merge-file.spec.ts +385 -289
- package/src/merge-file.ts +14 -2
- package/src/template-sync.spec.ts +13 -9
- package/src/template-sync.ts +21 -11
- package/src/types.ts +2 -0
|
@@ -53,6 +53,11 @@ jobs:
|
|
|
53
53
|
run: |
|
|
54
54
|
npm run lint
|
|
55
55
|
npx prettier . --check
|
|
56
|
+
# Add git config user for some intgration testing
|
|
57
|
+
- name: setup git
|
|
58
|
+
run: |
|
|
59
|
+
git config --global user.name github-int-test
|
|
60
|
+
git config --global user.email github-int-test@github.com
|
|
56
61
|
- name: testing
|
|
57
62
|
run: |
|
|
58
63
|
npm run test
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.1.0](https://github.com/HanseltimeIndustries/template-repo-sync/compare/v2.0.4...v2.1.0) (2026-02-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add support for template repo file deletions ([dd5d732](https://github.com/HanseltimeIndustries/template-repo-sync/commit/dd5d7324e17a0c015a4fb1abd78c079eea58693d))
|
|
7
|
+
|
|
1
8
|
## [2.0.4](https://github.com/HanseltimeIndustries/template-repo-sync/compare/v2.0.3...v2.0.4) (2026-02-05)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* For a given directory with a git configuration, this will return the modified, added, and
|
|
3
|
+
* deleted files since the last afterRef
|
|
4
|
+
* @param gitDir The git directory folder
|
|
5
|
+
* @param afterRef The git ref to look for diffs after
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export declare function gitDiff(gitDir: string, afterRef: string): Promise<{
|
|
9
|
+
modified: string[];
|
|
10
|
+
added: string[];
|
|
11
|
+
deleted: string[];
|
|
12
|
+
}>;
|
|
@@ -2,12 +2,40 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.gitDiff = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* For a given directory with a git configuration, this will return the modified, added, and
|
|
7
|
+
* deleted files since the last afterRef
|
|
8
|
+
* @param gitDir The git directory folder
|
|
9
|
+
* @param afterRef The git ref to look for diffs after
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
5
12
|
async function gitDiff(gitDir, afterRef) {
|
|
6
|
-
const
|
|
13
|
+
const baseCommand = `git diff ${afterRef}.. --no-renames --name-only`;
|
|
14
|
+
const modifiedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=M`, {
|
|
7
15
|
cwd: gitDir,
|
|
8
16
|
})
|
|
9
17
|
.toString()
|
|
10
|
-
.trim()
|
|
11
|
-
|
|
18
|
+
.trim()
|
|
19
|
+
.split("\n")
|
|
20
|
+
.filter((s) => s !== "");
|
|
21
|
+
const addedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=A`, {
|
|
22
|
+
cwd: gitDir,
|
|
23
|
+
})
|
|
24
|
+
.toString()
|
|
25
|
+
.trim()
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter((s) => s !== "");
|
|
28
|
+
const deletedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=D`, {
|
|
29
|
+
cwd: gitDir,
|
|
30
|
+
})
|
|
31
|
+
.toString()
|
|
32
|
+
.trim()
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter((s) => s !== "");
|
|
35
|
+
return {
|
|
36
|
+
modified: modifiedFiles,
|
|
37
|
+
added: addedFiles,
|
|
38
|
+
deleted: deletedFiles,
|
|
39
|
+
};
|
|
12
40
|
}
|
|
13
41
|
exports.gitDiff = gitDiff;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
export interface DiffResult {
|
|
2
|
+
modified: string[];
|
|
3
|
+
added: string[];
|
|
4
|
+
deleted: string[];
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
7
|
* A function that operates within the gitDir, and returns the list of file paths
|
|
3
8
|
* since the last sha
|
|
4
9
|
*/
|
|
5
|
-
export type TemplateDiffDriverFn = (gitDir: string, afterRef: string) => Promise<
|
|
10
|
+
export type TemplateDiffDriverFn = (gitDir: string, afterRef: string) => Promise<DiffResult>;
|
package/lib/cjs/merge-file.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Config } from "./types";
|
|
1
|
+
import { Config, FileOperation } from "./types";
|
|
2
2
|
import { Change } from "diff";
|
|
3
3
|
interface MergeFileOptions {
|
|
4
4
|
localTemplateSyncConfig: Config;
|
|
5
5
|
templateSyncConfig: Config;
|
|
6
6
|
tempCloneDir: string;
|
|
7
7
|
cwd: string;
|
|
8
|
+
fileOperation: FileOperation;
|
|
8
9
|
}
|
|
9
10
|
interface MergeFileReturn {
|
|
10
11
|
/**
|
package/lib/cjs/merge-file.js
CHANGED
|
@@ -26,6 +26,16 @@ async function mergeFile(relPath, context) {
|
|
|
26
26
|
const ext = (0, path_1.extname)(relPath);
|
|
27
27
|
const filePath = (0, path_1.join)(cwd, relPath);
|
|
28
28
|
const templatePath = (0, path_1.join)(tempCloneDir, relPath);
|
|
29
|
+
// Unless there's a need, we remove files that were deleted and don't pass them to plugins yet
|
|
30
|
+
if (context.fileOperation === "deleted") {
|
|
31
|
+
if ((0, fs_1.existsSync)(templatePath)) {
|
|
32
|
+
await (0, promises_1.rm)(templatePath);
|
|
33
|
+
return {
|
|
34
|
+
ignoredDueToLocal: false,
|
|
35
|
+
localChanges: [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
29
39
|
const mergeConfig = templateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
30
40
|
const localMergeConfig = localTemplateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
31
41
|
const mergeHandler = mergeConfig
|
package/lib/cjs/template-sync.js
CHANGED
|
@@ -69,27 +69,38 @@ async function templateSync(options) {
|
|
|
69
69
|
filesToSync = await diffDriver(tempCloneDir, localTemplateSyncConfig.afterRef);
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
filesToSync =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
filesToSync = {
|
|
73
|
+
added: (0, match_1.getAllFilesInDir)(tempCloneDir, [
|
|
74
|
+
...templateSyncConfig.ignore,
|
|
75
|
+
".git/**",
|
|
76
|
+
]),
|
|
77
|
+
deleted: [],
|
|
78
|
+
modified: [],
|
|
79
|
+
};
|
|
76
80
|
}
|
|
77
81
|
const localSkipFiles = [];
|
|
78
82
|
const localFileChanges = {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
const fileSyncFactory = (op) => {
|
|
84
|
+
return async (f) => {
|
|
85
|
+
const result = await (0, merge_file_1.mergeFile)(f, {
|
|
86
|
+
localTemplateSyncConfig,
|
|
87
|
+
templateSyncConfig,
|
|
88
|
+
tempCloneDir,
|
|
89
|
+
cwd: options.repoDir,
|
|
90
|
+
fileOperation: op,
|
|
91
|
+
});
|
|
92
|
+
if (result.ignoredDueToLocal) {
|
|
93
|
+
localSkipFiles.push(f);
|
|
94
|
+
}
|
|
95
|
+
else if (result?.localChanges && result.localChanges.length > 0) {
|
|
96
|
+
localFileChanges[f] = result.localChanges;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
// Added and modified have the same setup for now
|
|
101
|
+
await Promise.all(filesToSync.added.map(fileSyncFactory("added")));
|
|
102
|
+
await Promise.all(filesToSync.modified.map(fileSyncFactory("modified")));
|
|
103
|
+
await Promise.all(filesToSync.deleted.map(fileSyncFactory("deleted")));
|
|
93
104
|
// apply after ref
|
|
94
105
|
if (options.updateAfterRef) {
|
|
95
106
|
const ref = await currentRefDriver({
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -2,12 +2,40 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.gitDiff = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* For a given directory with a git configuration, this will return the modified, added, and
|
|
7
|
+
* deleted files since the last afterRef
|
|
8
|
+
* @param gitDir The git directory folder
|
|
9
|
+
* @param afterRef The git ref to look for diffs after
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
5
12
|
async function gitDiff(gitDir, afterRef) {
|
|
6
|
-
const
|
|
13
|
+
const baseCommand = `git diff ${afterRef}.. --no-renames --name-only`;
|
|
14
|
+
const modifiedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=M`, {
|
|
7
15
|
cwd: gitDir,
|
|
8
16
|
})
|
|
9
17
|
.toString()
|
|
10
|
-
.trim()
|
|
11
|
-
|
|
18
|
+
.trim()
|
|
19
|
+
.split("\n")
|
|
20
|
+
.filter((s) => s !== "");
|
|
21
|
+
const addedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=A`, {
|
|
22
|
+
cwd: gitDir,
|
|
23
|
+
})
|
|
24
|
+
.toString()
|
|
25
|
+
.trim()
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter((s) => s !== "");
|
|
28
|
+
const deletedFiles = (0, child_process_1.execSync)(`${baseCommand} --diff-filter=D`, {
|
|
29
|
+
cwd: gitDir,
|
|
30
|
+
})
|
|
31
|
+
.toString()
|
|
32
|
+
.trim()
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter((s) => s !== "");
|
|
35
|
+
return {
|
|
36
|
+
modified: modifiedFiles,
|
|
37
|
+
added: addedFiles,
|
|
38
|
+
deleted: deletedFiles,
|
|
39
|
+
};
|
|
12
40
|
}
|
|
13
41
|
exports.gitDiff = gitDiff;
|
package/lib/esm/merge-file.js
CHANGED
|
@@ -26,6 +26,16 @@ async function mergeFile(relPath, context) {
|
|
|
26
26
|
const ext = (0, path_1.extname)(relPath);
|
|
27
27
|
const filePath = (0, path_1.join)(cwd, relPath);
|
|
28
28
|
const templatePath = (0, path_1.join)(tempCloneDir, relPath);
|
|
29
|
+
// Unless there's a need, we remove files that were deleted and don't pass them to plugins yet
|
|
30
|
+
if (context.fileOperation === "deleted") {
|
|
31
|
+
if ((0, fs_1.existsSync)(templatePath)) {
|
|
32
|
+
await (0, promises_1.rm)(templatePath);
|
|
33
|
+
return {
|
|
34
|
+
ignoredDueToLocal: false,
|
|
35
|
+
localChanges: [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
29
39
|
const mergeConfig = templateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
30
40
|
const localMergeConfig = localTemplateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
31
41
|
const mergeHandler = mergeConfig
|
package/lib/esm/template-sync.js
CHANGED
|
@@ -69,27 +69,38 @@ async function templateSync(options) {
|
|
|
69
69
|
filesToSync = await diffDriver(tempCloneDir, localTemplateSyncConfig.afterRef);
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
filesToSync =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
filesToSync = {
|
|
73
|
+
added: (0, match_1.getAllFilesInDir)(tempCloneDir, [
|
|
74
|
+
...templateSyncConfig.ignore,
|
|
75
|
+
".git/**",
|
|
76
|
+
]),
|
|
77
|
+
deleted: [],
|
|
78
|
+
modified: [],
|
|
79
|
+
};
|
|
76
80
|
}
|
|
77
81
|
const localSkipFiles = [];
|
|
78
82
|
const localFileChanges = {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
const fileSyncFactory = (op) => {
|
|
84
|
+
return async (f) => {
|
|
85
|
+
const result = await (0, merge_file_1.mergeFile)(f, {
|
|
86
|
+
localTemplateSyncConfig,
|
|
87
|
+
templateSyncConfig,
|
|
88
|
+
tempCloneDir,
|
|
89
|
+
cwd: options.repoDir,
|
|
90
|
+
fileOperation: op,
|
|
91
|
+
});
|
|
92
|
+
if (result.ignoredDueToLocal) {
|
|
93
|
+
localSkipFiles.push(f);
|
|
94
|
+
}
|
|
95
|
+
else if (result?.localChanges && result.localChanges.length > 0) {
|
|
96
|
+
localFileChanges[f] = result.localChanges;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
// Added and modified have the same setup for now
|
|
101
|
+
await Promise.all(filesToSync.added.map(fileSyncFactory("added")));
|
|
102
|
+
await Promise.all(filesToSync.modified.map(fileSyncFactory("modified")));
|
|
103
|
+
await Promise.all(filesToSync.deleted.map(fileSyncFactory("deleted")));
|
|
93
104
|
// apply after ref
|
|
94
105
|
if (options.updateAfterRef) {
|
|
95
106
|
const ref = await currentRefDriver({
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { gitDiff } from "./git-diff";
|
|
5
|
+
|
|
6
|
+
describe("gitDiff", () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync("gitdiff-test");
|
|
11
|
+
// Initialize a git repo
|
|
12
|
+
execSync("git init", { cwd: tempDir });
|
|
13
|
+
fs.writeFileSync(path.join(tempDir, "file1.txt"), "hello");
|
|
14
|
+
fs.writeFileSync(path.join(tempDir, "second.txt"), "second");
|
|
15
|
+
fs.writeFileSync(path.join(tempDir, "third.txt"), "third");
|
|
16
|
+
execSync("git add . && git commit -m 'initial'", { cwd: tempDir });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles a rename as a delete and an add", async () => {
|
|
24
|
+
const firstCommit = execSync("git rev-parse HEAD", { cwd: tempDir })
|
|
25
|
+
.toString()
|
|
26
|
+
.trim();
|
|
27
|
+
|
|
28
|
+
// Perform a rename
|
|
29
|
+
execSync("git mv file1.txt file2.txt", { cwd: tempDir });
|
|
30
|
+
execSync("git commit -m 'rename'", { cwd: tempDir });
|
|
31
|
+
|
|
32
|
+
const result = await gitDiff(tempDir, firstCommit);
|
|
33
|
+
|
|
34
|
+
// Because of --no-renames:
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
deleted: ["file1.txt"],
|
|
37
|
+
added: ["file2.txt"],
|
|
38
|
+
modified: [],
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it("handles a added, deleted, and modified appropriately across multiple commits", async () => {
|
|
42
|
+
const firstCommit = execSync("git rev-parse HEAD", { cwd: tempDir })
|
|
43
|
+
.toString()
|
|
44
|
+
.trim();
|
|
45
|
+
|
|
46
|
+
// Perform a rename
|
|
47
|
+
execSync("git mv file1.txt file2.txt", { cwd: tempDir });
|
|
48
|
+
execSync("git commit -m 'rename'", { cwd: tempDir });
|
|
49
|
+
|
|
50
|
+
// Perform adds
|
|
51
|
+
fs.writeFileSync(path.join(tempDir, "another.txt"), "another file");
|
|
52
|
+
execSync("git add . && git commit -m 'adding another file'", {
|
|
53
|
+
cwd: tempDir,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Perform a modify
|
|
57
|
+
fs.writeFileSync(path.join(tempDir, "third.txt"), "third+");
|
|
58
|
+
execSync("git add . && git commit -m 'modifying third'", { cwd: tempDir });
|
|
59
|
+
|
|
60
|
+
// Perform a delete
|
|
61
|
+
fs.rmSync(path.join(tempDir, "second.txt"));
|
|
62
|
+
execSync("git add . && git commit -m 'deleting second'", { cwd: tempDir });
|
|
63
|
+
|
|
64
|
+
const result = await gitDiff(tempDir, firstCommit);
|
|
65
|
+
|
|
66
|
+
// Because of --no-renames:
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
deleted: expect.arrayContaining(["file1.txt", "second.txt"]),
|
|
69
|
+
added: expect.arrayContaining(["file2.txt", "another.txt"]),
|
|
70
|
+
modified: ["third.txt"],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* For a given directory with a git configuration, this will return the modified, added, and
|
|
5
|
+
* deleted files since the last afterRef
|
|
6
|
+
* @param gitDir The git directory folder
|
|
7
|
+
* @param afterRef The git ref to look for diffs after
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
3
10
|
export async function gitDiff(gitDir: string, afterRef: string) {
|
|
4
|
-
const
|
|
11
|
+
const baseCommand = `git diff ${afterRef}.. --no-renames --name-only`;
|
|
12
|
+
const modifiedFiles = execSync(`${baseCommand} --diff-filter=M`, {
|
|
5
13
|
cwd: gitDir,
|
|
6
14
|
})
|
|
7
15
|
.toString()
|
|
8
|
-
.trim()
|
|
9
|
-
|
|
16
|
+
.trim()
|
|
17
|
+
.split("\n")
|
|
18
|
+
.filter((s) => s !== "");
|
|
19
|
+
const addedFiles = execSync(`${baseCommand} --diff-filter=A`, {
|
|
20
|
+
cwd: gitDir,
|
|
21
|
+
})
|
|
22
|
+
.toString()
|
|
23
|
+
.trim()
|
|
24
|
+
.split("\n")
|
|
25
|
+
.filter((s) => s !== "");
|
|
26
|
+
const deletedFiles = execSync(`${baseCommand} --diff-filter=D`, {
|
|
27
|
+
cwd: gitDir,
|
|
28
|
+
})
|
|
29
|
+
.toString()
|
|
30
|
+
.trim()
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((s) => s !== "");
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
modified: modifiedFiles,
|
|
36
|
+
added: addedFiles,
|
|
37
|
+
deleted: deletedFiles,
|
|
38
|
+
};
|
|
10
39
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface DiffResult {
|
|
2
|
+
modified: string[];
|
|
3
|
+
added: string[];
|
|
4
|
+
deleted: string[];
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* A function that operates within the gitDir, and returns the list of file paths
|
|
3
9
|
* since the last sha
|
|
@@ -5,4 +11,4 @@
|
|
|
5
11
|
export type TemplateDiffDriverFn = (
|
|
6
12
|
gitDir: string,
|
|
7
13
|
afterRef: string,
|
|
8
|
-
) => Promise<
|
|
14
|
+
) => Promise<DiffResult>;
|