@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,29 @@
|
|
|
1
|
+
import { Config } from "./types";
|
|
2
|
+
import { Change } from "diff";
|
|
3
|
+
interface MergeFileOptions {
|
|
4
|
+
localTemplateSyncConfig: Config;
|
|
5
|
+
templateSyncConfig: Config;
|
|
6
|
+
tempCloneDir: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
}
|
|
9
|
+
interface MergeFileReturn {
|
|
10
|
+
/**
|
|
11
|
+
* If the file was ignored due to the local config
|
|
12
|
+
*/
|
|
13
|
+
ignoredDueToLocal: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Only available if the file wasn't ignored, this is a list of lineDiffs
|
|
16
|
+
* from the the diff library that were applied to what would've been removed
|
|
17
|
+
*/
|
|
18
|
+
localChanges?: Change[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Applies the merge to a file according to the context information.
|
|
22
|
+
*
|
|
23
|
+
* Returns true if merged and false if skipped
|
|
24
|
+
* @param relPath
|
|
25
|
+
* @param context
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeFile(relPath: string, context: MergeFileOptions): Promise<MergeFileReturn>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeFile = void 0;
|
|
4
|
+
const micromatch_1 = require("micromatch");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const promises_1 = require("fs/promises");
|
|
8
|
+
const load_plugin_1 = require("./load-plugin");
|
|
9
|
+
const diff_1 = require("diff");
|
|
10
|
+
const fs_extra_1 = require("fs-extra");
|
|
11
|
+
/**
|
|
12
|
+
* Applies the merge to a file according to the context information.
|
|
13
|
+
*
|
|
14
|
+
* Returns true if merged and false if skipped
|
|
15
|
+
* @param relPath
|
|
16
|
+
* @param context
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
async function mergeFile(relPath, context) {
|
|
20
|
+
const { localTemplateSyncConfig, templateSyncConfig, tempCloneDir, cwd } = context;
|
|
21
|
+
if ((0, micromatch_1.some)(relPath, localTemplateSyncConfig.ignore)) {
|
|
22
|
+
return {
|
|
23
|
+
ignoredDueToLocal: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const ext = (0, path_1.extname)(relPath);
|
|
27
|
+
const filePath = (0, path_1.join)(cwd, relPath);
|
|
28
|
+
const templatePath = (0, path_1.join)(tempCloneDir, relPath);
|
|
29
|
+
const mergeConfig = templateSyncConfig.merge?.[ext];
|
|
30
|
+
const localMergeConfig = localTemplateSyncConfig.merge?.[ext];
|
|
31
|
+
// Either write the merge or write
|
|
32
|
+
let fileContents;
|
|
33
|
+
const localChanges = [];
|
|
34
|
+
if ((0, fs_1.existsSync)(filePath) && (mergeConfig || localMergeConfig)) {
|
|
35
|
+
const originalCurrentFile = (await (0, promises_1.readFile)(filePath)).toString();
|
|
36
|
+
if (mergeConfig) {
|
|
37
|
+
// Apply the template's most recent merges
|
|
38
|
+
const handler = await (0, load_plugin_1.loadPlugin)(mergeConfig, ext, tempCloneDir);
|
|
39
|
+
const mergeOptions = mergeConfig.rules.find((rule) => {
|
|
40
|
+
return (0, micromatch_1.isMatch)(relPath, rule.glob);
|
|
41
|
+
});
|
|
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
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Apply overwrite if we didn't set up merge
|
|
56
|
+
fileContents = (await (0, promises_1.readFile)(templatePath)).toString();
|
|
57
|
+
}
|
|
58
|
+
// We apply the localMerge Config to the fileContent output by the template merge
|
|
59
|
+
if (localMergeConfig) {
|
|
60
|
+
const handler = await (0, load_plugin_1.loadPlugin)(localMergeConfig, ext, cwd);
|
|
61
|
+
const mergeOptions = localMergeConfig.rules.find((rule) => {
|
|
62
|
+
return (0, micromatch_1.isMatch)(relPath, rule.glob);
|
|
63
|
+
});
|
|
64
|
+
if (mergeOptions) {
|
|
65
|
+
const localContents = await safeMerge(handler, localMergeConfig.plugin ?? `default for ${ext}`, originalCurrentFile, fileContents, {
|
|
66
|
+
relFilePath: relPath,
|
|
67
|
+
mergeArguments: mergeOptions.options,
|
|
68
|
+
isLocalOptions: true,
|
|
69
|
+
});
|
|
70
|
+
localChanges.push(...(0, diff_1.diffLines)(fileContents, localContents));
|
|
71
|
+
fileContents = localContents;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Just perform simple overwrite
|
|
77
|
+
fileContents = (await (0, promises_1.readFile)(templatePath)).toString();
|
|
78
|
+
}
|
|
79
|
+
await (0, fs_extra_1.outputFile)(filePath, fileContents);
|
|
80
|
+
return {
|
|
81
|
+
ignoredDueToLocal: false,
|
|
82
|
+
localChanges,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
exports.mergeFile = mergeFile;
|
|
86
|
+
/**
|
|
87
|
+
* Simple helper function to ensure that we don't let bad plugins corrupt the call flow
|
|
88
|
+
* @param plugin
|
|
89
|
+
*/
|
|
90
|
+
async function safeMerge(plugin, pluginPath, current, fromTemplate, context) {
|
|
91
|
+
const ret = await plugin.merge(current, fromTemplate, context);
|
|
92
|
+
if (typeof ret !== "string") {
|
|
93
|
+
throw new Error(`Plugin ${pluginPath} did not return string for merge function! This is not allowed!`);
|
|
94
|
+
}
|
|
95
|
+
if (!ret) {
|
|
96
|
+
throw new Error(`Plugin ${pluginPath} should not make a merge be an empty string!`);
|
|
97
|
+
}
|
|
98
|
+
return ret;
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultExtensionMap = void 0;
|
|
4
|
+
const json_merge_1 = require("./json-merge");
|
|
5
|
+
exports.defaultExtensionMap = {
|
|
6
|
+
".json": {
|
|
7
|
+
merge: json_merge_1.merge,
|
|
8
|
+
validate: json_merge_1.validate,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.merge = exports.validate = void 0;
|
|
7
|
+
const lodash_merge_1 = __importDefault(require("lodash.merge"));
|
|
8
|
+
const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
|
|
9
|
+
const jsonpath_1 = __importDefault(require("jsonpath"));
|
|
10
|
+
const infer_json_indent_1 = require("../formatting/infer-json-indent");
|
|
11
|
+
function stringOptionError(value) {
|
|
12
|
+
if (value === "overwrite" ||
|
|
13
|
+
value === "merge-template" ||
|
|
14
|
+
value === "merge-current") {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
return `${value} must be one of type overwrite, merge-template, or merge-current`;
|
|
18
|
+
}
|
|
19
|
+
function validate(options) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
// check for flat options
|
|
22
|
+
if (typeof options === "string") {
|
|
23
|
+
const errMsg = stringOptionError(options);
|
|
24
|
+
if (errMsg) {
|
|
25
|
+
errors.push(errMsg);
|
|
26
|
+
}
|
|
27
|
+
return errors;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (typeof options !== "object") {
|
|
31
|
+
errors.push(`Options must be an object and not ${typeof options}`);
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(options)) {
|
|
35
|
+
errors.push(`Options must be an object and not Array`);
|
|
36
|
+
return errors;
|
|
37
|
+
}
|
|
38
|
+
if (options === null) {
|
|
39
|
+
errors.push("Options cannot be null");
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const optionsCast = options;
|
|
44
|
+
const optionKeys = Object.keys(optionsCast);
|
|
45
|
+
optionKeys.forEach((k) => {
|
|
46
|
+
if (k === "paths") {
|
|
47
|
+
optionsCast.paths.forEach((pathObj) => {
|
|
48
|
+
const [jsonPath, options] = pathObj;
|
|
49
|
+
if (jsonPath.startsWith("$.")) {
|
|
50
|
+
try {
|
|
51
|
+
jsonpath_1.default.parse(jsonPath);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
errors.push(`Invalid jsonpath key: ${err}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const errMsg = stringOptionError(options);
|
|
58
|
+
if (errMsg) {
|
|
59
|
+
errors.push(`jsonpath ${jsonPath}: ${errMsg}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
errors.push(`Unrecognized jsonpath syntax: ${jsonPath}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (k === "ignoreNewProperties" || k === "missingIsDelete") {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
errors.push(`Unrecognized key: ${k}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return errors;
|
|
75
|
+
}
|
|
76
|
+
exports.validate = validate;
|
|
77
|
+
async function merge(current, fromTemplateRepo, context) {
|
|
78
|
+
if (context.mergeArguments === "overwrite") {
|
|
79
|
+
return fromTemplateRepo;
|
|
80
|
+
}
|
|
81
|
+
const currentJson = JSON.parse(current);
|
|
82
|
+
const fromTemplateJson = JSON.parse(fromTemplateRepo);
|
|
83
|
+
if (context.mergeArguments === "merge-current") {
|
|
84
|
+
// Performs Lodash Merge with current as the override
|
|
85
|
+
return JSON.stringify((0, lodash_merge_1.default)(fromTemplateJson, currentJson), null, 4);
|
|
86
|
+
}
|
|
87
|
+
if (context.mergeArguments === "merge-template") {
|
|
88
|
+
// Performs Lodash Merge with current as the override
|
|
89
|
+
return JSON.stringify((0, lodash_merge_1.default)(currentJson, fromTemplateJson), null, 4);
|
|
90
|
+
}
|
|
91
|
+
const { missingIsDelete, ignoreNewProperties, paths } = context.mergeArguments;
|
|
92
|
+
const returnJson = (0, lodash_clonedeep_1.default)(currentJson);
|
|
93
|
+
paths.forEach((p) => {
|
|
94
|
+
const [jPath, overrideType] = p;
|
|
95
|
+
const fromTemplatePaths = new Map();
|
|
96
|
+
if (overrideType === "merge-template") {
|
|
97
|
+
// We want to make sure there aren't extra paths from the template that didn't get added
|
|
98
|
+
jsonpath_1.default.nodes(fromTemplateJson, jPath).forEach((n) => {
|
|
99
|
+
fromTemplatePaths.set(jsonpath_1.default.stringify(n.path), n.path);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
jsonpath_1.default.nodes(currentJson, jPath).forEach(({ path, value }) => {
|
|
103
|
+
// This solves for wildcard operators
|
|
104
|
+
const fullPath = jsonpath_1.default.stringify(path);
|
|
105
|
+
// track the paths in the template we've walked
|
|
106
|
+
fromTemplatePaths.delete(fullPath);
|
|
107
|
+
jsonpath_1.default.apply(returnJson, fullPath, () => {
|
|
108
|
+
const templateValue = jsonpath_1.default.value(fromTemplateJson, fullPath);
|
|
109
|
+
if (overrideType === "merge-template") {
|
|
110
|
+
if (templateValue === undefined) {
|
|
111
|
+
if (missingIsDelete) {
|
|
112
|
+
return templateValue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return applyValueMerge(value, templateValue);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (overrideType === "merge-current") {
|
|
120
|
+
return applyValueMerge(templateValue, value);
|
|
121
|
+
}
|
|
122
|
+
else if (overrideType === "overwrite") {
|
|
123
|
+
if (templateValue !== undefined || missingIsDelete) {
|
|
124
|
+
return templateValue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
throw new Error(`Unexpected JsonPath merge value ${overrideType}`);
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
if (!ignoreNewProperties) {
|
|
134
|
+
for (const fromTemplatePath of fromTemplatePaths.values()) {
|
|
135
|
+
applyPerPath(fromTemplatePath, returnJson, fromTemplateJson);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (!ignoreNewProperties) {
|
|
140
|
+
Object.keys(fromTemplateJson).forEach((key) => {
|
|
141
|
+
if (!returnJson[key]) {
|
|
142
|
+
returnJson[key] = fromTemplateJson[key];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return JSON.stringify(returnJson, null, (0, infer_json_indent_1.inferJSONIndent)(current));
|
|
147
|
+
}
|
|
148
|
+
exports.merge = merge;
|
|
149
|
+
/**
|
|
150
|
+
* A merge from a template to template perspective is either melding objects
|
|
151
|
+
* or completely overwriting primitive types
|
|
152
|
+
*/
|
|
153
|
+
function applyValueMerge(base, toMerge) {
|
|
154
|
+
if (typeof base === "object" && typeof toMerge === "object") {
|
|
155
|
+
return (0, lodash_merge_1.default)(base, toMerge);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return toMerge;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Given a nodePath on the "map" tree, this will go to the first missing element
|
|
163
|
+
* and copy that node onto the object
|
|
164
|
+
*
|
|
165
|
+
* @param nodePath
|
|
166
|
+
* @param onto
|
|
167
|
+
* @param map
|
|
168
|
+
* @param forIdx
|
|
169
|
+
*/
|
|
170
|
+
function applyPerPath(nodePath,
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
|
+
onto,
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
map, forIdx = 0) {
|
|
175
|
+
if (nodePath[forIdx] === "$") {
|
|
176
|
+
applyPerPath(nodePath, onto, map, forIdx + 1);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const selector = nodePath[forIdx];
|
|
180
|
+
if (onto[selector]) {
|
|
181
|
+
applyPerPath(nodePath, onto[selector], map[selector], forIdx + 1);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
onto[selector] = map[selector];
|
|
185
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Change } from "diff";
|
|
2
|
+
import { TemplateCloneDriverFn } from "./clone-drivers";
|
|
3
|
+
import { TemplateDiffDriverFn } from "./diff-drivers";
|
|
4
|
+
export interface TemplateSyncOptions {
|
|
5
|
+
repoUrl: string;
|
|
6
|
+
/**
|
|
7
|
+
* The directory for cloning our template repo into via the cloneDriver
|
|
8
|
+
*/
|
|
9
|
+
tmpCloneDir: string;
|
|
10
|
+
/**
|
|
11
|
+
* The repo directory path that we are going to merge toward
|
|
12
|
+
*/
|
|
13
|
+
repoDir: string;
|
|
14
|
+
/**
|
|
15
|
+
* Defaults to using git clone
|
|
16
|
+
*/
|
|
17
|
+
cloneDriver?: TemplateCloneDriverFn;
|
|
18
|
+
/**
|
|
19
|
+
* Defaults to using git diff
|
|
20
|
+
*/
|
|
21
|
+
diffDriver?: TemplateDiffDriverFn;
|
|
22
|
+
}
|
|
23
|
+
export interface TemplateSyncReturn {
|
|
24
|
+
/**
|
|
25
|
+
* An array of files that were skipped outright due to a templatesync.local ignore glob
|
|
26
|
+
*/
|
|
27
|
+
localSkipFiles: string[];
|
|
28
|
+
/**
|
|
29
|
+
* An object mapping all file paths to any merge rules that would've overridden the merge rules
|
|
30
|
+
* of the template sync file.
|
|
31
|
+
*
|
|
32
|
+
* Note: right now, this shows non-changed diffs as well so you have to look for added or removed
|
|
33
|
+
*/
|
|
34
|
+
localFileChanges: {
|
|
35
|
+
[filePath: string]: Change[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export declare const TEMPLATE_SYNC_CONFIG = "templatesync";
|
|
39
|
+
export declare const TEMPLATE_SYNC_LOCAL_CONFIG = "templatesync.local";
|
|
40
|
+
export declare function templateSync(options: TemplateSyncOptions): Promise<TemplateSyncReturn>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.templateSync = exports.TEMPLATE_SYNC_LOCAL_CONFIG = exports.TEMPLATE_SYNC_CONFIG = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const match_1 = require("./match");
|
|
7
|
+
const merge_file_1 = require("./merge-file");
|
|
8
|
+
const git_clone_1 = require("./clone-drivers/git-clone");
|
|
9
|
+
const diff_drivers_1 = require("./diff-drivers");
|
|
10
|
+
exports.TEMPLATE_SYNC_CONFIG = "templatesync";
|
|
11
|
+
exports.TEMPLATE_SYNC_LOCAL_CONFIG = "templatesync.local";
|
|
12
|
+
async function templateSync(options) {
|
|
13
|
+
const cloneDriver = options.cloneDriver ?? git_clone_1.gitClone;
|
|
14
|
+
const diffDriver = options.diffDriver ?? diff_drivers_1.gitDiff;
|
|
15
|
+
const tempCloneDir = await cloneDriver(options.tmpCloneDir, options.repoUrl);
|
|
16
|
+
// Get the clone Config
|
|
17
|
+
const cloneConfigPath = (0, path_1.join)(tempCloneDir, `${exports.TEMPLATE_SYNC_CONFIG}.json`);
|
|
18
|
+
const templateSyncConfig = (0, fs_1.existsSync)(cloneConfigPath)
|
|
19
|
+
? JSON.parse((0, fs_1.readFileSync)(cloneConfigPath).toString())
|
|
20
|
+
: {};
|
|
21
|
+
const localConfigPath = (0, path_1.join)(options.repoDir, `${exports.TEMPLATE_SYNC_LOCAL_CONFIG}.json`);
|
|
22
|
+
const localTemplateSyncConfig = (0, fs_1.existsSync)(localConfigPath)
|
|
23
|
+
? JSON.parse((0, fs_1.readFileSync)(localConfigPath).toString())
|
|
24
|
+
: {};
|
|
25
|
+
let filesToSync;
|
|
26
|
+
if (localTemplateSyncConfig.afterRef) {
|
|
27
|
+
filesToSync = await diffDriver(tempCloneDir, localTemplateSyncConfig.afterRef);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
filesToSync = (0, match_1.getAllFilesInDir)(tempCloneDir, [
|
|
31
|
+
...templateSyncConfig.ignore,
|
|
32
|
+
".git/**",
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
const localSkipFiles = [];
|
|
36
|
+
const localFileChanges = {};
|
|
37
|
+
await Promise.all(filesToSync.map(async (f) => {
|
|
38
|
+
const result = await (0, merge_file_1.mergeFile)(f, {
|
|
39
|
+
localTemplateSyncConfig,
|
|
40
|
+
templateSyncConfig,
|
|
41
|
+
tempCloneDir,
|
|
42
|
+
cwd: options.repoDir,
|
|
43
|
+
});
|
|
44
|
+
if (result.ignoredDueToLocal) {
|
|
45
|
+
localSkipFiles.push(f);
|
|
46
|
+
}
|
|
47
|
+
else if (result?.localChanges && result.localChanges.length > 0) {
|
|
48
|
+
localFileChanges[f] = result.localChanges;
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
localSkipFiles,
|
|
53
|
+
localFileChanges,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
exports.templateSync = templateSync;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tempDir = exports.TEST_FIXTURES_DIR = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
exports.TEST_FIXTURES_DIR = (0, path_1.resolve)(__dirname, "..", "..", "test-fixtures");
|
|
7
|
+
function tempDir() {
|
|
8
|
+
return process.env.RUNNER_TEMP ?? (0, os_1.tmpdir)();
|
|
9
|
+
}
|
|
10
|
+
exports.tempDir = tempDir;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How we want to merge json
|
|
3
|
+
*
|
|
4
|
+
* overwrite - the template sync overwrites completely
|
|
5
|
+
* merge-template - we merge template into the current json with overrides on matching values happening from the template
|
|
6
|
+
* merge-current - we merge the current json into the template json with overrides on matching values happening from the current json
|
|
7
|
+
*/
|
|
8
|
+
export type BaseJsonMergeOptions = "overwrite" | "merge-template" | "merge-current";
|
|
9
|
+
export interface JsonPathOverrides {
|
|
10
|
+
/**
|
|
11
|
+
* If set to true, this means we won't add new properties from the template
|
|
12
|
+
*/
|
|
13
|
+
ignoreNewProperties?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* If set to true, overwrite will apply undefined values as deleted for the jsonpaths
|
|
16
|
+
* or for values that are supposed to be merged on top of other values
|
|
17
|
+
*/
|
|
18
|
+
missingIsDelete?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Note, if multiple json paths match a rule, we pick the first one in the list that matches
|
|
21
|
+
*/
|
|
22
|
+
paths: [
|
|
23
|
+
jsonPath: `$.${string}`,
|
|
24
|
+
options: BaseJsonMergeOptions
|
|
25
|
+
][];
|
|
26
|
+
}
|
|
27
|
+
export type JsonFileMergeOptions = BaseJsonMergeOptions | JsonPathOverrides;
|
|
28
|
+
type MergePluginOptions = JsonFileMergeOptions;
|
|
29
|
+
/**
|
|
30
|
+
* Configuration object for a given file type merge configuration
|
|
31
|
+
*/
|
|
32
|
+
export interface MergeConfig<T> {
|
|
33
|
+
/**
|
|
34
|
+
* The node module, available on the calling node context, that you want to run.
|
|
35
|
+
* If not provided, we try to use any built-in merge plugins.
|
|
36
|
+
*/
|
|
37
|
+
plugin?: string;
|
|
38
|
+
/**
|
|
39
|
+
* An array of first match file globs that will
|
|
40
|
+
*/
|
|
41
|
+
rules: {
|
|
42
|
+
glob: string;
|
|
43
|
+
options: MergePluginOptions | T;
|
|
44
|
+
}[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The shape of a template sync config file
|
|
48
|
+
*/
|
|
49
|
+
export interface Config<T = unknown> {
|
|
50
|
+
ignore: string[];
|
|
51
|
+
/**
|
|
52
|
+
* If there is no merge config, then we will always just overwrite the file for the diff
|
|
53
|
+
*/
|
|
54
|
+
merge?: {
|
|
55
|
+
[fileExt: string]: MergeConfig<T>;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The shape of a local template sync config file that overrides the root template repo
|
|
60
|
+
*/
|
|
61
|
+
export interface LocalConfig<T = unknown> {
|
|
62
|
+
/**
|
|
63
|
+
* This is the ref that we compare against. If empty, we use all files that have changed since the
|
|
64
|
+
* beginning of the template repo.
|
|
65
|
+
*/
|
|
66
|
+
afterRef?: string;
|
|
67
|
+
ignore: string[];
|
|
68
|
+
/**
|
|
69
|
+
* If there is no merge config, then we will always just overwrite the file for the diff
|
|
70
|
+
*/
|
|
71
|
+
merge?: {
|
|
72
|
+
[fileExt: string]: MergeConfig<T>;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Information around the file we are trying to merge and that arguments for that merge
|
|
77
|
+
* from matching a merge configuration
|
|
78
|
+
*/
|
|
79
|
+
export interface MergeContext<PluginOptions = unknown> {
|
|
80
|
+
relFilePath: string;
|
|
81
|
+
/**
|
|
82
|
+
* If you have provided a custom merge plugin, this will be the "options" section of
|
|
83
|
+
* the matching glob
|
|
84
|
+
*/
|
|
85
|
+
mergeArguments: PluginOptions;
|
|
86
|
+
/**
|
|
87
|
+
* If this run is for the local config. Keep in mind that any local config overrides will
|
|
88
|
+
* be run after the template sync options and we would like to report any changes that
|
|
89
|
+
* are done
|
|
90
|
+
*/
|
|
91
|
+
isLocalOptions?: boolean;
|
|
92
|
+
}
|
|
93
|
+
export interface MergePlugin<PluginOptions> {
|
|
94
|
+
/**
|
|
95
|
+
* This method will be called when a file from the template and it's analog in the downstream repo
|
|
96
|
+
* have some differences. The plugin must perform the merge and return the appropriate file contents
|
|
97
|
+
* as a string
|
|
98
|
+
*
|
|
99
|
+
* TODO: we may create a V2 plugin that could deal with large files and not pass around strings in memory,
|
|
100
|
+
* but for now, this is the current implementation
|
|
101
|
+
*
|
|
102
|
+
* @param current - The downstream repos current file contents
|
|
103
|
+
* @param fromTemplateRepo - the current
|
|
104
|
+
* @param context - an object defining the context around the file and the specific options
|
|
105
|
+
*/
|
|
106
|
+
merge(current: string, fromTemplateRepo: string, context: MergeContext<PluginOptions>): Promise<string>;
|
|
107
|
+
/**
|
|
108
|
+
* Given an options object for the merge, this validates the options object and returns error messages if there is anything wrong.
|
|
109
|
+
* @param options any json value that the user provided - must be validated against the expected options
|
|
110
|
+
*/
|
|
111
|
+
validate(options: unknown): string[] | undefined;
|
|
112
|
+
}
|
|
113
|
+
export {};
|
package/lib/cjs/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gitClone = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const CLONE_DIR = "cloned_repo";
|
|
7
|
+
async function gitClone(tmpDir, repoUrl) {
|
|
8
|
+
(0, child_process_1.execSync)(`git clone ${repoUrl} ${CLONE_DIR}`, {
|
|
9
|
+
cwd: tmpDir,
|
|
10
|
+
env: process.env,
|
|
11
|
+
});
|
|
12
|
+
return (0, path_1.resolve)(tmpDir, CLONE_DIR);
|
|
13
|
+
}
|
|
14
|
+
exports.gitClone = gitClone;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./git-clone"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gitDiff = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
async function gitDiff(gitDir, afterRef) {
|
|
6
|
+
const diffFilesStr = (0, child_process_1.execSync)(`git diff ${afterRef}.. --name-only`, {
|
|
7
|
+
cwd: gitDir,
|
|
8
|
+
})
|
|
9
|
+
.toString()
|
|
10
|
+
.trim();
|
|
11
|
+
return diffFilesStr.split("\n");
|
|
12
|
+
}
|
|
13
|
+
exports.gitDiff = gitDiff;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./git-diff"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./sync-results-to-md"), exports);
|
|
18
|
+
__exportStar(require("./infer-json-indent"), exports);
|