@mirta/cli 0.3.5 → 0.4.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/README.md +295 -78
- package/README.ru.md +303 -79
- package/dist/constants.mjs +9 -0
- package/dist/deploy.mjs +902 -0
- package/dist/index.mjs +759 -20
- package/dist/package.mjs +128 -172
- package/dist/publish.mjs +58 -38
- package/dist/release.mjs +258 -158
- package/dist/resolve.mjs +457 -0
- package/locales/en-US.json +58 -0
- package/locales/ru-RU.json +58 -0
- package/package.json +21 -9
- package/dist/github.mjs +0 -135
- package/dist/locales/en-US.json +0 -21
- package/dist/locales/ru-RU.json +0 -21
- package/dist/shell.mjs +0 -189
package/dist/release.mjs
CHANGED
|
@@ -1,195 +1,295 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { c as checkIsInWorkTreeAsync, g as getRepositoryDetails, b as assertIsSyncedWithRemoteAsync, p as prompts, d as assertWorkflowResultAsync } from './github.mjs';
|
|
5
|
-
import { g as getLocalized, u as useLogger, r as runCommandAsync } from './shell.mjs';
|
|
1
|
+
import { u as updateVersion, h as hasScript, g as getCurrentVersion } from './package.mjs';
|
|
2
|
+
import { inc, valid, prerelease } from 'semver';
|
|
3
|
+
import { a as assertNoParseErrors, l as logger, p as prompts, g as getRepositoryDetails, b as assertIsSyncedWithRemoteAsync, c as assertWorkflowResultAsync, r as runCommandAsync, t, d as checkIsInWorkTreeAsync } from './index.mjs';
|
|
6
4
|
import chalk from 'chalk';
|
|
7
|
-
import
|
|
5
|
+
import { r as resolveConfigAsync } from './resolve.mjs';
|
|
8
6
|
import 'node:path';
|
|
9
|
-
import '
|
|
7
|
+
import 'p-map';
|
|
8
|
+
import 'node:fs/promises';
|
|
9
|
+
import '@mirta/workspace';
|
|
10
|
+
import '@mirta/package';
|
|
11
|
+
import './constants.mjs';
|
|
10
12
|
import 'prompts';
|
|
13
|
+
import '@mirta/staged-args';
|
|
11
14
|
import 'node:child_process';
|
|
12
|
-
import '
|
|
13
|
-
import '
|
|
15
|
+
import '@mirta/i18n';
|
|
16
|
+
import '../package.json' with { type: 'json' };
|
|
17
|
+
import '@mirta/basics/fuzzy';
|
|
18
|
+
import 'node:os';
|
|
19
|
+
import 'node:path/posix';
|
|
20
|
+
import 'jsonc-parser';
|
|
21
|
+
import '@mirta/basics/object';
|
|
14
22
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
const options = ({
|
|
24
|
+
'config': {
|
|
25
|
+
type: 'string',
|
|
26
|
+
short: 'c',
|
|
27
|
+
},
|
|
28
|
+
'dry-run': {
|
|
18
29
|
type: 'boolean',
|
|
19
|
-
default: false,
|
|
20
30
|
},
|
|
21
|
-
preid: {
|
|
31
|
+
'preid': {
|
|
22
32
|
type: 'string',
|
|
23
33
|
},
|
|
24
|
-
|
|
34
|
+
'skip-git': {
|
|
25
35
|
type: 'boolean',
|
|
26
|
-
default: false,
|
|
27
36
|
},
|
|
28
|
-
|
|
37
|
+
'skip-prompts': {
|
|
29
38
|
type: 'boolean',
|
|
30
|
-
default: false,
|
|
31
39
|
},
|
|
32
|
-
|
|
40
|
+
// Deprecated. Use 'skip-git' instead
|
|
41
|
+
'skipGit': {
|
|
33
42
|
type: 'boolean',
|
|
34
|
-
default: false,
|
|
35
43
|
},
|
|
36
|
-
|
|
44
|
+
// Deprecated. Use 'skip-prompts' instead
|
|
45
|
+
'skipPrompts': {
|
|
37
46
|
type: 'boolean',
|
|
38
|
-
short: 'h',
|
|
39
|
-
default: false,
|
|
40
47
|
},
|
|
41
|
-
|
|
48
|
+
// Deprecated. Use 'dry-run' instead
|
|
49
|
+
'dry': {
|
|
42
50
|
type: 'boolean',
|
|
43
|
-
short: 'v',
|
|
44
|
-
default: false,
|
|
45
51
|
},
|
|
46
52
|
});
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
function parseArgs(args) {
|
|
54
|
+
const parseResult = args.parseFinal(options);
|
|
55
|
+
assertNoParseErrors(parseResult);
|
|
56
|
+
const { values, positionals } = parseResult.data;
|
|
57
|
+
if (values.dry) {
|
|
58
|
+
logger.warn('Deprecated flag "--dry" used. Please use "--dry-run" instead');
|
|
59
|
+
values['dry-run'] = values['dry-run'] !== false;
|
|
60
|
+
}
|
|
61
|
+
if (values.skipGit) {
|
|
62
|
+
logger.warn('Deprecated flag "--skipGit" used. Please use "--skip-git" instead');
|
|
63
|
+
values['skip-git'] = values['skip-git'] !== false;
|
|
64
|
+
}
|
|
65
|
+
if (values.skipPrompts) {
|
|
66
|
+
logger.warn('Deprecated flag "--skipPrompts" used. Please use "--skip-prompts" instead');
|
|
67
|
+
values['skip-prompts'] = values['skip-prompts'] !== false;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
values,
|
|
71
|
+
positionals,
|
|
72
|
+
};
|
|
56
73
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
|
|
75
|
+
function getReleaseTypes(preid) {
|
|
76
|
+
const releaseTypes = [
|
|
77
|
+
'patch',
|
|
78
|
+
'minor',
|
|
79
|
+
'major',
|
|
80
|
+
...(preid
|
|
81
|
+
? (['prepatch', 'preminor', 'premajor', 'prerelease'])
|
|
82
|
+
: []),
|
|
83
|
+
];
|
|
84
|
+
return releaseTypes;
|
|
60
85
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
async function determineTargetVersion(currentVersion, preid, skipPrompts, preferredVersion) {
|
|
87
|
+
let targetVersion = preferredVersion;
|
|
88
|
+
const releaseTypes = getReleaseTypes(preid);
|
|
89
|
+
if (!targetVersion) {
|
|
90
|
+
if (skipPrompts) {
|
|
91
|
+
logger.info('Skipping prompts. Default to "patch" release.');
|
|
92
|
+
targetVersion = inc(currentVersion, 'patch', undefined, preid) ?? '';
|
|
93
|
+
if (!targetVersion)
|
|
94
|
+
throw new Error(`Failed to compute target version from ${currentVersion}`);
|
|
95
|
+
if (!valid(targetVersion))
|
|
96
|
+
throw new Error(`Invalid target version: ${targetVersion}`);
|
|
97
|
+
return targetVersion;
|
|
98
|
+
}
|
|
99
|
+
const choices = releaseTypes
|
|
100
|
+
.map((releaseType) => {
|
|
101
|
+
const version = inc(currentVersion, releaseType, undefined, preid);
|
|
102
|
+
return {
|
|
103
|
+
title: `${releaseType} (${version})`,
|
|
104
|
+
value: version,
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.filter(choice => choice.value !== null)
|
|
108
|
+
.concat({
|
|
109
|
+
title: 'custom',
|
|
110
|
+
value: 'custom',
|
|
111
|
+
});
|
|
112
|
+
const { release } = await prompts({
|
|
113
|
+
type: 'select',
|
|
114
|
+
name: 'release',
|
|
115
|
+
message: 'Select release type',
|
|
116
|
+
choices,
|
|
117
|
+
});
|
|
118
|
+
if (release === 'custom') {
|
|
119
|
+
const { version } = await prompts({
|
|
120
|
+
type: 'text',
|
|
121
|
+
name: 'version',
|
|
122
|
+
message: 'Type custom version',
|
|
123
|
+
initial: currentVersion,
|
|
124
|
+
});
|
|
125
|
+
targetVersion = version;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
targetVersion = release;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Если вместо номера версии передан тип релиза — инкрементируем
|
|
132
|
+
//
|
|
133
|
+
if (releaseTypes.includes(targetVersion))
|
|
134
|
+
targetVersion = inc(currentVersion, targetVersion, undefined, preid) ?? '';
|
|
135
|
+
if (!targetVersion)
|
|
136
|
+
throw new Error(`Failed to compute target version from ${currentVersion}`);
|
|
137
|
+
if (!valid(targetVersion))
|
|
138
|
+
throw new Error(`Invalid target version: ${targetVersion}`);
|
|
139
|
+
return targetVersion;
|
|
94
140
|
}
|
|
95
|
-
|
|
96
|
-
|
|
141
|
+
|
|
142
|
+
const { yellow: yellow$2 } = chalk;
|
|
143
|
+
async function runGitChecksAsync(context) {
|
|
144
|
+
if (!context.inWorkTree || context.skipGit)
|
|
145
|
+
return {};
|
|
146
|
+
const repoDetails = await getRepositoryDetails();
|
|
147
|
+
const { name: repository, connectionType } = repoDetails;
|
|
148
|
+
logger.log(`Repository: ${yellow$2(repository)}`);
|
|
149
|
+
await assertIsSyncedWithRemoteAsync(repository);
|
|
150
|
+
logger.step('Ensuring CI status for HEAD...');
|
|
151
|
+
await assertWorkflowResultAsync(repository, 'build');
|
|
152
|
+
return { repository, connectionType };
|
|
97
153
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
154
|
+
|
|
155
|
+
const { yellow: yellow$1 } = chalk;
|
|
156
|
+
async function executeReleaseAsync(context, config) {
|
|
157
|
+
const runAsync = runCommandAsync.dry(context.isDryRun);
|
|
158
|
+
let isCommitted = false;
|
|
159
|
+
try {
|
|
160
|
+
await updateVersion(context.targetVersion, config);
|
|
161
|
+
logger.info(t('release.versionUpdated', {
|
|
162
|
+
newVersion: yellow$1(`v${context.targetVersion}`),
|
|
163
|
+
}));
|
|
164
|
+
if (context.inWorkTree && !context.skipGit && hasScript('changelog')) {
|
|
165
|
+
logger.step(t('release.changelogGenerating'));
|
|
166
|
+
await runCommandAsync('pnpm', ['run', 'changelog'], { shell: true });
|
|
167
|
+
if (!context.skipPrompts) {
|
|
168
|
+
const { isContinue } = await prompts({
|
|
169
|
+
type: 'confirm',
|
|
170
|
+
name: 'isContinue',
|
|
171
|
+
message: t('release.changelogConfirm'),
|
|
172
|
+
});
|
|
173
|
+
if (!isContinue) {
|
|
174
|
+
logger.cancel(t('release.canceled'));
|
|
175
|
+
logger.step(t('release.versionReverting'));
|
|
176
|
+
await updateVersion(context.currentVersion, config);
|
|
177
|
+
logger.step(t('release.versionReverted', {
|
|
178
|
+
oldVersion: yellow$1(`v${context.currentVersion}`),
|
|
179
|
+
}));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
logger.step(t('release.lockfileUpdating'));
|
|
185
|
+
await runAsync('pnpm', ['install', '--prefer-offline'], { shell: true });
|
|
186
|
+
if (!context.inWorkTree || !context.repository) {
|
|
187
|
+
logger.note(t('release.final.noGit'));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!context.skipGit && context.connectionType === 'ssh') {
|
|
191
|
+
const { stdout } = await runCommandAsync('git', ['diff'], { stdio: 'pipe' });
|
|
192
|
+
if (stdout) {
|
|
193
|
+
logger.step(t('release.committing'));
|
|
194
|
+
await runAsync('git', ['add', '-A']);
|
|
195
|
+
await runAsync('git', ['commit', '-m', `release: v${context.targetVersion}`]);
|
|
196
|
+
if (!context.isDryRun)
|
|
197
|
+
isCommitted = true;
|
|
198
|
+
logger.step(t('release.pushing'));
|
|
199
|
+
const tagName = `v${context.targetVersion}`;
|
|
200
|
+
const { stdout: existingTag } = await runCommandAsync('git', ['tag', '-l', tagName], { stdio: 'pipe' });
|
|
201
|
+
if (!existingTag) {
|
|
202
|
+
await runAsync('git', ['tag', tagName]);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
logger.warn(t('release.tagAlreadyExists', {
|
|
206
|
+
tag: yellow$1(tagName),
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
await runAsync('git', ['push']);
|
|
210
|
+
await runAsync('git', ['push', 'origin', `refs/tags/v${context.targetVersion}`]);
|
|
211
|
+
logger.note(yellow$1(t('release.final.gitRemote')));
|
|
212
|
+
logger.note(t('release.final.gitRemoteStatus', {
|
|
213
|
+
workflowLink: `https://github.com/${context.repository}/actions/workflows/release.yml`,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
logger.step(t('release.final.gitNoChanges'));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
logger.note(t('release.final.gitManual', {
|
|
222
|
+
version: yellow$1(`v${context.targetVersion}`),
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
121
225
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
226
|
+
catch (e) {
|
|
227
|
+
if (!isCommitted) {
|
|
228
|
+
logger.step(t('release.versionReverting'));
|
|
229
|
+
try {
|
|
230
|
+
await updateVersion(context.currentVersion, config);
|
|
231
|
+
logger.step(t('release.versionReverted', {
|
|
232
|
+
oldVersion: yellow$1(`v${context.currentVersion}`),
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
catch (rollbackError) {
|
|
236
|
+
logger.error(t('release.error.versionRevertingFailed')
|
|
237
|
+
+ '\n'
|
|
238
|
+
+ (rollbackError instanceof Error ? rollbackError.message : String(rollbackError)));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
logger.warn(t('release.error.versionAlreadyCommitted'));
|
|
243
|
+
}
|
|
244
|
+
throw e;
|
|
125
245
|
}
|
|
126
246
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
247
|
+
|
|
248
|
+
const { yellow } = chalk;
|
|
249
|
+
async function runAsync(args) {
|
|
250
|
+
// === 1. Парсинг аргументов ===
|
|
251
|
+
const { values: argv, positionals } = parseArgs(args);
|
|
252
|
+
const currentVersion = getCurrentVersion();
|
|
253
|
+
const preid = argv.preid ?? prerelease(currentVersion)?.[0];
|
|
254
|
+
const isDryRun = argv['dry-run'] ?? false;
|
|
255
|
+
const skipGit = argv['skip-git'] ?? false;
|
|
256
|
+
const skipPrompts = argv['skip-prompts'] ?? false;
|
|
257
|
+
// === 2. Проверка окружения ===
|
|
258
|
+
const inWorkTree = await checkIsInWorkTreeAsync();
|
|
259
|
+
// === 3. Git-проверки ===
|
|
260
|
+
const gitContext = await runGitChecksAsync({ inWorkTree, skipGit });
|
|
261
|
+
// === 4. Определение версии ===
|
|
262
|
+
const targetVersion = await determineTargetVersion(currentVersion, preid, skipPrompts, positionals[1]);
|
|
263
|
+
// === 5. Формирование контекста ===
|
|
264
|
+
const context = {
|
|
265
|
+
currentVersion,
|
|
266
|
+
targetVersion,
|
|
267
|
+
preid,
|
|
268
|
+
isDryRun,
|
|
269
|
+
skipGit,
|
|
270
|
+
skipPrompts,
|
|
271
|
+
inWorkTree,
|
|
272
|
+
...gitContext,
|
|
273
|
+
};
|
|
274
|
+
// === 6. Подтверждение (если нужно) ===
|
|
275
|
+
if (!skipPrompts) {
|
|
140
276
|
const { confirmRelease } = await prompts({
|
|
141
277
|
type: 'confirm',
|
|
142
278
|
name: 'confirmRelease',
|
|
143
|
-
message: `Releasing v${targetVersion}
|
|
279
|
+
message: `Releasing ${yellow(`v${targetVersion}`)} → Continue?`,
|
|
144
280
|
});
|
|
145
281
|
if (!confirmRelease) {
|
|
146
|
-
logger.cancel('No changes
|
|
282
|
+
logger.cancel('Release canceled. No changes made');
|
|
147
283
|
return;
|
|
148
284
|
}
|
|
149
285
|
}
|
|
150
|
-
|
|
151
|
-
logger.log(
|
|
152
|
-
await assertWorkflowResultAsync(repository, 'build');
|
|
153
|
-
}
|
|
154
|
-
updateVersion(targetVersion);
|
|
155
|
-
isVersionUpdated = true;
|
|
156
|
-
if (inWorkTree && hasScript('changelog')) {
|
|
157
|
-
logger.log('Generating changelog...');
|
|
158
|
-
await runCommandAsync('pnpm', ['run', 'changelog']);
|
|
159
|
-
if (!skipPrompts) {
|
|
160
|
-
const { isContinue } = await prompts({
|
|
161
|
-
type: 'confirm',
|
|
162
|
-
name: 'isContinue',
|
|
163
|
-
message: 'Changelog generated. Does it look good?',
|
|
164
|
-
});
|
|
165
|
-
if (!isContinue)
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
logger.log('Updating lock-file...');
|
|
170
|
-
await runCommandAsync('pnpm', ['install', '--prefer-offline']);
|
|
171
|
-
if (!skipGit && connectionType === 'ssh') {
|
|
172
|
-
const { stdout } = await runCommandAsync('git', ['diff'], { stdio: 'pipe' });
|
|
173
|
-
if (stdout) {
|
|
174
|
-
logger.step('Committing version changes...');
|
|
175
|
-
await runCommandIfNotDryAsync('git', ['add', '-A']);
|
|
176
|
-
await runCommandIfNotDryAsync('git', ['commit', '-m', `release: v${targetVersion}`]);
|
|
177
|
-
logger.step('Pushing to GitHub');
|
|
178
|
-
await runCommandIfNotDryAsync('git', ['tag', `v${targetVersion}`]);
|
|
179
|
-
await runCommandIfNotDryAsync('git', ['push', 'origin', `refs/tags/v${targetVersion}`]);
|
|
180
|
-
await runCommandIfNotDryAsync('git', ['push']);
|
|
181
|
-
logger.note(yellow('Release will be done via GitHub Actions.')
|
|
182
|
-
+ `\nCheck status at https://github.com/${repository}/actions/workflows/release.yml`);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
logger.info('No changes to commit.');
|
|
186
|
-
}
|
|
286
|
+
else {
|
|
287
|
+
logger.log(`Releasing ${yellow(`v${targetVersion}`)}`);
|
|
187
288
|
}
|
|
289
|
+
// === 7. Загружаем конфиг ===
|
|
290
|
+
const { config: mirtaConfig } = await resolveConfigAsync(process.cwd(), argv.config);
|
|
291
|
+
// === 8. Выполнение релиза ===
|
|
292
|
+
await executeReleaseAsync(context, mirtaConfig);
|
|
188
293
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// Revert version changes on failed release
|
|
192
|
-
updateVersion(currentVersion);
|
|
193
|
-
}
|
|
194
|
-
throw e;
|
|
195
|
-
});
|
|
294
|
+
|
|
295
|
+
export { runAsync };
|