@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.
@@ -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
- * If not provided, we try to use any built-in merge plugins.
40
+ * Built-in plugins can be specified via their built-in name (see documentation):
41
+ *
42
+ * Example: _json
36
43
  */
37
- plugin?: string;
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
- rules: {
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 fetch ${remoteName} ${branch}`, {
7
+ const remoteInfo = (0, child_process_1.execSync)(`git remote show ${remoteName}`, {
8
8
  cwd: tmpDir,
9
9
  env: process.env,
10
- });
11
- (0, child_process_1.execSync)(`git checkout -b ${branch} --track ${remoteName}/${branch}`, {
12
- cwd: tmpDir,
13
- env: process.env,
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;
@@ -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
- async function loadPlugin(mergeConfig, forExt, configDir) {
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 (mergeConfig.plugin) {
10
- // First check if this is a loal .js file
11
- const localPath = (0, path_1.resolve)(configDir, mergeConfig.plugin);
12
- const importPath = (0, fs_1.existsSync)(localPath) ? localPath : mergeConfig.plugin;
13
- try {
14
- // Sad workaround for testing since dynamic import segfaults
15
- if (process.env.JEST_WORKER_ID !== undefined) {
16
- // eslint-disable-next-line @typescript-eslint/no-var-requires
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
- catch (err) {
28
- console.error(err);
29
- throw err;
32
+ else {
33
+ handler = (await import(importPath));
30
34
  }
31
- }
32
- else {
33
- if (!plugins_1.defaultExtensionMap[forExt]) {
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
- handler = plugins_1.defaultExtensionMap[forExt];
39
+ }
40
+ catch (err) {
41
+ console.error(err);
42
+ throw err;
37
43
  }
38
44
  return handler;
39
45
  }
@@ -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?.[ext];
30
- const localMergeConfig = localTemplateSyncConfig.merge?.[ext];
31
- // Either write the merge or write
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) && (mergeConfig || localMergeConfig)) {
40
+ if ((0, fs_1.existsSync)(filePath) && (mergeHandler || localMergeHandler)) {
35
41
  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);
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 (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);
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
- 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
- }
60
+ localChanges.push(...(0, diff_1.diffLines)(fileContents, localContents));
61
+ fileContents = localContents;
73
62
  }
74
63
  }
75
64
  else {
@@ -6,5 +6,6 @@ exports.defaultExtensionMap = {
6
6
  ".json": {
7
7
  merge: json_merge_1.merge,
8
8
  validate: json_merge_1.validate,
9
+ builtinName: "_json",
9
10
  },
10
11
  };
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@hanseltime/template-repo-sync",
3
- "version": "1.3.0",
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
- "@semantic-release/npm",
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
- execSync(`git fetch ${remoteName} ${branch}`, {
13
- cwd: tmpDir,
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
  }
@@ -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
- 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]) {
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 default merge function supplied for ${forExt}. Cannot have mere config without custom plugin supplied.`,
23
+ `No builtin merge function supplied for ${mergeConfig.plugin}. Cannot have merge config without custom plugin supplied.`,
36
24
  );
37
25
  }
38
- handler = defaultExtensionMap[forExt];
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>;