@hanseltime/template-repo-sync 1.3.0 → 2.0.2
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/release.yaml +34 -10
- package/.github/workflows/test-flow.yaml +3 -3
- package/CHANGELOG.md +33 -0
- package/README.md +75 -35
- package/docs/merge-plugins/CURRENT_PLUGINS.md +3 -3
- package/docs/merge-plugins/{DEVELOPMENT.md → PLUGIN_DEVELOPMENT.md} +18 -25
- package/docs/merge-plugins/README.md +20 -30
- package/jest.config.js +1 -1
- package/lib/cjs/checkout-drivers/git-checkout.js +18 -6
- package/lib/cjs/load-plugin.d.ts +8 -1
- package/lib/cjs/load-plugin.js +32 -26
- package/lib/cjs/merge-file.js +22 -33
- package/lib/cjs/plugins/index.d.ts +3 -1
- package/lib/cjs/plugins/index.js +1 -0
- package/lib/cjs/types.d.ts +12 -12
- package/lib/esm/checkout-drivers/git-checkout.js +18 -6
- package/lib/esm/load-plugin.js +32 -26
- package/lib/esm/merge-file.js +22 -33
- package/lib/esm/plugins/index.js +1 -0
- package/package.json +6 -1
- package/release.config.js +12 -1
- package/src/checkout-drivers/git-checkout.spec.ts +11 -0
- package/src/checkout-drivers/git-checkout.ts +22 -6
- package/src/load-plugin.ts +35 -26
- package/src/merge-file.spec.ts +112 -134
- package/src/merge-file.ts +41 -50
- package/src/plugins/index.ts +2 -1
- package/src/template-sync.spec.ts +5 -10
- package/src/types.ts +12 -9
- package/test-fixtures/downstream/templatesync.json +12 -15
- package/test-fixtures/downstream/templatesync.local.json +5 -9
- package/test-fixtures/template/templatesync.json +12 -15
package/lib/cjs/types.d.ts
CHANGED
|
@@ -30,18 +30,22 @@ type MergePluginOptions = JsonFileMergeOptions;
|
|
|
30
30
|
* Configuration object for a given file type merge configuration
|
|
31
31
|
*/
|
|
32
32
|
export interface MergeConfig<T> {
|
|
33
|
+
/**
|
|
34
|
+
* A glob or array of globs (using micromatch syntax) that selects the files that this applies to
|
|
35
|
+
* when merging
|
|
36
|
+
*/
|
|
37
|
+
glob: string | string[];
|
|
33
38
|
/**
|
|
34
39
|
* The node module, available on the calling node context, that you want to run.
|
|
35
|
-
*
|
|
40
|
+
* Built-in plugins can be specified via their built-in name (see documentation):
|
|
41
|
+
*
|
|
42
|
+
* Example: _json
|
|
36
43
|
*/
|
|
37
|
-
plugin
|
|
44
|
+
plugin: string;
|
|
38
45
|
/**
|
|
39
46
|
* An array of first match file globs that will then call the plugin with the appropriate options
|
|
40
47
|
*/
|
|
41
|
-
|
|
42
|
-
glob: string;
|
|
43
|
-
options: MergePluginOptions | T;
|
|
44
|
-
}[];
|
|
48
|
+
options: MergePluginOptions | T;
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* The shape of a template sync config file
|
|
@@ -51,9 +55,7 @@ export interface Config<T = unknown> {
|
|
|
51
55
|
/**
|
|
52
56
|
* If there is no merge config, then we will always just overwrite the file for the diff
|
|
53
57
|
*/
|
|
54
|
-
merge?:
|
|
55
|
-
[fileExt: string]: MergeConfig<T>;
|
|
56
|
-
};
|
|
58
|
+
merge?: MergeConfig<T>[];
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* The shape of a local template sync config file that overrides the root template repo
|
|
@@ -68,9 +70,7 @@ export interface LocalConfig<T = unknown> {
|
|
|
68
70
|
/**
|
|
69
71
|
* If there is no merge config, then we will always just overwrite the file for the diff
|
|
70
72
|
*/
|
|
71
|
-
merge?:
|
|
72
|
-
[fileExt: string]: MergeConfig<T>;
|
|
73
|
-
};
|
|
73
|
+
merge?: MergeConfig<T>[];
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
76
|
* Information around the file we are trying to merge and that arguments for that merge
|
|
@@ -4,14 +4,26 @@ exports.gitCheckout = void 0;
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
async function gitCheckout(options) {
|
|
6
6
|
const { branch, remoteName, tmpDir } = options;
|
|
7
|
-
(0, child_process_1.execSync)(`git
|
|
7
|
+
const remoteInfo = (0, child_process_1.execSync)(`git remote show ${remoteName}`, {
|
|
8
8
|
cwd: tmpDir,
|
|
9
9
|
env: process.env,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
}).toString();
|
|
11
|
+
const defaultMatch = /HEAD branch:\s*([a-zA-Z0-9_-]+)/.exec(remoteInfo);
|
|
12
|
+
if (!defaultMatch || !defaultMatch[1]) {
|
|
13
|
+
throw new Error(`Could not determine default branch of cloned repo.\nAttempted to find in remote info:\n${remoteInfo} `);
|
|
14
|
+
}
|
|
15
|
+
const defaultBranch = defaultMatch[1];
|
|
16
|
+
// Skip this if the default branch is already pulled
|
|
17
|
+
if (defaultBranch !== branch) {
|
|
18
|
+
(0, child_process_1.execSync)(`git fetch ${remoteName} ${branch}`, {
|
|
19
|
+
cwd: tmpDir,
|
|
20
|
+
env: process.env,
|
|
21
|
+
});
|
|
22
|
+
(0, child_process_1.execSync)(`git checkout -b ${branch} --track ${remoteName}/${branch}`, {
|
|
23
|
+
cwd: tmpDir,
|
|
24
|
+
env: process.env,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
15
27
|
return true;
|
|
16
28
|
}
|
|
17
29
|
exports.gitCheckout = gitCheckout;
|
package/lib/esm/load-plugin.js
CHANGED
|
@@ -4,36 +4,42 @@ exports.loadPlugin = void 0;
|
|
|
4
4
|
const plugins_1 = require("./plugins");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path_1 = require("path");
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Loads the plugin associated with the merge config
|
|
9
|
+
* @param mergeConfig
|
|
10
|
+
* @param forExt
|
|
11
|
+
* @param configDir
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
async function loadPlugin(mergeConfig, configDir) {
|
|
15
|
+
if (mergeConfig.plugin.startsWith("_")) {
|
|
16
|
+
const defaultHandler = Object.values(plugins_1.defaultExtensionMap).find((el) => el.builtinName === mergeConfig.plugin);
|
|
17
|
+
if (!defaultHandler) {
|
|
18
|
+
throw new Error(`No builtin merge function supplied for ${mergeConfig.plugin}. Cannot have merge config without custom plugin supplied.`);
|
|
19
|
+
}
|
|
20
|
+
return defaultHandler;
|
|
21
|
+
}
|
|
8
22
|
let handler;
|
|
9
|
-
if
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
handler = require(importPath);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
handler = (await import(importPath));
|
|
21
|
-
}
|
|
22
|
-
if (!handler.merge) {
|
|
23
|
-
handler = handler
|
|
24
|
-
.default;
|
|
25
|
-
}
|
|
23
|
+
// First check if this is a local .js file
|
|
24
|
+
const localPath = (0, path_1.resolve)(configDir, mergeConfig.plugin);
|
|
25
|
+
const importPath = (0, fs_1.existsSync)(localPath) ? localPath : mergeConfig.plugin;
|
|
26
|
+
try {
|
|
27
|
+
// Sad workaround for testing since dynamic import segfaults
|
|
28
|
+
if (process.env.JEST_WORKER_ID !== undefined) {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
30
|
+
handler = require(importPath);
|
|
26
31
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
throw err;
|
|
32
|
+
else {
|
|
33
|
+
handler = (await import(importPath));
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new Error(`No default merge function supplied for ${forExt}. Cannot have mere config without custom plugin supplied.`);
|
|
35
|
+
if (!handler.merge) {
|
|
36
|
+
handler = handler
|
|
37
|
+
.default;
|
|
35
38
|
}
|
|
36
|
-
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(err);
|
|
42
|
+
throw err;
|
|
37
43
|
}
|
|
38
44
|
return handler;
|
|
39
45
|
}
|
package/lib/esm/merge-file.js
CHANGED
|
@@ -26,50 +26,39 @@ 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
|
-
const mergeConfig = templateSyncConfig.merge?.
|
|
30
|
-
const localMergeConfig = localTemplateSyncConfig.merge?.
|
|
31
|
-
|
|
29
|
+
const mergeConfig = templateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
30
|
+
const localMergeConfig = localTemplateSyncConfig.merge?.find((mergeConfig) => (0, micromatch_1.isMatch)(relPath, mergeConfig.glob));
|
|
31
|
+
const mergeHandler = mergeConfig
|
|
32
|
+
? await (0, load_plugin_1.loadPlugin)(mergeConfig, tempCloneDir)
|
|
33
|
+
: undefined;
|
|
34
|
+
const localMergeHandler = localMergeConfig
|
|
35
|
+
? await (0, load_plugin_1.loadPlugin)(localMergeConfig, cwd)
|
|
36
|
+
: undefined;
|
|
37
|
+
// Either write the merge or write the file
|
|
32
38
|
let fileContents;
|
|
33
39
|
const localChanges = [];
|
|
34
|
-
if ((0, fs_1.existsSync)(filePath) && (
|
|
40
|
+
if ((0, fs_1.existsSync)(filePath) && (mergeHandler || localMergeHandler)) {
|
|
35
41
|
const originalCurrentFile = (await (0, promises_1.readFile)(filePath)).toString();
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
if (mergeHandler) {
|
|
43
|
+
fileContents = await safeMerge(mergeHandler, mergeConfig?.plugin ?? `default for ${ext}`, originalCurrentFile, (await (0, promises_1.readFile)(templatePath)).toString(), {
|
|
44
|
+
relFilePath: relPath,
|
|
45
|
+
mergeArguments: mergeConfig?.options ?? {},
|
|
46
|
+
isLocalOptions: false,
|
|
41
47
|
});
|
|
42
|
-
if (mergeOptions) {
|
|
43
|
-
fileContents = await safeMerge(handler, mergeConfig.plugin ?? `default for ${ext}`, originalCurrentFile, (await (0, promises_1.readFile)(templatePath)).toString(), {
|
|
44
|
-
relFilePath: relPath,
|
|
45
|
-
mergeArguments: mergeOptions.options,
|
|
46
|
-
isLocalOptions: true,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// Apply overwrite if we didn't set up merge
|
|
51
|
-
fileContents = (await (0, promises_1.readFile)(templatePath)).toString();
|
|
52
|
-
}
|
|
53
48
|
}
|
|
54
49
|
else {
|
|
55
50
|
// Apply overwrite if we didn't set up merge
|
|
56
51
|
fileContents = (await (0, promises_1.readFile)(templatePath)).toString();
|
|
57
52
|
}
|
|
58
53
|
// We apply the localMerge Config to the fileContent output by the template merge
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
if (localMergeHandler) {
|
|
55
|
+
const localContents = await safeMerge(localMergeHandler, localMergeConfig?.plugin ?? `default for ${ext}`, originalCurrentFile, fileContents, {
|
|
56
|
+
relFilePath: relPath,
|
|
57
|
+
mergeArguments: localMergeConfig?.options ?? {},
|
|
58
|
+
isLocalOptions: true,
|
|
63
59
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
relFilePath: relPath,
|
|
67
|
-
mergeArguments: mergeOptions.options,
|
|
68
|
-
isLocalOptions: true,
|
|
69
|
-
});
|
|
70
|
-
localChanges.push(...(0, diff_1.diffLines)(fileContents, localContents));
|
|
71
|
-
fileContents = localContents;
|
|
72
|
-
}
|
|
60
|
+
localChanges.push(...(0, diff_1.diffLines)(fileContents, localContents));
|
|
61
|
+
fileContents = localContents;
|
|
73
62
|
}
|
|
74
63
|
}
|
|
75
64
|
else {
|
package/lib/esm/plugins/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanseltime/template-repo-sync",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
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",
|
|
7
7
|
"module": "lib/esm/index.js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/HanseltimeIndustries/template-repo-sync.git"
|
|
11
|
+
},
|
|
8
12
|
"scripts": {
|
|
9
13
|
"format": "prettier . --write",
|
|
10
14
|
"lint": "eslint .",
|
|
@@ -28,6 +32,7 @@
|
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"@commitlint/config-angular": "^19.0.3",
|
|
30
34
|
"@semantic-release/changelog": "^6.0.3",
|
|
35
|
+
"@semantic-release/exec": "^6.0.3",
|
|
31
36
|
"@semantic-release/git": "^10.0.1",
|
|
32
37
|
"@types/diff": "^5.0.9",
|
|
33
38
|
"@types/fs-extra": "^11.0.4",
|
package/release.config.js
CHANGED
|
@@ -19,7 +19,18 @@ module.exports = {
|
|
|
19
19
|
"@semantic-release/commit-analyzer",
|
|
20
20
|
"@semantic-release/release-notes-generator",
|
|
21
21
|
"@semantic-release/changelog",
|
|
22
|
-
|
|
22
|
+
[
|
|
23
|
+
"@semantic-release/npm",
|
|
24
|
+
{
|
|
25
|
+
npmPublish: false, // We just use this to increment the versions since semantic-release doesn't handle OIDC well
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
"@semantic-release/exec",
|
|
30
|
+
{
|
|
31
|
+
publishCmd: "npm publish --access public",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
23
34
|
[
|
|
24
35
|
"@semantic-release/git",
|
|
25
36
|
{
|
|
@@ -55,4 +55,15 @@ describe("gitCheckout", () => {
|
|
|
55
55
|
}),
|
|
56
56
|
).rejects.toThrow();
|
|
57
57
|
});
|
|
58
|
+
it("does not throw if the branch was the default", async () => {
|
|
59
|
+
await gitCheckout({
|
|
60
|
+
tmpDir: tmpRepoDir,
|
|
61
|
+
remoteName: "origin",
|
|
62
|
+
branch: "master", // We set this in the gitDir
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(readFileSync(join(tmpRepoDir, "README.md")).toString()).toContain(
|
|
66
|
+
"# This is the master branch",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
58
69
|
});
|
|
@@ -9,14 +9,30 @@ export async function gitCheckout(options: {
|
|
|
9
9
|
branch: string;
|
|
10
10
|
}): Promise<boolean> {
|
|
11
11
|
const { branch, remoteName, tmpDir } = options;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
env: process.env,
|
|
15
|
-
});
|
|
16
|
-
execSync(`git checkout -b ${branch} --track ${remoteName}/${branch}`, {
|
|
12
|
+
|
|
13
|
+
const remoteInfo = execSync(`git remote show ${remoteName}`, {
|
|
17
14
|
cwd: tmpDir,
|
|
18
15
|
env: process.env,
|
|
19
|
-
});
|
|
16
|
+
}).toString();
|
|
17
|
+
const defaultMatch = /HEAD branch:\s*([a-zA-Z0-9_-]+)/.exec(remoteInfo);
|
|
18
|
+
if (!defaultMatch || !defaultMatch[1]) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Could not determine default branch of cloned repo.\nAttempted to find in remote info:\n${remoteInfo} `,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultBranch = defaultMatch[1];
|
|
25
|
+
// Skip this if the default branch is already pulled
|
|
26
|
+
if (defaultBranch !== branch) {
|
|
27
|
+
execSync(`git fetch ${remoteName} ${branch}`, {
|
|
28
|
+
cwd: tmpDir,
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
execSync(`git checkout -b ${branch} --track ${remoteName}/${branch}`, {
|
|
32
|
+
cwd: tmpDir,
|
|
33
|
+
env: process.env,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
20
36
|
|
|
21
37
|
return true;
|
|
22
38
|
}
|
package/src/load-plugin.ts
CHANGED
|
@@ -3,39 +3,48 @@ import { MergeConfig, MergePlugin } from "./types";
|
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
4
|
import { resolve } from "path";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Loads the plugin associated with the merge config
|
|
8
|
+
* @param mergeConfig
|
|
9
|
+
* @param forExt
|
|
10
|
+
* @param configDir
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
6
13
|
export async function loadPlugin<T>(
|
|
7
14
|
mergeConfig: MergeConfig<T>,
|
|
8
|
-
forExt: string,
|
|
9
15
|
configDir: string,
|
|
10
16
|
): Promise<MergePlugin<T>> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
// Sad workaround for testing since dynamic import segfaults
|
|
18
|
-
if (process.env.JEST_WORKER_ID !== undefined) {
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
-
handler = require(importPath) as MergePlugin<unknown>;
|
|
21
|
-
} else {
|
|
22
|
-
handler = (await import(importPath)) as MergePlugin<unknown>;
|
|
23
|
-
}
|
|
24
|
-
if (!handler.merge) {
|
|
25
|
-
handler = (handler as unknown as { default: MergePlugin<unknown> })
|
|
26
|
-
.default;
|
|
27
|
-
}
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.error(err);
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
if (!defaultExtensionMap[forExt]) {
|
|
17
|
+
if (mergeConfig.plugin.startsWith("_")) {
|
|
18
|
+
const defaultHandler = Object.values(defaultExtensionMap).find(
|
|
19
|
+
(el) => el.builtinName === mergeConfig.plugin,
|
|
20
|
+
);
|
|
21
|
+
if (!defaultHandler) {
|
|
34
22
|
throw new Error(
|
|
35
|
-
`No
|
|
23
|
+
`No builtin merge function supplied for ${mergeConfig.plugin}. Cannot have merge config without custom plugin supplied.`,
|
|
36
24
|
);
|
|
37
25
|
}
|
|
38
|
-
|
|
26
|
+
return defaultHandler as MergePlugin<T>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let handler: MergePlugin<unknown>;
|
|
30
|
+
// First check if this is a local .js file
|
|
31
|
+
const localPath = resolve(configDir, mergeConfig.plugin);
|
|
32
|
+
const importPath = existsSync(localPath) ? localPath : mergeConfig.plugin;
|
|
33
|
+
try {
|
|
34
|
+
// Sad workaround for testing since dynamic import segfaults
|
|
35
|
+
if (process.env.JEST_WORKER_ID !== undefined) {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
37
|
+
handler = require(importPath) as MergePlugin<unknown>;
|
|
38
|
+
} else {
|
|
39
|
+
handler = (await import(importPath)) as MergePlugin<unknown>;
|
|
40
|
+
}
|
|
41
|
+
if (!handler.merge) {
|
|
42
|
+
handler = (handler as unknown as { default: MergePlugin<unknown> })
|
|
43
|
+
.default;
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(err);
|
|
47
|
+
throw err;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
return handler as MergePlugin<T>;
|