spectrum-cli 1.1.1 → 1.2.0

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/src/release.js CHANGED
@@ -1,81 +1,128 @@
1
1
  #!/usr/bin/env node
2
2
  const { goToDevBranch, goToMainBranch, updateCurrentBranch } = require('./git');
3
3
  const { changelogChangeHeader, changelogRemoveEmptyChapters, changelogAddUnreleasedBlock, changelogCommit } = require('./changelog');
4
- const { getVersion, logSuccess, logError, execCommand, getCurrentBranch, getMainBranch, colors } = require('./utils');
4
+ const path = require('path');
5
+ const { getVersion, logSuccess, logError, execCommand, getCurrentBranch, getMainBranch, getMergeRequestUrl } = require('./utils');
6
+ const { runCommand } = require('./command-executor');
7
+ const {
8
+ requireGitRepo,
9
+ requireCleanWorkingTree,
10
+ requireCurrentBranchUpToDateWithRemote,
11
+ requireMainAndDevBranches,
12
+ requireOnReleaseBranch,
13
+ requireReleaseBranchMissing,
14
+ requirePackageVersion,
15
+ requireFileExists,
16
+ requireChangelogFormatted
17
+ } = require('./preflight');
5
18
 
6
19
  function releaseCreate() {
7
- goToDevBranch();
8
- updateCurrentBranch();
9
-
20
+ if (!goToDevBranch()) return false;
21
+ if (!updateCurrentBranch()) return false;
22
+
10
23
  const currentVersion = getVersion();
11
24
  if (!currentVersion) {
12
- logError('❌', 'Cannot get version from package.json');
25
+ logError('❌', 'Не удалось получить версию из package.json');
13
26
  return false;
14
27
  }
15
-
16
- if (execCommand(`git switch -c release/${currentVersion}`, null, null)) {
17
- logSuccess('🌱', `Create new release branch ${getCurrentBranch()}.`);
18
- return true;
28
+
29
+ if (!execCommand(`git switch -c release/${currentVersion}`)) {
30
+ return false;
19
31
  }
20
-
21
- return false;
32
+
33
+ logSuccess('🌱', 'Создана новая release-ветка %s.', getCurrentBranch());
34
+ return true;
22
35
  }
23
36
 
24
37
  function releasePush() {
25
38
  const currentBranch = getCurrentBranch();
26
-
27
- if (execCommand(`git push origin ${currentBranch}`, null, null)) {
28
- logSuccess('📤', `Push release branch ${currentBranch}.`);
29
- logSuccess('🌐', 'Go to https://gitlab.spectrumdata.tech/ and merge branch manually.');
30
- goToMainBranch();
31
- return true;
39
+ if (!execCommand(`git push origin ${currentBranch}`)) {
40
+ return false;
32
41
  }
33
-
34
- return false;
35
- }
36
42
 
37
- function releaseClose() {
38
- const mainBranch = getMainBranch();
39
-
40
- goToMainBranch();
41
- updateCurrentBranch();
42
-
43
- goToDevBranch();
44
- updateCurrentBranch();
45
-
46
- if (execCommand(`git merge ${mainBranch}`, null, null)) {
47
- logSuccess('🔀', `Merge branch ${getCurrentBranch()} with ${mainBranch}.`);
48
-
49
- if (execCommand(`git push origin ${getCurrentBranch()} -o ci.skip`, null, null)) {
50
- logSuccess('📤', `Push branch ${getCurrentBranch()}.`);
51
- return true;
52
- }
43
+ logSuccess('📤', 'Release-ветка %s отправлена.', currentBranch);
44
+ const mrUrl = getMergeRequestUrl(currentBranch, getMainBranch());
45
+ if (mrUrl) {
46
+ logSuccess('🌐', 'Создать Merge Request: %s', mrUrl);
53
47
  }
54
-
55
- return false;
48
+ return goToMainBranch();
56
49
  }
57
50
 
58
- function releaseStart() {
59
- const steps = [
60
- { name: 'Create release branch', fn: releaseCreate },
61
- { name: 'Change header', fn: changelogChangeHeader },
62
- { name: 'Remove empty chapters', fn: changelogRemoveEmptyChapters },
63
- { name: 'Add unreleased block', fn: changelogAddUnreleasedBlock },
64
- { name: 'Commit changelog', fn: changelogCommit },
65
- { name: 'Push release', fn: releasePush }
66
- ];
67
-
68
- for (const step of steps) {
69
- if (!step.fn()) {
70
- logError('✖', `Failed at step: ${step.name}`);
71
- return false;
72
- }
51
+ function releaseCheckChangelogLint() {
52
+ const result = requireChangelogFormatted();
53
+ if (!result.ok) {
54
+ logError('❌', result.reason || 'Файл CHANGELOG.md не прошел линтинг.');
55
+ return false;
73
56
  }
74
-
75
- logSuccess('🚀', 'Release started successfully!');
76
57
  return true;
77
58
  }
78
59
 
60
+ function releaseClose() {
61
+ return runCommand({
62
+ name: 'release close',
63
+ checks: [
64
+ { name: 'git-repo', run: requireGitRepo },
65
+ { name: 'clean-working-tree', run: requireCleanWorkingTree },
66
+ { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
67
+ { name: 'on-release-branch', run: requireOnReleaseBranch },
68
+ { name: 'main-and-dev-branches', run: requireMainAndDevBranches }
69
+ ],
70
+ steps: [
71
+ { name: 'switch-main', run: () => goToMainBranch() },
72
+ { name: 'update-main', run: () => updateCurrentBranch() },
73
+ { name: 'switch-dev', run: () => goToDevBranch() },
74
+ { name: 'update-dev', run: () => updateCurrentBranch() },
75
+ {
76
+ name: 'merge-main-into-dev',
77
+ run: () => {
78
+ const mainBranch = getMainBranch();
79
+ if (!execCommand(`git merge ${mainBranch}`)) {
80
+ return false;
81
+ }
82
+ logSuccess('🔀', 'Ветка %s смержена с %s.', getCurrentBranch(), mainBranch);
83
+ return true;
84
+ }
85
+ },
86
+ {
87
+ name: 'push-dev',
88
+ run: () => {
89
+ const currentBranch = getCurrentBranch();
90
+ if (!execCommand(`git push origin ${currentBranch} -o ci.skip`)) {
91
+ return false;
92
+ }
93
+ logSuccess('📤', 'Ветка %s отправлена.', currentBranch);
94
+ return true;
95
+ }
96
+ }
97
+ ]
98
+ });
99
+ }
100
+
101
+ function releaseStart() {
102
+ return runCommand({
103
+ name: 'release start',
104
+ checks: [
105
+ { name: 'git-repo', run: requireGitRepo },
106
+ { name: 'clean-working-tree', run: requireCleanWorkingTree },
107
+ { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
108
+ { name: 'main-and-dev-branches', run: requireMainAndDevBranches },
109
+ { name: 'package-version', run: requirePackageVersion },
110
+ { name: 'changelog-exists', run: () => requireFileExists('CHANGELOG.md') },
111
+ { name: 'unreleased-template-exists', run: () => requireFileExists(path.join(__dirname, 'UNRELEASED.md')) },
112
+ { name: 'changelog-prettier-check', run: requireChangelogFormatted },
113
+ { name: 'release-branch-missing', run: (ctx) => requireReleaseBranchMissing(`release/${ctx.version}`) }
114
+ ],
115
+ steps: [
116
+ { name: 'change-header', run: changelogChangeHeader },
117
+ { name: 'remove-empty-chapters', run: changelogRemoveEmptyChapters },
118
+ { name: 'add-unreleased-block', run: changelogAddUnreleasedBlock },
119
+ { name: 'lint-changelog', run: releaseCheckChangelogLint },
120
+ { name: 'create-release-branch', run: releaseCreate },
121
+ { name: 'commit-changelog', run: changelogCommit },
122
+ { name: 'push-release', run: releasePush }
123
+ ]
124
+ });
125
+ }
79
126
 
80
127
  module.exports = {
81
128
  releaseCreate,
package/src/utils.js CHANGED
@@ -14,12 +14,29 @@ function log(emoji, message, color = colors.green) {
14
14
  console.log(` ${emoji} ${message}`);
15
15
  }
16
16
 
17
- function logSuccess(emoji, message) {
18
- log(emoji, `${colors.green}${message}${colors.reset}`);
17
+ function formatLogMessage(template, args, color) {
18
+ if (!args || args.length === 0) {
19
+ return template;
20
+ }
21
+
22
+ let argIndex = 0;
23
+ return template.replace(/%s/g, () => {
24
+ if (argIndex >= args.length) {
25
+ return '%s';
26
+ }
27
+
28
+ const value = args[argIndex];
29
+ argIndex += 1;
30
+ return `${color}${String(value)}${colors.reset}`;
31
+ });
19
32
  }
20
33
 
21
- function logError(emoji, message) {
22
- log(emoji, `${colors.red}${message}${colors.reset}`);
34
+ function logSuccess(emoji, message, ...args) {
35
+ log(emoji, formatLogMessage(message, args, colors.green));
36
+ }
37
+
38
+ function logError(emoji, message, ...args) {
39
+ log(emoji, formatLogMessage(message, args, colors.red));
23
40
  }
24
41
 
25
42
  function execSilent(command) {
@@ -30,14 +47,11 @@ function execSilent(command) {
30
47
  }
31
48
  }
32
49
 
33
- function execCommand(command, successMessage, errorMessage) {
50
+ function execCommand(command) {
34
51
  try {
35
52
  execSync(command, { stdio: 'pipe' });
36
53
  return true;
37
54
  } catch (error) {
38
- if (errorMessage) {
39
- console.error(errorMessage);
40
- }
41
55
  return false;
42
56
  }
43
57
  }
@@ -78,6 +92,28 @@ function getVersion() {
78
92
  }
79
93
  }
80
94
 
95
+ function getRemoteUrl() {
96
+ const raw = execSilent('git remote get-url origin');
97
+ if (!raw) return null;
98
+
99
+ const sshMatch = raw.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
100
+ if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
101
+
102
+ const httpsMatch = raw.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/);
103
+ if (httpsMatch) return `https://${httpsMatch[1]}/${httpsMatch[2]}`;
104
+
105
+ return null;
106
+ }
107
+
108
+ function getMergeRequestUrl(sourceBranch, targetBranch) {
109
+ const baseUrl = getRemoteUrl();
110
+ if (!baseUrl) return null;
111
+
112
+ const source = encodeURIComponent(sourceBranch);
113
+ const target = encodeURIComponent(targetBranch);
114
+ return `${baseUrl}/-/merge_requests/new?merge_request[source_branch]=${source}&merge_request[target_branch]=${target}`;
115
+ }
116
+
81
117
  module.exports = {
82
118
  colors,
83
119
  log,
@@ -89,5 +125,7 @@ module.exports = {
89
125
  getMainBranch,
90
126
  getDevelopBranch,
91
127
  getPackageManager,
92
- getVersion
128
+ getVersion,
129
+ getRemoteUrl,
130
+ getMergeRequestUrl
93
131
  };
package/src/version.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  const fs = require('fs');
3
- const { execCommand, logSuccess, logError, colors } = require('./utils');
3
+ const { logSuccess, logError } = require('./utils');
4
+ const { runCommand } = require('./command-executor');
5
+ const { requireGitRepo, requireCleanWorkingTree, requireFileExists, requirePackageVersion } = require('./preflight');
4
6
 
5
7
  function upVersion(oldVersion, upType) {
6
8
  const [major, minor, patch] = oldVersion.split('.').map(Number);
@@ -16,35 +18,39 @@ function upVersion(oldVersion, upType) {
16
18
  }
17
19
  }
18
20
 
19
- function getVersionTypescript() {
20
- try {
21
- const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
22
- return packageJson.version;
23
- } catch (error) {
24
- return null;
25
- }
26
- }
27
-
28
- function setVersionTypescript(versionType) {
21
+ function updateVersionFile(versionType) {
29
22
  try {
30
23
  const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
31
24
  const oldVersion = packageJson.version;
32
25
  const newVersion = upVersion(oldVersion, versionType);
33
-
26
+
34
27
  packageJson.version = newVersion;
35
28
  fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
36
-
37
- logSuccess('🔖', `Current version ${oldVersion} up to ${colors.green}${newVersion}${colors.reset}.`);
29
+ logSuccess('🔖', 'Версия обновлена с %s до %s.', oldVersion, newVersion);
38
30
  return true;
39
31
  } catch (error) {
40
- logError('❌', 'Error updating version in package.json');
32
+ logError('❌', 'Ошибка при обновлении версии в package.json');
41
33
  return false;
42
34
  }
43
35
  }
44
36
 
37
+ function setVersion(versionType) {
38
+ return runCommand({
39
+ name: `version up ${versionType}`,
40
+ checks: [
41
+ { name: 'git-repo', run: requireGitRepo },
42
+ { name: 'package-json-exists', run: () => requireFileExists('package.json') },
43
+ { name: 'package-version', run: requirePackageVersion },
44
+ { name: 'clean-working-tree', run: requireCleanWorkingTree }
45
+ ],
46
+ steps: [
47
+ { name: 'update-package-version', run: () => updateVersionFile(versionType) }
48
+ ]
49
+ });
50
+ }
51
+
45
52
 
46
53
  module.exports = {
47
54
  upVersion,
48
- getVersionTypescript,
49
- setVersionTypescript
55
+ setVersion
50
56
  };