release-it 14.13.0 → 14.14.1

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 ADDED
@@ -0,0 +1,102 @@
1
+ # Changelog
2
+
3
+ This document lists breaking changes for each major release.
4
+
5
+ See the GitHub Releases page for detailed changelogs:
6
+ [https://github.com/release-it/release-it/releases](https://github.com/release-it/release-it/releases)
7
+
8
+ ## v14
9
+
10
+ - Removed `global` property from plugins. Use `this.config[key]` instead.
11
+ - Removed deprecated `npm.access` option. Set this in `package.json` instead.
12
+
13
+ ## v13
14
+
15
+ - Dropped support for Node v8
16
+ - Dropped support for GitLab v11.6 and lower.
17
+ - Deprecated `scripts` are removed (in favor of [hooks](https://github.com/release-it/release-it#hooks)).
18
+ - Removed deprecated `--non-interactive` (`-n`) argument. Use `--ci` instead.
19
+ - Removed old `%s` and `[REV_RANGE]` syntax in command substitutions. Use `${version}` and `${latestTag}` instead.
20
+
21
+ ## v12
22
+
23
+ - The `--follow-tags` argument for `git push` has been moved to the default configuration. This is only a breaking
24
+ change if `git.pushArgs` was not empty (it was empty by default).
25
+
26
+ ## v11
27
+
28
+ - The custom `conventional-changelog` increment (e.g. `"increment": "conventional:angular"`) with additional script
29
+ configuration is replaced with a plugin. Please see
30
+ [conventional changelog](https://github.com/release-it/release-it/blob/master/docs/changelog.md#conventional-changelog)
31
+ how to use this plugin.
32
+ - The `pkgFiles` option has been removed. If there's a need to bump other files than what `npm version` bumps, it should
33
+ be (part of) a plugin.
34
+ - By default, the latest version was derived from the latest Git tag. From v11, if the repo has a `package.json` then
35
+ that `version` is used instead. The `use` option has been removed. Also see
36
+ [latest version](https://github.com/release-it/release-it#latest-version).
37
+ - `scripts.changelog` has been moved to `git.changelog`
38
+
39
+ ## v10
40
+
41
+ - Dropped support for Node v6
42
+ - Deprecated options from v9 are removed, the `dist.repo` config in particular (also see
43
+ [distribution repository](https://github.com/release-it/release-it/blob/master/docs/recipes/distribution-repo.md) for
44
+ alternatives).
45
+ - Drop the `--debug` flag. `DEBUG=release-it:* ...` still works.
46
+
47
+ ## v9
48
+
49
+ There should be no breaking changes, but there have been major internal refactorings and an improved UI. A bunch of new
50
+ features and bug fixes have been implemented. Last but not least, the configuration structure is changed significantly.
51
+ For this (backwards compatible) change, deprecation warnings are shown, and configurations must be migrated with the
52
+ next major release (v10).
53
+
54
+ - All "command hooks" have been moved to `scripts.*`, and some have been renamed.
55
+ - All `src.*` options have been moved to `git.*` (and `scripts.*`).
56
+ - The `dist.repo` configuration and functionality has been removed.
57
+
58
+ ## v8
59
+
60
+ - Drop the `--force` flag. It's only use was to move a Git tag.
61
+
62
+ ## v7
63
+
64
+ - No longer adds untracked files to release commit. (#230)
65
+
66
+ ## v6
67
+
68
+ - Default value for `requireCleanWorkingDir` is now `true` (previously: `false`). (#173)
69
+ - Skip prompt (interactive) if corresponding task (non-interactive) is disabled. E.g. `npm.publish: false` will also not
70
+ show "publish" prompt.
71
+
72
+ ## v5
73
+
74
+ - Drop support for Node v4.
75
+
76
+ [Release notes for v5](https://github.com/release-it/release-it/releases/tag/5.0.0-beta.0)
77
+
78
+ ## v4
79
+
80
+ - Use `shell.exec` for build commands by default (previously this required a `!` prefix).
81
+
82
+ [Release notes for v4](https://github.com/release-it/release-it/releases/tag/4.0.0-rc.0)
83
+
84
+ ## v3
85
+
86
+ - Configuration filename must be `.release-it.json` (previously `.release.json`).
87
+ - Refactored configuration structure in this file (and the CLI arguments with it).
88
+
89
+ [Release notes for v3](https://github.com/release-it/release-it/releases/tag/3.0.0)
90
+
91
+ ## v2
92
+
93
+ - Build command is executed before git commit/push.
94
+ - Configuration options are better organized. Most of them are backwards compatible with a deprecation notice.
95
+
96
+ [Release notes for v2](https://github.com/release-it/release-it/releases/tag/2.0.0)
97
+
98
+ ## v1
99
+
100
+ Initial major release.
101
+
102
+ [Release notes for v1](https://github.com/release-it/release-it/releases/tag/1.0.0)
package/README.md CHANGED
@@ -271,6 +271,14 @@ Use `--verbose` to log the output of the commands.
271
271
  For the sake of verbosity, the full list of hooks is actually: `init`, `beforeBump`, `bump`, `beforeRelease`, `release`
272
272
  or `afterRelease`. However, hooks like `before:beforeRelease` look weird and are usually not useful in practice.
273
273
 
274
+ Note that arguments need to be quoted properly when used from the command line:
275
+
276
+ ```bash
277
+ release-it --'hooks.after:release="echo Successfully released ${name} v${version} to ${repo.repository}."'
278
+ ```
279
+
280
+ Using Inquirer.js inside custom hook scripts might cause issues (since release-it also uses this itself).
281
+
274
282
  ## Plugins
275
283
 
276
284
  Since v11, release-it can be extended in many, many ways. Here are some plugins:
@@ -323,7 +331,7 @@ While mostly used as a CLI tool, release-it can be used as a dependency to integ
323
331
  - [metalsmith/metalsmith](https://github.com/metalsmith/metalsmith)
324
332
  - [react-native-paper](https://github.com/callstack/react-native-paper)
325
333
  - [js-cookie/js-cookie](https://github.com/js-cookie/js-cookie)
326
- - [mirumee/saleor](https://github.com/mirumee/saleor)
334
+ - [saleor/saleor](https://github.com/saleor/saleor)
327
335
  - [mozilla/readability](https://github.com/mozilla/readability)
328
336
  - [redis/node-redis](https://github.com/redis/node-redis)
329
337
  - [shipshapecode/shepherd](https://github.com/shipshapecode/shepherd)
@@ -49,6 +49,7 @@
49
49
  "release": false,
50
50
  "releaseName": "Release ${version}",
51
51
  "releaseNotes": null,
52
+ "milestones": [],
52
53
  "tokenRef": "GITLAB_TOKEN",
53
54
  "tokenHeader": "Private-Token",
54
55
  "certificateAuthorityFile": null,
@@ -10,7 +10,7 @@ class GitRelease extends GitBase {
10
10
  getInitialOptions(options) {
11
11
  const baseOptions = super.getInitialOptions(...arguments);
12
12
  const git = options.git || defaultConfig.git;
13
- const gitOptions = _.pick(git, ['tagName', 'pushRepo', 'changelog']);
13
+ const gitOptions = _.pick(git, ['tagName', 'tagMatch', 'pushRepo', 'changelog']);
14
14
  return _.defaults(baseOptions, gitOptions);
15
15
  }
16
16
 
@@ -4,6 +4,7 @@ const got = require('got');
4
4
  const globby = require('globby');
5
5
  const FormData = require('form-data');
6
6
  const _ = require('lodash');
7
+ const allSettled = require('promise.allsettled');
7
8
  const Release = require('../GitRelease');
8
9
  const { format, e } = require('../../util');
9
10
  const prompts = require('./prompts');
@@ -107,6 +108,66 @@ class GitLab extends Release {
107
108
  }
108
109
  }
109
110
 
111
+ async beforeRelease() {
112
+ await super.beforeRelease();
113
+ await this.checkReleaseMilestones();
114
+ }
115
+
116
+ async checkReleaseMilestones() {
117
+ if (this.options.skipChecks) return;
118
+
119
+ const releaseMilestones = this.getReleaseMilestones();
120
+ if (releaseMilestones.length < 1) {
121
+ return;
122
+ }
123
+
124
+ this.log.exec(`gitlab releases#checkReleaseMilestones`);
125
+
126
+ const { id } = this.getContext();
127
+ const endpoint = `projects/${id}/milestones`;
128
+ const requests = releaseMilestones.map(milestone => {
129
+ const options = {
130
+ method: 'GET',
131
+ searchParams: {
132
+ title: milestone,
133
+ include_parent_milestones: true
134
+ }
135
+ };
136
+ return this.request(endpoint, options).then(response => {
137
+ if (!Array.isArray(response)) {
138
+ const { baseUrl } = this.getContext();
139
+ throw new Error(
140
+ `Unexpected response from ${baseUrl}/${endpoint}. Expected an array but got: ${JSON.stringify(response)}`
141
+ );
142
+ }
143
+ if (response.length === 0) {
144
+ const error = new Error(`Milestone '${milestone}' does not exist.`);
145
+ this.log.warn(error.message);
146
+ throw error;
147
+ }
148
+ this.log.verbose(`gitlab releases#checkReleaseMilestones: milestone '${milestone}' exists`);
149
+ });
150
+ });
151
+ try {
152
+ await allSettled(requests).then(results => {
153
+ for (const result of results) {
154
+ if (result.status === 'rejected') {
155
+ throw e('Missing one or more milestones in GitLab. Creating a GitLab release will fail.', docs);
156
+ }
157
+ }
158
+ });
159
+ } catch (err) {
160
+ this.debug(err);
161
+ throw err;
162
+ }
163
+ this.log.verbose('gitlab releases#checkReleaseMilestones: done');
164
+ }
165
+
166
+ getReleaseMilestones() {
167
+ const { milestones } = this.options;
168
+ return (milestones || []).map(milestone => format(milestone, this.config.getContext()));
169
+ }
170
+
110
171
  async release() {
111
172
  const glRelease = () => this.createRelease();
112
173
  const glUploadAssets = () => this.uploadAssets();
@@ -138,6 +199,7 @@ class GitLab extends Release {
138
199
  const name = format(releaseName, this.config.getContext());
139
200
  const description = releaseNotes || '-';
140
201
  const releaseUrl = `${origin}/${repo.repository}/-/releases`;
202
+ const releaseMilestones = this.getReleaseMilestones();
141
203
 
142
204
  this.log.exec(`gitlab releases#createRelease "${name}" (${tagName})`, { isDryRun });
143
205
 
@@ -161,6 +223,10 @@ class GitLab extends Release {
161
223
  };
162
224
  }
163
225
 
226
+ if (releaseMilestones.length) {
227
+ options.json.milestones = releaseMilestones;
228
+ }
229
+
164
230
  try {
165
231
  await this.request(endpoint, options);
166
232
  this.log.verbose('gitlab releases#createRelease: done');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-it",
3
- "version": "14.13.0",
3
+ "version": "14.14.1",
4
4
  "description": "Generic CLI tool to automate versioning and package publishing related tasks.",
5
5
  "keywords": [
6
6
  "build",
@@ -60,7 +60,7 @@
60
60
  "async-retry": "1.3.3",
61
61
  "chalk": "4.1.2",
62
62
  "cosmiconfig": "7.0.1",
63
- "debug": "4.3.3",
63
+ "debug": "4.3.4",
64
64
  "execa": "5.1.1",
65
65
  "form-data": "4.0.0",
66
66
  "git-url-parse": "11.6.0",
@@ -70,13 +70,14 @@
70
70
  "inquirer": "8.2.0",
71
71
  "is-ci": "3.0.1",
72
72
  "lodash": "4.17.21",
73
- "mime-types": "2.1.34",
73
+ "mime-types": "2.1.35",
74
74
  "new-github-release-url": "1.0.0",
75
75
  "open": "7.4.2",
76
76
  "ora": "5.4.1",
77
77
  "os-name": "4.0.1",
78
78
  "parse-json": "5.2.0",
79
- "semver": "7.3.5",
79
+ "promise.allsettled": "1.0.5",
80
+ "semver": "7.3.6",
80
81
  "shelljs": "0.8.5",
81
82
  "update-notifier": "5.1.0",
82
83
  "url-join": "4.0.1",
@@ -88,19 +89,19 @@
88
89
  "devDependencies": {
89
90
  "@octokit/request-error": "2.1.0",
90
91
  "ava": "3.15.0",
91
- "eslint": "8.7.0",
92
- "eslint-config-prettier": "8.3.0",
92
+ "eslint": "7.32.0",
93
+ "eslint-config-prettier": "8.5.0",
93
94
  "eslint-plugin-ava": "13.2.0",
94
- "eslint-plugin-import": "2.25.4",
95
+ "eslint-plugin-import": "2.26.0",
95
96
  "eslint-plugin-prettier": "4.0.0",
96
97
  "markdown-toc": "1.2.0",
97
- "mock-fs": "5.1.2",
98
+ "mock-fs": "4.14.0",
98
99
  "mock-stdio": "1.0.3",
99
- "nock": "13.2.2",
100
+ "nock": "13.2.4",
100
101
  "node-fetch": "2.6.7",
101
- "prettier": "2.5.1",
102
+ "prettier": "2.6.2",
102
103
  "proxyquire": "2.1.3",
103
- "sinon": "12.0.1",
104
+ "sinon": "13.0.1",
104
105
  "strip-ansi": "6.0.0"
105
106
  },
106
107
  "engines": {
package/test/github.js CHANGED
@@ -214,7 +214,7 @@ test('should release to enterprise host', async t => {
214
214
  });
215
215
 
216
216
  test('should release to alternative host and proxy', async t => {
217
- const remote = { api: 'https://my-custom-host.org/api/v3', host: 'my-custom-host.org' };
217
+ const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
218
218
  interceptAuthentication(remote);
219
219
  interceptCollaborator(remote);
220
220
  interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
@@ -222,8 +222,8 @@ test('should release to alternative host and proxy', async t => {
222
222
  git,
223
223
  github: {
224
224
  tokenRef,
225
- pushRepo: `git://my-custom-host.org:user/repo`,
226
- host: 'my-custom-host.org',
225
+ pushRepo: `git://custom.example.org:user/repo`,
226
+ host: 'custom.example.org',
227
227
  proxy: 'http://proxy:8080'
228
228
  }
229
229
  };
@@ -235,24 +235,24 @@ test('should release to alternative host and proxy', async t => {
235
235
 
236
236
  const { isReleased, releaseUrl } = github.getContext();
237
237
  t.true(isReleased);
238
- t.is(releaseUrl, `https://my-custom-host.org/user/repo/releases/tag/1.0.1`);
238
+ t.is(releaseUrl, `https://custom.example.org/user/repo/releases/tag/1.0.1`);
239
239
  exec.restore();
240
240
  });
241
241
 
242
242
  test('should release to git.pushRepo', async t => {
243
- const remote = { api: 'https://my-custom-host.org/api/v3', host: 'my-custom-host.org' };
243
+ const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
244
244
  interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
245
245
  const options = { git: { pushRepo: 'upstream', changelog: '' }, github: { tokenRef, skipChecks: true } };
246
246
  const github = factory(GitHub, { options });
247
247
  const exec = sinon.stub(github.shell, 'exec').callThrough();
248
248
  exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('1.0.0');
249
- exec.withArgs('git remote get-url upstream').resolves('https://my-custom-host.org/user/repo');
249
+ exec.withArgs('git remote get-url upstream').resolves('https://custom.example.org/user/repo');
250
250
 
251
251
  await runTasks(github);
252
252
 
253
253
  const { isReleased, releaseUrl } = github.getContext();
254
254
  t.true(isReleased);
255
- t.is(releaseUrl, 'https://my-custom-host.org/user/repo/releases/tag/1.0.1');
255
+ t.is(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
256
256
  exec.restore();
257
257
  });
258
258
 
@@ -385,10 +385,10 @@ test('should generate GitHub web release url for enterprise host', async t => {
385
385
  const options = {
386
386
  git,
387
387
  github: {
388
- pushRepo: 'git://my-custom-host.org:user/repo',
388
+ pushRepo: 'git://custom.example.org:user/repo',
389
389
  release: true,
390
390
  web: true,
391
- host: 'my-custom-host.org',
391
+ host: 'custom.example.org',
392
392
  releaseName: 'The Launch',
393
393
  releaseNotes: 'echo It happened'
394
394
  }
@@ -403,7 +403,7 @@ test('should generate GitHub web release url for enterprise host', async t => {
403
403
  t.true(isReleased);
404
404
  t.is(
405
405
  releaseUrl,
406
- 'https://my-custom-host.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
406
+ 'https://custom.example.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
407
407
  );
408
408
  exec.restore();
409
409
  });
package/test/gitlab.js CHANGED
@@ -7,7 +7,8 @@ const {
7
7
  interceptCollaborator,
8
8
  interceptCollaboratorFallback,
9
9
  interceptPublish,
10
- interceptAsset
10
+ interceptAsset,
11
+ interceptMilestones
11
12
  } = require('./stub/gitlab');
12
13
  const { factory, runTasks } = require('./util');
13
14
 
@@ -55,7 +56,8 @@ test.serial('should upload assets and release', async t => {
55
56
  release: true,
56
57
  releaseName: 'Release ${version}',
57
58
  releaseNotes: 'echo Custom notes',
58
- assets: 'test/resources/file-v${version}.txt'
59
+ assets: 'test/resources/file-v${version}.txt',
60
+ milestones: ['${version}', '${latestVersion} UAT']
59
61
  }
60
62
  };
61
63
  const gitlab = factory(GitLab, { options });
@@ -63,6 +65,26 @@ test.serial('should upload assets and release', async t => {
63
65
 
64
66
  interceptUser();
65
67
  interceptCollaborator();
68
+ interceptMilestones({
69
+ query: { title: '2.0.1' },
70
+ milestones: [
71
+ {
72
+ id: 17,
73
+ iid: 3,
74
+ title: '2.0.1'
75
+ }
76
+ ]
77
+ });
78
+ interceptMilestones({
79
+ query: { title: '2.0.0 UAT' },
80
+ milestones: [
81
+ {
82
+ id: 42,
83
+ iid: 4,
84
+ title: '2.0.0 UAT'
85
+ }
86
+ ]
87
+ });
66
88
  interceptAsset();
67
89
  interceptPublish({
68
90
  body: {
@@ -76,7 +98,8 @@ test.serial('should upload assets and release', async t => {
76
98
  url: `${pushRepo}/uploads/7e8bec1fe27cc46a4bc6a91b9e82a07c/file-v2.0.1.txt`
77
99
  }
78
100
  ]
79
- }
101
+ },
102
+ milestones: ['2.0.1', '2.0.0 UAT']
80
103
  }
81
104
  });
82
105
 
@@ -88,6 +111,41 @@ test.serial('should upload assets and release', async t => {
88
111
  t.is(releaseUrl, `${pushRepo}/-/releases`);
89
112
  });
90
113
 
114
+ test.serial('should throw when release milestone is missing', async t => {
115
+ const pushRepo = 'https://gitlab.com/user/repo';
116
+ const options = {
117
+ git: { pushRepo },
118
+ gitlab: {
119
+ tokenRef,
120
+ release: true,
121
+ milestones: ['${version}', '${latestVersion} UAT']
122
+ }
123
+ };
124
+ const gitlab = factory(GitLab, { options });
125
+ sinon.stub(gitlab, 'getLatestVersion').resolves('2.0.0');
126
+
127
+ interceptUser();
128
+ interceptCollaborator();
129
+ interceptMilestones({
130
+ query: { title: '2.0.1' },
131
+ milestones: [
132
+ {
133
+ id: 17,
134
+ iid: 3,
135
+ title: '2.0.1'
136
+ }
137
+ ]
138
+ });
139
+ interceptMilestones({
140
+ query: { title: '2.0.0 UAT' },
141
+ milestones: []
142
+ });
143
+
144
+ await t.throwsAsync(runTasks(gitlab), {
145
+ message: /^Missing one or more milestones in GitLab. Creating a GitLab release will fail./
146
+ });
147
+ });
148
+
91
149
  test.serial('should release to self-managed host', async t => {
92
150
  const host = 'https://gitlab.example.org';
93
151
  const scope = nock(host);
@@ -195,7 +253,14 @@ test('should not make requests in dry run', async t => {
195
253
  });
196
254
 
197
255
  test('should skip checks', async t => {
198
- const options = { gitlab: { tokenRef, skipChecks: true } };
256
+ const options = { gitlab: { tokenRef, skipChecks: true, release: true, milestones: ['v1.0.0'] } };
199
257
  const gitlab = factory(GitLab, { options });
258
+ const spy = sinon.spy(gitlab, 'client', ['get']);
259
+
200
260
  await t.notThrowsAsync(gitlab.init());
261
+ await t.notThrowsAsync(gitlab.beforeRelease());
262
+
263
+ t.is(spy.get.callCount, 0);
264
+
265
+ t.is(gitlab.log.exec.args.filter(entry => /checkReleaseMilestones/.test(entry[0])).length, 0);
201
266
  });
package/test/npm.js CHANGED
@@ -13,9 +13,9 @@ test('should return npm package url', t => {
13
13
  });
14
14
 
15
15
  test('should return npm package url (custom registry)', t => {
16
- const options = { npm: { name: 'my-cool-package', publishConfig: { registry: 'https://my-registry.com/' } } };
16
+ const options = { npm: { name: 'my-cool-package', publishConfig: { registry: 'https://registry.example.org/' } } };
17
17
  const npmClient = factory(npm, { options });
18
- t.is(npmClient.getPackageUrl(), 'https://my-registry.com/package/my-cool-package');
18
+ t.is(npmClient.getPackageUrl(), 'https://registry.example.org/package/my-cool-package');
19
19
  });
20
20
 
21
21
  test('should return default tag', async t => {
@@ -19,6 +19,22 @@ module.exports.interceptCollaboratorFallback = (
19
19
  .get(`/api/v4/projects/${group ? `${group}%2F` : ''}${owner}%2F${project}/members/${userId}`)
20
20
  .reply(200, { id: userId, username: owner, access_level: 30 });
21
21
 
22
+ module.exports.interceptMilestones = (
23
+ { host = 'https://gitlab.com', owner = 'user', project = 'repo', query = {}, milestones = [] } = {},
24
+ options
25
+ ) =>
26
+ nock(host, options)
27
+ .get(`/api/v4/projects/${owner}%2F${project}/milestones`)
28
+ .query(
29
+ Object.assign(
30
+ {
31
+ include_parent_milestones: true
32
+ },
33
+ query
34
+ )
35
+ )
36
+ .reply(200, JSON.stringify(milestones));
37
+
22
38
  module.exports.interceptPublish = (
23
39
  { host = 'https://gitlab.com', owner = 'user', project = 'repo', body } = {},
24
40
  options
package/test/tasks.js CHANGED
@@ -358,7 +358,7 @@ test.serial('should initially publish non-private scoped npm package privately',
358
358
  test.serial('should use pkg.publishConfig.registry', async t => {
359
359
  const { target } = t.context;
360
360
  const pkgName = path.basename(target);
361
- const registry = 'https://my-registry.com';
361
+ const registry = 'https://my-registry.example.org';
362
362
 
363
363
  gitAdd(
364
364
  JSON.stringify({
@@ -47,10 +47,11 @@ module.exports.runTasks = async plugin => {
47
47
 
48
48
  const name = (await plugin.getName()) || '__test__';
49
49
  const latestVersion = (await plugin.getLatestVersion()) || '1.0.0';
50
+ const latestTag = plugin.config.getContext('latestTag');
50
51
  const changelog = (await plugin.getChangelog(latestVersion)) || null;
51
52
  const increment = getIncrement(plugin);
52
53
 
53
- plugin.config.setContext({ name, latestVersion, latestTag: latestVersion, changelog });
54
+ plugin.config.setContext({ name, latestVersion, latestTag, changelog });
54
55
 
55
56
  const { preRelease } = plugin.config.options;
56
57
  const isPreRelease = Boolean(preRelease);