release-it 0.0.0-pl.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/LICENSE +21 -0
- package/README.md +421 -0
- package/bin/release-it.js +42 -0
- package/config/release-it.json +70 -0
- package/lib/cli.js +44 -0
- package/lib/config.js +139 -0
- package/lib/index.js +152 -0
- package/lib/log.js +69 -0
- package/lib/plugin/GitBase.js +125 -0
- package/lib/plugin/GitRelease.js +58 -0
- package/lib/plugin/Plugin.js +73 -0
- package/lib/plugin/factory.js +89 -0
- package/lib/plugin/git/Git.js +220 -0
- package/lib/plugin/git/prompts.js +19 -0
- package/lib/plugin/github/GitHub.js +403 -0
- package/lib/plugin/github/prompts.js +16 -0
- package/lib/plugin/github/util.js +39 -0
- package/lib/plugin/gitlab/GitLab.js +277 -0
- package/lib/plugin/gitlab/prompts.js +9 -0
- package/lib/plugin/npm/npm.js +281 -0
- package/lib/plugin/npm/prompts.js +12 -0
- package/lib/plugin/version/Version.js +129 -0
- package/lib/prompt.js +33 -0
- package/lib/shell.js +91 -0
- package/lib/spinner.js +29 -0
- package/lib/util.js +109 -0
- package/package.json +122 -0
- package/test/cli.js +20 -0
- package/test/config.js +144 -0
- package/test/git.init.js +250 -0
- package/test/git.js +358 -0
- package/test/github.js +487 -0
- package/test/gitlab.js +252 -0
- package/test/log.js +143 -0
- package/test/npm.js +417 -0
- package/test/plugin-name.js +9 -0
- package/test/plugins.js +238 -0
- package/test/prompt.js +97 -0
- package/test/resources/file-v2.0.1.txt +1 -0
- package/test/resources/file-v2.0.2.txt +1 -0
- package/test/resources/file1 +1 -0
- package/test/shell.js +74 -0
- package/test/spinner.js +58 -0
- package/test/stub/config/default/.release-it.json +5 -0
- package/test/stub/config/invalid-config-rc +1 -0
- package/test/stub/config/invalid-config-txt +2 -0
- package/test/stub/config/merge/.release-it.json +5 -0
- package/test/stub/config/merge/package.json +7 -0
- package/test/stub/config/toml/.release-it.toml +2 -0
- package/test/stub/config/yaml/.release-it.yaml +2 -0
- package/test/stub/config/yml/.release-it.yml +2 -0
- package/test/stub/github.js +130 -0
- package/test/stub/gitlab.js +44 -0
- package/test/stub/plugin-context.js +36 -0
- package/test/stub/plugin-replace.js +9 -0
- package/test/stub/plugin.js +39 -0
- package/test/stub/shell.js +24 -0
- package/test/tasks.interactive.js +208 -0
- package/test/tasks.js +585 -0
- package/test/util/helpers.js +32 -0
- package/test/util/index.js +78 -0
- package/test/util/setup.js +5 -0
- package/test/utils.js +97 -0
- package/test/version.js +173 -0
package/test/git.init.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import sh from 'shelljs';
|
|
3
|
+
import Shell from '../lib/shell.js';
|
|
4
|
+
import Git from '../lib/plugin/git/Git.js';
|
|
5
|
+
import { readJSON } from '../lib/util.js';
|
|
6
|
+
import { factory } from './util/index.js';
|
|
7
|
+
import { mkTmpDir, gitAdd } from './util/helpers.js';
|
|
8
|
+
|
|
9
|
+
const { git } = readJSON(new URL('../config/release-it.json', import.meta.url));
|
|
10
|
+
|
|
11
|
+
test.serial.beforeEach(t => {
|
|
12
|
+
const bare = mkTmpDir();
|
|
13
|
+
const target = mkTmpDir();
|
|
14
|
+
sh.pushd('-q', bare);
|
|
15
|
+
sh.exec(`git init --bare .`);
|
|
16
|
+
sh.exec(`git clone ${bare} ${target}`);
|
|
17
|
+
sh.pushd('-q', target);
|
|
18
|
+
gitAdd('line', 'file', 'Add file');
|
|
19
|
+
t.context = { bare, target };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test.serial('should throw if on wrong branch', async t => {
|
|
23
|
+
const options = { git: { requireBranch: 'dev' } };
|
|
24
|
+
const gitClient = factory(Git, { options });
|
|
25
|
+
sh.exec('git remote remove origin');
|
|
26
|
+
await t.throwsAsync(gitClient.init(), { message: /^Must be on branch dev/ });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test.serial('should throw if on negated branch', async t => {
|
|
30
|
+
const options = { git: { requireBranch: '!main' } };
|
|
31
|
+
const gitClient = factory(Git, { options });
|
|
32
|
+
sh.exec('git checkout -b main');
|
|
33
|
+
await t.throwsAsync(gitClient.init(), { message: /^Must be on branch !main/ });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test.serial('should not throw if required branch matches', async t => {
|
|
37
|
+
const options = { git: { requireBranch: 'ma?*' } };
|
|
38
|
+
const gitClient = factory(Git, { options });
|
|
39
|
+
await t.notThrowsAsync(gitClient.init());
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test.serial('should not throw if one of required branch matches', async t => {
|
|
43
|
+
const options = { git: { requireBranch: ['release/*', 'hotfix/*'] } };
|
|
44
|
+
const gitClient = factory(Git, { options });
|
|
45
|
+
sh.exec('git checkout -b release/v1');
|
|
46
|
+
await t.notThrowsAsync(gitClient.init());
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.serial('should throw if there is no remote Git url', async t => {
|
|
50
|
+
const gitClient = factory(Git, { options: { git } });
|
|
51
|
+
sh.exec('git remote remove origin');
|
|
52
|
+
await t.throwsAsync(gitClient.init(), { message: /^Could not get remote Git url/ });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test.serial('should throw if working dir is not clean', async t => {
|
|
56
|
+
const gitClient = factory(Git, { options: { git } });
|
|
57
|
+
sh.exec('rm file');
|
|
58
|
+
await t.throwsAsync(gitClient.init(), { message: /^Working dir must be clean/ });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test.serial('should throw if no upstream is configured', async t => {
|
|
62
|
+
const gitClient = factory(Git, { options: { git } });
|
|
63
|
+
sh.exec('git checkout -b foo');
|
|
64
|
+
await t.throwsAsync(gitClient.init(), { message: /^No upstream configured for current branch/ });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test.serial('should throw if there are no commits', async t => {
|
|
68
|
+
const options = { git: { requireCommits: true } };
|
|
69
|
+
const gitClient = factory(Git, { options });
|
|
70
|
+
sh.exec('git tag 1.0.0');
|
|
71
|
+
await t.throwsAsync(gitClient.init(), { message: /^There are no commits since the latest tag/ });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test.serial('should not throw if there are commits', async t => {
|
|
75
|
+
const options = { git: { requireCommits: true } };
|
|
76
|
+
const gitClient = factory(Git, { options });
|
|
77
|
+
sh.exec('git tag 1.0.0');
|
|
78
|
+
gitAdd('line', 'file', 'Add file');
|
|
79
|
+
await t.notThrowsAsync(gitClient.init());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test.serial('should fail (exit code 1) if there are no commits', async t => {
|
|
83
|
+
const options = { git: { requireCommits: true } };
|
|
84
|
+
const gitClient = factory(Git, { options });
|
|
85
|
+
sh.exec('git tag 1.0.0');
|
|
86
|
+
await t.throwsAsync(gitClient.init(), { code: 1 });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test.serial('should not fail (exit code 0) if there are no commits', async t => {
|
|
90
|
+
const options = { git: { requireCommits: true, requireCommitsFail: false } };
|
|
91
|
+
const gitClient = factory(Git, { options });
|
|
92
|
+
sh.exec('git tag 1.0.0');
|
|
93
|
+
await t.throwsAsync(gitClient.init(), { code: 0 });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test.serial('should throw if there are no commits in specified path', async t => {
|
|
97
|
+
const options = { git: { requireCommits: true, commitsPath: 'dir' } };
|
|
98
|
+
const gitClient = factory(Git, { options });
|
|
99
|
+
sh.mkdir('dir');
|
|
100
|
+
sh.exec('git tag 1.0.0');
|
|
101
|
+
await t.throwsAsync(gitClient.init(), { message: /^There are no commits since the latest tag/ });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test.serial('should not throw if there are commits in specified path', async t => {
|
|
105
|
+
const options = { git: { requireCommits: true, commitsPath: 'dir' } };
|
|
106
|
+
const gitClient = factory(Git, { options });
|
|
107
|
+
sh.exec('git tag 1.0.0');
|
|
108
|
+
gitAdd('line', 'dir/file', 'Add file');
|
|
109
|
+
await t.notThrowsAsync(gitClient.init());
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test.serial('should not throw if there are no tags', async t => {
|
|
113
|
+
const options = { git: { requireCommits: true } };
|
|
114
|
+
const gitClient = factory(Git, { options });
|
|
115
|
+
gitAdd('line', 'file', 'Add file');
|
|
116
|
+
await t.notThrowsAsync(gitClient.init());
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test.serial('should not throw if origin remote is renamed', async t => {
|
|
120
|
+
sh.exec('git remote rename origin upstream');
|
|
121
|
+
const gitClient = factory(Git);
|
|
122
|
+
await t.notThrowsAsync(gitClient.init());
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test.serial('should detect and include version prefix ("v")', async t => {
|
|
126
|
+
const gitClient = factory(Git, { options: { git } });
|
|
127
|
+
sh.exec('git tag v1.0.0');
|
|
128
|
+
await gitClient.init();
|
|
129
|
+
await gitClient.bump('1.0.1');
|
|
130
|
+
t.is(gitClient.config.getContext('tagName'), 'v1.0.1');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test.serial('should detect and exclude version prefix', async t => {
|
|
134
|
+
const gitClient = factory(Git, { options: { git } });
|
|
135
|
+
sh.exec('git tag 1.0.0');
|
|
136
|
+
await gitClient.init();
|
|
137
|
+
await gitClient.bump('1.0.1');
|
|
138
|
+
t.is(gitClient.config.getContext('tagName'), '1.0.1');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test.serial('should detect and exclude version prefix (configured)', async t => {
|
|
142
|
+
const gitClient = factory(Git, { options: { git: { tagName: 'v${version}' } } });
|
|
143
|
+
sh.exec('git tag 1.0.0');
|
|
144
|
+
await gitClient.init();
|
|
145
|
+
await gitClient.bump('1.0.1');
|
|
146
|
+
t.is(gitClient.config.getContext('tagName'), 'v1.0.1');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test.serial('should honor custom tagName configuration', async t => {
|
|
150
|
+
const gitClient = factory(Git, { options: { git: { tagName: 'TAGNAME-${repo.project}-v${version}' } } });
|
|
151
|
+
sh.exec('git tag 1.0.0');
|
|
152
|
+
await gitClient.init();
|
|
153
|
+
await gitClient.bump('1.0.1');
|
|
154
|
+
const { project } = gitClient.getContext('repo');
|
|
155
|
+
t.is(gitClient.config.getContext('tagName'), `TAGNAME-${project}-v1.0.1`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test.serial('should get the latest tag after fetch', async t => {
|
|
159
|
+
const shell = factory(Shell);
|
|
160
|
+
const gitClient = factory(Git, { container: { shell } });
|
|
161
|
+
const { bare, target } = t.context;
|
|
162
|
+
const other = mkTmpDir();
|
|
163
|
+
sh.exec('git push');
|
|
164
|
+
sh.exec(`git clone ${bare} ${other}`);
|
|
165
|
+
sh.pushd('-q', other);
|
|
166
|
+
sh.exec('git tag 1.0.0');
|
|
167
|
+
sh.exec('git push --tags');
|
|
168
|
+
sh.pushd('-q', target);
|
|
169
|
+
await gitClient.init();
|
|
170
|
+
t.is(gitClient.config.getContext('latestTag'), '1.0.0');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test.serial('should get the latest custom tag after fetch when tagName is configured', async t => {
|
|
174
|
+
const shell = factory(Shell);
|
|
175
|
+
const gitClient = factory(Git, {
|
|
176
|
+
options: { git: { tagName: 'TAGNAME-v${version}' } },
|
|
177
|
+
container: { shell }
|
|
178
|
+
});
|
|
179
|
+
const { bare, target } = t.context;
|
|
180
|
+
const other = mkTmpDir();
|
|
181
|
+
sh.exec('git push');
|
|
182
|
+
sh.exec(`git clone ${bare} ${other}`);
|
|
183
|
+
sh.pushd('-q', other);
|
|
184
|
+
sh.exec('git tag TAGNAME-OTHER-v2.0.0');
|
|
185
|
+
sh.exec('git tag TAGNAME-v1.0.0');
|
|
186
|
+
sh.exec('git tag TAGNAME-OTHER-v2.0.2');
|
|
187
|
+
sh.exec('git push --tags');
|
|
188
|
+
sh.pushd('-q', target);
|
|
189
|
+
await gitClient.init();
|
|
190
|
+
t.is(gitClient.config.getContext('latestTag'), 'TAGNAME-v1.0.0');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test.serial('should get the latest tag based on tagMatch', async t => {
|
|
194
|
+
const shell = factory(Shell);
|
|
195
|
+
const gitClient = factory(Git, {
|
|
196
|
+
options: { git: { tagMatch: '[0-9][0-9]\\.[0-1][0-9]\\.[0-9]*' } },
|
|
197
|
+
container: { shell }
|
|
198
|
+
});
|
|
199
|
+
sh.exec('git tag 1.0.0');
|
|
200
|
+
sh.exec('git tag 21.04.3');
|
|
201
|
+
sh.exec('git tag 1.0.1');
|
|
202
|
+
sh.exec('git push --tags');
|
|
203
|
+
await gitClient.init();
|
|
204
|
+
t.is(gitClient.config.getContext('latestTag'), '21.04.3');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test.serial('should get the latest tag based on tagExclude', async t => {
|
|
208
|
+
const shell = factory(Shell);
|
|
209
|
+
const gitClient = factory(Git, {
|
|
210
|
+
options: { git: { tagExclude: '*[-]*' } },
|
|
211
|
+
container: { shell }
|
|
212
|
+
});
|
|
213
|
+
sh.exec('git tag 1.0.0');
|
|
214
|
+
sh.exec('git commit --allow-empty -m "commit 1"');
|
|
215
|
+
sh.exec('git tag 1.0.1-rc.0');
|
|
216
|
+
sh.exec('git tag 1.0.1');
|
|
217
|
+
sh.exec('git commit --allow-empty -m "commit 2"');
|
|
218
|
+
sh.exec('git tag 1.1.0-rc.0');
|
|
219
|
+
sh.exec('git push --tags');
|
|
220
|
+
await gitClient.init();
|
|
221
|
+
t.is(gitClient.config.getContext('latestTag'), '1.0.1');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test.serial('should generate correct changelog', async t => {
|
|
225
|
+
const gitClient = factory(Git, { options: { git } });
|
|
226
|
+
sh.exec('git tag 1.0.0');
|
|
227
|
+
gitAdd('line', 'file', 'Add file');
|
|
228
|
+
gitAdd('line', 'file', 'Add file');
|
|
229
|
+
await gitClient.init();
|
|
230
|
+
const changelog = await gitClient.getChangelog();
|
|
231
|
+
t.regex(changelog, /\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)/);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test.serial('should get the full changelog since latest major tag', async t => {
|
|
235
|
+
const shell = factory(Shell);
|
|
236
|
+
const gitClient = factory(Git, {
|
|
237
|
+
options: { git: { tagMatch: '[0-9]\\.[0-9]\\.[0-9]', changelog: git.changelog } },
|
|
238
|
+
container: { shell }
|
|
239
|
+
});
|
|
240
|
+
sh.exec('git tag 1.0.0');
|
|
241
|
+
gitAdd('line', 'file', 'Add file');
|
|
242
|
+
sh.exec('git tag 2.0.0-rc.0');
|
|
243
|
+
gitAdd('line', 'file', 'Add file');
|
|
244
|
+
sh.exec('git tag 2.0.0-rc.1');
|
|
245
|
+
gitAdd('line', 'file', 'Add file');
|
|
246
|
+
await gitClient.init();
|
|
247
|
+
t.is(gitClient.config.getContext('latestTag'), '1.0.0');
|
|
248
|
+
const changelog = await gitClient.getChangelog();
|
|
249
|
+
t.regex(changelog, /\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)\n\* Add file \(\w{7}\)/);
|
|
250
|
+
});
|
package/test/git.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { EOL } from 'node:os';
|
|
2
|
+
import test from 'ava';
|
|
3
|
+
import sinon from 'sinon';
|
|
4
|
+
import sh from 'shelljs';
|
|
5
|
+
import Git from '../lib/plugin/git/Git.js';
|
|
6
|
+
import { factory } from './util/index.js';
|
|
7
|
+
import { mkTmpDir, readFile, gitAdd } from './util/helpers.js';
|
|
8
|
+
|
|
9
|
+
test.beforeEach(() => {
|
|
10
|
+
const tmp = mkTmpDir();
|
|
11
|
+
sh.pushd('-q', tmp);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test.serial('should return whether repo has upstream branch', async t => {
|
|
15
|
+
const gitClient = factory(Git);
|
|
16
|
+
sh.exec('git init');
|
|
17
|
+
gitAdd('line', 'file', 'Add file');
|
|
18
|
+
t.false(await gitClient.hasUpstreamBranch());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test.serial('should return branch name', async t => {
|
|
22
|
+
const gitClient = factory(Git);
|
|
23
|
+
sh.exec('git init');
|
|
24
|
+
t.is(await gitClient.getBranchName(), null);
|
|
25
|
+
sh.exec('git checkout -b feat');
|
|
26
|
+
gitAdd('line', 'file', 'Add file');
|
|
27
|
+
t.is(await gitClient.getBranchName(), 'feat');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test.serial('should return whether tag exists and if working dir is clean', async t => {
|
|
31
|
+
const gitClient = factory(Git);
|
|
32
|
+
sh.exec('git init');
|
|
33
|
+
t.false(await gitClient.tagExists('1.0.0'));
|
|
34
|
+
sh.touch('file');
|
|
35
|
+
t.false(await gitClient.isWorkingDirClean());
|
|
36
|
+
gitAdd('line', 'file', 'Add file');
|
|
37
|
+
sh.exec('git tag 1.0.0');
|
|
38
|
+
t.true(await gitClient.tagExists('1.0.0'));
|
|
39
|
+
t.true(await gitClient.isWorkingDirClean());
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test.serial('should throw if tag exists', async t => {
|
|
43
|
+
const gitClient = factory(Git);
|
|
44
|
+
sh.exec('git init');
|
|
45
|
+
sh.touch('file');
|
|
46
|
+
gitAdd('line', 'file', 'Add file');
|
|
47
|
+
sh.exec('git tag 0.0.2');
|
|
48
|
+
gitClient.config.setContext({ latestTag: '0.0.1', tagName: '0.0.2' });
|
|
49
|
+
const expected = { instanceOf: Error, message: /fatal: tag '0\.0\.2' already exists/ };
|
|
50
|
+
await t.throwsAsync(gitClient.tag({ name: '0.0.2' }), expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test.serial('should only warn if tag exists intentionally', async t => {
|
|
54
|
+
const gitClient = factory(Git);
|
|
55
|
+
const { warn } = gitClient.log;
|
|
56
|
+
sh.exec('git init');
|
|
57
|
+
sh.touch('file');
|
|
58
|
+
gitAdd('line', 'file', 'Add file');
|
|
59
|
+
sh.exec('git tag 1.0.0');
|
|
60
|
+
gitClient.config.setContext({ latestTag: '1.0.0', tagName: '1.0.0' });
|
|
61
|
+
await t.notThrowsAsync(gitClient.tag());
|
|
62
|
+
t.is(warn.callCount, 1);
|
|
63
|
+
t.is(warn.firstCall.args[0], 'Tag "1.0.0" already exists');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test.serial('should return the remote url', async t => {
|
|
67
|
+
sh.exec(`git init`);
|
|
68
|
+
{
|
|
69
|
+
const options = { git: { pushRepo: 'origin' } };
|
|
70
|
+
const gitClient = factory(Git, { options });
|
|
71
|
+
t.is(await gitClient.getRemoteUrl(), null);
|
|
72
|
+
sh.exec(`git remote add origin foo`);
|
|
73
|
+
t.is(await gitClient.getRemoteUrl(), 'foo');
|
|
74
|
+
}
|
|
75
|
+
{
|
|
76
|
+
const options = { git: { pushRepo: 'another' } };
|
|
77
|
+
const gitClient = factory(Git, { options });
|
|
78
|
+
t.is(await gitClient.getRemoteUrl(), null);
|
|
79
|
+
sh.exec(`git remote add another bar`);
|
|
80
|
+
t.is(await gitClient.getRemoteUrl(), 'bar');
|
|
81
|
+
}
|
|
82
|
+
{
|
|
83
|
+
const options = { git: { pushRepo: 'git://github.com/webpro/release-it.git' } };
|
|
84
|
+
const gitClient = factory(Git, { options });
|
|
85
|
+
t.is(await gitClient.getRemoteUrl(), 'git://github.com/webpro/release-it.git');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test.serial('should return the non-origin remote', async t => {
|
|
90
|
+
const bare = mkTmpDir();
|
|
91
|
+
sh.exec(`git init --bare ${bare}`);
|
|
92
|
+
sh.exec(`git clone ${bare} .`);
|
|
93
|
+
gitAdd('line', 'file', 'Add file');
|
|
94
|
+
sh.exec('git remote rename origin upstream');
|
|
95
|
+
const gitClient = factory(Git);
|
|
96
|
+
t.is(await gitClient.getRemoteUrl(), bare);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test.serial('should stage, commit, tag and push', async t => {
|
|
100
|
+
const bare = mkTmpDir();
|
|
101
|
+
sh.exec(`git init --bare ${bare}`);
|
|
102
|
+
sh.exec(`git clone ${bare} .`);
|
|
103
|
+
const version = '1.2.3';
|
|
104
|
+
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
|
105
|
+
{
|
|
106
|
+
const gitClient = factory(Git);
|
|
107
|
+
sh.exec(`git tag ${version}`);
|
|
108
|
+
t.is(await gitClient.getLatestTagName(), version);
|
|
109
|
+
}
|
|
110
|
+
{
|
|
111
|
+
const gitClient = factory(Git);
|
|
112
|
+
gitAdd('line', 'file', 'Add file');
|
|
113
|
+
sh.exec('npm --no-git-tag-version version patch');
|
|
114
|
+
await gitClient.stage('package.json');
|
|
115
|
+
await gitClient.commit({ message: `Release v1.2.4` });
|
|
116
|
+
await gitClient.tag({ name: 'v1.2.4', annotation: 'Release v1.2.4' });
|
|
117
|
+
t.is(await gitClient.getLatestTagName(), 'v1.2.4');
|
|
118
|
+
await gitClient.push();
|
|
119
|
+
const status = sh.exec('git status -uno');
|
|
120
|
+
t.true(status.includes('nothing to commit'));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test.serial('should commit, tag and push with extra args', async t => {
|
|
125
|
+
const bare = mkTmpDir();
|
|
126
|
+
sh.exec(`git init --bare ${bare}`);
|
|
127
|
+
sh.exec(`git clone ${bare} .`);
|
|
128
|
+
gitAdd('line', 'file', 'Add file');
|
|
129
|
+
const options = { git: { commitArgs: '-S', tagArgs: ['-T', 'foo'], pushArgs: ['-U', 'bar', '-V'] } };
|
|
130
|
+
const gitClient = factory(Git, { options });
|
|
131
|
+
const stub = sinon.stub(gitClient.shell, 'exec').resolves();
|
|
132
|
+
await gitClient.stage('package.json');
|
|
133
|
+
await gitClient.commit({ message: `Release v1.2.4` });
|
|
134
|
+
await gitClient.tag({ name: 'v1.2.4', annotation: 'Release v1.2.4' });
|
|
135
|
+
await gitClient.push();
|
|
136
|
+
t.true(stub.secondCall.args[0].includes('-S'));
|
|
137
|
+
t.is(stub.thirdCall.args[0][5], '-T');
|
|
138
|
+
t.is(stub.thirdCall.args[0][6], 'foo');
|
|
139
|
+
t.true(stub.lastCall.args[0].join(' ').includes('-U bar -V'));
|
|
140
|
+
stub.restore();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test.serial('should amend commit without message if not provided', async t => {
|
|
144
|
+
const bare = mkTmpDir();
|
|
145
|
+
sh.exec(`git init --bare ${bare}`);
|
|
146
|
+
sh.exec(`git clone ${bare} .`);
|
|
147
|
+
gitAdd('line', 'file', 'Add file');
|
|
148
|
+
const options = { git: { commitArgs: ['--amend', '--no-edit', '--no-verify'] } };
|
|
149
|
+
const gitClient = factory(Git, { options });
|
|
150
|
+
const stub = sinon.stub(gitClient.shell, 'exec').resolves();
|
|
151
|
+
await gitClient.stage('package.json');
|
|
152
|
+
await gitClient.commit();
|
|
153
|
+
t.deepEqual(stub.secondCall.args[0], ['git', 'commit', '--amend', '--no-edit', '--no-verify']);
|
|
154
|
+
stub.restore();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test.serial('should commit and tag with quoted characters', async t => {
|
|
158
|
+
const bare = mkTmpDir();
|
|
159
|
+
sh.exec(`git init --bare ${bare}`);
|
|
160
|
+
sh.exec(`git clone ${bare} .`);
|
|
161
|
+
const gitClient = factory(Git, {
|
|
162
|
+
options: { git: { commitMessage: 'Release ${version}', tagAnnotation: 'Release ${version}\n\n${changelog}' } }
|
|
163
|
+
});
|
|
164
|
+
sh.touch('file');
|
|
165
|
+
const changelog = `- Foo's${EOL}- "$bar"${EOL}- '$baz'${EOL}- foo`;
|
|
166
|
+
gitClient.config.setContext({ version: '1.0.0', changelog });
|
|
167
|
+
|
|
168
|
+
await gitClient.stage('file');
|
|
169
|
+
await gitClient.commit();
|
|
170
|
+
await gitClient.tag({ name: '1.0.0' });
|
|
171
|
+
await gitClient.push();
|
|
172
|
+
{
|
|
173
|
+
const { stdout } = sh.exec('git log -1 --format=%s');
|
|
174
|
+
t.is(stdout.trim(), 'Release 1.0.0');
|
|
175
|
+
}
|
|
176
|
+
{
|
|
177
|
+
const { stdout } = sh.exec('git tag -n99');
|
|
178
|
+
t.is(stdout.trim(), `1.0.0 Release 1.0.0\n \n - Foo's\n - "$bar"\n - '$baz'\n - foo`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test.serial('should push to origin', async t => {
|
|
183
|
+
const bare = mkTmpDir();
|
|
184
|
+
sh.exec(`git init --bare ${bare}`);
|
|
185
|
+
sh.exec(`git clone ${bare} .`);
|
|
186
|
+
gitAdd('line', 'file', 'Add file');
|
|
187
|
+
const gitClient = factory(Git);
|
|
188
|
+
const spy = sinon.spy(gitClient.shell, 'exec');
|
|
189
|
+
await gitClient.push();
|
|
190
|
+
t.deepEqual(spy.lastCall.args[0], ['git', 'push']);
|
|
191
|
+
const actual = sh.exec('git ls-tree -r HEAD --name-only', { cwd: bare });
|
|
192
|
+
t.is(actual.trim(), 'file');
|
|
193
|
+
spy.restore();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test.serial('should push to tracked upstream branch', async t => {
|
|
197
|
+
const bare = mkTmpDir();
|
|
198
|
+
sh.exec(`git init --bare ${bare}`);
|
|
199
|
+
sh.exec(`git clone ${bare} .`);
|
|
200
|
+
sh.exec(`git remote rename origin upstream`);
|
|
201
|
+
gitAdd('line', 'file', 'Add file');
|
|
202
|
+
const gitClient = factory(Git);
|
|
203
|
+
const spy = sinon.spy(gitClient.shell, 'exec');
|
|
204
|
+
await gitClient.push();
|
|
205
|
+
t.deepEqual(spy.lastCall.args[0], ['git', 'push']);
|
|
206
|
+
const actual = sh.exec('git ls-tree -r HEAD --name-only', { cwd: bare });
|
|
207
|
+
t.is(actual.trim(), 'file');
|
|
208
|
+
spy.restore();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test.serial('should push to repo url', async t => {
|
|
212
|
+
const bare = mkTmpDir();
|
|
213
|
+
sh.exec(`git init --bare ${bare}`);
|
|
214
|
+
sh.exec(`git clone ${bare} .`);
|
|
215
|
+
gitAdd('line', 'file', 'Add file');
|
|
216
|
+
const options = { git: { pushRepo: 'https://host/repo.git' } };
|
|
217
|
+
const gitClient = factory(Git, { options });
|
|
218
|
+
const spy = sinon.spy(gitClient.shell, 'exec');
|
|
219
|
+
try {
|
|
220
|
+
await gitClient.push();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
t.deepEqual(spy.lastCall.args[0], ['git', 'push', 'https://host/repo.git']);
|
|
223
|
+
}
|
|
224
|
+
spy.restore();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test.serial('should push to remote name (not "origin")', async t => {
|
|
228
|
+
const bare = mkTmpDir();
|
|
229
|
+
sh.exec(`git init --bare ${bare}`);
|
|
230
|
+
sh.exec(`git clone ${bare} .`);
|
|
231
|
+
gitAdd('line', 'file', 'Add file');
|
|
232
|
+
sh.exec(`git remote add upstream ${sh.exec('git config --get remote.origin.url')}`);
|
|
233
|
+
const options = { git: { pushRepo: 'upstream' } };
|
|
234
|
+
const gitClient = factory(Git, { options });
|
|
235
|
+
const spy = sinon.spy(gitClient.shell, 'exec');
|
|
236
|
+
await gitClient.push();
|
|
237
|
+
t.deepEqual(spy.lastCall.args[0], ['git', 'push', 'upstream']);
|
|
238
|
+
const actual = sh.exec('git ls-tree -r HEAD --name-only', { cwd: bare });
|
|
239
|
+
t.is(actual.trim(), 'file');
|
|
240
|
+
{
|
|
241
|
+
sh.exec(`git checkout -b foo`);
|
|
242
|
+
gitAdd('line', 'file', 'Add file');
|
|
243
|
+
await gitClient.push();
|
|
244
|
+
t.deepEqual(spy.lastCall.args[0], ['git', 'push', '--set-upstream', 'upstream', 'foo']);
|
|
245
|
+
t.regex(
|
|
246
|
+
await spy.lastCall.returnValue,
|
|
247
|
+
/branch .?foo.? set up to track (remote branch .?foo.? from .?upstream.?|.?upstream\/foo.?)/i
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
spy.restore();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test.serial('should return repo status', async t => {
|
|
254
|
+
const gitClient = factory(Git);
|
|
255
|
+
sh.exec('git init');
|
|
256
|
+
gitAdd('line', 'file1', 'Add file');
|
|
257
|
+
sh.ShellString('line').toEnd('file1');
|
|
258
|
+
sh.ShellString('line').toEnd('file2');
|
|
259
|
+
sh.exec('git add file2');
|
|
260
|
+
t.is(await gitClient.status(), ' M file1\nA file2');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test.serial('should reset files', async t => {
|
|
264
|
+
const gitClient = factory(Git);
|
|
265
|
+
sh.exec('git init');
|
|
266
|
+
gitAdd('line', 'file', 'Add file');
|
|
267
|
+
sh.ShellString('line').toEnd('file');
|
|
268
|
+
t.regex(await readFile('file'), /^line\s*line\s*$/);
|
|
269
|
+
await gitClient.reset('file');
|
|
270
|
+
t.regex(await readFile('file'), /^line\s*$/);
|
|
271
|
+
await gitClient.reset(['file2, file3']);
|
|
272
|
+
t.regex(gitClient.log.warn.firstCall.args[0], /Could not reset file2, file3/);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test.serial('should roll back when cancelled', async t => {
|
|
276
|
+
sh.exec('git init');
|
|
277
|
+
sh.exec(`git remote add origin file://foo`);
|
|
278
|
+
const version = '1.2.3';
|
|
279
|
+
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
|
280
|
+
const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true, tagName: 'v${version}' } };
|
|
281
|
+
const gitClient = factory(Git, { options });
|
|
282
|
+
const exec = sinon.spy(gitClient.shell, 'execFormattedCommand');
|
|
283
|
+
sh.exec(`git tag ${version}`);
|
|
284
|
+
gitAdd('line', 'file', 'Add file');
|
|
285
|
+
|
|
286
|
+
await gitClient.init();
|
|
287
|
+
|
|
288
|
+
sh.exec('npm --no-git-tag-version version patch');
|
|
289
|
+
|
|
290
|
+
gitClient.bump('1.2.4');
|
|
291
|
+
await gitClient.beforeRelease();
|
|
292
|
+
await gitClient.stage('package.json');
|
|
293
|
+
await gitClient.commit({ message: 'Add this' });
|
|
294
|
+
await gitClient.tag();
|
|
295
|
+
await gitClient.rollbackOnce();
|
|
296
|
+
|
|
297
|
+
t.is(exec.args[11][0], 'git tag --delete v1.2.4');
|
|
298
|
+
t.is(exec.args[12][0], 'git reset --hard HEAD~1');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test.serial('should not touch existing history when rolling back', async t => {
|
|
302
|
+
sh.exec('git init');
|
|
303
|
+
const version = '1.2.3';
|
|
304
|
+
gitAdd(`{"version":"${version}"}`, 'package.json', 'Add package.json');
|
|
305
|
+
const options = { git: { requireCleanWorkingDir: true, commit: true, tag: true } };
|
|
306
|
+
const gitClient = factory(Git, { options });
|
|
307
|
+
sh.exec(`git tag ${version}`);
|
|
308
|
+
|
|
309
|
+
const exec = sinon.spy(gitClient.shell, 'execFormattedCommand');
|
|
310
|
+
gitClient.config.setContext({ version: '1.2.4' });
|
|
311
|
+
await gitClient.beforeRelease();
|
|
312
|
+
await gitClient.commit();
|
|
313
|
+
await gitClient.rollbackOnce();
|
|
314
|
+
|
|
315
|
+
t.is(exec.args[3][0], 'git reset --hard HEAD');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test.serial('should not roll back with risky config', async t => {
|
|
319
|
+
sh.exec('git init');
|
|
320
|
+
const options = { git: { requireCleanWorkingDir: false, commit: true, tag: true } };
|
|
321
|
+
const gitClient = factory(Git, { options });
|
|
322
|
+
await gitClient.beforeRelease();
|
|
323
|
+
t.is('rollbackOnce' in gitClient, false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test.serial('should return latest tag from default branch (not parent commit)', async t => {
|
|
327
|
+
sh.exec('git init');
|
|
328
|
+
|
|
329
|
+
{
|
|
330
|
+
const options = { git: { getLatestTagFromAllRefs: true } };
|
|
331
|
+
const gitClient = factory(Git, { options });
|
|
332
|
+
gitAdd('main', 'file', 'Add file in main');
|
|
333
|
+
const defaultBranchName = await gitClient.getBranchName();
|
|
334
|
+
const developBranchName = 'develop';
|
|
335
|
+
const featureBranchPrefix = 'feature';
|
|
336
|
+
await gitClient.tag({ name: '1.0.0' });
|
|
337
|
+
sh.exec(`git branch ${developBranchName} ${defaultBranchName}`);
|
|
338
|
+
sh.exec(`git checkout -b ${featureBranchPrefix}/first ${developBranchName}`);
|
|
339
|
+
gitAdd('feature/1', 'file', 'Update file in feature branch (1)');
|
|
340
|
+
sh.exec(`git checkout ${developBranchName}`);
|
|
341
|
+
sh.exec(`git merge --no-ff ${featureBranchPrefix}/first`);
|
|
342
|
+
await gitClient.tag({ name: '1.1.0-rc.1' });
|
|
343
|
+
sh.exec(`git checkout ${defaultBranchName}`);
|
|
344
|
+
sh.exec(`git merge --no-ff ${developBranchName}`);
|
|
345
|
+
await gitClient.tag({ name: '1.1.0' });
|
|
346
|
+
sh.exec(`git checkout -b ${featureBranchPrefix}/second ${developBranchName}`);
|
|
347
|
+
gitAdd('feature/2', 'file', 'Update file again, in feature branch (2)');
|
|
348
|
+
sh.exec(`git checkout ${developBranchName}`);
|
|
349
|
+
sh.exec(`git merge --no-ff ${featureBranchPrefix}/second`);
|
|
350
|
+
t.is(await gitClient.getLatestTagName(), '1.1.0');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
{
|
|
354
|
+
const options = { git: { getLatestTagFromAllRefs: false } };
|
|
355
|
+
const gitClient = factory(Git, { options });
|
|
356
|
+
t.is(await gitClient.getLatestTagName(), '1.1.0-rc.1');
|
|
357
|
+
}
|
|
358
|
+
});
|