@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/github.mjs
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import p from 'prompts';
|
|
2
|
-
import { r as runCommandAsync } from './shell.mjs';
|
|
3
|
-
|
|
4
|
-
class PromptCanceledError extends Error {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
// Убедимся, что экземпляр имеет правильный прототип
|
|
8
|
-
Object.setPrototypeOf(this, PromptCanceledError.prototype);
|
|
9
|
-
this.name = 'PromptCanceledError';
|
|
10
|
-
Error.captureStackTrace(this, PromptCanceledError);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* @param {import('prompts').PromptObject<string> | Array<import('prompts').PromptObject<string>>} questions
|
|
15
|
-
* @param {import('prompts').Options} options
|
|
16
|
-
*/
|
|
17
|
-
async function prompts(questions, options) {
|
|
18
|
-
const po = {
|
|
19
|
-
onCancel: () => {
|
|
20
|
-
throw new PromptCanceledError();
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
return await p(questions, po);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const baseUrl = 'https://api.github.com/repos';
|
|
27
|
-
/** Класс ошибки уровня Git. */
|
|
28
|
-
class GitError extends Error {
|
|
29
|
-
constructor(message) {
|
|
30
|
-
super(message);
|
|
31
|
-
// Убедимся, что экземпляр имеет правильный прототип
|
|
32
|
-
Object.setPrototypeOf(this, GitError.prototype);
|
|
33
|
-
this.name = 'GitError';
|
|
34
|
-
this.message = message;
|
|
35
|
-
Error.captureStackTrace(this, GitError);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/** Класс ошибки уровня взаимодействия GitHub. */
|
|
39
|
-
class GithubError extends Error {
|
|
40
|
-
constructor(message) {
|
|
41
|
-
super(message);
|
|
42
|
-
// Убедимся, что экземпляр имеет правильный прототип
|
|
43
|
-
Object.setPrototypeOf(this, GithubError.prototype);
|
|
44
|
-
this.name = 'GithubError';
|
|
45
|
-
this.message = message;
|
|
46
|
-
Error.captureStackTrace(this, GithubError);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Класс ошибки уровня GitHub Workflow. */
|
|
50
|
-
class WorkflowStatusError extends Error {
|
|
51
|
-
constructor(message) {
|
|
52
|
-
super(message);
|
|
53
|
-
// Убедимся, что экземпляр имеет правильный прототип
|
|
54
|
-
Object.setPrototypeOf(this, WorkflowStatusError.prototype);
|
|
55
|
-
this.name = 'WorkflowStatusError';
|
|
56
|
-
this.message = message;
|
|
57
|
-
Error.captureStackTrace(this, WorkflowStatusError);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** Определяет, находится ли текущее решение в рабочем дереве Git. */
|
|
61
|
-
async function checkIsInWorkTreeAsync() {
|
|
62
|
-
try {
|
|
63
|
-
const { stdout } = await runCommandAsync('git', ['rev-parse', '--is-inside-work-tree'], { stdio: 'pipe' });
|
|
64
|
-
return stdout === 'true';
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const getConnectionType = (url) => {
|
|
71
|
-
if (url.startsWith('git@'))
|
|
72
|
-
return 'ssh';
|
|
73
|
-
if (url.startsWith('https://'))
|
|
74
|
-
return 'https';
|
|
75
|
-
};
|
|
76
|
-
/** Возвращает имя репозитория и тип подключения. */
|
|
77
|
-
async function getRepositoryDetails() {
|
|
78
|
-
const { stdout, stderr } = await runCommandAsync('git', ['config', 'remote.origin.url'], { stdio: 'pipe' });
|
|
79
|
-
if (stderr)
|
|
80
|
-
throw new GitError(stderr);
|
|
81
|
-
// Match the URLs like:
|
|
82
|
-
// git@github.com:wb-mirta/core.git
|
|
83
|
-
// https://github.com/wb-mirta/core.git
|
|
84
|
-
//
|
|
85
|
-
const regex = /^(?:git@|https:\/\/)(?:[^/:]+)[/:]?(.*?).git$/i;
|
|
86
|
-
const match = regex
|
|
87
|
-
.exec(stdout);
|
|
88
|
-
if (!match?.[1])
|
|
89
|
-
throw new GitError(`Unable to detect remote origin url`);
|
|
90
|
-
return {
|
|
91
|
-
name: match[1],
|
|
92
|
-
connectionType: getConnectionType(stdout),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/** Возвращает SHA текущего HEAD. */
|
|
96
|
-
async function getShaAsync() {
|
|
97
|
-
return (await runCommandAsync('git', ['rev-parse', 'HEAD'])).stdout;
|
|
98
|
-
}
|
|
99
|
-
/** Возвращает имя текущей ветки. */
|
|
100
|
-
async function getBranchAsync() {
|
|
101
|
-
return (await runCommandAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout;
|
|
102
|
-
}
|
|
103
|
-
/** Проверяет, синхронизирован ли текущий HEAD с удаленным. */
|
|
104
|
-
async function assertIsSyncedWithRemoteAsync(repository) {
|
|
105
|
-
let isSynced = false;
|
|
106
|
-
try {
|
|
107
|
-
const branch = await getBranchAsync();
|
|
108
|
-
const remote = await fetch(`${baseUrl}/${repository}/commits/${branch}?per_page=1`);
|
|
109
|
-
const data = await remote.json();
|
|
110
|
-
isSynced = data.sha === await getShaAsync();
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
throw new GithubError('Failed to check whether local HEAD is up-to-date with remote');
|
|
114
|
-
}
|
|
115
|
-
if (!isSynced)
|
|
116
|
-
throw new GithubError('Local HEAD is not up-to-date with remote');
|
|
117
|
-
}
|
|
118
|
-
/** Проверяет успешность CI-построения последнего коммита. */
|
|
119
|
-
async function assertWorkflowResultAsync(repository, name) {
|
|
120
|
-
let isBuildPassed = false;
|
|
121
|
-
try {
|
|
122
|
-
const sha = await getShaAsync();
|
|
123
|
-
const result = await fetch(`${baseUrl}/${repository}/actions/runs?head_sha=${sha}&status=completed&exclude_pull_requests=true`);
|
|
124
|
-
const data = await result.json();
|
|
125
|
-
isBuildPassed = data.workflow_runs.some(({ name: workflowName, conclusion }) => workflowName.toLowerCase() === name.toLowerCase()
|
|
126
|
-
&& conclusion === 'success');
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
throw new GithubError('Unable to get CI status for the current commit');
|
|
130
|
-
}
|
|
131
|
-
if (!isBuildPassed)
|
|
132
|
-
throw new WorkflowStatusError('CI build of the latest commit has not passed yet');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export { GitError as G, PromptCanceledError as P, WorkflowStatusError as W, GithubError as a, assertIsSyncedWithRemoteAsync as b, checkIsInWorkTreeAsync as c, assertWorkflowResultAsync as d, getRepositoryDetails as g, prompts as p };
|
package/dist/locales/en-US.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"validation": {
|
|
3
|
-
"required": "Must be provided"
|
|
4
|
-
},
|
|
5
|
-
"status": {
|
|
6
|
-
"info": "Info",
|
|
7
|
-
"note": "Note",
|
|
8
|
-
"success": "Success",
|
|
9
|
-
"warn": "Warn",
|
|
10
|
-
"error": "Error",
|
|
11
|
-
"canceled": "Canceled"
|
|
12
|
-
},
|
|
13
|
-
"accent": {
|
|
14
|
-
"recommended": "(recommended)",
|
|
15
|
-
"ifConfigured": "(if configured)"
|
|
16
|
-
},
|
|
17
|
-
"errors": {
|
|
18
|
-
"operationCanceled": "Operation canceled",
|
|
19
|
-
"rootIsNotRelative": "Location is out of working directory"
|
|
20
|
-
}
|
|
21
|
-
}
|
package/dist/locales/ru-RU.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"validation": {
|
|
3
|
-
"required": "Требуется указать значение"
|
|
4
|
-
},
|
|
5
|
-
"status": {
|
|
6
|
-
"info": "Инфо",
|
|
7
|
-
"note": "Примечание",
|
|
8
|
-
"success": "Успешно",
|
|
9
|
-
"warn": "Внимание",
|
|
10
|
-
"error": "Ошибка",
|
|
11
|
-
"canceled": "Отменено"
|
|
12
|
-
},
|
|
13
|
-
"accent": {
|
|
14
|
-
"recommended": "(рекомендуется)",
|
|
15
|
-
"ifConfigured": "(если настроен)"
|
|
16
|
-
},
|
|
17
|
-
"errors": {
|
|
18
|
-
"operationCanceled": "Операция прервана",
|
|
19
|
-
"rootIsNotRelative": "Расположение находится вне рабочей директории"
|
|
20
|
-
}
|
|
21
|
-
}
|
package/dist/shell.mjs
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { existsSync, promises } from 'node:fs';
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
import merge from 'lodash.merge';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
|
|
8
|
-
const fallbackLocale = 'en-US';
|
|
9
|
-
const localesPath = './locales';
|
|
10
|
-
let currentLocale = '';
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
13
|
-
* Used to link obtained locale with correct locale file.
|
|
14
|
-
*
|
|
15
|
-
* @param locale Obtained locale
|
|
16
|
-
* @returns locale that linked with correct name
|
|
17
|
-
*/
|
|
18
|
-
function linkLocale(locale) {
|
|
19
|
-
if (locale === 'C')
|
|
20
|
-
return fallbackLocale;
|
|
21
|
-
let linkedLocale;
|
|
22
|
-
try {
|
|
23
|
-
linkedLocale = Intl.getCanonicalLocales(locale)[0];
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.warn(`${JSON.stringify(error)}, invalid language tag: "${locale}"`);
|
|
27
|
-
}
|
|
28
|
-
switch (linkedLocale) {
|
|
29
|
-
default:
|
|
30
|
-
linkedLocale = locale;
|
|
31
|
-
}
|
|
32
|
-
return linkedLocale;
|
|
33
|
-
}
|
|
34
|
-
function getLocale() {
|
|
35
|
-
if (currentLocale)
|
|
36
|
-
return currentLocale;
|
|
37
|
-
const shellLocale = process.env.LC_ALL
|
|
38
|
-
?? process.env.LC_MESSAGES
|
|
39
|
-
?? process.env.LANG
|
|
40
|
-
?? Intl.DateTimeFormat().resolvedOptions().locale;
|
|
41
|
-
return currentLocale = linkLocale(shellLocale.split('.')[0].replace('_', '-'));
|
|
42
|
-
}
|
|
43
|
-
async function loadLanguageFile(filePath) {
|
|
44
|
-
return await promises.readFile(filePath, 'utf-8').then((content) => {
|
|
45
|
-
const data = JSON.parse(content);
|
|
46
|
-
return data;
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
async function loadLocale(localesRoot) {
|
|
50
|
-
currentLocale = getLocale();
|
|
51
|
-
const fallbackFilePath = resolve(localesRoot, `${fallbackLocale}.json`);
|
|
52
|
-
const targetFilePath = resolve(localesRoot, `${currentLocale}.json`);
|
|
53
|
-
const messages = await loadLanguageFile(fallbackFilePath);
|
|
54
|
-
if (!messages)
|
|
55
|
-
throw Error('Fallback locale file not found.');
|
|
56
|
-
if (existsSync(targetFilePath))
|
|
57
|
-
merge(messages, await loadLanguageFile(targetFilePath));
|
|
58
|
-
return messages;
|
|
59
|
-
}
|
|
60
|
-
let localized;
|
|
61
|
-
async function getLocalized() {
|
|
62
|
-
const path = fileURLToPath(new URL(localesPath, import.meta.url));
|
|
63
|
-
return localized ??= await loadLocale(path);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { dim, red, cyan, green, yellow, bgRed, bgCyan, bgGreen, bgYellow, } = chalk;
|
|
67
|
-
const dot = '•';
|
|
68
|
-
const banner = `Mirta ${dot}`;
|
|
69
|
-
const redBanner = red(banner);
|
|
70
|
-
const cyanBanner = cyan(banner);
|
|
71
|
-
const greenBanner = green(banner);
|
|
72
|
-
const yellowBanner = yellow(banner);
|
|
73
|
-
const infoPill = (message) => message ? bgCyan.black(` ${message} `) + (` ${cyan(dot)} `) : '';
|
|
74
|
-
const successPill = (message) => message
|
|
75
|
-
? bgGreen.black(` ${message} `) + ' '
|
|
76
|
-
: '';
|
|
77
|
-
const warnPill = (message) => message ? bgYellow.black(` ${message} `) + (` ${yellow(dot)} `) : '';
|
|
78
|
-
const errorPill = (message) => message
|
|
79
|
-
? bgRed.white(` ${message} `) + ' '
|
|
80
|
-
: '';
|
|
81
|
-
const formatMessage = (message) => message ? `${greenBanner} ${message}` : '';
|
|
82
|
-
function useLogger(localized) {
|
|
83
|
-
function log(message) {
|
|
84
|
-
const formatted = formatMessage(message);
|
|
85
|
-
if (formatted)
|
|
86
|
-
console.log(formatted);
|
|
87
|
-
}
|
|
88
|
-
function step(message) {
|
|
89
|
-
if (message)
|
|
90
|
-
console.log(dim(message));
|
|
91
|
-
}
|
|
92
|
-
function info(message, title = localized.status.info) {
|
|
93
|
-
if (message)
|
|
94
|
-
console.log(`${cyanBanner} ${infoPill(title)}${cyan(message)}`);
|
|
95
|
-
}
|
|
96
|
-
function note(message, title = localized.status.note) {
|
|
97
|
-
if (message)
|
|
98
|
-
console.log(`${yellowBanner} ${warnPill(title)}${message}`);
|
|
99
|
-
}
|
|
100
|
-
function success(message, title = localized.status.success) {
|
|
101
|
-
if (message)
|
|
102
|
-
console.log(`${greenBanner} ${successPill(title)}${green(dot, message)}`);
|
|
103
|
-
}
|
|
104
|
-
function warn(message, title = localized.status.warn) {
|
|
105
|
-
if (message)
|
|
106
|
-
console.log(`${yellowBanner} ${warnPill(title)}${yellow(message)}`);
|
|
107
|
-
}
|
|
108
|
-
function error(message, title = localized.status.error) {
|
|
109
|
-
if (message)
|
|
110
|
-
console.log(`${redBanner} ${errorPill(title)}${red(dot, message)}`);
|
|
111
|
-
}
|
|
112
|
-
function cancel(message, title = localized.status.canceled) {
|
|
113
|
-
if (message)
|
|
114
|
-
console.log(`${redBanner} ${errorPill(title)}${red(dot, message)}`);
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
log,
|
|
118
|
-
step,
|
|
119
|
-
info,
|
|
120
|
-
note,
|
|
121
|
-
success,
|
|
122
|
-
warn,
|
|
123
|
-
error,
|
|
124
|
-
cancel,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const messages = await getLocalized();
|
|
129
|
-
const logger = useLogger(messages);
|
|
130
|
-
class ShellError extends Error {
|
|
131
|
-
constructor(message) {
|
|
132
|
-
super(message);
|
|
133
|
-
// Убедимся, что экземпляр имеет правильный прототип
|
|
134
|
-
Object.setPrototypeOf(this, ShellError.prototype);
|
|
135
|
-
this.name = 'ShellError';
|
|
136
|
-
this.message = message;
|
|
137
|
-
Error.captureStackTrace(this, ShellError);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* @typedef ExecutionResult
|
|
142
|
-
* @property {boolean} isDone
|
|
143
|
-
* @property {number} code
|
|
144
|
-
* @property {string} stderr
|
|
145
|
-
* @property {string} stdout
|
|
146
|
-
*/
|
|
147
|
-
async function execAsync(command, args, options) {
|
|
148
|
-
args ??= [];
|
|
149
|
-
return new Promise((resolve, reject) => {
|
|
150
|
-
const runner = spawn(command, args, {
|
|
151
|
-
stdio: [
|
|
152
|
-
'ignore', // stdin
|
|
153
|
-
'pipe', // stdout
|
|
154
|
-
'pipe', // stderr
|
|
155
|
-
],
|
|
156
|
-
...options,
|
|
157
|
-
shell: process.platform === 'win32',
|
|
158
|
-
});
|
|
159
|
-
const stdoutChunks = [];
|
|
160
|
-
const stderrChunks = [];
|
|
161
|
-
runner.stdout?.on('data', (chunk) => {
|
|
162
|
-
stdoutChunks.push(chunk);
|
|
163
|
-
});
|
|
164
|
-
runner.stderr?.on('data', (chunk) => {
|
|
165
|
-
stderrChunks.push(chunk);
|
|
166
|
-
});
|
|
167
|
-
runner.on('error', (error) => {
|
|
168
|
-
reject(error);
|
|
169
|
-
});
|
|
170
|
-
runner.on('exit', (code) => {
|
|
171
|
-
const isDone = code === 0;
|
|
172
|
-
const stdout = Buffer.concat(stdoutChunks).toString().trim();
|
|
173
|
-
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
174
|
-
if (isDone) {
|
|
175
|
-
resolve({ isDone, code, stdout, stderr });
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
reject(new ShellError(`Failed to execute command ${command} ${args.join(' ')}: ${stderr}`));
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
const runCommandAsync = async (command, args, options = {}) => await execAsync(command, args, { ...options });
|
|
184
|
-
const dryRunCommandAsync = (command, args) => {
|
|
185
|
-
logger.info(`${command} ${args?.join(' ')}`, 'Dry');
|
|
186
|
-
};
|
|
187
|
-
runCommandAsync.ifNotDry = (isDryRun) => isDryRun ? dryRunCommandAsync : runCommandAsync;
|
|
188
|
-
|
|
189
|
-
export { ShellError as S, getLocale as a, getLocalized as g, runCommandAsync as r, useLogger as u };
|