spectrum-cli 2.1.1 → 3.0.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/CHANGELOG.md +14 -24
- package/README.md +97 -60
- package/index.js +16 -33
- package/package.json +1 -1
- package/src/changelog-config.js +38 -0
- package/src/changelog.js +222 -251
- package/src/chart.js +256 -6
- package/src/git.js +2 -2
- package/src/preflight.js +255 -25
- package/src/release.js +95 -108
- package/src/token.js +27 -19
- package/src/utils.js +0 -10
- package/src/version.js +31 -40
- package/src/UNRELEASED.md +0 -33
package/src/token.js
CHANGED
|
@@ -123,11 +123,11 @@ function parseTargets(urls, kind) {
|
|
|
123
123
|
return { ok: true, targets };
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async function
|
|
126
|
+
async function promptHidden(message) {
|
|
127
127
|
return new Promise((resolve) => {
|
|
128
128
|
const stdin = process.stdin;
|
|
129
129
|
const stdout = process.stdout;
|
|
130
|
-
stdout.write(
|
|
130
|
+
stdout.write(message);
|
|
131
131
|
|
|
132
132
|
if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
|
|
133
133
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
@@ -165,9 +165,9 @@ async function promptPrivateToken() {
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
async function gitlabRequest(origin,
|
|
168
|
+
async function gitlabRequest(origin, accessToken, method, apiPath, body) {
|
|
169
169
|
const url = `${origin}/api/v4${apiPath}`;
|
|
170
|
-
const headers = { 'PRIVATE-TOKEN':
|
|
170
|
+
const headers = { 'PRIVATE-TOKEN': accessToken };
|
|
171
171
|
const options = { method, headers };
|
|
172
172
|
if (body !== undefined) {
|
|
173
173
|
headers['Content-Type'] = 'application/json';
|
|
@@ -193,13 +193,13 @@ async function gitlabRequest(origin, privateToken, method, apiPath, body) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
async function gitlabGetAll(origin,
|
|
196
|
+
async function gitlabGetAll(origin, accessToken, apiPath) {
|
|
197
197
|
const items = [];
|
|
198
198
|
let page = 1;
|
|
199
199
|
|
|
200
200
|
while (true) {
|
|
201
201
|
const separator = apiPath.includes('?') ? '&' : '?';
|
|
202
|
-
const result = await gitlabRequest(origin,
|
|
202
|
+
const result = await gitlabRequest(origin, accessToken, 'GET', `${apiPath}${separator}per_page=100&page=${page}`);
|
|
203
203
|
if (!result.ok) {
|
|
204
204
|
return result;
|
|
205
205
|
}
|
|
@@ -272,10 +272,17 @@ function loadOrCreateConfig() {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
async function
|
|
276
|
-
logSuccess('🔑', 'Запрашиваю
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
275
|
+
async function askTokens(ctx) {
|
|
276
|
+
logSuccess('🔑', 'Запрашиваю owner PAT для работы с CI variables. Значение останется только в памяти.');
|
|
277
|
+
const ownerToken = await module.exports.promptHidden('🔑 Введите owner PAT: ');
|
|
278
|
+
if (!ownerToken) {
|
|
279
|
+
return { ok: false, reason: 'Owner PAT не указан.' };
|
|
280
|
+
}
|
|
281
|
+
logSuccess('🔑', 'Owner PAT принят в память.');
|
|
282
|
+
|
|
283
|
+
logSuccess('🔑', 'Запрашиваю %s для записи в CI variables. Значение останется только в памяти.', TOKEN_NAME);
|
|
284
|
+
const ciToken = await module.exports.promptHidden(`🔑 Введите ${TOKEN_NAME}: `);
|
|
285
|
+
if (!ciToken) {
|
|
279
286
|
return { ok: false, reason: `${TOKEN_NAME} не указан.` };
|
|
280
287
|
}
|
|
281
288
|
|
|
@@ -284,7 +291,8 @@ async function askPrivateToken(ctx) {
|
|
|
284
291
|
return {
|
|
285
292
|
ok: true,
|
|
286
293
|
data: {
|
|
287
|
-
|
|
294
|
+
ownerToken,
|
|
295
|
+
ciToken,
|
|
288
296
|
expiresAt,
|
|
289
297
|
variableDescription: buildDescription(ctx.bot, expiresAt)
|
|
290
298
|
}
|
|
@@ -297,7 +305,7 @@ async function checkTargetsAccess(ctx) {
|
|
|
297
305
|
const target = ctx.targets[index];
|
|
298
306
|
const endpoint = target.kind === 'группы' ? 'groups' : 'projects';
|
|
299
307
|
logSuccess('🔍', '[%s/%s] Проверяю доступ к %s %s', String(index + 1), String(total), target.kind, target.url);
|
|
300
|
-
const result = await gitlabRequest(ctx.origin, ctx.
|
|
308
|
+
const result = await gitlabRequest(ctx.origin, ctx.ownerToken, 'GET', `/${endpoint}/${target.encodedPath}`);
|
|
301
309
|
if (!result.ok) {
|
|
302
310
|
return { ok: false, reason: `${target.kind === 'группы' ? 'Группа' : 'Проект'} недоступен: ${target.url} (HTTP ${result.status}).` };
|
|
303
311
|
}
|
|
@@ -311,7 +319,7 @@ async function updateTargetVariable(ctx, target, index, total) {
|
|
|
311
319
|
const label = target.kind === 'группы' ? 'группе' : 'проекте';
|
|
312
320
|
logSuccess('📦', '[%s/%s] Обновляю CI variable %s в %s %s', String(index + 1), String(total), TOKEN_NAME, label, target.path);
|
|
313
321
|
|
|
314
|
-
const listed = await gitlabGetAll(ctx.origin, ctx.
|
|
322
|
+
const listed = await gitlabGetAll(ctx.origin, ctx.ownerToken, `/${endpoint}/${target.encodedPath}/variables`);
|
|
315
323
|
if (!listed.ok) {
|
|
316
324
|
logError('❌', 'Не удалось получить CI variables для %s (HTTP %s).', target.path, String(listed.status));
|
|
317
325
|
return false;
|
|
@@ -320,7 +328,7 @@ async function updateTargetVariable(ctx, target, index, total) {
|
|
|
320
328
|
const exists = (listed.data || []).some((variable) => variable.key === TOKEN_NAME);
|
|
321
329
|
if (exists) {
|
|
322
330
|
logSuccess('🗑', '[%s/%s] Удаляю старую CI variable %s в %s %s', String(index + 1), String(total), TOKEN_NAME, label, target.path);
|
|
323
|
-
const deleted = await gitlabRequest(ctx.origin, ctx.
|
|
331
|
+
const deleted = await gitlabRequest(ctx.origin, ctx.ownerToken, 'DELETE', `/${endpoint}/${target.encodedPath}/variables/${TOKEN_NAME}`);
|
|
324
332
|
if (!deleted.ok && deleted.status !== 204) {
|
|
325
333
|
logError('❌', 'Не удалось удалить CI variable в %s (HTTP %s).', target.path, String(deleted.status));
|
|
326
334
|
return false;
|
|
@@ -331,9 +339,9 @@ async function updateTargetVariable(ctx, target, index, total) {
|
|
|
331
339
|
}
|
|
332
340
|
|
|
333
341
|
logSuccess('📝', '[%s/%s] Создаю CI variable %s в %s %s', String(index + 1), String(total), TOKEN_NAME, label, target.path);
|
|
334
|
-
const created = await gitlabRequest(ctx.origin, ctx.
|
|
342
|
+
const created = await gitlabRequest(ctx.origin, ctx.ownerToken, 'POST', `/${endpoint}/${target.encodedPath}/variables`, {
|
|
335
343
|
key: TOKEN_NAME,
|
|
336
|
-
value: ctx.
|
|
344
|
+
value: ctx.ciToken,
|
|
337
345
|
masked_and_hidden: true,
|
|
338
346
|
description: ctx.variableDescription
|
|
339
347
|
});
|
|
@@ -364,7 +372,7 @@ function tokenRotate() {
|
|
|
364
372
|
name: 'token rotate',
|
|
365
373
|
checks: [
|
|
366
374
|
{ name: 'load-config', run: loadOrCreateConfig },
|
|
367
|
-
{ name: 'ask-
|
|
375
|
+
{ name: 'ask-tokens', run: askTokens },
|
|
368
376
|
{ name: 'check-access', run: checkTargetsAccess }
|
|
369
377
|
],
|
|
370
378
|
steps: [
|
|
@@ -382,9 +390,9 @@ module.exports = {
|
|
|
382
390
|
addMonths,
|
|
383
391
|
buildDescription,
|
|
384
392
|
validateConfig,
|
|
385
|
-
|
|
393
|
+
promptHidden,
|
|
386
394
|
loadOrCreateConfig,
|
|
387
|
-
|
|
395
|
+
askTokens,
|
|
388
396
|
checkTargetsAccess,
|
|
389
397
|
updateCiVariables,
|
|
390
398
|
tokenRotate
|
package/src/utils.js
CHANGED
|
@@ -83,15 +83,6 @@ function getPackageManager() {
|
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function getVersion() {
|
|
87
|
-
try {
|
|
88
|
-
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
89
|
-
return packageJson.version;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
86
|
function getRemoteUrl() {
|
|
96
87
|
const raw = execSilent('git remote get-url origin');
|
|
97
88
|
if (!raw) return null;
|
|
@@ -125,7 +116,6 @@ module.exports = {
|
|
|
125
116
|
getMainBranch,
|
|
126
117
|
getDevelopBranch,
|
|
127
118
|
getPackageManager,
|
|
128
|
-
getVersion,
|
|
129
119
|
getRemoteUrl,
|
|
130
120
|
getMergeRequestUrl
|
|
131
121
|
};
|
package/src/version.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
const STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
4
|
+
|
|
5
|
+
function parseStableVersion(version) {
|
|
6
|
+
const match = String(version || '').match(STABLE_VERSION_PATTERN);
|
|
7
|
+
if (!match) return null;
|
|
8
|
+
return match.slice(1).map(Number);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function compareVersions(left, right) {
|
|
12
|
+
const leftParts = parseStableVersion(left);
|
|
13
|
+
const rightParts = parseStableVersion(right);
|
|
14
|
+
if (!leftParts || !rightParts) {
|
|
15
|
+
throw new Error('Для сравнения требуются стабильные SemVer версии X.Y.Z.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
19
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
20
|
+
return leftParts[index] - rightParts[index];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
6
25
|
|
|
7
26
|
function upVersion(oldVersion, upType) {
|
|
8
|
-
const
|
|
9
|
-
|
|
27
|
+
const parts = parseStableVersion(oldVersion);
|
|
28
|
+
if (!parts) return null;
|
|
29
|
+
const [major, minor, patch] = parts;
|
|
30
|
+
|
|
10
31
|
switch (upType) {
|
|
11
32
|
case 'major':
|
|
12
33
|
return `${major + 1}.0.0`;
|
|
@@ -18,39 +39,9 @@ function upVersion(oldVersion, upType) {
|
|
|
18
39
|
}
|
|
19
40
|
}
|
|
20
41
|
|
|
21
|
-
function updateVersionFile(versionType) {
|
|
22
|
-
try {
|
|
23
|
-
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
24
|
-
const oldVersion = packageJson.version;
|
|
25
|
-
const newVersion = upVersion(oldVersion, versionType);
|
|
26
|
-
|
|
27
|
-
packageJson.version = newVersion;
|
|
28
|
-
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
|
|
29
|
-
logSuccess('🔖', 'Версия обновлена с %s до %s.', oldVersion, newVersion);
|
|
30
|
-
return true;
|
|
31
|
-
} catch (error) {
|
|
32
|
-
logError('❌', 'Ошибка при обновлении версии в package.json');
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
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
|
-
],
|
|
45
|
-
steps: [
|
|
46
|
-
{ name: 'update-package-version', run: () => updateVersionFile(versionType) }
|
|
47
|
-
]
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
42
|
module.exports = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
STABLE_VERSION_PATTERN,
|
|
44
|
+
parseStableVersion,
|
|
45
|
+
compareVersions,
|
|
46
|
+
upVersion
|
|
56
47
|
};
|
package/src/UNRELEASED.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
## [Unreleased]
|
|
2
|
-
|
|
3
|
-
### 💥 Breaking change
|
|
4
|
-
|
|
5
|
-
_Список обратно несовместимых изменений._
|
|
6
|
-
|
|
7
|
-
### 🆕 Added
|
|
8
|
-
|
|
9
|
-
_Список новой функциональности._
|
|
10
|
-
|
|
11
|
-
### 🛠 Changed
|
|
12
|
-
|
|
13
|
-
_Список изменившейся функциональности._
|
|
14
|
-
|
|
15
|
-
### 📜 Deprecated
|
|
16
|
-
|
|
17
|
-
_Список устаревшей функциональности._
|
|
18
|
-
|
|
19
|
-
### 🗑 Removed
|
|
20
|
-
|
|
21
|
-
_Список удаленной функциональности._
|
|
22
|
-
|
|
23
|
-
### 🪲 Fixed
|
|
24
|
-
|
|
25
|
-
_Список исправлений багов._
|
|
26
|
-
|
|
27
|
-
### 🔐 Security
|
|
28
|
-
|
|
29
|
-
_Список правок для обеспечения безопасности._
|
|
30
|
-
|
|
31
|
-
### 📦 Support
|
|
32
|
-
|
|
33
|
-
_Список правок для обеспечения технической поддержки._
|