@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,22 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`syncResultsToMd snapshots 1`] = `
|
|
4
|
+
"# templatesync.local
|
|
5
|
+
|
|
6
|
+
## Stopped the following files from syncing:
|
|
7
|
+
|
|
8
|
+
* src/file1.ts
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Changed the following files from what they would have synced:
|
|
12
|
+
|
|
13
|
+
package.json
|
|
14
|
+
\`\`\`diff
|
|
15
|
+
|
|
16
|
+
+my thing
|
|
17
|
+
|
|
18
|
+
-your thang
|
|
19
|
+
|
|
20
|
+
\`\`\`
|
|
21
|
+
"
|
|
22
|
+
`;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { inferJSONIndent } from "./infer-json-indent";
|
|
2
|
+
|
|
3
|
+
const withNewLine = `{
|
|
4
|
+
|
|
5
|
+
"whoa": true,
|
|
6
|
+
"thing": here,
|
|
7
|
+
"something": {
|
|
8
|
+
"nested": false
|
|
9
|
+
}
|
|
10
|
+
}`;
|
|
11
|
+
|
|
12
|
+
const fourSpaces = `{
|
|
13
|
+
"whoa": true,
|
|
14
|
+
"thing": here,
|
|
15
|
+
"something": {
|
|
16
|
+
"nested": false
|
|
17
|
+
}
|
|
18
|
+
}`;
|
|
19
|
+
|
|
20
|
+
const tabSpaces = `{
|
|
21
|
+
"whoa": true,
|
|
22
|
+
"thing": here,
|
|
23
|
+
"something": {
|
|
24
|
+
"nested": false
|
|
25
|
+
}
|
|
26
|
+
}`;
|
|
27
|
+
|
|
28
|
+
const twoSpaces = `{
|
|
29
|
+
"whoa": true,
|
|
30
|
+
"thing": here,
|
|
31
|
+
"something": {
|
|
32
|
+
"nested": false
|
|
33
|
+
}
|
|
34
|
+
}`;
|
|
35
|
+
|
|
36
|
+
describe("inferJSONIndent", () => {
|
|
37
|
+
it("returns spaces when the first indent is spaces with a new line", () => {
|
|
38
|
+
expect(inferJSONIndent(withNewLine)).toBe(" ");
|
|
39
|
+
});
|
|
40
|
+
it("returns spaces when the first indent is spaces", () => {
|
|
41
|
+
expect(inferJSONIndent(fourSpaces)).toBe(" ");
|
|
42
|
+
});
|
|
43
|
+
it("returns spaces when the first indent is spaces", () => {
|
|
44
|
+
expect(inferJSONIndent(twoSpaces)).toBe(" ");
|
|
45
|
+
});
|
|
46
|
+
it("returns tabs when the first indent is a tab", () => {
|
|
47
|
+
expect(inferJSONIndent(tabSpaces)).toBe("\t");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const spacingRegex = /[{[]\n?(?<spacing>\s+)["tf\d]/;
|
|
2
|
+
export function inferJSONIndent(rawJSON: string) {
|
|
3
|
+
const match = spacingRegex.exec(rawJSON);
|
|
4
|
+
if (!match?.groups?.spacing) {
|
|
5
|
+
// eslint-disable-next-line no-console
|
|
6
|
+
console.warn(
|
|
7
|
+
`Could not find json indentation for json string: ${rawJSON.slice(40)} ... \nDefaulting to 4 spaces`,
|
|
8
|
+
);
|
|
9
|
+
// Four spaces
|
|
10
|
+
return " ";
|
|
11
|
+
}
|
|
12
|
+
const spacing = match.groups.spacing;
|
|
13
|
+
// Handle the case where there were multiple newlines before a value
|
|
14
|
+
const lastNewLine = spacing.lastIndexOf("\n");
|
|
15
|
+
return match?.groups.spacing.slice(lastNewLine >= 0 ? lastNewLine + 1 : 0);
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { syncResultsToMd } from "./sync-results-to-md";
|
|
2
|
+
|
|
3
|
+
describe("syncResultsToMd", () => {
|
|
4
|
+
it("snapshots", () => {
|
|
5
|
+
expect(
|
|
6
|
+
syncResultsToMd({
|
|
7
|
+
localSkipFiles: ["src/file1.ts"],
|
|
8
|
+
localFileChanges: {
|
|
9
|
+
"package.json": [
|
|
10
|
+
{
|
|
11
|
+
count: 8,
|
|
12
|
+
value: "my thing",
|
|
13
|
+
added: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
count: 10,
|
|
17
|
+
value: "your thang",
|
|
18
|
+
removed: true,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
).toMatchSnapshot();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Change } from "diff";
|
|
2
|
+
import {
|
|
3
|
+
TEMPLATE_SYNC_LOCAL_CONFIG,
|
|
4
|
+
TemplateSyncReturn,
|
|
5
|
+
} from "../template-sync";
|
|
6
|
+
|
|
7
|
+
export function syncResultsToMd(result: TemplateSyncReturn) {
|
|
8
|
+
return `# ${TEMPLATE_SYNC_LOCAL_CONFIG}
|
|
9
|
+
|
|
10
|
+
## Stopped the following files from syncing:
|
|
11
|
+
|
|
12
|
+
${result.localSkipFiles.reduce((s, file) => {
|
|
13
|
+
return `${s}* ${file}\n`;
|
|
14
|
+
}, "")}
|
|
15
|
+
|
|
16
|
+
## Changed the following files from what they would have synced:
|
|
17
|
+
|
|
18
|
+
${Object.keys(result.localFileChanges).reduce((s, file) => {
|
|
19
|
+
return `${s}${file}
|
|
20
|
+
\`\`\`diff
|
|
21
|
+
${result.localFileChanges[file].reduce((diffS, change) => {
|
|
22
|
+
return `${diffS}\n${makeChangeIntoDiffLines(change)}`;
|
|
23
|
+
}, "")}
|
|
24
|
+
\`\`\``;
|
|
25
|
+
}, "")}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeChangeIntoDiffLines(change: Change, nonchangeMax = 3) {
|
|
30
|
+
const operator = change.added ? "+" : change.removed ? "-" : "";
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
!operator &&
|
|
34
|
+
nonchangeMax > 0 &&
|
|
35
|
+
change.count &&
|
|
36
|
+
change.count > nonchangeMax
|
|
37
|
+
) {
|
|
38
|
+
const lines = change.value.split("\n");
|
|
39
|
+
const partial = lines.slice(lines.length - nonchangeMax);
|
|
40
|
+
return `...\n${partial.join("\n")}\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return change.value.split("\n").reduce((s, line) => {
|
|
44
|
+
return `${s}${operator}${line}\n`;
|
|
45
|
+
}, "");
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defaultExtensionMap } from "./plugins";
|
|
2
|
+
import { MergeConfig, MergePlugin } from "./types";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
|
|
6
|
+
export async function loadPlugin<T>(
|
|
7
|
+
mergeConfig: MergeConfig<T>,
|
|
8
|
+
forExt: string,
|
|
9
|
+
configDir: string,
|
|
10
|
+
): Promise<MergePlugin<T>> {
|
|
11
|
+
let handler: MergePlugin<unknown>;
|
|
12
|
+
if (mergeConfig.plugin) {
|
|
13
|
+
// First check if this is a loal .js file
|
|
14
|
+
const localPath = resolve(configDir, mergeConfig.plugin);
|
|
15
|
+
const importPath = existsSync(localPath) ? localPath : mergeConfig.plugin;
|
|
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]) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`No default merge function supplied for ${forExt}. Cannot have mere config without custom plugin supplied.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
handler = defaultExtensionMap[forExt];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return handler as MergePlugin<T>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { getAllFilesInDir } from "./match";
|
|
3
|
+
import { TEST_FIXTURES_DIR } from "./test-utils";
|
|
4
|
+
|
|
5
|
+
const TEST_GLOB_DIR = resolve(TEST_FIXTURES_DIR, "glob-test");
|
|
6
|
+
|
|
7
|
+
describe("getAllFilesInDir", () => {
|
|
8
|
+
it("gets all files in the directory with no ignores", () => {
|
|
9
|
+
const files = getAllFilesInDir(TEST_GLOB_DIR, []);
|
|
10
|
+
expect(files).toEqual(
|
|
11
|
+
expect.arrayContaining([
|
|
12
|
+
"folder1/something.js",
|
|
13
|
+
"folder1/something.ts",
|
|
14
|
+
"toplevel.js",
|
|
15
|
+
"toplevel.txt",
|
|
16
|
+
]),
|
|
17
|
+
);
|
|
18
|
+
try {
|
|
19
|
+
expect(files.length).toBe(4);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error("All found files: " + files);
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
it("gets all files in the directory that do not match the ignore (single)", () => {
|
|
26
|
+
const files = getAllFilesInDir(TEST_GLOB_DIR, ["**/*.ts"]);
|
|
27
|
+
expect(files).toEqual(
|
|
28
|
+
expect.arrayContaining([
|
|
29
|
+
"folder1/something.js",
|
|
30
|
+
"toplevel.js",
|
|
31
|
+
"toplevel.txt",
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
34
|
+
try {
|
|
35
|
+
expect(files.length).toBe(3);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error("All found files: " + files);
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
it("gets all files in the directory that do not match the ignore (multiple)", () => {
|
|
42
|
+
const files = getAllFilesInDir(TEST_GLOB_DIR, ["**/*.ts", "**/*.txt"]);
|
|
43
|
+
expect(files).toEqual(
|
|
44
|
+
expect.arrayContaining(["folder1/something.js", "toplevel.js"]),
|
|
45
|
+
);
|
|
46
|
+
try {
|
|
47
|
+
expect(files.length).toBe(2);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("All found files: " + files);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("gets all files in the directory that do not match the ignore with a folder level directive (multiple)", () => {
|
|
55
|
+
const files = getAllFilesInDir(TEST_GLOB_DIR, [
|
|
56
|
+
"**/*.ts",
|
|
57
|
+
"**/*.txt",
|
|
58
|
+
"folder1",
|
|
59
|
+
]);
|
|
60
|
+
expect(files).toEqual(expect.arrayContaining(["toplevel.js"]));
|
|
61
|
+
try {
|
|
62
|
+
expect(files.length).toBe(1);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("All found files: " + files);
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
package/src/match.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readdirSync } from "fs";
|
|
2
|
+
import { some } from "micromatch";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
export function invertMatchPatterns(patterns: string[]) {
|
|
6
|
+
return patterns.map((pattern) => {
|
|
7
|
+
if (pattern.startsWith("!")) {
|
|
8
|
+
return pattern.slice(1);
|
|
9
|
+
}
|
|
10
|
+
return `!${pattern}`;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gets all the files in the directory recursively while avoiding any micromatch patterns
|
|
16
|
+
* that would match for ignoring
|
|
17
|
+
*
|
|
18
|
+
* @param dir
|
|
19
|
+
* @param ignorePatterns
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
export function getAllFilesInDir(
|
|
23
|
+
dir: string,
|
|
24
|
+
ignorePatterns: string[],
|
|
25
|
+
): string[] {
|
|
26
|
+
return recurseDirsForFiles(dir, ignorePatterns);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function recurseDirsForFiles(
|
|
30
|
+
dirpath: string,
|
|
31
|
+
ignorePatterns: string[],
|
|
32
|
+
relativeRoot = "",
|
|
33
|
+
): string[] {
|
|
34
|
+
return readdirSync(dirpath, {
|
|
35
|
+
withFileTypes: true,
|
|
36
|
+
}).reduce((files, f) => {
|
|
37
|
+
const relPath = join(relativeRoot, f.name);
|
|
38
|
+
if (some(relPath, ignorePatterns)) {
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Ensure we aren't ignoring these folders explicitly
|
|
43
|
+
if (f.isDirectory()) {
|
|
44
|
+
files.push(
|
|
45
|
+
...recurseDirsForFiles(join(dirpath, f.name), ignorePatterns, relPath),
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
files.push(relPath);
|
|
49
|
+
}
|
|
50
|
+
return files;
|
|
51
|
+
}, [] as string[]);
|
|
52
|
+
}
|