@elliemae/ds-monorepo-devops 3.51.0-next.7 → 3.51.0-next.9

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.
@@ -0,0 +1,210 @@
1
+ /* eslint-disable max-lines */
2
+ /* eslint-disable max-len */
3
+ /* eslint-disable complexity, no-console, max-statements */
4
+ import defGlob from 'glob';
5
+ import inquirer from 'inquirer';
6
+ import { exec } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ const { glob } = defGlob;
9
+
10
+ const execSyntaxSugar = async ({ command, dryRun = false, silentFail = false }) => {
11
+ const noLFNorCRLFCmd = command.replace(/\r?\n|\r/g, '');
12
+ if (dryRun) {
13
+ console.log(noLFNorCRLFCmd);
14
+ return;
15
+ }
16
+ return new Promise((resolve, reject) => {
17
+ exec(noLFNorCRLFCmd, (error, stdout) => {
18
+ if (error && !silentFail) {
19
+ console.error(error);
20
+ reject(error);
21
+ }
22
+ resolve(stdout);
23
+ });
24
+ });
25
+ };
26
+
27
+ export async function promptForCommandOptions() {
28
+ const preAnswears = await inquirer.prompt([
29
+ {
30
+ type: 'list',
31
+ name: 'readyForProcedure',
32
+ message: 'are you currently on the branch you want to reverse merge into?',
33
+ default: 'n',
34
+ choices: ['y', 'n'],
35
+ },
36
+ {
37
+ type: 'list',
38
+ name: 'pendingChanges',
39
+ message: 'you have pending changes in your working directory?',
40
+ default: 'y',
41
+ choices: ['y', 'n'],
42
+ },
43
+ ]);
44
+ if (preAnswears.readyForProcedure === 'n') {
45
+ console.log('Please checkout the branch you want to reverse merge into and run the command again');
46
+ process.exit(0);
47
+ }
48
+ if (preAnswears.pendingChanges === 'y') {
49
+ console.log('Please commit or stash your changes and run the command again');
50
+ process.exit(0);
51
+ }
52
+ // we use git remote to get the list of remotes to choose from
53
+ const gitRemotes = await execSyntaxSugar({ command: 'git remote' });
54
+ const gitRemotesArray = gitRemotes.split('\n').filter((remote) => remote !== '');
55
+
56
+ const questions = [
57
+ {
58
+ type: 'list',
59
+ name: 'gitRemote',
60
+ message: 'which remote do you want to take the file from?',
61
+ choices: gitRemotesArray,
62
+ default: gitRemotesArray[0],
63
+ },
64
+ {
65
+ type: 'text',
66
+ name: 'gitBranch',
67
+ message: 'which branch do you want to take the files from?',
68
+ default: 'develop',
69
+ },
70
+ ];
71
+
72
+ const answers = await inquirer.prompt(questions);
73
+
74
+ return {
75
+ ...answers,
76
+ };
77
+ }
78
+
79
+ /*
80
+ * given a file name, gitRemote and gitBranch, return the command to restore the file in all subfolders, taking the file from the remote branch
81
+ * @param options
82
+ * @param {string} [options.gitRemote] - the remote to take the file from
83
+ * @param {string} [options.gitBranch] - the branch to take the file from
84
+ * @param {string} [options.fileName] - the file to restore
85
+ * @param {boolean} [options.wantSubfolder] - whether to restore the file in all subfolders or current folder only
86
+ * @returns {string} - the command to restore the file in all subfolders
87
+ *
88
+ * @example
89
+ * // => git ls-files origin/develop '** /CHANGELOG.md' | xargs git restore --source=origin/develop'
90
+ * getRestoreFileFromBranchUnixCmd({gitRemote: 'origin', gitBranch: 'develop', fileName: 'CHANGELOG.md'})
91
+ */
92
+ const getRestoreFileFromBranchUnixCmd = ({ gitRemote, gitBranch, fileName, wantSubfolder = true }) =>
93
+ wantSubfolder
94
+ ? `git ls-files ${gitRemote}/${gitBranch} '**/${fileName}' | xargs git restore --source=${gitRemote}/${gitBranch} `
95
+ : `git ls-files ${gitRemote}/${gitBranch} '${fileName}' | xargs git restore --source=${gitRemote}/${gitBranch} `;
96
+
97
+ // 0 nothing to do, 1 can't auto-solve, -1 can auto-solve
98
+ const canAutosolvePackageJsonConflict = async (packageJsonFilePath) => {
99
+ const conflictRegexp = /<<<<<<<[\s\S]*=======[\s\S]*?>>>>>>>[\s\S]*?\n/g;
100
+ const packageJsonString = fs.readFileSync(packageJsonFilePath, 'utf8');
101
+
102
+ // check if only one conflict marker exists, if multiple, this requires manual resolution
103
+ const foundConflictMarkers = [...packageJsonString.matchAll(conflictRegexp)];
104
+ //check if no conflict markers exist
105
+ if (foundConflictMarkers.length === 0) return 0;
106
+ if (foundConflictMarkers.length === 1) {
107
+ // check if the conflict is only in the version field by matching "version" and see if it's found twice
108
+ const foundVersionKeys = [...packageJsonString.matchAll(/"version"/g)];
109
+ return foundVersionKeys.length === 2 ? -1 : 1;
110
+ }
111
+ return 1;
112
+ };
113
+
114
+ /*
115
+ * reverse merge a file from a remote branch using the following strategy:
116
+ * 1 - keep current branch ./lerna.json
117
+ * 2 - keep current branch ./ci_cd folder content
118
+ * 3 - keep current branch CHANGELOG.md (they will be auto-generated, but keeping "current" makes the diff easier to read)
119
+ * 4 - keep current branch ** /CHANGELOG.md (they will be auto-generated, but keeping "current" makes the diff easier to read
120
+ * 5 - check `** /* /package.json` that the conflict is ONLY in `version` field, any other conflict is marked for manual resolution
121
+ * 6 - run 'pnpm run use-registry-version && pnpm run use-workspace-version' to update the package.json files with the correct version
122
+ * also automatically switch to the $gitBranch-$gitCurrent-mmddyyyy new branch
123
+ * @param options
124
+ * @param {string} [options.gitRemote] - the remote to take the file from
125
+ * @param {string} [options.gitBranch] - the branch to take the file from
126
+ * @returns {Promise<void>}
127
+ */
128
+ const gitReverseMerge = async (options) => {
129
+ const { gitRemote, gitBranch } = options;
130
+
131
+ const gitCurrent = await execSyntaxSugar({ command: 'git branch --show-current', dryRun: false });
132
+ const todayMMDDYYYY = new Intl.DateTimeFormat('en-US', {
133
+ month: '2-digit',
134
+ day: '2-digit',
135
+ year: 'numeric',
136
+ })
137
+ .format(new Date())
138
+ .replace(/\//g, '');
139
+ const newBranchName = `${gitBranch}-${gitCurrent}-${todayMMDDYYYY}`;
140
+
141
+ // create a new branch
142
+ await execSyntaxSugar({ command: `git checkout -b ${newBranchName}` });
143
+ // run the git merge command
144
+ await execSyntaxSugar({
145
+ command: `git merge --no-edit --no-commit --no-ff ${gitRemote}/${gitBranch}`,
146
+ silentFail: true,
147
+ });
148
+
149
+ // apply the strategy
150
+ // 1 - keep current branch ./lerna.json
151
+ const restoreLernaJsonCmd = `git restore --source=HEAD lerna.json`;
152
+ await execSyntaxSugar({ command: restoreLernaJsonCmd });
153
+ // 2 - keep current branch ./ci_cd folder content
154
+ const restoreCiCdCmd = `git restore --source=HEAD ci_cd`;
155
+ await execSyntaxSugar({ command: restoreCiCdCmd });
156
+ // 3 - keep any branch CHANGELOG.md (they will be auto-generated anyway)
157
+ const restoreChangelogCmd = getRestoreFileFromBranchUnixCmd({
158
+ gitRemote,
159
+ gitCurrent,
160
+ fileName: 'CHANGELOG.md',
161
+ wantSubfolder: false,
162
+ });
163
+ await execSyntaxSugar({ command: restoreChangelogCmd });
164
+ // 4 - keep any branch ** /CHANGELOG.md (they will be auto-generated anyway)
165
+ const restoreChangelogsCmd = getRestoreFileFromBranchUnixCmd({
166
+ gitRemote,
167
+ gitCurrent,
168
+ fileName: 'CHANGELOG.md',
169
+ wantSubfolder: true,
170
+ });
171
+ await execSyntaxSugar({ command: restoreChangelogsCmd });
172
+
173
+ // we check all package.json files for conflicts and resolve those that can be auto-resolved, listin the ones that can't
174
+ const cantAutoSolve = [];
175
+ const globPatternPackage = '**/*/package.json';
176
+ const packageJsonFilesPaths = glob(globPatternPackage, { sync: true, ignore: ['**/node_modules/**', '**/dist/**'] });
177
+
178
+ for (let i = 0; i < packageJsonFilesPaths.length; i += 1) {
179
+ const packageJsonFilePath = packageJsonFilesPaths[i];
180
+ const canAutoSolve = await canAutosolvePackageJsonConflict(packageJsonFilePath);
181
+ if (canAutoSolve === 0) continue;
182
+
183
+ if (canAutoSolve === 1) {
184
+ cantAutoSolve.push(packageJsonFilePath);
185
+ continue;
186
+ }
187
+
188
+ const restorePackageJsonCmd = getRestoreFileFromBranchUnixCmd({
189
+ gitRemote,
190
+ gitBranch,
191
+ fileName: packageJsonFilePath,
192
+ wantSubfolder: false,
193
+ });
194
+ await execSyntaxSugar({ command: restorePackageJsonCmd });
195
+ }
196
+
197
+ // 6 - run 'pnpm run use-registry-version && pnpm run use-workspace-version' to update the package.json files with the correct version
198
+ await execSyntaxSugar({ command: 'pnpm run use-registry-version && pnpm run use-workspace-version' });
199
+
200
+ // if there are conflicts that can't be auto-solved, list them
201
+ if (cantAutoSolve.length > 0) {
202
+ console.log("The following package.json files have conflicts that can't be auto-solved:");
203
+ console.log(cantAutoSolve.join('\n'));
204
+ }
205
+ };
206
+
207
+ export const execGitReverseMerge = async (options) => {
208
+ const commandOptions = await promptForCommandOptions(options);
209
+ await gitReverseMerge(commandOptions);
210
+ };
@@ -1,8 +1,10 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { execSyncNxTags } from './execSyncNxTags/index.mjs';
3
+ import {execGitReverseMerge} from './execGitReverseMerge/index.mjs';
3
4
 
4
5
  const COMMANDS = {
5
6
  SYNC_NX_TAGS: 'sync-nx-tags',
7
+ REVERSE_MERGE_BRANCH: 'reverse-merge-branch',
6
8
  EXIT: 'exit',
7
9
  };
8
10
  export const checkCommandOrInquire = async (options) => {
@@ -27,6 +29,9 @@ export async function executeCommandsMap(args, options) {
27
29
  case COMMANDS.SYNC_NX_TAGS:
28
30
  execSyncNxTags(options);
29
31
  break;
32
+ case COMMANDS.REVERSE_MERGE_BRANCH:
33
+ execGitReverseMerge(options);
34
+ break;
30
35
  default:
31
36
  break;
32
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-monorepo-devops",
3
- "version": "3.51.0-next.7",
3
+ "version": "3.51.0-next.9",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Monorepo Devops",
6
6
  "type": "module",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "engines": {
27
27
  "pnpm": ">=8",
28
- "node": ">=18"
28
+ "node": ">=22"
29
29
  },
30
30
  "author": "ICE MT",
31
31
  "jestSonar": {