release-it 17.6.0 → 17.7.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/config/release-it.json +1 -0
- package/lib/config.js +1 -1
- package/lib/plugin/github/GitHub.js +13 -6
- package/lib/plugin/github/util.js +4 -2
- package/lib/plugin/gitlab/GitLab.js +26 -22
- package/package.json +3 -5
- package/schema/git.json +5 -1
- package/schema/github.json +4 -0
- package/schema/gitlab.json +5 -1
- package/test/config.js +1 -1
- package/test/github.js +23 -0
- package/test/gitlab.js +26 -6
- package/test/stub/github.js +11 -2
- package/test/stub/gitlab.js +1 -0
- package/types/config.d.ts +11 -1
package/config/release-it.json
CHANGED
package/lib/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import util from 'node:util';
|
|
|
2
2
|
import { cosmiconfigSync } from 'cosmiconfig';
|
|
3
3
|
import parseToml from '@iarna/toml/parse-string.js';
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import isCI from '
|
|
5
|
+
import { isCI } from 'ci-info';
|
|
6
6
|
import { readJSON, getSystemInfo } from './util.js';
|
|
7
7
|
|
|
8
8
|
const debug = util.debug('release-it:config');
|
|
@@ -2,7 +2,6 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import open from 'open';
|
|
4
4
|
import { Octokit } from '@octokit/rest';
|
|
5
|
-
import fetch from 'node-fetch';
|
|
6
5
|
import { globby } from 'globby';
|
|
7
6
|
import mime from 'mime-types';
|
|
8
7
|
import _ from 'lodash';
|
|
@@ -14,6 +13,10 @@ import Release from '../GitRelease.js';
|
|
|
14
13
|
import prompts from './prompts.js';
|
|
15
14
|
import { getCommitsFromChangelog, getResolvedIssuesFromChangelog, searchQueries } from './util.js';
|
|
16
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('@octokit/rest').RestEndpointMethodTypes['repos']['createRelease']['parameters']} CreateReleaseOptions
|
|
18
|
+
*/
|
|
19
|
+
|
|
17
20
|
const pkg = readJSON(new URL('../../../package.json', import.meta.url));
|
|
18
21
|
|
|
19
22
|
const docs = 'https://git.io/release-it-github';
|
|
@@ -177,8 +180,7 @@ class GitHub extends Release {
|
|
|
177
180
|
userAgent: `release-it/${pkg.version}`,
|
|
178
181
|
log: this.config.isDebug ? console : null,
|
|
179
182
|
request: {
|
|
180
|
-
timeout
|
|
181
|
-
fetch
|
|
183
|
+
timeout
|
|
182
184
|
}
|
|
183
185
|
};
|
|
184
186
|
|
|
@@ -207,15 +209,19 @@ class GitHub extends Release {
|
|
|
207
209
|
|
|
208
210
|
getOctokitReleaseOptions(options = {}) {
|
|
209
211
|
const { owner, project: repo } = this.getContext('repo');
|
|
210
|
-
const { releaseName, draft = false, preRelease = false, autoGenerate = false } = this.options;
|
|
212
|
+
const { releaseName, draft = false, preRelease = false, autoGenerate = false, makeLatest = true } = this.options;
|
|
211
213
|
const { tagName } = this.config.getContext();
|
|
212
214
|
const { version, releaseNotes, isUpdate } = this.getContext();
|
|
213
215
|
const { isPreRelease } = parseVersion(version);
|
|
214
216
|
const name = format(releaseName, this.config.getContext());
|
|
215
217
|
const body = autoGenerate ? (isUpdate ? null : '') : truncateBody(releaseNotes);
|
|
216
218
|
|
|
217
|
-
|
|
219
|
+
/**
|
|
220
|
+
* @type {CreateReleaseOptions}
|
|
221
|
+
*/
|
|
222
|
+
const contextOptions = {
|
|
218
223
|
owner,
|
|
224
|
+
make_latest: makeLatest.toString(),
|
|
219
225
|
repo,
|
|
220
226
|
tag_name: tagName,
|
|
221
227
|
name,
|
|
@@ -223,7 +229,8 @@ class GitHub extends Release {
|
|
|
223
229
|
draft,
|
|
224
230
|
prerelease: isPreRelease || preRelease,
|
|
225
231
|
generate_release_notes: autoGenerate
|
|
226
|
-
}
|
|
232
|
+
};
|
|
233
|
+
return Object.assign(options, contextOptions);
|
|
227
234
|
}
|
|
228
235
|
|
|
229
236
|
retry(fn) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Totally much borrowed from https://github.com/semantic-release/github/blob/master/lib/success.js
|
|
2
2
|
import issueParser from 'issue-parser';
|
|
3
3
|
|
|
4
|
-
const getSearchQueries = (base, commits, separator = '+') => {
|
|
4
|
+
export const getSearchQueries = (base, commits, separator = '+') => {
|
|
5
|
+
const encodedSeparator = encodeURIComponent(separator);
|
|
6
|
+
|
|
5
7
|
return commits.reduce((searches, commit) => {
|
|
6
8
|
const lastSearch = searches[searches.length - 1];
|
|
7
|
-
if (lastSearch && lastSearch.length + commit.length <= 256 -
|
|
9
|
+
if (lastSearch && encodeURIComponent(lastSearch).length + commit.length <= 256 - encodedSeparator.length) {
|
|
8
10
|
searches[searches.length - 1] = `${lastSearch}${separator}${commit}`;
|
|
9
11
|
} else {
|
|
10
12
|
searches.push(`${base}${separator}${commit}`);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import got from 'got';
|
|
4
3
|
import { globby } from 'globby';
|
|
5
|
-
import { FormData, fileFromSync } from 'node-fetch';
|
|
6
4
|
import _ from 'lodash';
|
|
7
5
|
import Release from '../GitRelease.js';
|
|
8
6
|
import { format, e } from '../../util.js';
|
|
@@ -30,22 +28,6 @@ class GitLab extends Release {
|
|
|
30
28
|
this.certificateAuthorityOption = _.isEmpty(https) ? {} : { https };
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
get client() {
|
|
34
|
-
if (this._client) return this._client;
|
|
35
|
-
const { tokenHeader } = this.options;
|
|
36
|
-
const { baseUrl } = this.getContext();
|
|
37
|
-
this._client = got.extend({
|
|
38
|
-
prefixUrl: baseUrl,
|
|
39
|
-
method: 'POST',
|
|
40
|
-
headers: {
|
|
41
|
-
'user-agent': 'webpro/release-it',
|
|
42
|
-
[tokenHeader]: this.token
|
|
43
|
-
},
|
|
44
|
-
...this.certificateAuthorityOption
|
|
45
|
-
});
|
|
46
|
-
return this._client;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
31
|
async init() {
|
|
50
32
|
await super.init();
|
|
51
33
|
|
|
@@ -179,8 +161,28 @@ class GitLab extends Release {
|
|
|
179
161
|
const { baseUrl } = this.getContext();
|
|
180
162
|
this.debug(Object.assign({ url: `${baseUrl}/${endpoint}` }, options));
|
|
181
163
|
const method = (options.method || 'POST').toLowerCase();
|
|
182
|
-
const
|
|
183
|
-
const
|
|
164
|
+
const { tokenHeader } = this.options;
|
|
165
|
+
const url = `${baseUrl}/${endpoint}${options.searchParams ? `?${new URLSearchParams(options.searchParams)}` : ''}`;
|
|
166
|
+
const headers = {
|
|
167
|
+
'user-agent': 'webpro/release-it',
|
|
168
|
+
[tokenHeader]: this.token
|
|
169
|
+
};
|
|
170
|
+
const requestOptions = {
|
|
171
|
+
method,
|
|
172
|
+
headers,
|
|
173
|
+
...this.certificateAuthorityOption
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const response = await fetch(
|
|
177
|
+
url,
|
|
178
|
+
options.json || options.body
|
|
179
|
+
? {
|
|
180
|
+
...requestOptions,
|
|
181
|
+
body: options.json ? JSON.stringify(options.json) : options.body
|
|
182
|
+
}
|
|
183
|
+
: requestOptions
|
|
184
|
+
);
|
|
185
|
+
const body = await response.json();
|
|
184
186
|
this.debug(body);
|
|
185
187
|
return body;
|
|
186
188
|
}
|
|
@@ -235,11 +237,13 @@ class GitLab extends Release {
|
|
|
235
237
|
|
|
236
238
|
async uploadAsset(filePath) {
|
|
237
239
|
const name = path.basename(filePath);
|
|
240
|
+
const { useIdsForUrls } = this.options;
|
|
238
241
|
const { id, origin, repo } = this.getContext();
|
|
239
242
|
const endpoint = `projects/${id}/uploads`;
|
|
240
243
|
|
|
241
244
|
const body = new FormData();
|
|
242
|
-
|
|
245
|
+
const rawData = await fs.promises.readFile(filePath);
|
|
246
|
+
body.set('file', new Blob([rawData]), name);
|
|
243
247
|
const options = { body };
|
|
244
248
|
|
|
245
249
|
try {
|
|
@@ -247,7 +251,7 @@ class GitLab extends Release {
|
|
|
247
251
|
this.log.verbose(`gitlab releases#uploadAsset: done (${body.url})`);
|
|
248
252
|
this.assets.push({
|
|
249
253
|
name,
|
|
250
|
-
url: `${origin}/${repo.repository}${body.url}`
|
|
254
|
+
url: useIdsForUrls ? `${origin}${body.full_path}` : `${origin}/${repo.repository}${body.url}`
|
|
251
255
|
});
|
|
252
256
|
} catch (err) {
|
|
253
257
|
this.debug(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-it",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.7.0",
|
|
4
4
|
"description": "Generic CLI tool to automate versioning and package publishing-related tasks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"build",
|
|
@@ -82,18 +82,16 @@
|
|
|
82
82
|
"@octokit/rest": "20.1.1",
|
|
83
83
|
"async-retry": "1.3.3",
|
|
84
84
|
"chalk": "5.3.0",
|
|
85
|
+
"ci-info": "^4.0.0",
|
|
85
86
|
"cosmiconfig": "9.0.0",
|
|
86
87
|
"execa": "8.0.1",
|
|
87
88
|
"git-url-parse": "14.0.0",
|
|
88
89
|
"globby": "14.0.2",
|
|
89
|
-
"got": "13.0.0",
|
|
90
90
|
"inquirer": "9.3.2",
|
|
91
|
-
"is-ci": "3.0.1",
|
|
92
91
|
"issue-parser": "7.0.1",
|
|
93
92
|
"lodash": "4.17.21",
|
|
94
93
|
"mime-types": "2.1.35",
|
|
95
94
|
"new-github-release-url": "2.0.0",
|
|
96
|
-
"node-fetch": "3.3.2",
|
|
97
95
|
"open": "10.1.0",
|
|
98
96
|
"ora": "8.0.1",
|
|
99
97
|
"os-name": "5.1.0",
|
|
@@ -123,7 +121,7 @@
|
|
|
123
121
|
"knip": "5.26.0",
|
|
124
122
|
"memfs": "4.9.3",
|
|
125
123
|
"mock-stdio": "1.0.3",
|
|
126
|
-
"nock": "
|
|
124
|
+
"nock": "14.0.0-beta.8",
|
|
127
125
|
"prettier": "3.3.3",
|
|
128
126
|
"remark-cli": "12.0.1",
|
|
129
127
|
"remark-preset-webpro": "1.1.0",
|
package/schema/git.json
CHANGED
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
"default": true
|
|
15
15
|
},
|
|
16
16
|
"requireBranch": {
|
|
17
|
-
"
|
|
17
|
+
"oneOf": [
|
|
18
|
+
{ "type": "boolean", "enum": [false] },
|
|
19
|
+
{ "type": "string" },
|
|
20
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
21
|
+
],
|
|
18
22
|
"default": false
|
|
19
23
|
},
|
|
20
24
|
"requireUpstream": {
|
package/schema/github.json
CHANGED
package/schema/gitlab.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"type": "boolean",
|
|
29
29
|
"default": false
|
|
30
30
|
},
|
|
31
|
-
"
|
|
31
|
+
"milestones": {
|
|
32
32
|
"type": "array",
|
|
33
33
|
"items": {
|
|
34
34
|
"type": "string"
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"assets": {
|
|
53
53
|
"default": null
|
|
54
54
|
},
|
|
55
|
+
"useIdsForUrls": {
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"default": false
|
|
58
|
+
},
|
|
55
59
|
"origin": {
|
|
56
60
|
"type": "string",
|
|
57
61
|
"default": null
|
package/test/config.js
CHANGED
package/test/github.js
CHANGED
|
@@ -2,6 +2,7 @@ import test from 'ava';
|
|
|
2
2
|
import sinon from 'sinon';
|
|
3
3
|
import { RequestError } from '@octokit/request-error';
|
|
4
4
|
import GitHub from '../lib/plugin/github/GitHub.js';
|
|
5
|
+
import { getSearchQueries } from '../lib/plugin/github/util.js';
|
|
5
6
|
import { factory, runTasks } from './util/index.js';
|
|
6
7
|
import {
|
|
7
8
|
interceptAuthentication,
|
|
@@ -485,3 +486,25 @@ test.skip('should truncate long body', async t => {
|
|
|
485
486
|
t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
|
|
486
487
|
exec.restore();
|
|
487
488
|
});
|
|
489
|
+
|
|
490
|
+
test('should generate search queries correctly', t => {
|
|
491
|
+
const generateCommit = () => Math.random().toString(36).substring(2, 9);
|
|
492
|
+
const base = 'repo:owner/repo+type:pr+is:merged';
|
|
493
|
+
const commits = Array.from({ length: 5 }, generateCommit);
|
|
494
|
+
const separator = '+';
|
|
495
|
+
|
|
496
|
+
const result = getSearchQueries(base, commits, separator);
|
|
497
|
+
|
|
498
|
+
// Test case 1: Check if all commits are included in the search queries
|
|
499
|
+
const allCommitsIncluded = commits.every(commit => result.some(query => query.includes(commit)));
|
|
500
|
+
t.true(allCommitsIncluded, 'All commits should be included in the search queries');
|
|
501
|
+
|
|
502
|
+
// Test case 2: Check if the function respects the 256 character limit
|
|
503
|
+
const manyCommits = Array.from({ length: 100 }, generateCommit);
|
|
504
|
+
const longResult = getSearchQueries(base, manyCommits, separator);
|
|
505
|
+
t.true(longResult.length > 1, 'Many commits should be split into multiple queries');
|
|
506
|
+
t.true(
|
|
507
|
+
longResult.every(query => encodeURIComponent(query).length <= 256),
|
|
508
|
+
'Each query should not exceed 256 characters after encoding'
|
|
509
|
+
);
|
|
510
|
+
});
|
package/test/gitlab.js
CHANGED
|
@@ -111,6 +111,31 @@ test.serial('should upload assets and release', async t => {
|
|
|
111
111
|
t.is(releaseUrl, `${pushRepo}/-/releases`);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
test.serial('should upload assets with ID-based URLs too', async t => {
|
|
115
|
+
const host = 'https://gitlab.com';
|
|
116
|
+
const pushRepo = `${host}/user/repo`;
|
|
117
|
+
const options = {
|
|
118
|
+
git: { pushRepo },
|
|
119
|
+
gitlab: {
|
|
120
|
+
tokenRef,
|
|
121
|
+
release: true,
|
|
122
|
+
assets: 'test/resources/file-v${version}.txt',
|
|
123
|
+
useIdsForUrls: true
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const gitlab = factory(GitLab, { options });
|
|
127
|
+
sinon.stub(gitlab, 'getLatestVersion').resolves('2.0.0');
|
|
128
|
+
|
|
129
|
+
interceptUser();
|
|
130
|
+
interceptCollaborator();
|
|
131
|
+
interceptAsset();
|
|
132
|
+
interceptPublish();
|
|
133
|
+
|
|
134
|
+
await runTasks(gitlab);
|
|
135
|
+
|
|
136
|
+
t.is(gitlab.assets[0].url, `${host}/-/project/1234/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file-v2.0.1.txt`);
|
|
137
|
+
});
|
|
138
|
+
|
|
114
139
|
test.serial('should throw when release milestone is missing', async t => {
|
|
115
140
|
const pushRepo = 'https://gitlab.com/user/repo';
|
|
116
141
|
const options = {
|
|
@@ -226,29 +251,24 @@ test('should not make requests in dry run', async t => {
|
|
|
226
251
|
const options = { 'dry-run': true, git: { pushRepo }, gitlab: { releaseName: 'R', tokenRef } };
|
|
227
252
|
const gitlab = factory(GitLab, { options });
|
|
228
253
|
sinon.stub(gitlab, 'getLatestVersion').resolves('1.0.0');
|
|
229
|
-
const spy = sinon.spy(gitlab, 'client', ['get']);
|
|
230
254
|
|
|
231
255
|
await runTasks(gitlab);
|
|
232
256
|
|
|
233
257
|
const { isReleased, releaseUrl } = gitlab.getContext();
|
|
234
|
-
|
|
258
|
+
|
|
235
259
|
t.is(gitlab.log.exec.args[2][0], 'gitlab releases#uploadAssets');
|
|
236
260
|
t.is(gitlab.log.exec.args[3][0], 'gitlab releases#createRelease "R" (1.0.1)');
|
|
237
261
|
t.true(isReleased);
|
|
238
262
|
t.is(releaseUrl, `${pushRepo}/-/releases`);
|
|
239
|
-
spy.get.restore();
|
|
240
263
|
});
|
|
241
264
|
|
|
242
265
|
test('should skip checks', async t => {
|
|
243
266
|
const options = { gitlab: { tokenRef, skipChecks: true, release: true, milestones: ['v1.0.0'] } };
|
|
244
267
|
const gitlab = factory(GitLab, { options });
|
|
245
|
-
const spy = sinon.spy(gitlab, 'client', ['get']);
|
|
246
268
|
|
|
247
269
|
await t.notThrowsAsync(gitlab.init());
|
|
248
270
|
await t.notThrowsAsync(gitlab.beforeRelease());
|
|
249
271
|
|
|
250
|
-
t.is(spy.get.callCount, 0);
|
|
251
|
-
|
|
252
272
|
t.is(gitlab.log.exec.args.filter(entry => /checkReleaseMilestones/.test(entry[0])).length, 0);
|
|
253
273
|
});
|
|
254
274
|
|
package/test/stub/github.js
CHANGED
|
@@ -53,7 +53,8 @@ const interceptCreate = ({
|
|
|
53
53
|
body,
|
|
54
54
|
prerelease,
|
|
55
55
|
draft,
|
|
56
|
-
generate_release_notes
|
|
56
|
+
generate_release_notes,
|
|
57
|
+
make_latest: 'true'
|
|
57
58
|
})
|
|
58
59
|
.reply(() => {
|
|
59
60
|
const id = 1;
|
|
@@ -80,7 +81,15 @@ const interceptUpdate = ({
|
|
|
80
81
|
body: { tag_name, name = '', body = null, prerelease = false, draft = false, generate_release_notes = false }
|
|
81
82
|
} = {}) => {
|
|
82
83
|
nock(api)
|
|
83
|
-
.patch(`/repos/${owner}/${project}/releases/1`, {
|
|
84
|
+
.patch(`/repos/${owner}/${project}/releases/1`, {
|
|
85
|
+
tag_name,
|
|
86
|
+
name,
|
|
87
|
+
body,
|
|
88
|
+
draft,
|
|
89
|
+
prerelease,
|
|
90
|
+
generate_release_notes,
|
|
91
|
+
make_latest: 'true'
|
|
92
|
+
})
|
|
84
93
|
.reply(200, {
|
|
85
94
|
id: 1,
|
|
86
95
|
tag_name,
|
package/test/stub/gitlab.js
CHANGED
|
@@ -39,6 +39,7 @@ export let interceptAsset = ({ host = 'https://gitlab.com', owner = 'user', proj
|
|
|
39
39
|
return {
|
|
40
40
|
alt: name,
|
|
41
41
|
url: `/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name}`,
|
|
42
|
+
full_path: `/-/project/1234/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name}`,
|
|
42
43
|
markdown: `[${name}](/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/${name})`
|
|
43
44
|
};
|
|
44
45
|
});
|
package/types/config.d.ts
CHANGED
|
@@ -132,6 +132,13 @@ export interface Config {
|
|
|
132
132
|
/** @default null */
|
|
133
133
|
proxy?: any;
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @default true
|
|
137
|
+
* 'legacy' - Github determines the latest release based on the release creation date and higher semantic version.
|
|
138
|
+
* See https://docs.github.com/en/rest/releases/releases?apiVersion=latest#create-a-release
|
|
139
|
+
*/
|
|
140
|
+
makeLatest?: boolean | 'legacy';
|
|
141
|
+
|
|
135
142
|
/** @default false */
|
|
136
143
|
skipChecks?: boolean;
|
|
137
144
|
|
|
@@ -170,13 +177,16 @@ export interface Config {
|
|
|
170
177
|
|
|
171
178
|
/** @default null */
|
|
172
179
|
certificateAuthorityFile?: any;
|
|
173
|
-
|
|
180
|
+
|
|
174
181
|
/** @default null */
|
|
175
182
|
secure?: boolean;
|
|
176
183
|
|
|
177
184
|
/** @default null */
|
|
178
185
|
assets?: any;
|
|
179
186
|
|
|
187
|
+
/** @default false */
|
|
188
|
+
useIdsForUrls?: boolean;
|
|
189
|
+
|
|
180
190
|
/** @default null */
|
|
181
191
|
origin?: any;
|
|
182
192
|
|