@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.
Files changed (119) hide show
  1. package/.eslintrc.js +10 -0
  2. package/.github/CODEOWNERS +6 -0
  3. package/.github/workflows/pr-checks.yaml +12 -0
  4. package/.github/workflows/release.yaml +36 -0
  5. package/.github/workflows/test-flow.yaml +58 -0
  6. package/.husky/commit-msg +4 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc +1 -0
  9. package/CHANGELOG.md +27 -0
  10. package/README.md +93 -0
  11. package/action.yml +13 -0
  12. package/commitlint.config.js +3 -0
  13. package/jest.config.js +19 -0
  14. package/lib/cjs/clone-drivers/git-clone.d.ts +1 -0
  15. package/lib/cjs/clone-drivers/git-clone.js +14 -0
  16. package/lib/cjs/clone-drivers/index.d.ts +2 -0
  17. package/lib/cjs/clone-drivers/index.js +18 -0
  18. package/lib/cjs/clone-drivers/types.d.ts +5 -0
  19. package/lib/cjs/clone-drivers/types.js +2 -0
  20. package/lib/cjs/diff-drivers/git-diff.d.ts +1 -0
  21. package/lib/cjs/diff-drivers/git-diff.js +13 -0
  22. package/lib/cjs/diff-drivers/index.d.ts +2 -0
  23. package/lib/cjs/diff-drivers/index.js +18 -0
  24. package/lib/cjs/diff-drivers/types.d.ts +5 -0
  25. package/lib/cjs/diff-drivers/types.js +2 -0
  26. package/lib/cjs/formatting/index.d.ts +2 -0
  27. package/lib/cjs/formatting/index.js +18 -0
  28. package/lib/cjs/formatting/infer-json-indent.d.ts +1 -0
  29. package/lib/cjs/formatting/infer-json-indent.js +18 -0
  30. package/lib/cjs/formatting/sync-results-to-md.d.ts +2 -0
  31. package/lib/cjs/formatting/sync-results-to-md.js +40 -0
  32. package/lib/cjs/index.d.ts +3 -0
  33. package/lib/cjs/index.js +19 -0
  34. package/lib/cjs/load-plugin.d.ts +2 -0
  35. package/lib/cjs/load-plugin.js +63 -0
  36. package/lib/cjs/match.d.ts +10 -0
  37. package/lib/cjs/match.js +45 -0
  38. package/lib/cjs/merge-file.d.ts +29 -0
  39. package/lib/cjs/merge-file.js +99 -0
  40. package/lib/cjs/plugins/index.d.ts +4 -0
  41. package/lib/cjs/plugins/index.js +10 -0
  42. package/lib/cjs/plugins/json-merge.d.ts +3 -0
  43. package/lib/cjs/plugins/json-merge.js +185 -0
  44. package/lib/cjs/template-sync.d.ts +40 -0
  45. package/lib/cjs/template-sync.js +56 -0
  46. package/lib/cjs/test-utils/index.d.ts +2 -0
  47. package/lib/cjs/test-utils/index.js +10 -0
  48. package/lib/cjs/types.d.ts +113 -0
  49. package/lib/cjs/types.js +2 -0
  50. package/lib/esm/clone-drivers/git-clone.js +14 -0
  51. package/lib/esm/clone-drivers/index.js +18 -0
  52. package/lib/esm/clone-drivers/types.js +2 -0
  53. package/lib/esm/diff-drivers/git-diff.js +13 -0
  54. package/lib/esm/diff-drivers/index.js +18 -0
  55. package/lib/esm/diff-drivers/types.js +2 -0
  56. package/lib/esm/formatting/index.js +18 -0
  57. package/lib/esm/formatting/infer-json-indent.js +18 -0
  58. package/lib/esm/formatting/sync-results-to-md.js +40 -0
  59. package/lib/esm/index.js +19 -0
  60. package/lib/esm/load-plugin.js +40 -0
  61. package/lib/esm/match.js +45 -0
  62. package/lib/esm/merge-file.js +99 -0
  63. package/lib/esm/plugins/index.js +10 -0
  64. package/lib/esm/plugins/json-merge.js +185 -0
  65. package/lib/esm/template-sync.js +56 -0
  66. package/lib/esm/test-utils/index.js +10 -0
  67. package/lib/esm/types.js +2 -0
  68. package/package.json +60 -0
  69. package/release.config.js +34 -0
  70. package/src/clone-drivers/git-clone.ts +16 -0
  71. package/src/clone-drivers/index.ts +2 -0
  72. package/src/clone-drivers/types.ts +8 -0
  73. package/src/diff-drivers/git-diff.ts +10 -0
  74. package/src/diff-drivers/index.ts +2 -0
  75. package/src/diff-drivers/types.ts +8 -0
  76. package/src/formatting/__snapshots__/sync-results-to-md.spec.ts.snap +22 -0
  77. package/src/formatting/index.ts +2 -0
  78. package/src/formatting/infer-json-indent.spec.ts +49 -0
  79. package/src/formatting/infer-json-indent.ts +16 -0
  80. package/src/formatting/sync-results-to-md.spec.ts +25 -0
  81. package/src/formatting/sync-results-to-md.ts +46 -0
  82. package/src/index.ts +3 -0
  83. package/src/load-plugin.ts +42 -0
  84. package/src/match.spec.ts +68 -0
  85. package/src/match.ts +52 -0
  86. package/src/merge-file.spec.ts +432 -0
  87. package/src/merge-file.ts +150 -0
  88. package/src/plugins/index.ts +11 -0
  89. package/src/plugins/json-merge.spec.ts +350 -0
  90. package/src/plugins/json-merge.ts +205 -0
  91. package/src/template-sync.spec.ts +216 -0
  92. package/src/template-sync.ts +113 -0
  93. package/src/test-utils/index.ts +13 -0
  94. package/src/types.ts +124 -0
  95. package/templatesync.local.json +15 -0
  96. package/test-fixtures/downstream/README.md +3 -0
  97. package/test-fixtures/downstream/package.json +18 -0
  98. package/test-fixtures/downstream/plugins/custom-plugin.js +11 -0
  99. package/test-fixtures/downstream/src/index.js +2 -0
  100. package/test-fixtures/downstream/src/index.ts +1 -0
  101. package/test-fixtures/downstream/src/templated.js +2 -0
  102. package/test-fixtures/downstream/src/templated.ts +1 -0
  103. package/test-fixtures/downstream/templatesync.json +19 -0
  104. package/test-fixtures/downstream/templatesync.local.json +14 -0
  105. package/test-fixtures/dummy-plugin.js +8 -0
  106. package/test-fixtures/glob-test/folder1/something.js +1 -0
  107. package/test-fixtures/glob-test/folder1/something.ts +0 -0
  108. package/test-fixtures/glob-test/toplevel.js +0 -0
  109. package/test-fixtures/glob-test/toplevel.txt +0 -0
  110. package/test-fixtures/template/custom-bin/something.txt +1 -0
  111. package/test-fixtures/template/package.json +17 -0
  112. package/test-fixtures/template/src/index.js +2 -0
  113. package/test-fixtures/template/src/index.ts +1 -0
  114. package/test-fixtures/template/src/templated.js +2 -0
  115. package/test-fixtures/template/src/templated.ts +1 -0
  116. package/test-fixtures/template/templatesync.json +19 -0
  117. package/tsconfig.cjs.json +12 -0
  118. package/tsconfig.esm.json +10 -0
  119. package/tsconfig.json +22 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A function that operates within the gitDir, and returns the list of file paths
3
+ * since the last sha
4
+ */
5
+ export type TemplateDiffDriverFn = (
6
+ gitDir: string,
7
+ afterRef: string,
8
+ ) => Promise<string[]>;
@@ -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,2 @@
1
+ export * from "./sync-results-to-md";
2
+ export * from "./infer-json-indent";
@@ -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,3 @@
1
+ export * from "./types";
2
+ export * from "./template-sync";
3
+ export * from "./formatting";
@@ -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
+ }