Compare commits

..
Author SHA1 Message Date
Pierre Vanduynslager 0716a45b7d feat: require Node.js >=10.13
BREAKING CHANGE: Require Node.js >= 10.13
2019-12-05 13:18:42 -05:00
Pierre Vanduynslager 916c2685c5 feat: allow to release any version on a branch if up to date with next branch 2019-12-04 18:00:16 -05:00
Pierre Vanduynslager 534c0dbc89 Merge branch 'master' into beta 2019-12-03 20:29:06 -05:00
Pierre Vanduynslager ec54c0be8a test: fix calls to fetch in tests 2019-12-03 20:20:04 -05:00
Pierre Vanduynslager cbef9d18da fix: modify fetch function to handle CircleCI specifics 2019-12-03 18:07:12 -05:00
Pierre Vanduynslager ecc4e663f5 docs: clarify GitLab CI/CD protected variable usage 2019-12-03 11:49:56 -05:00
Pierre Vanduynslager b2c1b2c670 feat: use Git notes to store the channels on which a version has been released
BREAKING CHANGE: this feature change the way semantic-release keep track of the channels on which a version has been released.
It now use a JSON object stored in a [Git note](https://git-scm.com/docs/git-notes) instead of Git tags formatted as v{version}@{channel}.

The tags formatted as v{version}@{channel} will now be ignored. If you have made releases with v16.0.0 on branches other than the default one you will have to update your repository.

The changes to make consist in:
- Finding all the versions that have been released on a branch other than the default one by searching for all tags formatted v{version}@{channel}
- For each of those version:
  - Create a tag without the {@channel} if none doesn't already exists
  - Add a Git note to the tag without the {@channel} containing the channels on which the version was released formatted as `{"channels":["channel1","channel2"]}` and using `null` for the default channel (for example.`{"channels":[null,"channel1","channel2"]}`)
  - Push the tags and notes
  - Update the GitHub releases that refer to a tag formatted as v{version}@{channel} to use the tag without it
  - Delete the tags formatted as v{version}@{channel}
2019-12-02 23:38:40 -05:00
Gregor MartynusandPierre Vanduynslager 29d6b5d6fa build: remove .github/airtable-crm.yml
I don't think we use that
2019-12-02 13:27:31 -05:00
Pierre Vanduynslager 2caafbaa2b fix: add a flag indicate which branch is the main one 2019-11-28 13:59:06 -05:00
Pierre Vanduynslager e722354f4f style: merge both filter functions in get-last-release 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 97748c5e25 fix: do not create tags in dry-run mode for released to add to a channel 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager aec96c791f fix: correctly determine release to add to a channel
- Add only the most recent release to a channel (rather than adding all the one not added yet)
- Avoid attempting to ad the version twice in case that version is already present in multiple upper branches
2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 5744c5ecd2 fix: add channel to publish success log 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 1275b8c005 revert: fix: update commit-analyzer dep version off errant publish
This reverts commit 8d8910577f.
2019-11-23 11:06:31 -05:00
Eddie LefflerandPierre Vanduynslager 8d8910577f fix: update commit-analyzer dep version off errant publish 2019-11-20 23:11:33 -05:00
EddieandPierre Vanduynslager 9890584007 fix: update plugins dependencies 2019-11-20 22:29:59 -05:00
Pierre Vanduynslager b2b7b57fbd style: prettier formatting 2019-11-20 20:25:36 -05:00
Pierre Vanduynslager 63f51ae6dd fix: allow multiple branches with same channel 2019-11-20 16:17:24 -05:00
Pierre Vanduynslager 20e7a38cdb style: prettier formatting 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager e06eb82cff chore: add Mockserver generated file to gitignore 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager 751a5f1349 fix: handle branch properties set to false 2019-11-20 14:16:13 -05:00
Sean DawsonandPierre Vanduynslager 9575198266 docs: add leiningen-semantic-release to list of community plugins 2019-11-18 11:29:10 -05:00
EdandPierre Vanduynslager 09754ac001 docs(shareable-configurations-list): add semantic-release-npm-github-publish 2019-11-18 11:27:53 -05:00
greenkeeper[bot]andPierre Vanduynslager 2c13136ff4 fix(package): update yargs to version 15.0.1 2019-11-17 18:13:39 -05:00
Pierre Vanduynslager 00420a83c0 fix: simplify get-tags algorithm 2019-11-08 12:35:34 -05:00
Pierre Vanduynslager 131734873e fix: throws error if the commit associated with a tag cannot be found 2019-11-07 14:33:53 -05:00
Pierre Vanduynslager de77a799a8 fix: call getTagHead only when necessary 2019-11-07 14:33:53 -05:00
greenkeeper[bot]andPierre Vanduynslager ffff100f47 fix(package): update cosmiconfig to version 6.0.0 2019-11-02 15:32:18 -04:00
Pierre Vanduynslager 56186419a7 fix(package): update micromatch to version 4.0.2 2019-11-01 22:23:18 -04:00
Pierre Vanduynslager 7a939a8970 fix: use authenticated URL to check if local branch is up to date 2019-11-01 20:44:31 -04:00
Pierre Vanduynslager 2ec856eb1d docs: add requirements section to README 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager cffe9a8d33 perf: use git tag --merge <branch> to filter tags present in a branch history
BREAKING CHANGE: Git CLI version 2.7.1 or higher is now required

The `--merge` option of the `git tag` command has been added in Git version 2.7.1 and is now used by semantic-release
2019-11-01 16:58:55 -04:00
Pierre Vanduynslager 844e0b07e0 fix: remove hack to workaround GitHub Rebase & Merge 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager 1edae67326 fix: display erroring git commands properly 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager e5344cd89b fix(package): update micromatch to version 3.1.10 2019-10-29 13:26:41 -04:00
Pierre Vanduynslager 2c655b2328 fix(package): update p-each-series to version 2.1.0 2019-10-29 13:26:08 -04:00
Pierre Vanduynslager 581998cff0 fix(package): update semver-diff to version 3.1.1 2019-10-29 13:25:07 -04:00
Pierre Vanduynslager 65c412293e fix(package): update @semantic-release/npm to version 6.0.0-beta.3 2019-10-29 12:01:59 -04:00
Pierre Vanduynslager 6053b89af3 fix(package): update @semantic-release/github to version 5.6.0-beta.1 2019-10-29 12:01:33 -04:00
Pierre Vanduynslager 3daf78081f Merge branch 'master' into beta 2019-10-29 11:57:31 -04:00
Justin DietzandPierre Vanduynslager 9eaf9552c1 docs(contributing): correct lint fix command 2019-10-27 21:56:05 -04:00
Pierre Vanduynslager 1693073814 fix(package): update execa to version 3.2.0 2019-10-26 00:26:50 -04:00
Pierre Vanduynslager 2f3d934069 fix: require Node.js >=8.16 2019-10-26 00:26:50 -04:00
f645547f2f docs(recipes): GitHub actions (#1317)
Co-Authored-By: Natan Sągol <m@merlinnot.com>
2019-10-23 10:47:24 -07:00
Pierre Vanduynslager 3438ec5c79 test: fix typo in test examples 2019-10-18 16:25:40 -04:00
Pierre Vanduynslager 4af8548366 fix: ignore custom port when converting ssh repo URL to https 2019-10-18 16:25:40 -04:00
Pierre Vanduynslager 95379270d6 fix(package): update execa to version 3.0.0 2019-10-14 12:56:56 -04:00
Pierre Vanduynslager ff60ac594c test: specify version of gitbox image 2019-10-10 11:57:58 -04:00
Pierre Vanduynslager 4b66303d2f docs: typo is link to workflow config 2019-10-10 11:57:58 -04:00
Alexander GandGregor Martynus dc19dfa2e9 docs: remove unnecessarily apostrophe
- improve punctuation of the how-to revert a release section
2019-10-10 07:32:14 -07:00
knidarknessandPierre Vanduynslager 79d22a2556 fix: clarify message for EGITNOPERMISSION error 2019-10-09 17:57:18 -04:00
Pierre Vanduynslager 9f2ec79d67 docs: limit list of suggested tools for commit linting 2019-10-09 16:42:52 -04:00
Pierre Vanduynslager 73f0c775da fix: update plugins dependencies 2019-10-04 18:59:55 -04:00
greenkeeper[bot]andPierre Vanduynslager 0e24022608 fix(package): update read-pkg-up to version 7.0.0 2019-09-27 10:37:25 -07:00
Pierre Vanduynslager 2769a5057d docs: add links to workflow configuration 2019-09-26 14:48:54 -07:00
Pierre Vanduynslager 53c85701df docs: fix links in configuration doc 2019-09-26 14:48:54 -07:00
Pierre Vanduynslager 8b2bb688f9 docs: fix link to FAQ node minimum version 2019-09-26 14:48:54 -07:00
greenkeeper[bot]andPierre Vanduynslager 5986bf920e chore(package): update xo to version 0.25.0 2019-09-26 10:18:12 -07:00
greenkeeper[bot]andPierre Vanduynslager c86045ca49 chore(package): update dockerode to version 3.0.0 2019-09-24 13:37:46 -07:00
Pierre Vanduynslager fa62d0ba0b revert: fix: revert to execa ^1.0.0
This reverts commit 6b3adf6bbe.
2019-09-13 17:09:03 -04:00
Pierre Vanduynslager f1d983ddb0 feat: require Node.js >=8.15
BREAKING CHANGE: Require Node.js => 8.15
2019-09-13 17:09:03 -04:00
Pierre Vanduynslager f5737c821b Merge branch 'master' into beta 2019-09-13 17:08:48 -04:00
Pierre Vanduynslager 0629f3cd8a revert: feat: create annotated tags
This reverts commit 4d581fc140.
2019-09-13 14:46:47 -04:00
Pierre Vanduynslager d120eaea8b revert: fix(package): update @semantic-release/github back to a build from the beta branch
This reverts commit 3f0a37944d.
2019-09-13 14:45:35 -04:00
Pierre Vanduynslager f67a667bcb revert: fix: prefix git auth url with "x-access-token:" when run in a GitHub Action
This reverts commit cb2c506d8b.
2019-09-13 14:45:35 -04:00
Scott WillekeandGregor Martynus 3f0a37944d fix(package): update @semantic-release/github back to a build from the beta branch
* Only the beta channel that is compatible with semantic-release v16
* 5.4.0-beta.1 worked, but subsequently v5.4.3 was released from master but didn't include the changes from the beta branch and  5.4.0-beta.1.
2019-09-12 12:30:03 -07:00
Scott WillekeandGregor Martynus cb2c506d8b fix: prefix git auth url with "x-access-token:" when run in a GitHub Action 2019-09-12 12:30:03 -07:00
Josh BiddickandGregor Martynus 9b78ab7c0d docs: spelling (#1284) 2019-09-05 15:01:13 -07:00
greenkeeper[bot]andGregor Martynus 8e4fa663fa chore(package): update nock to version 11.1.0 2019-09-04 14:20:39 -07:00
Pierre Vanduynslager 6f33c76fde docs: various clarifications and language improvvements 2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 9fd93ac37a docs: fix node version requirement link is FAQ 2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 7ddc2cfcbc docs: update table of content in README.md and in Gitbook SUMMARY.md 2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 55c3616e3f revert: docs(contributing): added instructions on how to run gitbook locally
This reverts commit a3da21a544.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager c517c7048e revert: docs(contributing): copy/pasted "Use gitbook locally" instruction from original url
This reverts commit cd00c2dc86.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 4b13280713 docs: correct list of default plugins 2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 95a9e89141 revert: fix(definitions): Repository documentation links
This reverts commit 1eb302545e.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 54d8e3fe99 revert: docs: note publishing on distribution channels in beta
This reverts commit 490d86d0d5.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager a6188d3902 revert: docs(recipes): cleaned doc and navigation
This reverts commit a8f8bffa37.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager e00b6c84df revert: docs: repared broken links to "CI configuration recipes"
This reverts commit 3f22978623.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 3c4a0fb773 revert: docs: cleaned "Developer guide" section navigation
This reverts commit b08486327d.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager e770c50f01 revert: docs: synched README.md and SUMMARY.md
This reverts commit 813e5f65ed.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 5e41dc89bd revert: docs: made doc file org clearer and augmented content
This reverts commit 5a5eaec3da.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager ce3d1bc715 revert: docs: corrections and further clarifications
This reverts commit 7cb5446bb5.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager 58aaf057f6 revert: docs: update semantic-release-cli broken link
This reverts commit 3bd63eb6a8.
2019-08-22 14:47:21 -04:00
Pierre Vanduynslager eea5de2863 revert: docs: broken link docs/03-recipes/travis.md
This reverts commit d2230fab7e.
2019-08-22 14:47:21 -04:00
greenkeeper[bot]andPierre Vanduynslager 3c2fe35ded fix(package): update yargs to version 14.0.0 2019-08-19 19:50:25 -04:00
Florian KellerandGregor Martynus 1eb302545e fix(definitions): Repository documentation links 2019-08-16 10:17:24 -07:00
GoisandPierre Vanduynslager d2230fab7e docs: broken link docs/03-recipes/travis.md
Fixed the link for `setup with semantic-release-cli.` in 'Environment Variables' section, refering a old file of this repo,
2019-08-13 14:15:28 -04:00
greenkeeper[bot]andGregor Martynus 391af986be fix(package): update hosted-git-info to version 3.0.0 2019-08-12 11:21:36 -07:00
Crhistian RamirezandGregor Martynus d45d8b6170 docs: fix typo 2019-08-11 09:32:29 -07:00
Pierre Vanduynslager 519df0d5c1 chore: remove commitizen from our dependencies 2019-08-09 14:13:09 -04:00
EmandGregor Martynus e168802379 docs: tried to fix #1250 2019-08-05 21:19:32 -07:00
Alvaro Pinot @alvaropinotandGregor Martynus 3bd63eb6a8 docs: update semantic-release-cli broken link
* Remove extra `01-`
2019-08-05 21:18:30 -07:00
Philipp RudloffandGregor Martynus 9a8a36ce45 fix(package): update dependency lodash to address security warnings (#1253) 2019-08-05 21:17:22 -07:00
Emmanuel SciaraandGregor Martynus 7cb5446bb5 docs: corrections and further clarifications
- (correction) removed comments from code samples.
-(correction) remove recommendation for global
install on CI environemnts.
- Synched README.md and SUMMARY.md
2019-07-31 14:40:10 -07:00
Emmanuel SciaraandGregor Martynus 5a5eaec3da docs: made doc file org clearer and augmented content
This is a first step to improving the doc: - renamed directories; - augmented a fair bit of content.
To be continued
2019-07-31 14:40:10 -07:00
Jed MaoandGregor Martynus 2d2bb38e61 docs(shareable-configurations-list): add @jedmao/semantic-release-npm-github-config (#1222) 2019-07-31 08:57:19 -07:00
Emmanuel SciaraandGregor Martynus cd00c2dc86 docs(contributing): copy/pasted "Use gitbook locally" instruction from original url 2019-07-30 17:44:23 -07:00
Emmanuel SciaraandGregor Martynus a3da21a544 docs(contributing): added instructions on how to run gitbook locally 2019-07-30 17:44:23 -07:00
Emmanuel SciaraandGregor Martynus 813e5f65ed docs: synched README.md and SUMMARY.md 2019-07-30 16:13:59 -07:00
Emmanuel SciaraandGregor Martynus b08486327d docs: cleaned "Developer guide" section navigation 2019-07-30 16:13:59 -07:00
Emmanuel SciaraandGregor Martynus 3f22978623 docs: repared broken links to "CI configuration recipes" 2019-07-30 16:13:59 -07:00
Emmanuel SciaraandGregor Martynus a8f8bffa37 docs(recipes): cleaned doc and navigation
Corrected inconsistancies between the sidebar and the actual pages. Remove non existing pages.
Clarified status of "Package managers and languages" to "to be completed".
2019-07-30 16:13:59 -07:00
scott willekeandGregor Martynus 490d86d0d5 docs: note publishing on distribution channels in beta
Since the readme hints at doing this, note that features to automate this are in beta. Also discussed at https://spectrum.chat/semantic-release/general/im-trying-to-configure-a-next-release~5bb8e692-a0cd-49be-8379-9fb6b25261fe
2019-07-21 19:03:45 -07:00
greenkeeper[bot]andGregor Martynus 665a582930 chore(package): update commitizen to version 4.0.0 2019-07-18 14:20:20 -07:00
David AghassiandGregor Martynus 9e28fd9da9 docs: added plugin developer guide docs 2019-07-18 14:20:11 -07:00
mehrad-rafighandGregor Martynus 6e60c58873 ci(node): set node 8 to node 8.3 and add node 12 2019-07-15 14:50:24 -07:00
greenkeeper[bot]andGregor Martynus 75f08302a9 fix(package): update marked to version 0.7.0 2019-07-06 10:15:52 -07:00
greenkeeper[bot]andGregor Martynus 4b2b2fb780 chore(package): update clear-module to version 4.0.0 2019-07-04 20:04:48 -07:00
Debjeet BiswasandGregor Martynus 3b8cae91fa docs: fix typo
fix misspelled `they`
2019-07-02 15:09:54 -07:00
Pierre Vanduynslager 6b3adf6bbe fix: revert to execa ^1.0.0 2019-06-26 12:33:59 -04:00
Rob CresswellandGregor Martynus 228fed7a0b docs: document that plugins config is an override (#1216)
This patch documents that the `plugin` config option is an override, not
a merge with the default. This is significant, for example, if you
customise the github plugin as it will then prevent npm publishing by
default unless the npm plugin is also defined manually.
2019-06-25 13:01:55 -07:00
Pierre Vanduynslager 01a0b2d08a test: use regexp to check missing module message 2019-06-25 11:30:37 -04:00
greenkeeper[bot]andPierre Vanduynslager 52c48be17b fix(package): update execa to version 2.0.0 2019-06-25 11:30:37 -04:00
Pierre Vanduynslager 3e4bb40990 style: fix prettier style 2019-06-07 16:03:30 -04:00
Pierre Vanduynslager 4d581fc140 feat: create annotated tags 2019-06-07 16:03:30 -04:00
Pierre Vanduynslager 4ed6213977 style: fix prettier style 2019-06-07 11:47:28 -04:00
greenkeeper[bot]andPierre Vanduynslager 8051294ffd fix(package): update env-ci to version 4.0.0 2019-06-07 11:21:44 -04:00
Pierre Vanduynslager 9948a74347 fix: revert to the correct refspec in fetch function 2019-06-06 12:54:21 -04:00
Pierre Vanduynslager 113157f42a test: fix parsing of test repo URL on Travis in unit tests 2019-06-05 17:18:17 -04:00
Pierre Vanduynslager d4f28abd47 test: fix parsing of MODULE_NOT_FOUND errors message in unit tests 2019-06-05 17:18:17 -04:00
greenkeeper[bot]andPierre Vanduynslager 95a0456a85 chore(package): update ava to version 2.0.0 2019-06-05 14:43:18 -04:00
jmodjeski75andGregor Martynus 3610422959 fix(repositoryUrl): on beta repositoryUrl needs auth for pre-release flows (#1186) 2019-06-05 11:33:54 -07:00
GregorandPierre Vanduynslager 038e640d5e fix: prefix git auth with "x-access-token:" when run in a GitHub Action 2019-06-05 12:17:21 -04:00
GregorandPierre Vanduynslager f3f9d1ef58 test: prefix git auth with "x-access-token:" when run in a GitHub Action 2019-06-05 12:17:21 -04:00
Johannes KleinandGregor Martynus 7e824b2c18 doc: add note to default plugins 2019-06-04 14:07:01 -07:00
Johannes KleinandGregor Martynus d4520e10ed doc: add info about default plugins 2019-06-04 14:07:01 -07:00
Ryan KennedyandGregor Martynus 37bcc9e515 fix: Add helpful detail to ERELEASEBRANCHES error message (#1188) 2019-05-23 12:21:17 -07:00
Niels HofmansandGregor Martynus bcf3a853f2 docs:optimize images (#1185)
*Total -- 258.67kb -> 182.47kb (29.46%)

/media/semantic-release-cli.png -- 116.80kb -> 65.42kb (43.99%)
/media/bender.png -- 141.88kb -> 117.05kb (17.5%)
2019-05-20 14:36:57 -07:00
Jan PiotrowskiandGregor Martynus d034ce5249 docs: Remove broken waffle.io 💀 badge 2019-05-20 14:08:48 -07:00
greenkeeper[bot]andPierre Vanduynslager 74103abac4 fix(package): update read-pkg-up to version 6.0.0 2019-05-14 15:00:17 -04:00
greenkeeper[bot]andPierre Vanduynslager 6e64745f6b chore(package): update fs-extra to version 8.0.0 2019-05-14 14:29:40 -04:00
Anton GolubandPierre Vanduynslager 4e001c34f9 docs: add semantic-release-github-pages to community plugins 2019-05-09 13:41:04 -04:00
Matt OakesandPierre Vanduynslager edf382f888 docs: add semantic-release-circleci-orb to the community plugin list 2019-05-08 16:01:01 -04:00
Hannes RaboandPierre Vanduynslager 39d15b37f9 docs: add semantic-release-slack-bot to community plugins 2019-05-08 15:45:30 -04:00
greenkeeper[bot]andPierre Vanduynslager 079251c6b8 chore(package): update tempy to version 0.3.0 2019-05-08 15:13:27 -04:00
greenkeeper[bot]andPierre Vanduynslager f4cf7c8b51 fix(package): update figures to version 3.0.0 2019-05-08 14:44:56 -04:00
Brett ZamirandPierre Vanduynslager c8ec0fd67f docs: fix grammar 2019-05-08 14:32:55 -04:00
greenkeeper[bot]andPierre Vanduynslager 1d776db143 chore(package): update nyc to version 14.0.0 2019-05-08 14:30:48 -04:00
Kengo TODAandPierre Vanduynslager 6b1938fd7e docs: add a plugin for Gradle project 2019-04-15 13:32:14 -04:00
greenkeeper[bot]andPierre Vanduynslager 6f3c21a10e fix(package): update resolve-from to version 5.0.0 2019-04-15 13:22:40 -04:00
greenkeeper[bot]andPierre Vanduynslager 06fe435254 fix(package): update aggregate-error to version 3.0.0 2019-04-15 11:38:39 -04:00
Ben HuttonandPierre Vanduynslager 61762e67a0 docs: update commitlint URL
The current commitlint URL goes to a repo which contains a redirect message and new URL.
Replace the outdated URL with the new URL for the commitlint repo.
2019-04-15 11:19:51 -04:00
greenkeeper[bot]andPierre Vanduynslager 5072fef3e4 chore(package): update file-url to version 3.0.0 2019-04-15 11:11:58 -04:00
greenkeeper[bot]andPierre Vanduynslager d61e3bce2e fix(package): update semver to version 6.0.0 2019-04-15 10:52:25 -04:00
Julien VialaandPierre Vanduynslager 0cd8a57b4f docs: correct way to print release plugin name 2019-04-15 09:58:35 -04:00
greenkeeper[bot]andPierre Vanduynslager db3fc3eaf7 fix(package): update hook-std to version 2.0.0 2019-04-13 23:57:25 -04:00
greenkeeper[bot]andPierre Vanduynslager a90a1038e7 fix(package): update read-pkg-up to version 5.0.0 2019-03-21 12:09:56 -04:00
greenkeeper[bot]andPierre Vanduynslager 30723c5998 fix(package): update p-reduce to version 2.0.0 2019-03-13 13:23:05 -04:00
greenkeeper[bot]andPierre Vanduynslager 840af0d734 chore(package): update p-retry to version 4.0.0 2019-03-13 11:34:08 -04:00
greenkeeper[bot]andPierre Vanduynslager 0a584def20 fix(package): update get-stream to version 5.0.0 2019-03-13 11:22:34 -04:00
greenkeeper[bot]andPierre Vanduynslager a5babc6157 fix(package): update p-locate to version 4.0.0 2019-03-13 11:02:21 -04:00
greenkeeper[bot]andPierre Vanduynslager 322c1c69ec chore(package): update ava to version 1.3.1 2019-03-06 13:22:00 -05:00
greenkeeper[bot]andPierre Vanduynslager 20e5d83456 chore(package): update sinon to version 7.2.7 2019-03-04 13:21:34 -05:00
Nicolas DelperdangeandPierre Vanduynslager e5a5867e82 docs(circle): fix incorrect script example
Little mistake in the example to add a ssh key for circleCi
2019-03-04 12:13:08 -05:00
greenkeeper[bot]andPierre Vanduynslager aed4ea2edc fix(package): update yargs to version 13.1.0 2019-02-15 12:11:12 -05:00
greenkeeper[bot]andPierre Vanduynslager 448a0ff977 chore(package): update xo to version 0.24.0 2019-01-30 17:27:04 -05:00
Kevin LluchandPierre Vanduynslager 1b7d8b2d21 docs: add semantic-release-ado to community plugins 2019-01-29 16:15:23 -05:00
Austin Cawley-EdwardsandPierre Vanduynslager 952e3fdf64 docs: fix typo in CircleCI recipe 2019-01-22 11:32:41 -05:00
David AghassiandPierre Vanduynslager 6a8eede96f docs: cleaned up wording and typos in docs 2019-01-12 14:27:56 -05:00
David AghassiandPierre Vanduynslager 4652cef1e6 docs: corrected typos in jsdocs 2019-01-12 14:27:56 -05:00
greenkeeper[bot]andPierre Vanduynslager b7aeabad93 fix(package): update marked to version 0.6.0 2019-01-10 16:14:04 -05:00
greenkeeper[bot]andPierre Vanduynslager 3819ca56a9 chore(package): update ava to version 1.0.1 2018-12-27 14:43:11 -05:00
greenkeeper[bot]andPierre Vanduynslager 1aefd98d31 fix(package): update aggregate-error to version 2.0.0 2018-12-26 13:22:26 -05:00
Pierre Vanduynslager 1243f79064 fix: correctly handle skipped releases 2018-12-16 16:28:52 -05:00
Pierre Vanduynslager 70c68ef3ed feat: allow publish plugins to return false in order to signify no release was done 2018-12-14 16:16:27 -05:00
Pierre VanduynslagerandGregor Martynus 048292baa5 docs: fix broken link in CircleCI recipe 2018-12-14 10:48:58 -08:00
Pierre VanduynslagerandGregor Martynus 0f642ffe4d docs: remove mentions of travis-deploy-once 2018-12-14 10:48:58 -08:00
greenkeeper[bot]andPierre Vanduynslager 89ad3a0b61 chore(package): update p-retry to version 3.0.0 2018-12-12 11:59:37 -05:00
Pierre Vanduynslager 649b53087a docs: mention that debug option is CLI only 2018-12-11 13:33:06 -05:00
Pierre Vanduynslager 862ec4c087 fix: allow to set ci option via API and config file 2018-12-11 13:33:06 -05:00
Pierre Vanduynslager 6b110b6e9e docs: switch to spectrum.chat 2018-12-03 17:48:15 -05:00
Pierre Vanduynslager e4c6649714 docs: syntax fixes in plugins list 2018-11-30 03:25:05 -05:00
Pierre Vanduynslager 6220641aba docs: add @semantic-release/apm to plugins list 2018-11-30 03:25:05 -05:00
Evan SirokyandPierre Vanduynslager a45273e921 docs: add maven-semantic-release to list of community plugins 2018-11-30 03:12:00 -05:00
Pierre Vanduynslager d1091133b9 chore(package): update nyc and sinon 2018-11-30 02:10:04 -05:00
Pierre Vanduynslager cd69583746 test: delete unused test helper file 2018-11-30 01:24:16 -05:00
Pierre Vanduynslager ffe1062830 fix: remove unnecessary branch parameter from push function 2018-11-29 20:08:25 -05:00
67 changed files with 1791 additions and 1097 deletions
-1
View File
@@ -1 +0,0 @@
base: app0ZOxG0FnHmOiuU
+3
View File
@@ -131,3 +131,6 @@ yarn.lock
# Gitbook
_book
# Mockserver
CertificateAuthorityCertificate.pem
+2 -2
View File
@@ -4,8 +4,8 @@ services:
- docker
node_js:
- 10
- 8
- 12
- 10.13
# Trigger a push build on release and greenkeeper branches + PRs build on every branches
# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
+1 -1
View File
@@ -224,7 +224,7 @@ All the [semantic-release](https://github.com/semantic-release) repositories use
Before pushing your code changes make sure there are no linting errors with `npm run lint`.
**Tips**:
- Most linting errors can be automatically fixed with `npm run lint --fix`.
- Most linting errors can be automatically fixed with `npm run lint -- --fix`.
- Install the [XO plugin](https://github.com/sindresorhus/xo#editor-plugins) for your editor to see linting errors directly in your editor and automatically fix them on save.
### Tests
+12 -8
View File
@@ -17,11 +17,6 @@
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
</p>
<p align="center">
<a href="https://waffle.io/semantic-release/semantic-release">
<img alt="Waffle.io" src="https://badge.waffle.io/semantic-release/semantic-release.svg?columns=all">
</a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/semantic-release">
<img alt="npm latest version" src="https://img.shields.io/npm/v/semantic-release/latest.svg">
@@ -61,7 +56,7 @@ This removes the immediate connection between human emotions and version numbers
By default **semantic-release** uses [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines). The commit message format can be changed with the [`preset` or `config` options](docs/usage/configuration.md#options) of the [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer#options) and [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator#options) plugins.
Tools such as [commitizen](https://github.com/commitizen/cz-cli), [commitlint](https://github.com/marionebl/commitlint) or [semantic-git-commit-cli](https://github.com/JPeer264/node-semantic-git-commit-cli) can be used to help contributors and enforce valid commit messages.
Tools such as [commitizen](https://github.com/commitizen/cz-cli) or [commitlint](https://github.com/conventional-changelog/commitlint) can be used to help contributors and enforce valid commit messages.
Here is an example of the release type that will be done based on a commit messages:
@@ -100,6 +95,14 @@ After running the tests, the command `semantic-release` will execute the followi
| Publish | Publish the release. |
| Notify | Notify of new releases or errors. |
## Requirements
In order to use **semantic-release** you need:
- To host your code in a [Git repository](https://git-scm.com)
- Use a Continuous Integration service that allows you to [securely set up credentials](docs/usage/ci-configuration.md#authentication)
- Git CLI version [2.7.1 or higher](docs/support/FAQ.md#why-does-semantic-release-require-git-version--271) installed in your Continuous Integration environment
- [Node.js](https://nodejs.org) version [8.16.0 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--816) installed in your Continuous Integration environment
## Documentation
- Usage
@@ -108,6 +111,7 @@ After running the tests, the command `semantic-release` will execute the followi
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
- [Configuration](docs/usage/configuration.md#configuration)
- [Plugins](docs/usage/plugins.md)
- [Workflow configuration](docs/usage/workflow-configuration.md)
- [Shareable configurations](docs/usage/shareable-configurations.md)
- Extending
- [Plugins](docs/extending/plugins-list.md)
@@ -119,8 +123,8 @@ After running the tests, the command `semantic-release` will execute the followi
- [Package managers and languages](docs/recipes/README.md)
- Developer guide
- [JavaScript API](docs/developer-guide/js-api.md)
- [Plugins](docs/developer-guide/plugin.md)
- [Shareable configuration](docs/developer-guide/shareable-configuration.md)
- [Plugins development](docs/developer-guide/plugin.md)
- [Shareable configuration development](docs/developer-guide/shareable-configuration.md)
- Support
- [Resources](docs/support/resources.md)
- [Frequently Asked Questions](docs/support/FAQ.md)
+8 -7
View File
@@ -1,10 +1,12 @@
# Summary
## Usage
- [Getting started](docs/usage/getting-started.md#getting-started)
- [Installation](docs/usage/installation.md#installation)
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
- [Configuration](docs/usage/configuration.md#configuration)
- [Plugins](docs/usage/plugins.md)
- [Workflow configuration](docs/usage/workflow-configuration.md)
- [Shareable configurations](docs/usage/shareable-configurations.md)
## Extending
@@ -12,19 +14,18 @@
- [Shareable configuration](docs/extending/shareable-configurations-list.md)
## Recipes
- [CI configurations](docs/recipes/README.md)
- [CircleCI 2.0 workflows](docs/recipes/circleci-workflows.md)
- [CI configurations](docs/recipes/README.md#ci-configurations)
- [CircleCI 2.0](docs/recipes/circleci-workflows.md)
- [Travis CI](docs/recipes/travis.md)
- [Travis CI with build stages](docs/recipes/travis-build-stages.md)
- [GitLab CI](docs/recipes/gitlab-ci.md)
- [Git hosted services](docs/recipes/README.md)
- [Git hosted services](docs/recipes/README.md#git-hosted-services)
- [Git authentication with SSH keys](docs/recipes/git-auth-ssh-keys.md)
- [Package managers and languages](docs/recipes/README.md)
- [Package managers and languages](docs/recipes/README.md#package-managers-and-languages)
## Developer guide
- [JavaScript API](docs/developer-guide/js-api.md)
- [Plugin](docs/developer-guide/plugin.md)
- [Shareable configuration](docs/developer-guide/shareable-configuration.md)
- [Plugin development](docs/developer-guide/plugin.md)
- [Shareable configuration development](docs/developer-guide/shareable-configuration.md)
## Support
- [Resources](docs/support/resources.md)
+4 -5
View File
@@ -10,7 +10,7 @@ var execa = require('execa');
var findVersions = require('find-versions');
var pkg = require('../package.json');
var MIN_GIT_VERSION = '2.0.0';
var MIN_GIT_VERSION = '2.7.1';
if (!semver.satisfies(process.version, pkg.engines.node)) {
console.error(
@@ -21,9 +21,8 @@ See https://github.com/semantic-release/semantic-release/blob/master/docs/suppor
process.exit(1);
}
execa
.stdout('git', ['--version'])
.then(stdout => {
execa('git', ['--version'])
.then(({stdout}) => {
var gitVersion = findVersions(stdout)[0];
if (semver.lt(gitVersion, MIN_GIT_VERSION)) {
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. Found ${gitVersion}.`);
@@ -36,7 +35,7 @@ execa
process.exit(1);
});
// Node 8+ from this point on
// Node 10+ from this point on
require('../cli')()
.then(exitCode => {
process.exitCode = exitCode;
+1 -1
View File
@@ -46,7 +46,7 @@ try {
}
for (const release of releases) {
console.log(`The release was published with plugin "${pluginName}".`);
console.log(`The release was published with plugin "${release.pluginName}".`);
}
} else {
console.log('No release published.');
+106 -1
View File
@@ -1 +1,106 @@
# Plugin developer guide
# Plugin Developer Guide
To create a plugin for `semantic-release`, you need to decide which parts of the release lifecycle are important to that plugin. For example, it is best to always have a `verify` step because you may be receiving inputs from a user and want to make sure they exist. A plugin can abide by any of the following lifecycles:
- `verify`
- `prepare`
- `publish`
- `success`
- `fail`
`semantic-release` will require the plugin via `node` and look through the required object for methods named like the lifecyles stated above. For example, if your plugin only had a `verify` and `success` step, the `main` file for your object would need to `export` an object with `verify` and `success` functions.
In addition to the lifecycle methods, each lifecyle is passed two objects:
1. `pluginConfig` - an object containing the options that a user may pass in via their `release.config.js` file (or similar)
2. `context` - provided by `semantic-release` for access to things like `env` variables set on the running process.
For each lifecycle you create, you will want to ensure it can accept `pluginConfig` and `context` as parameters.
## Creating a Plugin Project
It is recommended that you generate a new project with `yarn init`. This will provide you with a basic node project to get started with. From there, create an `index.js` file, and make sure it is specified as the `main` in the `package.json`. We will use this file to orchestrate the lifecycle methods later on.
Next, create a `src` or `lib` folder in the root of the project. This is where we will store our logic and code for how our lifecycle methods work. Finally, create a `test` folder so you can write tests related to your logic.
We recommend you setup a linting system to ensure good javascript practices are enforced. ESLint is usually the system of choice, and the configuration can be whatever you or your team fancies.
## Exposing Lifecycle Methods
In your `index.js` file, you can start by writing the following code
```javascript
const verifyConditions = require('./src/verify');
let verified;
/**
* Called by semantic-release during the verification step
* @param {*} pluginConfig The semantic-release plugin config
* @param {*} context The context provided by semantic-release
*/
async function verify(pluginConfig, context) {
await verifyConditions(pluginConfig, context);
verified = true;
}
module.exports = { verify };
```
Then, in your `src` folder, create a file called `verify.js` and add the following
```javascript
const AggregateError = require('aggregate-error');
/**
* A method to verify that the user has given us a slack webhook url to post to
*/
module.exports = async (pluginConfig, context) => {
const { logger } = context;
const errors = [];
// Throw any errors we accumulated during the validation
if (errors.length > 0) {
throw new AggregateError(errors);
}
};
```
As of right now, this code won't do anything. However, if you were to run this plugin via `semantic-release`, it would run when the `verify` step occurred.
Following this structure, you can create different steps and checks to run through out the release process.
## Supporting Options
Let's say we want to verify that an `option` is passed. An `option` is a configuration object that is specific to your plugin. For example, the user may set an `option` in their release config like:
```js
{
prepare: {
path: "@semantic-release/my-special-plugin"
message: "My cool release message"
}
}
```
This `message` option will be passed to the `pluginConfig` object mentioned earlier. We can use the validation method we created to verify this option exists so we can perform logic based on that knowledge. In our `verify` file, we can add the following:
```js
const { message } = pluginConfig;
if (message.length) {
//...
}
```
## Supporting Environment Variables
Similar to `options`, environment variables exist to allow users to pass tokens and set special URLs. These are set on the `context` object instead of the `pluginConfig` object. Let's say we wanted to check for `GITHUB_TOKEN` in the environment because we want to post to GitHub on the user's behalf. To do this, we can add the following to our `verify` command:
```js
const { env } = context;
if (env.GITHUB_TOKEN) {
//...
}
```
+23 -1
View File
@@ -2,15 +2,19 @@
## Official plugins
- [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer)
- **Note**: this is already part of semantic-release and does not have to be installed separately
- `analyzeCommits`: Determine the type of release by analyzing commits with [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog)
- [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator)
- **Note**: this is already part of semantic-release and does not have to be installed separately
- `generateNotes`: Generate release notes for the commits added since the last release with [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog)
- [@semantic-release/github](https://github.com/semantic-release/github)
- **Note**: this is already part of semantic-release and does not have to be installed separately
- `verifyConditions`: Verify the presence and the validity of the GitHub authentication and release configuration
- `publish`: Publish a [GitHub release](https://help.github.com/articles/about-releases)
- `success`: Add a comment to GitHub issues and pull requests resolved in the release
- `fail`: Open a GitHub issue when a release fails
- [@semantic-release/npm](https://github.com/semantic-release/npm)
- **Note**: this is already part of semantic-release and does not have to be installed separately
- `verifyConditions`: Verify the presence and the validity of the npm authentication and release configuration
- `prepare`: Update the package.json version and create the npm package tarball
- `publish`: Publish the package on the npm registry
@@ -41,6 +45,10 @@
[Open a Pull Request](https://github.com/semantic-release/semantic-release/blob/master/CONTRIBUTING.md#submitting-a-pull-request) to add your plugin to the list.
- [semantic-release-slack-bot](https://github.com/juliuscc/semantic-release-slack-bot)
- `verifyConditions`: Verify that the environment variable `SLACK_WEBHOOK` has been defined.
- `success`: Publish a message about the success to a slack channel.
- `fail`: Optionally publish a message about failure to a slack channel.
- [semantic-release-docker](https://github.com/felixfbecker/semantic-release-docker)
- `verifyConditions`: Verify that all needed configuration is present and login to the Docker registry.
- `publish`: Tag the image specified by `name` with the new version, push it to Docker Hub and update the latest tag
@@ -72,4 +80,18 @@
- `prepare`: Changes the version number in the `pom.xml` (or all `pom.xml` files in maven projects with multiple `pom.xml` files) and optionally creates a commit with this version number and pushes it to `master`
- `publish`: Runs `mvn deploy` to deploy to maven central and optionally will update to next snapshot version and merge changes to development branch
- [semantic-release-ado](https://github.com/lluchmk/semantic-release-ado)
- `prepare`: Stores the version number as an Azure DevOps pipeline variable availabe to downstream steps on the job
- `prepare`: Stores the version number as an Azure DevOps pipeline variable availabe to downstream steps on the job
- [gradle-semantic-release](https://github.com/KengoTODA/gradle-semantic-release-plugin)
- `verifyConditions`: Verify that project has a Gradle wrapper script, and `build.gradle` contains a task to publish artifacts.
- `prepare`: Changes the version number in the `gradle.properties`
- `publish`: Triggers Gradle to publish artifacts.
- [semantic-release-circleci-orb](https://github.com/matt-oakes/semantic-release-circleci-orb)
- `verifyConditions`: Verify the presence of the `CIRCLECI_API_TOKEN` environment variable, `orbName` option, and the `circleci` CLI.
- `publish`: Publish the CircleCI orb.
- [semantic-release-github-pages](https://github.com/qiwi/semantic-release-gh-pages-plugin)
- `verifyConditions`: Verify the presence of the auth token set via environment variables.
- `publish`: Pushes commit to the documentation branch.
- [leiningen-semantic-release](https://github.com/NoxHarmonium/leiningen-semantic-release)
- `verifyConditions`: Checks the project.clj is syntactically valid.
- `prepare`: Update the project.clj version and package the output jar file.
- `publish`: Publish the jar (and generated Maven metadata) to a maven repository (or clojars).
@@ -5,3 +5,17 @@
- [@semantic-release/gitlab-config](https://github.com/semantic-release/gitlab-config) - semantic-release shareable configuration for GitLab
## Community configurations
- [@jedmao/semantic-release-npm-github-config](https://github.com/jedmao/semantic-release-npm-github-config)
- Provides an informative [git](https://github.com/semantic-release/git) commit message for the release commit that does not trigger continuous integration and conforms to the [conventional commits specification](https://www.conventionalcommits.org/) (e.g., "chore(release): 1.2.3 [skip ci]\n\nnotes").
- Creates a tarball that gets uploaded with each [GitHub release](https://github.com/semantic-release/github).
- Publishes the same tarball to [npm](https://github.com/semantic-release/npm).
- Commits the version change in `package.json`.
- Creates or updates a [changelog](https://github.com/semantic-release/changelog) file.
- [semantic-release-npm-github-publish](https://github.com/oleg-koval/semantic-release-npm-github-publish)
- Based on [angular preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
- Adds more keywords for the `chore` **PATCH** release.
- Generates or updates a [changelog](https://github.com/semantic-release/changelog) file including all **PATCH** keywords (not included in default angular package).
- Updates GitHub release with release-notes.
- Bumps a version in package.json.
- Publishes the new version to [NPM](https://npmjs.org).
+1
View File
@@ -4,6 +4,7 @@
- [CircleCI 2.0 workflows](circleci-workflows.md)
- [Travis CI](travis.md)
- [GitLab CI](gitlab-ci.md)
- [GitHub Actions](github-actions.md)
## Git hosted services
- [Git authentication with SSH keys](git-auth-ssh-keys.md)
+1 -1
View File
@@ -137,7 +137,7 @@ jobs:
# Make sure only the current user can read the private key
- chmod 600 /tmp/git_deploy_key
# Create a script to return the passphrase environment variable to ssh-add
- echo 'echo ${SSH_PASSPHRASE}' > /tmp/askpass && chmod +x /tmp/askpass
- echo 'echo ${SSL_PASSPHRASE}' > /tmp/askpass && chmod +x /tmp/askpass
# Start the authentication agent
- eval "$(ssh-agent -s)"
# Add the key to the authentication agent
+68
View File
@@ -0,0 +1,68 @@
# Using semantic-release with [GitHub Actions](https://help.github.com/en/categories/automating-your-workflow-with-github-actions)
## Environment variables
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Secret Variables](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables).
In this example an [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
## Node project configuration
[GitHub Actions](https://github.com/features/actions) support [Workflows](https://help.github.com/en/articles/configuring-workflows), allowing to run tests on multiple Node versions and publish a release only when all test pass.
**Note**: The publish pipeline must run on [Node version >= 10.13](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
### `.github/workflows/release.yml` configuration for Node projects
The following is a minimal configuration for [`semantic-release`](https://github.com/semantic-release/semantic-release) with a build running on Node 12 when a new commit is pushed to a `master` branch. See [Configuring a Workflow](https://help.github.com/en/articles/configuring-a-workflow) for additional configuration options.
```yaml
name: Release
on:
push:
branches:
- master
jobs:
release:
name: Release
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
```
## Pushing `package.json` changes to a `master` branch
To keep `package.json` updated in the `master` branch, [`@semantic-release/git`](https://github.com/semantic-release/git) plugin can be used.
**Note**: Automatically populated `GITHUB_TOKEN` cannot be used if branch protection is enabled for the target branch. It is **not** advised to mitigate this limitation by overriding an automatically populated `GITHUB_TOKEN` variable with a [Personal Access Tokens](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line), as it poses a security risk. Since Secret Variables are available for Workflows triggered by any branch, it becomes a potential vector of attack, where a Workflow triggered from a non-protected branch can expose and use a token with elevated permissions, yielding branch protection insignificant. One can use Personal Access Tokens in trusted environments, where all developers should have the ability to perform administrative actions in the given repository and branch protection is enabled solely for convenience purposes, to remind about required reviews or CI checks.
## Trigger semantic-release on demand
There is a way to trigger semantic-relase on demand. Use [`repository_dispatch`](https://help.github.com/en/articles/events-that-trigger-workflows#external-events-repository_dispatch) event to have control on when to generate a release by making an HTTP request, e.g.:
```yaml
name: Release
on:
repository_dispatch:
types: [semantic-release]
jobs:
# ...
```
To trigger a release, call (with a [Personal Access Tokens](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) stored in `GITHUB_TOKEN` environment variable):
```
$ curl -v -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/[org-name-or-username]/[repository]/dispatches -d '{ "event_type": "semantic-release" }'
```
+4 -2
View File
@@ -2,13 +2,15 @@
## Environment variables
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Secret variables](https://docs.gitlab.com/ce/ci/variables/README.html#secret-variables).
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Protected variables](https://docs.gitlab.com/ce/ci/variables/README.html#protected-environment-variables).
**Note**: Make sure to configure your release branch as [protected](https://docs.gitlab.com/ce/user/project/protected_branches.html) in order for the CI/CD build to access the protected variables.
## Node project configuration
GitLab CI supports [Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html) allowing to test on multiple Node versions and publishing a release only when all test pass.
**Note**: The publish pipeline must run a [Node >= 8 version](../support/FAQ.md#why-does-semantic-release-require-node-version--83).
**Note**: The publish pipeline must run a [Node >= 10.13 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
### `.gitlab-ci.yml` configuration for Node projects
+14 -10
View File
@@ -4,7 +4,7 @@
**semantic-release** takes care of updating the `package.json`’s version before publishing to [npm](https://www.npmjs.com).
By default, only the published package will contains the version, which is the only place where it is *really* required, but the updated `package.json` will not be pushed to the Git repository
By default, only the published package will contain the version, which is the only place where it is *really* required, but the updated `package.json` will not be pushed to the Git repository
However, the [`@semantic-release/git`](https://github.com/semantic-release/git) plugin can be used to push the updated `package.json` as well as other files to the Git repository.
@@ -38,7 +38,7 @@ Yes with the [dry-run options](../usage/configuration.md#dryrun) which prints to
## Can I use semantic-release with Yarn?
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 8.3](#why-does-semantic-release-require-node-version--83) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 10.13](#why-does-semantic-release-require-node-version--1013) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
The recommended solution is to use the [Yarn](https://yarnpkg.com) [--ignore-engines](https://yarnpkg.com/en/docs/cli/install#toc-yarn-install-ignore-engines) option to install the project dependencies on the CI environment, so Yarn will ignore the **semantic-release**'s `engines` key:
@@ -48,7 +48,7 @@ $ yarn install --ignore-engines
**Note**: Several CI services use Yarn by default if your repository contains a `yarn.lock` file. So you should override the install step to specify `yarn install --ignore-engines`.
Alternatively you can use a [global](../usage/installation.md#global-installation) **semantic-release** installation and make sure to install and run the `semantic-release` command only in a CI jobs running with Node >= 8.3.
Alternatively you can use a [global](../usage/installation.md#global-installation) **semantic-release** installation and make sure to install and run the `semantic-release` command only in a CI jobs running with Node >= 10.13.
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can switch to Node 8 before installing and running the `semantic-release` command:
@@ -73,7 +73,7 @@ Yes, **semantic-release** is a Node CLI application but it can be used to publis
To publish a non-Node package (without a `package.json`) you would need to:
- Use a [global](../usage/installation.md#global-installation) **semantic-release** installation
- Set **semantic-release** [options](../usage/configuration.md#options) via [CLI arguments or rc file](../usage/configuration.md#configuration)
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 8](#why-does-semantic-release-require-node-version--83) to execute the `semantic-release` command
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 10.13](#why-does-semantic-release-require-node-version--1013) to execute the `semantic-release` command
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
@@ -141,11 +141,11 @@ See the [`@semantic-release/npm`](https://github.com/semantic-release/npm#semant
If you have introduced a breaking bug in a release you have 2 options:
- If you have a fix immediately ready, commit and push it (or merge it via a pull request) to the release branch
- Otherwise [revert the commit](https://git-scm.com/docs/git-revert) that introduced the bug and push the revert commit (or merge it via a pull request) to the release branch
- Otherwise, [revert the commit](https://git-scm.com/docs/git-revert) that introduced the bug and push the revert commit (or merge it via a pull request) to the release branch
In both cases **semantic-release** will publish a new release, so your package users' will get the fixed/reverted version.
In both cases **semantic-release** will publish a new release, so your package users will get the fixed/reverted version.
Depending on the package manager you are using, you might be able to un-publish or deprecate a release, in order to prevent users from downloading it by accident. For example npm allows you to [un-publish](https://docs.npmjs.com/cli/unpublish) [within 72 hours](https://www.npmjs.com/policies/unpublish) after releasing. You may also [deprecate](https://docs.npmjs.com/cli/deprecate) a release if you would rather avoid un-publishing.
Depending on the package manager you are using, you might be able to un-publish or deprecate a release, in order to prevent users from downloading it by accident. For example, npm allows you to [un-publish](https://docs.npmjs.com/cli/unpublish) [within 72 hours](https://www.npmjs.com/policies/unpublish) after release. You may also [deprecate](https://docs.npmjs.com/cli/deprecate) a release if you would rather avoid un-publishing.
In any case **do not remove the Git tag associated with the buggy version**, otherwise **semantic-release** will later try to republish that version. Publishing a version after un-publishing is not supported by most package managers.
@@ -232,11 +232,15 @@ See [“Introduction to SemVer” - Irina Gebauer](https://blog.greenkeeper.io/i
In addition the [verify conditions step](../../README.md#release-steps) verifies that all necessary conditions for proceeding with a release are met, and a new release will be performed [only if all your tests pass](../usage/ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded).
## Why does semantic-release require Node version >= 8.3?
## Why does semantic-release require Node version >= 10.13?
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 8.3 or higher**.
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 10.13 or higher**.
See [Node version requirement](../support/node-version.md#node-version-requirement) for more details and solutions.
See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions.
## Why does semantic-release require Git version >= 2.7.1?
**semantic-release** uses Git CLI commands to read information about the repository such as branches, commit history and tags. Certain commands and options (such as [the `--merged` option of the `git tag` command](https://git-scm.com/docs/git-tag/2.7.0#git-tag---no-mergedltcommitgt) or bug fixes related to `git ls-files`) used by **semantic-release** are only available in Git version 2.7.1 and higher.
## What is npx?
+3 -3
View File
@@ -1,6 +1,6 @@
# Node version requirement
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which requires **requires Node version 8.3 or higher**.
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which requires **requires Node version 10 or higher**.
**semantic-release** is meant to be used in a CI environment as a development support tool, not as a production dependency. Therefore the only constraint is to run the `semantic-release` in a CI environment providing Node 8 or higher.
@@ -8,9 +8,9 @@ See our [Node Support Policy](node-support-policy.md) for our long-term promise
## Recommended solution
### Run at least one CI job with Node >= 8.3
### Run at least one CI job with Node >= 10.13
The recommended approach is to run the `semantic-release` command from a CI job running on Node 8.3 or higher. This can either be a job used by your project to test on Node >= 8.3 or a dedicated job for the release steps.
The recommended approach is to run the `semantic-release` command from a CI job running on Node 10.13 or higher. This can either be a job used by your project to test on Node >= 10.13 or a dedicated job for the release steps.
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/README.md#ci-configurations) for more details.
+1
View File
@@ -5,4 +5,5 @@
- [CI Configuration](ci-configuration.md#ci-configuration)
- [Configuration](configuration.md#configuration)
- [Plugins](plugins.md)
- [Workflow configuration](workflow-configuration.md)
- [Shareable configurations](shareable-configurations.md)
+16 -1
View File
@@ -2,12 +2,23 @@
## Run `semantic-release` only after all tests succeeded
The `semantic-release` command must be executed only after all the tests in the CI build pass. If the build runs multiple jobs (for example to test on multiple Operating Systems or Node versions) the CI has to be configured to guarantee that the `semantic-release` command is executed only after all jobs are successful. This can be achieved with [Travis Build Stages](https://docs.travis-ci.com/user/build-stages), [CircleCI Workflows](https://circleci.com/docs/2.0/workflows), [Codeship Deployment Pipelines](https://documentation.codeship.com/basic/builds-and-configuration/deployment-pipelines), [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html#introduction-to-pipelines-and-jobs), [Codefresh Pipelines](https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines), [Wercker Workflows](http://devcenter.wercker.com/docs/workflows) or [GoCD Pipelines](https://docs.gocd.org/current/introduction/concepts_in_go.html#pipeline).
The `semantic-release` command must be executed only after all the tests in the CI build pass. If the build runs multiple jobs (for example to test on multiple Operating Systems or Node versions) the CI has to be configured to guarantee that the `semantic-release` command is executed only after all jobs are successful.
Here is a few example of the CI services that can be used to achieve this:
- [Travis Build Stages](https://docs.travis-ci.com/user/build-stages)
- [CircleCI Workflows](https://circleci.com/docs/2.0/workflows)
- [GitHub Actions](https://github.com/features/actions)
- [Codeship Deployment Pipelines](https://documentation.codeship.com/basic/builds-and-configuration/deployment-pipelines)
- [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html#introduction-to-pipelines-and-jobs)
- [Codefresh Pipelines](https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines)
- [Wercker Workflows](http://devcenter.wercker.com/docs/workflows)
- [GoCD Pipelines](https://docs.gocd.org/current/introduction/concepts_in_go.html#pipeline).
See [CI configuration recipes](../recipes/README.md#ci-configurations) for more details.
## Authentication
### Push access to the remote repository
**semantic-release** requires push access to the project Git repository in order to create [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). The Git authentication can be set with one of the following environment variables:
| Variable | Description |
@@ -19,6 +30,8 @@ See [CI configuration recipes](../recipes/README.md#ci-configurations) for more
Alternatively the Git authentication can be set up via [SSH keys](../recipes/git-auth-ssh-keys.md).
### Authentication for plugins
Most **semantic-release** [plugins](plugins.md) require setting up authentication in order to publish to a package manager registry. The default [@semantic-release/npm](https://github.com/semantic-release/npm#environment-variables) and [@semantic-release/github](https://github.com/semantic-release/github#environment-variables) plugins require the following environment variables:
| Variable | Description |
@@ -31,3 +44,5 @@ See each plugin's documentation for the environment variables required.
The authentication token/credentials have to be made available in the CI service via environment variables.
See [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on how to configure environment variables in your CI service.
**Note**: The environment variables `GH_TOKEN`, `GITHUB_TOKEN`, `GL_TOKEN` and `GITLAB_TOKEN` can be used for both the Git authentication and the API authentication required by [@semantic-release/github](https://github.com/semantic-release/github) and [@semantic-release/gitlab](https://github.com/semantic-release/gitlab).
+13 -19
View File
@@ -1,17 +1,17 @@
# Configuration
**semantic-release** configuration consists of:
- Git repository options ([URL](#repositoryurl), [release branches](#branches) and [tag format](#tagformat))
- [plugins](#plugins) definition
- run mode ([debug](#debug), [dry run](#dryrun) and [local (no CI)](#ci))
- Git repository ([URL](#repositoryurl) and options [release branches](#branches) and [tag format](#tagformat))
- Plugins [declaration](#plugins) and options
- Run mode ([debug](#debug), [dry run](#dryrun) and [local (no CI)](#ci))
All of these options can be configured directly or by extending a [shareable configuration](shareable-configurations.md).
All of these options can be configured through config file, CLI arguments or by extending a [shareable configuration](shareable-configurations.md).
Additionally, metadata of Git tags generated by **semantic-release** can be customized via standard [Git environment variables](#git-environment-variables).
## Configuration file
**semantic-release**’s [options](#options), mode and [plugins](plugins.md) can be set via:
**semantic-release**’s [options](#options), mode and [plugins](plugins.md) can be set via either:
- A `.releaserc` file, written in YAML or JSON, with optional extensions: .`yaml`/`.yml`/`.json`/`.js`
- A `release.config.js` file that exports an object
- A `release` key in the project's `package.json` file
@@ -20,8 +20,7 @@ Alternatively, some options can be set via CLI arguments.
The following three examples are the same.
Via `release` key in the project's `package.json` file:
- Via `release` key in the project's `package.json` file:
```json
{
"release": {
@@ -29,23 +28,15 @@ Via `release` key in the project's `package.json` file:
}
}
```
```bash
$ semantic-release
```
Via `.releaserc` file:
- Via `.releaserc` file:
```json
{
"branches": ["master", "next"]
}
```
```bash
$ semantic-release
```
Via CLI argument:
- Via CLI argument:
```bash
$ semantic-release --branch next
```
@@ -81,9 +72,11 @@ The branches on which releases should happen. By default **semantic-release** wi
- prereleases to the `beta` distribution channel from the branch `beta` if it exists
- prereleases to the `alpha` distribution channel from the branch `alpha` if it exists
**Note**: If your repository does not have a release branch, then **semantic-release** will fail with an `ERELEASEBRANCHES` error message. If you are using the default configuration, you can fix this error by pushing a `master` branch.
**Note**: Once **semantic-release** is configured, any user with the permission to push commits on one of those branches will be able to publish a release. It is recommended to protect those branches, for example with [GitHub protected branches](https://help.github.com/articles/about-protected-branches).
See [Plugins configuration](plugins.md#plugins) for more details.
See [Workflow configuration](workflow-configuration.md#workflow-configuration) for more details.
### repositoryUrl
@@ -156,7 +149,8 @@ Output debugging information. This can also be enabled by setting the `DEBUG` en
## Existing version tags
**semantic-release** uses [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging) to determine the commits added since the last release. If a release has been published before setting up **semantic-release** you must make sure the most recent commit included in the last published release is in the [release branches](#branches) history and is tagged with the version released, formatted according to the [tag format](#tagformat) configured (defaults to `vx.y.z`).
**semantic-release** uses [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging) to determine the commits added since the last release.
If a release has been published before setting up **semantic-release** you must make sure the most recent commit included in the last published release is in the [release branches](#branches) history and is tagged with the version released, formatted according to the [tag format](#tagformat) configured (defaults to `vx.y.z`).
If the previous releases were published with [`npm publish`](https://docs.npmjs.com/cli/publish) this should already be the case.
+6 -6
View File
@@ -1,10 +1,10 @@
# Getting started
In order to use **semantic-release** you must follows this steps:
- [Install](./installation.md#installation) **semantic-release** in your project
- Configure your Continuous Integration service to [run **semantic-release**](./ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded)
- Configure your Git repository and package manager repository [authentication](ci-configuration.md#authentication) in your Continuous Integration service
- Configure **semantic-release** [options and plugins](./configuration.md#configuration)
In order to use **semantic-release** you must follow these steps:
1. [Install](./installation.md#installation) **semantic-release** in your project
2. Configure your Continuous Integration service to [run **semantic-release**](./ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded)
3. Configure your Git repository and package manager repository [authentication](ci-configuration.md#authentication) in your Continuous Integration service
4. Configure **semantic-release** [options and plugins](./configuration.md#configuration)
Alternatively those steps can be easily done with the [**semantic-release** interactive CLI](https://github.com/semantic-release/cli):
@@ -19,4 +19,4 @@ semantic-release-cli setup
See the [semantic-release-cli](https://github.com/semantic-release/cli#what-it-does) documentation for more details.
**Note**: only a limited number of options, CI services and plugins is currently supported by `semantic-release-cli`.
**Note**: only a limited number of options, CI services and plugins are currently supported by `semantic-release-cli`.
+2 -2
View File
@@ -24,6 +24,6 @@ For other type of projects we recommend installing **semantic-release** directly
$ npx semantic-release
```
**Note:** For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with with `npx semantic-release@15`, or `npm install -g semantic-release@15`). This way your build will not automatically use the next major **semantic-release** release that could possibly break your build. You will have to upgrade manually when a new major version is released.
**Note**: For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with with `npx semantic-release@15`). This way your build will not automatically use the next major **semantic-release** release that could possibly break your build. You will have to upgrade manually when a new major version is released.
**Note:** `npx` is a tool bundled with `npm@>=5.2.0`. It is used to conveniently install the semantic-release binary and to execute it. See [What is npx](../support/FAQ.md#what-is-npx) for more details.
**Note**: `npx` is a tool bundled with `npm@>=5.2.0`. It is used to conveniently install the semantic-release binary and to execute it. See [What is npx](../support/FAQ.md#what-is-npx) for more details.
+23 -9
View File
@@ -17,25 +17,39 @@ A plugin is a npm module that can implement one or more of the following steps:
**Note:** If no plugin with a `analyzeCommits` step is defined `@semantic-release/commit-analyzer` will be used.
See [available plugins](../extending/plugins-list.md).
## Plugins installation
## Plugins configuration
### Default plugins
Each plugin must be installed and configured with the [`plugins` options](./configuration.md#plugins) by specifying the list of plugins by npm module name.
These four plugins are already part of **semantic-release** and don't have to be installed separately:
```
"@semantic-release/commit-analyzer"
"@semantic-release/github"
"@semantic-release/npm"
"@semantic-release/release-notes-generator"
```
### Additional plugins
[Additional plugins](../extending/plugins-list.md) have to be installed via npm:
```bash
$ npm install @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/npm -D
$ npm install @semantic-release/git @semantic-release/changelog -D
```
## Plugins declaration and execution order
Each plugin must be configured with the [`plugins` options](./configuration.md#plugins) by specifying the list of plugins by npm module name.
```json
{
"plugins": ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/npm"]
}
```
## Plugin ordering
**Note:** If the `plugins` option is defined, it overrides the default plugin list, rather than merging with it.
For each [release step](../../README.md#release-steps) the plugins that implement that step will be executed in the order in which the are defined.
For each [release step](../../README.md#release-steps) the plugins that implement that step will be executed in the order in which they are defined.
```json
{
@@ -55,11 +69,11 @@ With this configuration **semantic-release** will:
- execute the `generateNotes` implementation of `@semantic-release/release-notes-generator`
- execute the `publish` implementation of `@semantic-release/npm`
## Plugin options
## Plugin options configuration
A plugin options can specified by wrapping the name and an options object in an array. Options configured this way will be passed only to that specific plugin.
A plugin configuration can be specified by wrapping the name and an options object in an array. Options configured this way will be passed only to that specific plugin.
Global plugin options can defined at the root of the **semantic-release** configuration object. Options configured this way will be passed to all plugins.
Global plugin configuration can be defined at the root of the **semantic-release** configuration object. Options configured this way will be passed to all plugins.
```json
{
+14 -15
View File
@@ -48,8 +48,10 @@ For example the configuration `['+([0-9])?(.{+([0-9]),x}).x', 'master', 'next']`
### channel
The `channel` can be defined for any branch type. If it's not defined, releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm).
The value of `channel`, if defined, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
The `channel` can be defined for any branch type. By default releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm) for the first [release branch](#release-branches) and on a distribution channel named based on the branch `name` for any other branch.
If the `channel` property is set to `false` the default channel will be used.
The value of `channel`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
For example the configuration `['master', {name: 'next', channel: 'channel-${name}'}]` will be expanded as:
```js
@@ -78,7 +80,7 @@ For example the configuration `['1.1.x', '1.2.x', 'master']` will be expanded as
### prerelease
A `prerelease` property applies only to pre-release branches, is required and The `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
A `prerelease` property applies only to pre-release branches and the `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
If the `prerelease` property is set to `true` the `name` value will be used.
The value of `prerelease`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
@@ -98,7 +100,7 @@ For example the configuration `['master', {name: 'pre/rc', prerelease: '${name.r
### Release branches
A release branch is the base type of branch used by **semantic-release** that allows to publish releases with a [semantic version](https://semver.org), optionally on a specific distribution channel. Distribution channels (for example [npm dist-tags](https://docs.npmjs.com/cli/dist-tag) or [Chrome release channels](https://www.chromium.org/getting-involved/dev-channel)) are a way to distribute new releases only to a subset of users in order to get early feedback. Later on those releases can be added to the general distribution channel to be made available to all users.
A release branch is the base type of branch used by **semantic-release** that allows to publish releases with a [semantic version](https://semver.org), optionally on a specific distribution channel. Distribution channels (for example [npm dist-tags](https://docs.npmjs.com/cli/dist-tag) or [Chrome release channels](https://www.chromium.org/getting-involved/dev-channel)) are a way to distribute new releases only to a subset of users in order to get early feedback. Later on, those releases can be added to the general distribution channel to be made available to all users.
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release branch to another](#merging-into-a-release-branch).
@@ -110,20 +112,17 @@ See [publishing on distribution channels recipe](../recipes/distribution-channel
#### Pushing to a release branch
With the configuration `"branches": ["master", "next"]`, if the last release published from `master` is `1.0.0` then:
With the configuration `"branches": ["master", "next"]`, if the last release published from `master` is `1.0.0` and the last one from `next` is `2.0.0` then:
- Only versions in range `1.x.x` can be published from `master`, so only `fix` and `feat` commits can be pushed to `master`
- Only versions in range `>=2.0.0` release can be published from `next`, so a `BREAKING CHANGE` commit must be pushed first on `next` and can be followed by any type of commits
- Once `next` get merged into `master` the release `2.0.0` will be made available on the channel associated with `master` and both `master` and `next` will accept any commit type
With the configuration `"branches": ["master", "next", "next-major"]`, if the last release published from `master` is `1.0.0` then:
- Only versions in range `1.0.x` can be published from `master`, so only `fix` commits can be pushed to `master`
- Only versions in range `>=1.1.0` can be published from `next`, so a `feat` commit must be pushed first on `next` and can be followed by `fix` and `feat` commits
- Only versions in range `>=2.0.0` release can be published from `next`, so a `BREAKING CHANGE` commit must be pushed first on `next-major` and can be followed by any type of commits
Those verifications prevent situations such as:
1. Create a `feat` commit on `next` which triggers the release of version `1.0.0` on channel `next`
This verification prevent scenario such as:
1. Create a `feat` commit on `next` which triggers the release of version `1.0.0` on the `next` channel
2. Merge `next` into `master` which adds `1.0.0` on the default channel
3. Push a `fix` commit to `master` which triggers the release of version `1.0.1` on the default channel
4. Push a `fix` commit to `next` which would attempt to release the version `1.0.1` on channel `next` and fails as this version already exists
3. Create a `feat` commit on `next` which triggers the release of version `1.1.0` on the `next` channel
4. Create a `feat` commit on `master` which would attempt to release the version `1.1.0` on the default channel
In step 4 **semantic-release** will throw an `EINVALIDNEXTVERSION` error to prevent the attempt at releasing version `1.1.0` which was already released on step 3 with a different codebase. The error will indicate that the commit should be created on `next` instead. Alternatively if the `next` branch is merged into `master`, the version `1.1.0` will be made available on the default channel and the `feat` commit would be allowed on `master` to release `1.2.0`.
#### Merging into a release branch
+50 -40
View File
@@ -3,7 +3,6 @@ const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const pEachSeries = require('p-each-series');
const semver = require('semver');
const AggregateError = require('aggregate-error');
const pkg = require('./package.json');
@@ -13,18 +12,18 @@ const verify = require('./lib/verify');
const getNextVersion = require('./lib/get-next-version');
const getCommits = require('./lib/get-commits');
const getLastRelease = require('./lib/get-last-release');
const getReleasesToAdd = require('./lib/get-releases-to-add');
const getReleaseToAdd = require('./lib/get-release-to-add');
const {extractErrors, makeTag} = require('./lib/utils');
const getGitAuthUrl = require('./lib/get-git-auth-url');
const getBranches = require('./lib/branches');
const getLogger = require('./lib/get-logger');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push} = require('./lib/git');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push, pushNotes, getTagHead, addNote} = require('./lib/git');
const getError = require('./lib/get-error');
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
marked.setOptions({renderer: new TerminalRenderer()});
/* eslint complexity: ["warn", 25] */
/* eslint complexity: off */
async function run(context, plugins) {
const {cwd, env, options, logger} = context;
const {isCi, branch: ciBranch, isPr} = envCi({env, cwd});
@@ -53,7 +52,8 @@ async function run(context, plugins) {
// Verify config
await verify(context);
context.branches = await getBranches(context);
options.repositoryUrl = await getGitAuthUrl(context);
context.branches = await getBranches(options.repositoryUrl, ciBranch, context);
context.branch = context.branches.find(({name}) => name === ciBranch);
if (!context.branch) {
@@ -69,17 +69,13 @@ async function run(context, plugins) {
`Run automated release from branch ${ciBranch}${options.dryRun ? ' in dry-run mode' : ''}`
);
options.repositoryUrl = await getGitAuthUrl(context);
try {
try {
await verifyAuth(options.repositoryUrl, context.branch.name, {cwd, env});
} catch (error) {
if (!(await isBranchUpToDate(context.branch.name, {cwd, env}))) {
if (!(await isBranchUpToDate(options.repositoryUrl, context.branch.name, {cwd, env}))) {
logger.log(
`The local branch ${
context.branch.name
} is behind the remote one, therefore a new version won't be published.`
`The local branch ${context.branch.name} is behind the remote one, therefore a new version won't be published.`
);
return false;
}
@@ -87,7 +83,7 @@ async function run(context, plugins) {
throw error;
}
} catch (error) {
logger.error(`The command "${error.cmd}" failed with the error message ${error.stderr}.`);
logger.error(`The command "${error.command}" failed with the error message ${error.stderr}.`);
throw getError('EGITNOPERMISSION', context);
}
@@ -95,45 +91,55 @@ async function run(context, plugins) {
await plugins.verifyConditions(context);
const releasesToAdd = getReleasesToAdd(context);
const errors = [];
context.releases = [];
const releaseToAdd = getReleaseToAdd(context);
await pEachSeries(releasesToAdd, async ({lastRelease, currentRelease, nextRelease}) => {
if (releaseToAdd) {
const {lastRelease, currentRelease, nextRelease} = releaseToAdd;
nextRelease.gitHead = await getTagHead(nextRelease.gitHead, {cwd, env});
currentRelease.gitHead = await getTagHead(currentRelease.gitHead, {cwd, env});
if (context.branch.mergeRange && !semver.satisfies(nextRelease.version, context.branch.mergeRange)) {
errors.push(getError('EINVALIDMAINTENANCEMERGE', {...context, nextRelease}));
return;
} else {
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
if (options.dryRun) {
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
} else {
await addNote({channels: [...currentRelease.channels, nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
await pushNotes(options.repositoryUrl, {cwd, env});
logger.success(`Add channel ${nextRelease.channel} to tag ${nextRelease.gitTag}`);
}
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
}
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
logger.log('Create tag %s', nextRelease.gitTag);
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
});
}
if (errors.length > 0) {
throw new AggregateError(errors);
}
context.lastRelease = await getLastRelease(context);
if (context.lastRelease.gitHead) {
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitHead, {cwd, env});
}
if (context.lastRelease.gitTag) {
logger.log(
`Found git tag ${context.lastRelease.gitTag} associated with version ${context.lastRelease.version} on branch ${
context.branch.name
}`
`Found git tag ${context.lastRelease.gitTag} associated with version ${context.lastRelease.version} on branch ${context.branch.name}`
);
} else {
logger.log(`No git tag version found on branch ${context.branch.name}`);
@@ -143,7 +149,7 @@ async function run(context, plugins) {
const nextRelease = {
type: await plugins.analyzeCommits(context),
channel: context.branch.channel,
channel: context.branch.channel || null,
gitHead: await getGitHead({cwd, env}),
};
if (!nextRelease.type) {
@@ -153,8 +159,8 @@ async function run(context, plugins) {
context.nextRelease = nextRelease;
nextRelease.version = getNextVersion(context);
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version, nextRelease.channel);
nextRelease.name = makeTag(options.tagFormat, nextRelease.version);
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version);
nextRelease.name = nextRelease.gitTag;
if (context.branch.type !== 'prerelease' && !semver.satisfies(nextRelease.version, context.branch.range)) {
throw getError('EINVALIDNEXTVERSION', {
@@ -176,7 +182,9 @@ async function run(context, plugins) {
} else {
// Create the tag before calling the publish plugins as some require the tag to exists
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await addNote({channels: [nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
await pushNotes(options.repositoryUrl, {cwd, env});
logger.success(`Created tag ${nextRelease.gitTag}`);
}
@@ -185,7 +193,9 @@ async function run(context, plugins) {
await plugins.success({...context, releases});
logger.success(`Published release ${nextRelease.version}`);
logger.success(
`Published release ${nextRelease.version} on ${nextRelease.channel ? nextRelease.channel : 'default'} channel`
);
if (options.dryRun) {
logger.log(`Release note for version ${nextRelease.version}:`);
+2 -2
View File
@@ -2,8 +2,8 @@ const {isString, remove, omit, mapValues, template} = require('lodash');
const micromatch = require('micromatch');
const {getBranches} = require('../git');
module.exports = async ({cwd}, branches) => {
const gitBranches = await getBranches({cwd});
module.exports = async (repositoryUrl, {cwd}, branches) => {
const gitBranches = await getBranches(repositoryUrl, {cwd});
return branches.reduce(
(branches, branch) => [
+9 -15
View File
@@ -2,32 +2,26 @@ const {template, escapeRegExp} = require('lodash');
const semver = require('semver');
const pReduce = require('p-reduce');
const debug = require('debug')('semantic-release:get-tags');
const {getTags, isRefInHistory, getTagHead} = require('../../lib/git');
const {getTags, getNote} = require('../../lib/git');
module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
// Generate a regex to parse tags formatted with `tagFormat`
// by replacing the `version` variable in the template by `(.+)`.
// The `tagFormat` is compiled with space as the `version` as it's an invalid tag character,
// so it's guaranteed to no be present in the `tagFormat`.
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`;
const tags = (await getTags({cwd, env}))
.map(tag => {
const [, version, channel] = tag.match(tagRegexp) || [];
return {gitTag: tag, version, channel};
})
.filter(({version}) => version && semver.valid(semver.clean(version)));
debug('found tags: %o', tags);
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)')}`;
return pReduce(
branches,
async (branches, branch) => {
const branchTags = await pReduce(
tags,
async (tags, {gitTag, ...rest}) =>
(await isRefInHistory(gitTag, branch.name, true, {cwd, env}))
? [...tags, {...rest, gitTag, gitHead: await getTagHead(gitTag, {cwd, env})}]
: tags,
await getTags(branch.name, {cwd, env}),
async (branchTags, tag) => {
const [, version] = tag.match(tagRegexp) || [];
return version && semver.valid(semver.clean(version))
? [...branchTags, {gitTag: tag, version, channels: (await getNote(tag, {cwd, env})).channels || [null]}]
: branchTags;
},
[]
);
+7 -4
View File
@@ -3,23 +3,26 @@ const AggregateError = require('aggregate-error');
const pEachSeries = require('p-each-series');
const DEFINITIONS = require('../definitions/branches');
const getError = require('../get-error');
const {fetch, verifyBranchName} = require('../git');
const {fetch, fetchNotes, verifyBranchName} = require('../git');
const expand = require('./expand');
const getTags = require('./get-tags');
const normalize = require('./normalize');
module.exports = async context => {
module.exports = async (repositoryUrl, ciBranch, context) => {
const {cwd, env} = context;
const remoteBranches = await expand(
repositoryUrl,
context,
context.options.branches.map(branch => (isString(branch) || isRegExp(branch) ? {name: branch} : branch))
);
await pEachSeries(remoteBranches, async ({name}) => {
await fetch(name, {cwd, env});
await fetch(repositoryUrl, name, ciBranch, {cwd, env});
});
await fetchNotes(repositoryUrl, {cwd, env});
const branches = await getTags(context, remoteBranches);
const errors = [];
@@ -47,7 +50,7 @@ module.exports = async context => {
const duplicates = [...branches]
.map(branch => branch.name)
.sort()
.filter((val, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
.filter((_, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
if (duplicates.length > 0) {
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
+14 -20
View File
@@ -1,5 +1,4 @@
const {sortBy, isNil} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const {FIRST_RELEASE, RELEASE_TYPE} = require('../definitions/constants');
const {
@@ -61,36 +60,31 @@ function release({release}) {
return release;
}
const breakpoints = release.length > 2 ? ['minor', 'major'] : ['major'];
// The intial bound is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
let bound = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
// The intial lastVersion is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
let lastVersion = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
return release.map(({name, tags, channel, ...rest}, idx) => {
const versions = tagsToVersions(tags);
// The lower bound is the highest version between the current branch last release and the previous branch upper bound (`bound`)
const min = highest(getLatestVersion(versions), bound);
if (release.length - 1 === idx) {
// If the current branch is the last one of the release branch, there is no upper bound
bound = undefined;
} else {
// The default upper bound is the lower bound increment with the release type of the current branch position
const upperBound = semver.inc(min, breakpoints[idx]);
// Find the lowest version that is present on the current branch but none of the previous ones
const nextFirstVersion = getFirstVersion(tagsToVersions(release[idx + 1].tags), release.slice(0, idx + 1));
// The upper bound is the lowest version between `nextFirstVersion` and the default upper bound
bound = lowest(nextFirstVersion, upperBound);
}
// The new lastVersion is the highest version between the current branch last release and the previous branch lastVersion
lastVersion = highest(getLatestVersion(versions), lastVersion);
// The upper bound is:
// - None if the current branch is the last one of the release branches
// - Otherwise, The upper bound is the lowest version that is present on the current branch but none of the previous ones
const bound =
release.length - 1 === idx
? undefined
: getFirstVersion(tagsToVersions(release[idx + 1].tags), release.slice(0, idx + 1));
const diff = bound ? semverDiff(min, bound) : null;
const diff = bound ? semverDiff(lastVersion, bound) : null;
return {
...rest,
channel: idx === 0 ? channel : isNil(channel) ? name : channel,
tags,
type: 'release',
name,
range: getRange(min, bound),
range: getRange(lastVersion, bound),
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
main: idx === 0,
};
});
}
+4 -4
View File
@@ -1,15 +1,15 @@
const {isUndefined, uniqBy} = require('lodash');
const {isNil, uniqBy} = require('lodash');
const semver = require('semver');
const {isMaintenanceRange} = require('../utils');
const maintenance = {
filter: ({name, range}) => !isUndefined(range) || isMaintenanceRange(name),
branchValidator: ({range}) => (isUndefined(range) ? true : isMaintenanceRange(range)),
filter: ({name, range}) => (!isNil(range) && range !== false) || isMaintenanceRange(name),
branchValidator: ({range}) => (isNil(range) ? true : isMaintenanceRange(range)),
branchesValidator: branches => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
};
const prerelease = {
filter: ({prerelease}) => !isUndefined(prerelease),
filter: ({prerelease}) => !isNil(prerelease) && prerelease !== false,
branchValidator: ({name, prerelease}) =>
Boolean(prerelease) && Boolean(semver.valid(`1.0.0-${prerelease === true ? name : prerelease}.1`)),
branchesValidator: branches => uniqBy(branches, 'prerelease').length === branches.length,
+3
View File
@@ -14,6 +14,8 @@ const SECRET_REPLACEMENT = '[secure]';
const SECRET_MIN_SIZE = 5;
const GIT_NOTE_REF = 'semantic-release';
module.exports = {
RELEASE_TYPE,
FIRST_RELEASE,
@@ -23,4 +25,5 @@ module.exports = {
RELEASE_NOTES_SEPARATOR,
SECRET_REPLACEMENT,
SECRET_MIN_SIZE,
GIT_NOTE_REF,
};
+10 -7
View File
@@ -29,14 +29,15 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
)}).`,
}),
EGITNOPERMISSION: ({options: {repositoryUrl}, branch: {name}}) => ({
message: 'The push permission to the Git repository is required.',
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on remote Git repository with URL \`${repositoryUrl}\`.
message: 'Cannot push to the Git repository.',
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on the remote Git repository with URL \`${repositoryUrl}\`.
Please refer to the [authentication configuration documentation](${linkify(
'docs/usage/ci-configuration.md#authentication'
)}) to configure the Git credentials on your CI environment and make sure the [repositoryUrl](${linkify(
'docs/usage/configuration.md#repositoryurl'
)}) is configured with a [valid Git URL](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).`,
This can be caused by:
- a misconfiguration of the [repositoryUrl](${linkify('docs/usage/configuration.md#repositoryurl')}) option
- the repository being unavailable
- or missing push permission for the user configured via the [Git credentials on your CI environment](${linkify(
'docs/usage/ci-configuration.md#authentication'
)})`,
}),
EINVALIDTAGFORMAT: ({options: {tagFormat}}) => ({
message: 'Invalid `tagFormat` option.',
@@ -184,6 +185,8 @@ Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
'docs/usage/configuration.md#branches'
)}).
This may occur if your repository does not have a release branch, such as \`master\`.
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
}),
EPRERELEASEBRANCH: ({branch}) => ({
+2
View File
@@ -1,3 +1,5 @@
/* eslint require-atomic-updates: off */
const {isString, isPlainObject} = require('lodash');
const {getGitHead} = require('../git');
const hideSensitive = require('../hide-sensitive');
+3 -3
View File
@@ -1,6 +1,6 @@
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const cosmiconfig = require('cosmiconfig');
const {cosmiconfig} = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
@@ -93,6 +93,6 @@ module.exports = async (context, opts) => {
};
async function pkgRepoUrl(opts) {
const {pkg} = await readPkgUp(opts);
return pkg && (isPlainObject(pkg.repository) ? pkg.repository.url : pkg.repository);
const {packageJson} = (await readPkgUp(opts)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}
+21 -13
View File
@@ -3,16 +3,6 @@ const {isNil} = require('lodash');
const hostedGitInfo = require('hosted-git-info');
const {verifyAuth} = require('./git');
const GIT_TOKENS = {
GIT_CREDENTIALS: undefined,
GH_TOKEN: undefined,
GITHUB_TOKEN: undefined,
GL_TOKEN: 'gitlab-ci-token:',
GITLAB_TOKEN: 'gitlab-ci-token:',
BB_TOKEN: 'x-token-auth:',
BITBUCKET_TOKEN: 'x-token-auth:',
};
/**
* Determine the the git repository URL to use to push, either:
* - The `repositoryUrl` as is if allowed to push
@@ -25,6 +15,18 @@ const GIT_TOKENS = {
* @return {String} The formatted Git repository URL.
*/
module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
const GIT_TOKENS = {
GIT_CREDENTIALS: undefined,
GH_TOKEN: undefined,
// GitHub Actions require the "x-access-token:" prefix for git access
// https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#http-based-git-access-by-an-installation
GITHUB_TOKEN: isNil(env.GITHUB_ACTION) ? undefined : 'x-access-token:',
GL_TOKEN: 'gitlab-ci-token:',
GITLAB_TOKEN: 'gitlab-ci-token:',
BB_TOKEN: 'x-token-auth:',
BITBUCKET_TOKEN: 'x-token-auth:',
};
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
const {protocol, ...parsed} = parse(repositoryUrl);
@@ -39,16 +41,22 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
try {
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
} catch (error) {
} catch (_) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
if (gitCredentials) {
// If credentials are set via environment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
const [match, auth, host, path] = /^(?!.+:\/\/)(?:(.*)@)?(.*?):(.*)$/.exec(repositoryUrl) || [];
const [match, auth, host, path] =
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = parse(
match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl
);
return format({
...parse(match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl),
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
}
+13 -6
View File
@@ -1,13 +1,16 @@
const {isUndefined} = require('lodash');
const semver = require('semver');
const {makeTag} = require('./utils');
const {makeTag, isSameChannel} = require('./utils');
/**
* Last release.
*
* @typedef {Object} LastRelease
* @property {string} version The version number of the last release.
* @property {string} [gitHead] The Git reference used to make the last release.
* @property {string} gitHead The Git reference used to make the last release.
* @property {string} gitTag The git tag associated with the last release.
* @property {string} channel The channel on which of the last release was published.
* @property {string} name The name of the last release.
*/
/**
@@ -24,13 +27,17 @@ const {makeTag} = require('./utils');
* @return {LastRelease} The last tagged release or empty object if none is found.
*/
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, gitHead, channel} = {}] = branch.tags
.filter(tag => (branch.type === 'prerelease' && branch.channel === tag.channel) || !semver.prerelease(tag.version))
.filter(tag => isUndefined(before) || semver.lt(tag.version, before))
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
.sort((a, b) => semver.rcompare(a.version, b.version));
if (gitTag) {
return {version, gitTag, gitHead, channel, name: makeTag(tagFormat, version)};
return {version, gitTag, channels, gitHead: gitTag, name: makeTag(tagFormat, version)};
}
return {};
+3 -1
View File
@@ -1,5 +1,6 @@
const semver = require('semver');
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
const {isSameChannel} = require('./utils');
module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
let version;
@@ -7,7 +8,8 @@ module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) =
const {major, minor, patch} = semver.parse(lastRelease.version);
version =
branch.type === 'prerelease'
? semver.prerelease(lastRelease.version) && lastRelease.channel === channel
? semver.prerelease(lastRelease.version) &&
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
? semver.inc(lastRelease.version, 'prerelease')
: `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}`
: semver.inc(lastRelease.version, type);
+60
View File
@@ -0,0 +1,60 @@
const {uniqBy, intersection} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
const higherChannels = branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
.map(({channel}) => channel || null);
const versiontoAdd = uniqBy(
branch.tags.filter(
({channels, version}) =>
!channels.includes(branch.channel || null) &&
intersection(channels, higherChannels).length > 0 &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
),
'version'
).sort((a, b) => semver.compare(b.version, a.version))[0];
if (versiontoAdd) {
const {version, gitTag, channels} = versiontoAdd;
const lastRelease = getLastRelease(context, {before: version});
if (semver.gt(getLastRelease(context).version, version)) {
return;
}
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channels, gitTag, name, gitHead: gitTag},
nextRelease: {
type,
version,
channel: branch.channel || null,
gitTag: makeTag(tagFormat, version),
name,
gitHead: gitTag,
},
};
}
};
-71
View File
@@ -1,71 +0,0 @@
const {uniq} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
return (
branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
// Find higher branch releases merged to building branch but not released on associated channel
.reduce(
(releases, higherBranch) => [
...releases,
// For all unique release version of the higher branch merged on current branch, excluding lower than start range version for maintenance branches
...uniq(
branch.tags.filter(
({channel, version}) =>
channel === higherBranch.channel &&
channel !== branch.channel &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
)
)
// Find ones that are not released on the building branch channel
.filter(tag =>
branch.tags.every(
({version, channel}) =>
version !== tag.version || channel === higherBranch.channel || channel !== branch.channel
)
)
// Sort in ascending order to add the most recent release last
.sort((a, b) => semver.compare(a.version, b.version))
// Construct the last and next release to add to the building branch channel
.map(({version, gitHead, gitTag}) => {
const lastRelease = getLastRelease(context, {before: version});
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channel: higherBranch.channel, gitTag, name, gitHead},
nextRelease: {
type,
version,
channel: branch.channel,
gitTag: makeTag(tagFormat, version, branch.channel),
name,
gitHead,
},
};
}),
],
[]
)
);
};
+113 -46
View File
@@ -1,8 +1,8 @@
const {matches, pick, memoize} = require('lodash');
const gitLogParser = require('git-log-parser');
const getStream = require('get-stream');
const execa = require('execa');
const debug = require('debug')('semantic-release:git');
const {GIT_NOTE_REF} = require('./definitions/constants');
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
@@ -15,23 +15,20 @@ Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', commi
* @return {String} The commit sha of the tag in parameter or `null`.
*/
async function getTagHead(tagName, execaOpts) {
try {
return await execa.stdout('git', ['rev-list', '-1', tagName], execaOpts);
} catch (error) {
debug(error);
}
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
}
/**
* Get all the repository tags.
* Get all the tags for a given branch.
*
* @param {String} branch The branch for which to retrieve the tags.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Array<String>} List of git tags.
* @throws {Error} If the `git` command fails.
*/
async function getTags(execaOpts) {
return (await execa.stdout('git', ['tag'], execaOpts))
async function getTags(branch, execaOpts) {
return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout
.split('\n')
.map(tag => tag.trim())
.filter(Boolean);
@@ -46,44 +43,42 @@ async function getTags(execaOpts) {
* @return {Promise<Array<Object>>} The list of commits between `from` and `to`.
*/
async function getCommits(from, to, execaOpts) {
return (await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
)
)
)).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
}
/**
* Get all the repository branches.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Array<String>} List of git branches.
* @throws {Error} If the `git` command fails.
*/
async function getBranches(execaOpts) {
return (await execa.stdout('git', ['ls-remote', '--heads', 'origin'], execaOpts))
async function getBranches(repositoryUrl, execaOpts) {
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOpts)).stdout
.split('\n')
.map(branch => branch.match(/^.+refs\/heads\/(.+)$/)[1])
.map(branch => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1])
.filter(Boolean);
}
const getBranchCommits = memoize((branch, execaOpts) =>
getStream.array(gitLogParser.parse({_: branch}, {cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}))
);
/**
* Verify if the `ref` is in the direct history of a given branch.
*
* @param {String} ref The reference to look for.
* @param {String} branch The branch for which to check if the `ref` is in history.
* @param {Boolean} findRebasedTags Weither consider in history tags associated with a commit that was rebased to another branch (i.e. GitHub Rebase and Merge feature).
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Boolean} `true` if the reference is in the history of the current branch, falsy otherwise.
*/
async function isRefInHistory(ref, branch, findRebasedTags, execaOpts) {
async function isRefInHistory(ref, branch, execaOpts) {
if (!(await isRefExists(branch, execaOpts))) {
return false;
}
@@ -92,14 +87,7 @@ async function isRefInHistory(ref, branch, findRebasedTags, execaOpts) {
await execa('git', ['merge-base', '--is-ancestor', ref, branch], execaOpts);
return true;
} catch (error) {
if (error.code === 1) {
if (findRebasedTags) {
const [tagCommit] = await getStream.array(
gitLogParser.parse({_: ref, n: '1'}, {cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}})
);
return (await getBranchCommits(branch, execaOpts)).some(matches(pick(tagCommit, ['message', 'author'])));
}
if (error.exitCode === 1) {
return false;
}
@@ -118,7 +106,7 @@ async function isRefInHistory(ref, branch, findRebasedTags, execaOpts) {
*/
async function isRefExists(ref, execaOpts) {
try {
return (await execa('git', ['rev-parse', '--verify', ref], execaOpts)).code === 0;
return (await execa('git', ['rev-parse', '--verify', ref], execaOpts)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -127,12 +115,13 @@ async function isRefExists(ref, execaOpts) {
/**
* Unshallow the git repository if necessary and fetch all the tags.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {String} branch The repository branch to fetch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetch(branch, execaOpts) {
async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
const isLocalExists =
(await execa('git', ['rev-parse', '--verify', branch], {...execaOpts, reject: false})).code === 0;
(await execa('git', ['rev-parse', '--verify', branch], {...execaOpts, reject: false})).exitCode === 0;
try {
await execa(
@@ -141,19 +130,48 @@ async function fetch(branch, execaOpts) {
'fetch',
'--unshallow',
'--tags',
...(isLocalExists ? [] : ['origin', `+refs/heads/${branch}:refs/heads/${branch}`]),
...(branch === ciBranch && isLocalExists
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
);
} catch (error) {
} catch (_) {
await execa(
'git',
['fetch', '--tags', ...(isLocalExists ? [] : ['origin', `+refs/heads/${branch}:refs/heads/${branch}`])],
[
'fetch',
'--tags',
...(branch === ciBranch && isLocalExists
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
);
}
}
/**
* Unshallow the git repository if necessary and fetch all the notes.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetchNotes(repositoryUrl, execaOpts) {
try {
await execa(
'git',
['fetch', '--unshallow', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
execaOpts
);
} catch (_) {
await execa('git', ['fetch', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
...execaOpts,
reject: false,
});
}
}
/**
* Get the HEAD sha.
*
@@ -161,8 +179,8 @@ async function fetch(branch, execaOpts) {
*
* @return {String} the sha of the HEAD commit.
*/
function getGitHead(execaOpts) {
return execa.stdout('git', ['rev-parse', 'HEAD'], execaOpts);
async function getGitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
}
/**
@@ -174,7 +192,7 @@ function getGitHead(execaOpts) {
*/
async function repoUrl(execaOpts) {
try {
return await execa.stdout('git', ['config', '--get', 'remote.origin.url'], execaOpts);
return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOpts)).stdout;
} catch (error) {
debug(error);
}
@@ -189,7 +207,7 @@ async function repoUrl(execaOpts) {
*/
async function isGitRepo(execaOpts) {
try {
return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).code === 0;
return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -238,6 +256,18 @@ async function push(repositoryUrl, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
}
/**
* Push notes to the remote repository.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @throws {Error} if the push failed.
*/
async function pushNotes(repositoryUrl, execaOpts) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOpts);
}
/**
* Verify a tag name is a valid Git reference.
*
@@ -248,7 +278,7 @@ async function push(repositoryUrl, execaOpts) {
*/
async function verifyTagName(tagName, execaOpts) {
try {
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).code === 0;
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -264,7 +294,7 @@ async function verifyTagName(tagName, execaOpts) {
*/
async function verifyBranchName(branch, execaOpts) {
try {
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOpts)).code === 0;
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOpts)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -273,20 +303,53 @@ async function verifyBranchName(branch, execaOpts) {
/**
* Verify the local branch is up to date with the remote one.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {String} branch The repository branch for which to verify status.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
*/
async function isBranchUpToDate(branch, execaOpts) {
const remoteHead = await execa.stdout('git', ['ls-remote', '--heads', 'origin', branch], execaOpts);
async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
const {stdout: remoteHead} = await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts);
try {
return await isRefInHistory(remoteHead.match(/^(\w+)?/)[1], branch, false, execaOpts);
return await isRefInHistory(remoteHead.match(/^(?<ref>\w+)?/)[1], branch, execaOpts);
} catch (error) {
debug(error);
}
}
/**
* Get and parse the JSON note of a given reference.
*
* @param {String} ref The Git reference for which to retrieve the note.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
*/
async function getNote(ref, execaOpts) {
try {
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout);
} catch (error) {
if (error.exitCode === 1) {
return {};
}
debug(error);
throw error;
}
}
/**
* Get and parse the JSON note of a given reference.
*
* @param {Object} note The object to save in the reference note.
* @param {String} ref The Git reference to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function addNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOpts);
}
module.exports = {
getTagHead,
getTags,
@@ -295,13 +358,17 @@ module.exports = {
isRefInHistory,
isRefExists,
fetch,
fetchNotes,
getGitHead,
repoUrl,
isGitRepo,
verifyAuth,
tag,
push,
pushNotes,
verifyTagName,
isBranchUpToDate,
verifyBranchName,
getNote,
addNote,
};
+11 -4
View File
@@ -31,11 +31,13 @@ function isMaintenanceRange(range) {
}
function getUpperBound(range) {
return semver.valid(range) ? range : ((semver.validRange(range) || '').match(/<(\d+\.\d+\.\d+)$/) || [])[1];
return semver.valid(range)
? range
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+)$/) || [])[1];
}
function getLowerBound(range) {
return ((semver.validRange(range) || '').match(/(\d+\.\d+\.\d+)/) || [])[1];
return ((semver.validRange(range) || '').match(/(?<lowerBound>\d+\.\d+\.\d+)/) || [])[1];
}
function highest(version1, version2) {
@@ -67,8 +69,12 @@ function getRange(min, max) {
return `>=${min}${max ? ` <${max}` : ''}`;
}
function makeTag(tagFormat, version, channel) {
return template(tagFormat)({version: `${version}${channel ? `@${channel}` : ''}`});
function makeTag(tagFormat, version) {
return template(tagFormat)({version});
}
function isSameChannel(channel, otherChannel) {
return channel === otherChannel || (!channel && !otherChannel);
}
module.exports = {
@@ -86,4 +92,5 @@ module.exports = {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
};
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 65 KiB

+41 -41
View File
@@ -3,75 +3,76 @@
"description": "Automated semver compliant package publishing",
"version": "0.0.0-development",
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
"ava": {
"files": [
"test/**/*.test.js"
],
"helpers": [
"test/helpers/**/*"
]
},
"bin": {
"semantic-release": "bin/semantic-release.js"
},
"bugs": {
"url": "https://github.com/semantic-release/semantic-release/issues"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"contributors": [
"Gregor Martynus (https://twitter.com/gr2m)",
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "^7.0.0-beta.1",
"@semantic-release/commit-analyzer": "^7.0.0-beta",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "^5.4.0-beta.1",
"@semantic-release/npm": "^5.2.0-beta.5",
"@semantic-release/github": "^5.6.0-beta",
"@semantic-release/npm": "^6.0.0-beta",
"@semantic-release/release-notes-generator": "^7.1.2",
"aggregate-error": "^2.0.0",
"cosmiconfig": "^5.0.1",
"aggregate-error": "^3.0.0",
"cosmiconfig": "^6.0.0",
"debug": "^4.0.0",
"env-ci": "^3.0.0",
"execa": "^1.0.0",
"figures": "^2.0.0",
"env-ci": "^4.0.0",
"execa": "^3.2.0",
"figures": "^3.0.0",
"find-versions": "^3.0.0",
"get-stream": "^4.0.0",
"get-stream": "^5.0.0",
"git-log-parser": "^1.2.0",
"hook-std": "^1.1.0",
"hosted-git-info": "^2.7.1",
"lodash": "^4.17.4",
"marked": "^0.6.0",
"hook-std": "^2.0.0",
"hosted-git-info": "^3.0.0",
"lodash": "^4.17.15",
"marked": "^0.7.0",
"marked-terminal": "^3.2.0",
"micromatch": "3.1.5",
"p-each-series": "^1.0.0",
"p-reduce": "^1.0.0",
"read-pkg-up": "^4.0.0",
"resolve-from": "^4.0.0",
"semver": "^5.4.1",
"semver-diff": "^2.1.0",
"micromatch": "^4.0.2",
"p-each-series": "^2.1.0",
"p-reduce": "^2.0.0",
"read-pkg-up": "^7.0.0",
"resolve-from": "^5.0.0",
"semver": "^6.0.0",
"semver-diff": "^3.1.1",
"signale": "^1.2.1",
"yargs": "^12.0.0"
"yargs": "^15.0.1"
},
"devDependencies": {
"ava": "^1.0.1",
"clear-module": "^3.0.0",
"ava": "^2.0.0",
"clear-module": "^4.0.0",
"codecov": "^3.0.0",
"commitizen": "^3.0.0",
"cz-conventional-changelog": "^2.0.0",
"delay": "^4.0.0",
"dockerode": "^2.5.2",
"file-url": "^2.0.2",
"fs-extra": "^7.0.0",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^9.0.0",
"js-yaml": "^3.10.0",
"mockserver-client": "^5.1.1",
"nock": "^10.0.0",
"nyc": "^13.1.0",
"p-retry": "^3.0.0",
"nock": "^11.1.0",
"nyc": "^14.0.0",
"p-retry": "^4.0.0",
"proxyquire": "^2.0.0",
"sinon": "^7.1.1",
"sinon": "^7.2.7",
"stream-buffers": "^3.0.2",
"tempy": "^0.2.1",
"xo": "^0.24.0"
"tempy": "^0.3.0",
"xo": "^0.25.0"
},
"engines": {
"node": ">=8.3"
"node": ">=10.13"
},
"files": [
"bin",
@@ -119,7 +120,6 @@
"url": "git+https://github.com/semantic-release/semantic-release.git"
},
"scripts": {
"cm": "git-cz",
"codecov": "codecov -f coverage/coverage-final.json",
"lint": "xo",
"pretest": "npm run lint",
+100 -40
View File
@@ -24,51 +24,82 @@ test('Enforce ranges with branching release workflow', async t => {
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
let result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
let result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
release(branches, 'master', '1.0.0');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
release(branches, 'master', '1.0.1');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
merge(branches, 'master', 'next');
merge(branches, 'master', 'next-major');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
release(branches, 'next', '1.1.0');
release(branches, 'next', '1.1.1');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1', 'Can release only > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.1.1', 'Can release > than 1.1.1 on next-major');
release(branches, 'next-major', '2.0.0');
release(branches, 'next-major', '2.0.1');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
merge(branches, 'next-major', 'beta');
release(branches, 'beta', '3.0.0-beta.1');
merge(branches, 'beta', 'alpha');
release(branches, 'alpha', '4.0.0-alpha.1');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
merge(branches, 'master', '1.0.x');
merge(branches, 'master', '1.x');
release(branches, 'master', '1.0.1');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(
getBranch(result, '1.0.x').range,
@@ -80,7 +111,10 @@ test('Enforce ranges with branching release workflow', async t => {
release(branches, 'master', '1.0.2');
release(branches, 'master', '1.0.3');
release(branches, 'master', '1.0.4');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
t.is(
getBranch(result, '1.0.x').range,
@@ -90,9 +124,13 @@ test('Enforce ranges with branching release workflow', async t => {
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 1.2.0 is released on master');
merge(branches, 'next', 'master');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
t.is(getBranch(result, 'next').range, '>=1.2.0 <2.0.0', 'Can release only patch or minor, > than 1.2.0 on next');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(
getBranch(result, '1.0.x').range,
@@ -102,34 +140,50 @@ test('Enforce ranges with branching release workflow', async t => {
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 2.0.0 is released on master');
merge(branches, 'master', '1.0.x', '1.0.4');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.1.0', 'Cannot release on 1.x before >= 2.0.0 is released on master');
merge(branches, 'master', '1.x');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <1.1.1', 'Cannot release on 1.x before >= 2.0.0 is released on master');
merge(branches, 'next-major', 'next');
merge(branches, 'next', 'master');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=2.0.1', 'Can release only > than 2.0.1 on master');
t.is(getBranch(result, 'next').range, '>=2.0.1', 'Can release only > than 2.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release only > than 2.0.1 on next-major');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <2.0.0', 'Can release on 1.x only within range');
merge(branches, 'beta', 'master');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
release(branches, 'master', '3.0.0');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=3.0.0', 'Can release only > than 3.0.0 on master');
t.is(getBranch(result, 'next').range, '>=3.0.0', 'Can release only > than 3.0.0 on next');
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only > than 3.0.0 on next-major');
branches.push({name: '1.1.x', tags: []});
merge(branches, '1.x', '1.1.x');
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
name,
range,
}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.1.x').range, '>=1.1.1 <1.2.0', 'Can release on 1.1.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
@@ -146,7 +200,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
{name: 'preview', prerelease: 'alpha', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches({options: {branches}})))];
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
@@ -171,10 +225,13 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
});
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
const branches = [{name: 'master', tags: []}, {name: 'master', tags: []}];
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches({options: {branches}})))];
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
@@ -183,10 +240,13 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
});
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
const branches = [{name: '~master', tags: []}, {name: '^master', tags: []}];
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches({options: {branches}})))];
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
+1 -1
View File
@@ -41,7 +41,7 @@ test('Expand branches defined with globs', async t => {
{name: 'beta', channel: `channel-\${name}`, prerelease: true},
];
t.deepEqual(await expand({cwd}, branches), [
t.deepEqual(await expand(repositoryUrl, {cwd}, branches), [
{name: '1.0.x'},
{name: '1.x.x'},
{name: '2.x'},
+36 -85
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, merge, changeAuthor} from '../helpers/git-utils';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} from '../helpers/git-utils';
test('Get the valid tags', async t => {
const {cwd} = await gitRepo();
@@ -20,9 +20,9 @@ test('Get the valid tags', async t => {
{
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[1].hash},
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channel: undefined, gitHead: commits[3].hash},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [null]},
],
},
]);
@@ -30,20 +30,21 @@ test('Get the valid tags', async t => {
test('Get the valid tags from multiple branches', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
commits.push(...(await gitCommits(['Second'], {cwd})));
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitTagVersion('v1.1.0@1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitTagVersion('v3.0.0@next', undefined, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
{name: '1.x'},
@@ -55,47 +56,17 @@ test('Get the valid tags from multiple branches', async t => {
{
name: '1.x',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x', gitHead: commits[0].hash},
{gitTag: 'v1.1.0', version: '1.1.0', channel: undefined, gitHead: commits[1].hash},
{gitTag: 'v1.1.0@1.x', version: '1.1.0', channel: '1.x', gitHead: commits[1].hash},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, '1.x']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '1.x']},
],
},
{
name: 'master',
tags: [
...result[0].tags,
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[2].hash},
{gitTag: 'v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[2].hash},
],
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [null, 'next']}],
},
{
name: 'next',
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channel: 'next', gitHead: commits[3].hash}],
},
]);
});
test('Match the tag name from the begining of the string and the channel from the last "@"', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitTagVersion('prefix@v1.0.0', undefined, {cwd});
await gitTagVersion('prefix@v1.0.0@next', undefined, {cwd});
await gitTagVersion('prefix@v2.0.0', undefined, {cwd});
await gitTagVersion('prefix@v2.0.0@next', undefined, {cwd});
await gitTagVersion('other-prefix@v3.0.0', undefined, {cwd});
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
t.deepEqual(result, [
{
name: 'master',
tags: [
{gitTag: 'prefix@v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'prefix@v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[0].hash},
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'prefix@v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[0].hash},
],
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
},
]);
});
@@ -109,48 +80,53 @@ test('Return branches with and empty tags array if no valid tag is found', async
await gitCommits(['Third'], {cwd});
await gitTagVersion('v3.0', undefined, {cwd});
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
t.deepEqual(result, [{name: 'master', tags: []}]);
});
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async t => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitCheckout('other-branch', true, {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitTagVersion('v3.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {cwd});
await gitCheckout('master', false, {cwd});
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
t.deepEqual(result, [
{name: 'master', tags: []},
{name: 'next', tags: []},
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('1.0.0', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [null]}]},
]);
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-\${version}-bar`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [null]}]},
]);
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
},
]);
@@ -158,7 +134,7 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
},
]);
@@ -166,37 +142,12 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channels: [null]}],
},
]);
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}-bar.2`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channel: undefined, gitHead: commits[0].hash}]},
]);
});
test('Get the tag on branch where commits have been rebased', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['Second/n/n/commit body'], {cwd})));
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitCheckout('master', false, {cwd});
await merge('next', {cwd});
// Simulate GitHub "Rebase and Merge" by changing the committer info, which will result in a new commit sha and losing the tag
await changeAuthor(commits[1].hash, {cwd});
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
t.deepEqual(result, [
{
name: 'master',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
},
{
name: 'next',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
},
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
]);
});
+127 -56
View File
@@ -4,7 +4,11 @@ import normalize from '../../lib/branches/normalize';
const toTags = versions => versions.map(version => ({version}));
test('Maintenance branches - initial state', t => {
const maintenance = [{name: '1.x', channel: '1.x', tags: []}, {name: '1.1.x', tags: []}, {name: '1.2.x', tags: []}];
const maintenance = [
{name: '1.x', channel: '1.x', tags: []},
{name: '1.1.x', tags: []},
{name: '1.2.x', tags: []},
];
const release = [{name: 'master', tags: []}];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
@@ -151,16 +155,33 @@ test('Release branches - initial state', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=1.0.0 <1.1.0', accept: ['patch'], channel: undefined},
{type: 'release', name: 'next', range: '>=1.1.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
{
type: 'release',
name: 'master',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.0.0',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -174,16 +195,26 @@ test('Release branches - 3 release branches', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined},
{type: 'release', name: 'next', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
{
type: 'release',
name: 'next',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -196,10 +227,26 @@ test('Release branches - 2 release branches', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: undefined},
{type: 'release', name: 'next', range: '>=2.1.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
{
type: 'release',
name: 'master',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
]
);
});
@@ -221,16 +268,19 @@ test('Release branches - cap ranges to first release only present on following b
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next'},
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
{
type: 'release',
name: 'next-major',
range: '>=2.2.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -243,38 +293,31 @@ test('Release branches - Handle missing previous tags in branch history', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=2.0.0 <3.0.0', accept: ['patch', 'minor'], channel: undefined},
{type: 'release', name: 'next', range: '>=3.0.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
]
);
});
test('Release branches - enforce release gaps after downstream merge', t => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
[
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
{type: 'release', name: 'next', range: '>=2.1.0 <3.0.0', accept: ['patch', 'minor'], channel: 'next'},
{
type: 'release',
name: 'next-major',
range: '>=3.0.0',
name: 'master',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
]
);
});
test('Release branches - limit releases on 2nd and 3rd branche based on 1st branch last release', t => {
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', t => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0', '3.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
@@ -282,16 +325,33 @@ test('Release branches - limit releases on 2nd and 3rd branche based on 1st bran
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{type: 'release', name: 'master', range: '>=3.0.0 <3.1.0', accept: ['patch'], channel: undefined},
{type: 'release', name: 'next', range: '>=3.1.0 <4.0.0', accept: ['patch', 'minor'], channel: 'next'},
{
type: 'release',
name: 'master',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=4.0.0',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -303,24 +363,35 @@ test('Prerelease branches', t => {
{name: 'alpha', prerelease: 'preview', tags: []},
];
t.deepEqual(normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})), [
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
]);
t.deepEqual(
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
[
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
]
);
});
test('Allow to set channel to "false" to prevent default', t => {
const maintenance = [{name: '1.x', channel: false, tags: []}];
const release = [{name: 'master', channel: false, tags: []}, {name: 'next', channel: false, tags: []}];
const release = [
{name: 'master', channel: false, tags: []},
{name: 'next', channel: false, tags: []},
];
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
t.deepEqual(normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})), [
{name: '1.x', channel: false},
]);
t.deepEqual(normalize.release({release}).map(({name, channel}) => ({name, channel})), [
{name: 'master', channel: false},
{name: 'next', channel: false},
]);
t.deepEqual(normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})), [
{name: 'beta', channel: false},
]);
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
[{name: '1.x', channel: false}]
);
t.deepEqual(
normalize.release({release}).map(({name, channel}) => ({name, channel})),
[
{name: 'master', channel: false},
{name: 'next', channel: false},
]
);
t.deepEqual(
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
[{name: 'beta', channel: false}]
);
});
+3 -3
View File
@@ -175,7 +175,7 @@ test.serial('Display help', async t => {
t.is(exitCode, 0);
});
test.serial('Return error code and prints help if called with a command', async t => {
test.serial('Return error exitCode and prints help if called with a command', async t => {
const run = stub().resolves(true);
const argv = ['', '', 'pre'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
@@ -187,7 +187,7 @@ test.serial('Return error code and prints help if called with a command', async
t.is(exitCode, 1);
});
test.serial('Return error code if multiple plugin are set for single plugin', async t => {
test.serial('Return error exitCode if multiple plugin are set for single plugin', async t => {
const run = stub().resolves(true);
const argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
@@ -199,7 +199,7 @@ test.serial('Return error code if multiple plugin are set for single plugin', as
t.is(exitCode, 1);
});
test.serial('Return error code if semantic-release throw error', async t => {
test.serial('Return error exitCode if semantic-release throw error', async t => {
const run = stub().rejects(new Error('semantic-release error'));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
+7 -7
View File
@@ -8,9 +8,10 @@ test('A "maintenance" branch is identified by having a "range" property or a "na
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
t.true(maintenance.filter({name: 'some-name', range: ''}));
t.true(maintenance.filter({name: 'some-name', range: null}));
t.true(maintenance.filter({name: 'some-name', range: false}));
t.true(maintenance.filter({name: 'some-name', range: true}));
t.false(maintenance.filter({name: 'some-name', range: null}));
t.false(maintenance.filter({name: 'some-name', range: false}));
t.false(maintenance.filter({name: 'some-name'}));
t.false(maintenance.filter({name: '1.0.0'}));
t.false(maintenance.filter({name: 'x.x.x'}));
@@ -24,9 +25,8 @@ test('A "maintenance" branches must have a "range" property formatted like "N.x"
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
t.false(maintenance.branchValidator({name: 'some-name', range: true}));
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
t.false(maintenance.branchValidator({name: 'some-name', range: null}));
t.false(maintenance.branchValidator({name: 'some-name', range: false}));
});
test('The "maintenance" branches must have unique ranges', t => {
@@ -36,13 +36,13 @@ test('The "maintenance" branches must have unique ranges', t => {
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
});
test('A "prerelease" branch is identified by having a range "prerelease" property', t => {
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', t => {
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
t.true(prerelease.filter({name: 'some-name', prerelease: null}));
t.true(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name', prerelease: null}));
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name'}));
});
+1 -1
View File
@@ -513,6 +513,6 @@ test('Throw an Error if one of the shareable config cannot be found', async t =>
const error = await t.throwsAsync(t.context.getConfig({cwd}), Error);
t.is(error.message, "Cannot find module 'non-existing-path'");
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
});
+43 -4
View File
@@ -86,9 +86,9 @@ test('Convert shorthand URL', async t => {
cwd,
env,
branch: {name: 'master'},
options: {repositoryUrl: 'semanitc-release/semanitc-release'},
options: {repositoryUrl: 'semantic-release/semantic-release'},
}),
'https://github.com/semanitc-release/semanitc-release.git'
'https://github.com/semantic-release/semantic-release.git'
);
});
@@ -100,9 +100,9 @@ test('Convert GitLab shorthand URL', async t => {
cwd,
env,
branch: {name: 'master'},
options: {repositoryUrl: 'gitlab:semanitc-release/semanitc-release'},
options: {repositoryUrl: 'gitlab:semantic-release/semantic-release'},
}),
'https://gitlab.com/semanitc-release/semanitc-release.git'
'https://gitlab.com/semantic-release/semantic-release.git'
);
});
@@ -161,6 +161,19 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL with custom port', async t => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'http://host.null:8080/owner/repo.git'},
}),
'http://user:pass@host.null:8080/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async t => {
const {cwd} = await gitRepo();
@@ -189,6 +202,19 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "ssh" URL', async t => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'ssh://git@host.null:2222/owner/repo.git'},
}),
'https://user:pass@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async t => {
const {cwd} = await gitRepo();
@@ -273,6 +299,19 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BITB
);
});
test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async t => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GITHUB_ACTION: 'foo', GITHUB_TOKEN: 'token'},
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
}),
'https://x-access-token:token@host.null/owner/repo.git'
);
});
test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async t => {
const {cwd} = await gitRepo();
+17 -17
View File
@@ -6,16 +6,16 @@ test('Get the highest non-prerelease valid tag', t => {
branch: {
name: 'master',
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '222'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '333'},
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1', gitHead: 'v3.0.0-beta.1'},
],
type: 'release',
},
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '222', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
test('Get the highest prerelease valid tag, ignoring other tags from other prerelease channels', t => {
@@ -25,9 +25,9 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
prerelease: 'beta',
channel: 'beta',
tags: [
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1@beta', gitHead: '111', channel: 'beta'},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2@beta', gitHead: '222', channel: 'beta'},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1@alpha', gitHead: '333', channel: 'alpha'},
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1', gitHead: 'v1.0.0-beta.1', channels: ['beta']},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2', gitHead: 'v1.0.0-beta.2', channels: ['beta']},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1', gitHead: 'v1.0.0-alpha.1', channels: ['alpha']},
],
type: 'prerelease',
},
@@ -36,10 +36,10 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
t.deepEqual(result, {
version: '1.0.0-beta.2',
gitTag: 'v1.0.0-beta.2@beta',
gitTag: 'v1.0.0-beta.2',
name: 'v1.0.0-beta.2',
gitHead: '222',
channel: 'beta',
gitHead: 'v1.0.0-beta.2',
channels: ['beta'],
});
});
@@ -47,7 +47,7 @@ test('Return empty object if no valid tag is found', t => {
const result = getLastRelease({
branch: {
name: 'master',
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '111'}],
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1', gitHead: 'v3.0.0-beta.1'}],
type: 'release',
},
options: {tagFormat: `v\${version}`},
@@ -63,11 +63,11 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
name: 'master',
channel: undefined,
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '333'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: '222'},
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: '444'},
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: '555'},
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1', gitHead: 'v2.0.0-beta.1'},
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: 'v2.1.0'},
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: 'v2.1.1'},
],
type: 'release',
},
@@ -76,5 +76,5 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
{before: '2.1.0'}
);
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '333', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
+12 -12
View File
@@ -13,7 +13,7 @@ test('Increase version for patch release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1'
@@ -25,7 +25,7 @@ test('Increase version for minor release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0'
@@ -37,7 +37,7 @@ test('Increase version for major release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0'
@@ -61,7 +61,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1-beta.1'
@@ -71,7 +71,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -81,7 +81,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'patch', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.0.1-alpha.1'
@@ -93,7 +93,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0-beta.1'
@@ -103,7 +103,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -113,7 +113,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'minor', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.1.0-alpha.1'
@@ -125,7 +125,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0-beta.1'
@@ -135,7 +135,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -145,7 +145,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'major', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'2.0.0-alpha.1'
+282
View File
@@ -0,0 +1,282 @@
import test from 'ava';
import getReleaseToAdd from '../lib/get-release-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '2.1.0', channels: [null], gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channels: [null],
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
});
});
test('Return versions merged between release branches', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['next-major']},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {
version: '1.1.0',
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
channels: ['next'],
},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: null,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
});
});
test('Return releases sorted by ascending order', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['next-major']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0', name: 'v1.1.0', gitHead: 'v1.1.0', channels: ['next']},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: null,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
});
});
test('No lastRelease', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: ['next']}],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channels: ['next'],
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0',
},
});
});
test('Ignore pre-release versions', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0-alpha.1', version: '2.0.0-alpha.1', channels: ['alpha']},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.0.0', channels: [null, 'next'], gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channels: ['next'],
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: null,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
},
});
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [
{name: '2.x', channel: 'latest'},
{name: 'master', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channels: ['latest'], version: '1.0.0'},
{gitTag: 'v1.1.0', channels: ['latest'], version: '1.1.0'},
{gitTag: 'v2.0.0', channels: ['latest'], version: '2.0.0'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
],
},
branches: [
{name: 'master', channel: false},
{name: 'next', channel: false},
{name: 'next-major', channel: false},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions number less than the latest version already released on that branch', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null, '2.x']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
-313
View File
@@ -1,313 +0,0 @@
import test from 'ava';
import getReleasesToAdd from '../lib/get-releases-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0@2.x', version: '2.0.0', channel: '2.x', gitHead: '111'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.1.0', version: '2.1.0', gitHead: '222'},
{gitTag: 'v2.1.1', version: '2.1.1', gitHead: '333'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '444'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '555'},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '2.0.0', channel: '2.x', gitTag: 'v2.0.0@2.x', name: 'v2.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '2.1.0',
channel: undefined,
gitTag: 'v2.1.0',
name: 'v2.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '2.1.0',
channel: '2.x',
gitTag: 'v2.1.0@2.x',
name: 'v2.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '2.1.0', channel: undefined, gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: '222'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channel: undefined,
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: '333',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1@2.x',
name: 'v2.1.1',
gitHead: '333',
},
},
]);
});
test('Return versions merged between release branches', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: '333',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: '333',
},
},
]);
});
test('Return releases sorted by ascending order', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: '333',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: '333',
},
},
]);
});
test('No lastRelease', t => {
const result = getReleasesToAdd({
branch: {name: 'master', tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'}]},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channel: 'next',
gitTag: 'v1.0.0@next',
name: 'v1.0.0',
gitHead: '111',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: undefined,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: '111',
},
},
]);
});
test('Ignore pre-release versions', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0', channel: 'alpha', gitHead: '333'},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
]);
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.1.0', version: '2.1.0', gitHead: '222'},
{gitTag: 'v2.1.1', version: '2.1.1', gitHead: '333'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '444'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '555'},
],
},
branches: [{name: '2.x', channel: 'latest'}, {name: 'master', channel: 'latest'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channel: 'latest', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.1.0', channel: 'latest', version: '1.1.0', gitHead: '222'},
{gitTag: 'v2.0.0', channel: 'latest', version: '2.0.0', gitHead: '333'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '222'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '333'},
],
},
branches: [{name: 'master', channel: false}, {name: 'next', channel: false}, {name: 'next-major', channel: false}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
+135 -17
View File
@@ -14,6 +14,9 @@ import {
isGitRepo,
verifyTagName,
isBranchUpToDate,
getNote,
addNote,
fetchNotes,
} from '../lib/git';
import {
gitRepo,
@@ -27,6 +30,9 @@ import {
gitRemoteTagHead,
gitPush,
gitDetachedHead,
gitDetachedHeadFromBranch,
gitAddNote,
gitGetNote,
} from './helpers/git-utils';
test('Get the last commit sha', async t => {
@@ -58,7 +64,7 @@ test('Unshallow and fetch repository', async t => {
// Verify the shallow clone contains only one commit
t.is((await gitGetCommits(undefined, {cwd})).length, 1);
await fetch('master', {cwd});
await fetch(repositoryUrl, 'master', 'master', {cwd});
// Verify the shallow clone contains all the commits
t.is((await gitGetCommits(undefined, {cwd})).length, 2);
@@ -73,8 +79,8 @@ test('Do not throw error when unshallow a complete repository', async t => {
await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, 'second-branch', {cwd});
await t.notThrowsAsync(fetch('master', {cwd}));
await t.notThrowsAsync(fetch('second-branch', {cwd}));
await t.notThrowsAsync(fetch(repositoryUrl, 'master', 'master', {cwd}));
await t.notThrowsAsync(fetch(repositoryUrl, 'second-branch', 'master', {cwd}));
});
test('Fetch all tags on a detached head repository', async t => {
@@ -89,9 +95,34 @@ test('Fetch all tags on a detached head repository', async t => {
await gitPush(repositoryUrl, 'master', {cwd});
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
await fetch('master', {cwd});
await fetch(repositoryUrl, 'master', 'master', {cwd});
t.deepEqual((await getTags({cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
});
test('Fetch all tags on a repository with a detached head from branch', async t => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.1', undefined, {cwd});
const [commit] = await gitCommits(['Third'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('other-branch', true, {cwd});
await gitPush(repositoryUrl, 'other-branch', {cwd});
await gitCheckout('master', false, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
cwd = await gitDetachedHeadFromBranch(repositoryUrl, 'other-branch', commit.hash);
await fetch(repositoryUrl, 'master', 'other-branch', {cwd});
await fetch(repositoryUrl, 'other-branch', 'other-branch', {cwd});
t.deepEqual((await getTags('other-branch', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v2.0.0'].sort());
});
test('Verify if the commit `sha` is in the direct history of the current branch', async t => {
@@ -105,10 +136,10 @@ test('Verify if the commit `sha` is in the direct history of the current branch'
const otherCommits = await gitCommits(['Second'], {cwd});
await gitCheckout('master', false, {cwd});
t.true(await isRefInHistory(commits[0].hash, 'master', false, {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', false, {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', false, {cwd}));
await t.throwsAsync(isRefInHistory('non-existant-sha', 'master', false, {cwd}));
t.true(await isRefInHistory(commits[0].hash, 'master', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', {cwd}));
await t.throwsAsync(isRefInHistory('non-existant-sha', 'master', {cwd}));
});
test('Verify if a branch exists', async t => {
@@ -137,10 +168,10 @@ test('Get all branches', async t => {
await gitCommits(['Third'], {cwd});
await gitPush(repositoryUrl, 'third-branch', {cwd});
t.deepEqual((await getBranches({cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
t.deepEqual((await getBranches(repositoryUrl, {cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
});
test('Get the commit sha for a given tag or falsy if the tag does not exists', async t => {
test('Get the commit sha for a given tag', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -149,7 +180,6 @@ test('Get the commit sha for a given tag or falsy if the tag does not exists', a
await gitTagVersion('v1.0.0', undefined, {cwd});
t.is(await getTagHead('v1.0.0', {cwd}), commits[0].hash);
t.falsy(await getTagHead('missing_tag', {cwd}));
});
test('Return git remote repository url from config', async t => {
@@ -243,7 +273,7 @@ test('Return falsy for invalid tag names', async t => {
test('Throws error if obtaining the tags fails', async t => {
const cwd = tempy.directory();
await t.throwsAsync(getTags({cwd}));
await t.throwsAsync(getTags('master', {cwd}));
});
test('Return "true" if repository is up to date', async t => {
@@ -251,7 +281,7 @@ test('Return "true" if repository is up to date', async t => {
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
t.true(await isBranchUpToDate('master', {cwd}));
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return falsy if repository is not up to date', async t => {
@@ -260,13 +290,13 @@ test('Return falsy if repository is not up to date', async t => {
await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
t.true(await isBranchUpToDate('master', {cwd}));
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
const tmpRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Third'], {cwd: tmpRepo});
await gitPush('origin', 'master', {cwd: tmpRepo});
t.falsy(await isBranchUpToDate('master', {cwd}));
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return "true" if local repository is ahead', async t => {
@@ -275,5 +305,93 @@ test('Return "true" if local repository is ahead', async t => {
await gitPush(repositoryUrl, 'master', {cwd});
await gitCommits(['Second'], {cwd});
t.true(await isBranchUpToDate('master', {cwd}));
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Get a commit note', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First'], {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
t.deepEqual(await getNote(commits[0].hash, {cwd}), {note: 'note'});
});
test('Return empty object if there is no commit note', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First'], {cwd});
t.deepEqual(await getNote(commits[0].hash, {cwd}), {});
});
test('Throw error if a commit note in invalid', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First'], {cwd});
await gitAddNote('non-json note', commits[0].hash, {cwd});
await t.throwsAsync(getNote(commits[0].hash, {cwd}));
});
test('Add a commit note', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First'], {cwd});
await addNote({note: 'note'}, commits[0].hash, {cwd});
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Overwrite a commit note', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First'], {cwd});
await addNote({note: 'note'}, commits[0].hash, {cwd});
await addNote({note: 'note2'}, commits[0].hash, {cwd});
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note2"}');
});
test('Unshallow and fetch repository with notes', async t => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First', 'Second'], {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
// Create a shallow clone with only 1 commit
cwd = await gitShallowClone(repositoryUrl);
// Verify the shallow clone doesn't contains the note
await t.throwsAsync(gitGetNote(commits[0].hash, {cwd}));
await fetch(repositoryUrl, 'master', 'master', {cwd});
await fetchNotes(repositoryUrl, {cwd});
// Verify the shallow clone contains the note
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Fetch all notes on a detached head repository', async t => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
const [commit] = await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commit.hash, {cwd});
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
await fetch(repositoryUrl, 'master', 'master', {cwd});
await fetchNotes(repositoryUrl, {cwd});
t.is(await gitGetNote(commit.hash, {cwd}), '{"note":"note"}');
});
+50 -27
View File
@@ -4,6 +4,7 @@ import fileUrl from 'file-url';
import pEachSeries from 'p-each-series';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import {GIT_NOTE_REF} from '../../lib/definitions/constants';
/**
* Commit message informations.
@@ -69,8 +70,9 @@ export async function initBareRepo(repositoryUrl, branch = 'master') {
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
*/
export async function gitCommits(messages, execaOpts) {
await pEachSeries(messages, message =>
execa.stdout('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)
await pEachSeries(
messages,
async message => (await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)).stdout
);
return (await gitGetCommits(undefined, execaOpts)).slice(0, messages.length);
}
@@ -85,9 +87,11 @@ export async function gitCommits(messages, execaOpts) {
*/
export async function gitGetCommits(from, execaOpts) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
return (await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
)).map(commit => {
return (
await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
)
).map(commit => {
commit.message = commit.message.trim();
commit.gitTags = commit.gitTags.trim();
return commit;
@@ -98,10 +102,10 @@ export async function gitGetCommits(from, execaOpts) {
* Checkout a branch on the current git repository.
*
* @param {String} branch Branch name.
* @param {Boolean} create `true` to create the branch, `false` to checkout an existing branch.
* @param {Boolean} create to create the branch, `false` to checkout an existing branch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitCheckout(branch, create = true, execaOpts) {
export async function gitCheckout(branch, create, execaOpts) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOpts);
}
@@ -112,8 +116,8 @@ export async function gitCheckout(branch, create = true, execaOpts) {
*
* @return {String} The sha of the head commit in the current git repository.
*/
export function gitHead(execaOpts) {
return execa.stdout('git', ['rev-parse', 'HEAD'], execaOpts);
export async function gitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
}
/**
@@ -162,6 +166,17 @@ export async function gitDetachedHead(repositoryUrl, head) {
return cwd;
}
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
await execa('git', ['fetch', '--force', repositoryUrl, `${branch}:remotes/origin/${branch}`], {cwd});
await execa('git', ['reset', '--hard', head], {cwd});
await execa('git', ['checkout', '-q', '-B', branch], {cwd});
return cwd;
}
/**
* Add a new Git configuration.
*
@@ -181,8 +196,8 @@ export async function gitAddConfig(name, value, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export function gitTagHead(tagName, execaOpts) {
return execa.stdout('git', ['rev-list', '-1', tagName], execaOpts);
export async function gitTagHead(tagName, execaOpts) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
}
/**
@@ -195,10 +210,10 @@ export function gitTagHead(tagName, execaOpts) {
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
return (await execa.stdout('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOpts))
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOpts)).stdout
.split('\n')
.filter(tag => Boolean(tag))
.map(tag => tag.match(/^(\S+)/)[1])[0];
.map(tag => tag.match(/^(?<tag>\S+)/)[1])[0];
}
/**
@@ -209,8 +224,8 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
*
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export function gitCommitTag(gitHead, execaOpts) {
return execa.stdout('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts);
export async function gitCommitTag(gitHead, execaOpts) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts)).stdout;
}
/**
@@ -222,7 +237,7 @@ export function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl = 'origin', branch = 'master', execaOpts) {
export async function gitPush(repositoryUrl, branch, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
}
@@ -256,15 +271,23 @@ export async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], execaOpts);
}
export async function changeAuthor(sha, execaOpts) {
await execa(
'git',
[
'filter-branch',
'-f',
'--env-filter',
`if [[ "$GIT_COMMIT" = "${sha}" ]]; then export GIT_COMMITTER_NAME="New Author" GIT_COMMITTER_EMAIL="author@test.com"; fi`,
],
execaOpts
);
/**
* Add a note to a Git reference.
*
* @param {String} note The note to add.
* @param {String} ref The ref to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOpts);
}
/**
* Get the note associated with a Git reference.
*
* @param {String} ref The ref to get the note from.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitGetNote(ref, execaOpts) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import getStream from 'get-stream';
import pRetry from 'p-retry';
import {initBareRepo, gitShallowClone} from './git-utils';
const IMAGE = 'pvdlg/docker-gitbox';
const IMAGE = 'pvdlg/docker-gitbox:latest';
const SERVER_PORT = 80;
const HOST_PORT = 2080;
const SERVER_HOST = 'localhost';
+1 -1
View File
@@ -30,7 +30,7 @@ async function start() {
minTimeout: 1000,
factor: 2,
});
} catch (error) {
} catch (_) {
throw new Error(`Couldn't start mock-server after 2 min`);
}
}
+1 -1
View File
@@ -39,7 +39,7 @@ async function start() {
minTimeout: 1000,
factor: 2,
});
} catch (error) {
} catch (_) {
throw new Error(`Couldn't start npm-registry-docker after 2 min`);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import execa from 'execa';
export async function npmView(packageName, env) {
return JSON.parse(await execa.stdout('npm', ['view', packageName, '--json'], {env}));
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
}
+198 -90
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {escapeRegExp, isString, sortBy} from 'lodash';
import {escapeRegExp, isString, sortBy, omit} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
@@ -19,6 +19,8 @@ import {
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
} from './helpers/git-utils';
const requireNoCache = proxyquire.noPreserveCache();
@@ -45,7 +47,8 @@ test('Plugins are called with expected values', async t => {
// Add commits to the master branch
let commits = await gitCommits(['First'], {cwd});
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.0.0', {cwd});
commits = (await gitCommits(['Second'], {cwd})).concat(commits);
await gitCheckout('next', true, {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
@@ -55,9 +58,9 @@ test('Plugins are called with expected values', async t => {
const lastRelease = {
version: '1.0.0',
gitHead: commits[commits.length - 1].hash,
gitTag: 'v1.0.0@next',
gitTag: 'v1.0.0',
name: 'v1.0.0',
channel: 'next',
channels: ['next'],
};
const nextRelease = {
name: 'v1.1.0',
@@ -65,7 +68,7 @@ test('Plugins are called with expected values', async t => {
version: '1.1.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v1.1.0',
channel: undefined,
channel: null,
};
const notes1 = 'Release notes 1';
const notes2 = 'Release notes 2';
@@ -83,7 +86,7 @@ test('Plugins are called with expected values', async t => {
const prepare = stub().resolves();
const publish = stub().resolves(release2);
const success = stub().resolves();
const env = {...process.env};
const env = {};
const config = {
branches: [{name: 'master'}, {name: 'next'}],
repositoryUrl,
@@ -94,18 +97,20 @@ test('Plugins are called with expected values', async t => {
{
channel: undefined,
name: 'master',
range: '>=1.0.0 <2.0.0',
accept: ['patch', 'minor'],
tags: [{channel: 'next', gitTag: 'v1.0.0@next', version: '1.0.0', gitHead: commits[commits.length - 1].hash}],
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
tags: [{channels: ['next'], gitTag: 'v1.0.0', version: '1.0.0'}],
type: 'release',
main: true,
},
{
channel: 'next',
name: 'next',
range: '>=2.0.0',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
tags: [{channel: 'next', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0@next', version: '1.0.0'}],
tags: [{channels: ['next'], gitTag: 'v1.0.0', version: '1.0.0'}],
type: 'release',
main: false,
},
];
const branch = branches[0];
@@ -124,11 +129,11 @@ test('Plugins are called with expected values', async t => {
const releases = [
{
...lastRelease,
...omit(lastRelease, 'channels'),
...release1,
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
pluginName: '[Function: functionStub]',
@@ -176,10 +181,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes1.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes1.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes1.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
});
@@ -193,10 +198,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes2.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes2.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes2.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: notes1,
@@ -211,10 +216,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes3.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes3.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes3.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}`,
@@ -222,7 +227,7 @@ test('Plugins are called with expected values', async t => {
branch.tags.push({
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
gitHead: commits[commits.length - 1].hash,
});
@@ -236,10 +241,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(addChannel.args[0][1].lastRelease, {});
t.deepEqual(addChannel.args[0][1].currentRelease, {...lastRelease, type: 'major'});
t.deepEqual(addChannel.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
@@ -330,10 +335,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(success.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(success.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(success.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
@@ -353,7 +358,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(result, {
lastRelease,
commits: [{...commits[0], gitTags: '(HEAD -> master, origin/master, origin/HEAD, next)'}],
commits: [{...commits[0], gitTags: '(HEAD -> master, next)'}],
nextRelease: {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`},
releases,
});
@@ -433,7 +438,7 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
version: '2.0.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v2.0.0',
channel: undefined,
channel: null,
};
const notes = 'Release notes';
@@ -492,15 +497,16 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
test('Make a new release when a commit is forward-ported to an upper branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.0.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.0.x']}), 'v1.0.0', {cwd});
await gitCheckout('1.0.x', true, {cwd});
commits.push(...(await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd})));
await gitTagVersion('v1.0.1@1.0.x', undefined, {cwd});
await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd});
await gitTagVersion('v1.0.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['1.0.x']}), 'v1.0.1', {cwd});
await gitPush('origin', '1.0.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
await gitCommits(['feat: new feature on master'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await merge('1.0.x', {cwd});
await gitPush('origin', 'master', {cwd});
@@ -562,34 +568,150 @@ test('Publish a pre-release version', async t => {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'beta', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, stdout: {write: () => {}}, stderr: {write: () => {}}});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.1');
t.is(releases[0].gitTag, 'v1.1.0-beta.1@beta');
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
t.is(await gitGetNote('v1.1.0-beta.1', {cwd}), '{"channels":["beta"]}');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.2');
t.is(releases[0].gitTag, 'v1.1.0-beta.2@beta');
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
t.is(await gitGetNote('v1.1.0-beta.2', {cwd}), '{"channels":["beta"]}');
});
test('Publish releases from different branch on the same channel', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next-major', true, {cwd});
await gitPush(repositoryUrl, 'next-major', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
const config = {
branches: ['master', {name: 'next', channel: false}, {name: 'next-major', channel: false}],
repositoryUrl,
};
const addChannel = stub().resolves({});
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
let semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'next', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0');
t.is(releases[0].gitTag, 'v1.1.0');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.1');
t.is(releases[0].gitTag, 'v1.1.1');
await gitCheckout('master', false, {cwd});
await merge('next', {cwd});
await gitPush('origin', 'master', {cwd});
semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
t.falsy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
t.is(addChannel.callCount, 0);
});
test('Publish pre-releases the same channel as regular releases', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('beta', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'beta', {cwd});
const config = {
branches: ['master', {name: 'beta', channel: false, prerelease: true}],
repositoryUrl,
};
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel: false,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
const semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'beta', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.1');
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.2');
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
});
test('Do not add pre-releases to a different channel', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@beta', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'beta']}), 'v1.0.0', {cwd});
await gitCheckout('beta', true, {cwd});
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
await gitTagVersion('v2.0.0-beta.1@beta', undefined, {cwd});
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
await gitTagVersion('v2.0.0-beta.2@beta', undefined, {cwd});
await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd});
await gitTagVersion('v2.0.0-beta.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.1', {cwd});
await gitCommits(['fix: a fix'], {cwd});
await gitTagVersion('v2.0.0-beta.2', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.2', {cwd});
await gitPush('origin', 'beta', {cwd});
await gitCheckout('master', false, {cwd});
await merge('beta', {cwd});
@@ -634,14 +756,18 @@ async function addChannelMacro(t, mergeFunction) {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.0', {cwd});
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
await gitTagVersion('v2.0.1@next', undefined, {cwd});
await gitTagVersion('v2.0.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.1', {cwd});
commits.push(...(await gitCommits(['feat: a feature'], {cwd})));
await gitTagVersion('v2.1.0@next', undefined, {cwd});
await gitTagVersion('v2.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.1.0', {cwd});
await gitPush('origin', 'next', {cwd});
await gitCheckout('master', false, {cwd});
// Merge all commits but last one from next to master
@@ -670,19 +796,11 @@ async function addChannelMacro(t, mergeFunction) {
publish,
success,
};
const nextRelease1 = {
name: 'v2.0.0',
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
gitHead: commits[1].hash,
};
const nextRelease2 = {
name: 'v2.0.1',
type: 'patch',
version: '2.0.1',
channel: undefined,
channel: null,
gitTag: 'v2.0.1',
gitHead: commits[2].hash,
};
@@ -694,15 +812,11 @@ async function addChannelMacro(t, mergeFunction) {
const result = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.deepEqual(result.releases, [
{...nextRelease1, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, notes, pluginName: '[Function: functionStub]'},
]);
// Verify the tag has been created on the local and remote repo and reference
t.is(await gitTagHead(nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitTagHead(nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
}
@@ -730,7 +844,7 @@ test('Call all "success" plugins even if one errors out', async t => {
version: '2.0.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v2.0.0',
channel: undefined,
channel: null,
};
const notes = 'Release notes';
const verifyConditions1 = stub().resolves();
@@ -865,9 +979,11 @@ test('Dry-run skips addChannel, prepare, publish and success', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next', true, {cwd});
await gitPush('origin', 'next', {cwd});
@@ -1147,8 +1263,10 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next', true, {cwd});
await gitPush('origin', 'next', {cwd});
@@ -1160,7 +1278,7 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
version: '1.2.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v1.2.0',
channel: undefined,
channel: null,
};
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
@@ -1233,16 +1351,16 @@ test('Returns false if triggered by a PR', async t => {
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial commit'], {cwd});
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitPush('origin', '1.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
await gitCommits(['feat: new feature on master'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitCheckout('1.x', false, {cwd});
commits.push(...(await gitCommits(['feat: feature on maintenance version 1.x'], {cwd})));
await gitCommits(['feat: feature on maintenance version 1.x'], {cwd});
await gitPush('origin', 'master', {cwd});
const verifyConditions = stub().resolves();
@@ -1284,16 +1402,17 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current release branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial commit'], {cwd});
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['feat: new feature on next'], {cwd})));
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitCommits(['feat: new feature on next'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush('origin', 'next', {cwd});
await gitCheckout('next-major', true, {cwd});
await gitPush('origin', 'next-major', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd})));
await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd});
await gitPush('origin', 'master', {cwd});
const verifyConditions = stub().resolves();
@@ -1335,18 +1454,18 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a maintenance branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.1.x', undefined, {cwd});
commits.push(...(await gitCommits(['Second'], {cwd})));
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitTagVersion('v1.1.0@1.1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.1.x', 'master', {cwd});
await gitPush('origin', '1.1.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitCommits(['Third'], {cwd});
await gitTagVersion('v1.1.1', undefined, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v1.2.0', undefined, {cwd});
await gitPush('origin', 'master', {cwd});
await gitCheckout('1.1.x', false, {cwd});
@@ -1378,15 +1497,6 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
fail,
};
const nextRelease = {
type: 'patch',
version: '1.1.1',
channel: '1.1.x',
gitTag: 'v1.1.1@1.1.x',
name: 'v1.1.1',
gitHead: commits[2].hash,
};
const semanticRelease = proxyquire('..', {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: '1.1.x', isPr: false}),
@@ -1397,13 +1507,11 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
)),
];
t.is(addChannel.callCount, 1);
t.deepEqual(addChannel.args[0][1].nextRelease, {...nextRelease, notes});
t.is(addChannel.callCount, 0);
t.is(publish.callCount, 0);
t.is(success.callCount, 1);
t.deepEqual(success.args[0][1].releases, [{...nextRelease, notes, pluginName: '[Function: functionStub]'}]);
t.is(success.callCount, 0);
t.is(fail.callCount, 1);
t.deepEqual(fail.args[0][1].errors, errors);
+40 -40
View File
@@ -16,6 +16,7 @@ import {
gitPush,
gitCheckout,
merge,
gitGetNote,
} from './helpers/git-utils';
import {npmView} from './helpers/npm-utils';
import gitbox from './helpers/gitbox';
@@ -83,9 +84,9 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a chore');
await gitCommits(['chore: Init repository'], {cwd});
t.log('$ semantic-release');
let {stdout, code} = await execa(cli, [], {env, cwd});
let {stdout, exitCode} = await execa(cli, [], {env, cwd});
t.regex(stdout, /There are no relevant changes, so no new version is released/);
t.is(code, 0);
t.is(exitCode, 0);
/* Initial release */
let version = '1.0.0';
@@ -106,10 +107,10 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -147,10 +148,10 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a fix');
await gitCommits(['fix: bar'], {cwd});
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -188,10 +189,10 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a feature');
await gitCommits(['feat: baz'], {cwd});
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -220,7 +221,7 @@ test('Release patch, minor and major versions', async t => {
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}@next`, name: `v${version}`},
body: {tag_name: `v${version}`, name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
@@ -231,10 +232,10 @@ test('Release patch, minor and major versions', async t => {
await gitPush('origin', 'next', {cwd});
await gitCommits(['feat: foo\n\n BREAKING CHANGE: bar'], {cwd});
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env: {...env, TRAVIS_BRANCH: 'next'}, cwd}));
({stdout, exitCode} = await execa(cli, [], {env: {...env, TRAVIS_BRANCH: 'next'}, cwd}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -246,8 +247,9 @@ test('Release patch, minor and major versions', async t => {
} = await npmView(packageName, testEnv));
head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}@next`, {cwd}), head);
t.is(await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd}), head);
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next"]}');
t.is(await gitTagHead(`v${version}`, {cwd}), head);
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
t.log(`+ released ${releasedVersion} on @next`);
await mockServer.verify(verifyMock);
@@ -262,14 +264,14 @@ test('Release patch, minor and major versions', async t => {
{body: {permissions: {push: true}}, method: 'GET'}
);
const getReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases/tags/v2.0.0@next`,
`/repos/${owner}/${packageName}/releases/tags/v2.0.0`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {id: releaseId}, method: 'GET'}
);
const updateReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases/${releaseId}`,
{
body: {tag_name: `v${version}`, name: `v${version}`, prerelease: false},
body: {name: `v${version}`, prerelease: false},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}, method: 'PATCH'}
@@ -280,10 +282,10 @@ test('Release patch, minor and major versions', async t => {
await merge('next', {cwd});
await gitPush('origin', 'master', {cwd});
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
t.regex(stdout, new RegExp(`Updated GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Adding version ${version} to npm registry on dist-tag latest`));
t.is(code, 0);
t.is(exitCode, 0);
// Wait for 3s as the change of dist-tag takes time to be reflected in the registry
await delay(3000);
@@ -292,11 +294,9 @@ test('Release patch, minor and major versions', async t => {
'dist-tags': {latest: releasedVersion},
} = await npmView(packageName, testEnv));
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}@next`, {cwd}));
t.is(
await gitRemoteTagHead(authUrl, `v${version}`, {cwd}),
await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd})
);
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next",null]}');
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}`, {cwd}));
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), await gitRemoteTagHead(authUrl, `v${version}`, {cwd}));
t.log(`+ added ${releasedVersion}`);
await mockServer.verify(verifyMock);
@@ -317,8 +317,8 @@ test('Exit with 1 if a plugin is not found', async t => {
release: {analyzeCommits: 'non-existing-path', success: false, fail: false},
});
const {code, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(code, 1);
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
@@ -335,8 +335,8 @@ test('Exit with 1 if a shareable config is not found', async t => {
release: {extends: 'non-existing-path', success: false, fail: false},
});
const {code, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(code, 1);
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
@@ -356,8 +356,8 @@ test('Exit with 1 if a shareable config reference a not found plugin', async t =
});
await writeJson(path.resolve(cwd, 'shareable.json'), shareable);
const {code, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(code, 1);
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
@@ -386,11 +386,11 @@ test('Dry-run', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release -d');
const {stdout, code} = await execa(cli, ['-d'], {env, cwd});
const {stdout, exitCode} = await execa(cli, ['-d'], {env, cwd});
t.regex(stdout, new RegExp(`There is no previous release, the next release version is ${version}`));
t.regex(stdout, new RegExp(`Release note for version ${version}`));
t.regex(stdout, /Initial commit/);
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and has not been modified
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '0.0.0-dev');
@@ -434,10 +434,10 @@ test('Allow local releases with "noCi" option', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release --no-ci');
const {stdout, code} = await execa(cli, ['--no-ci'], {env: envNoCi, cwd});
const {stdout, exitCode} = await execa(cli, ['--no-ci'], {env: envNoCi, cwd});
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and has been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -474,7 +474,7 @@ test('Pass options via CLI arguments', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release');
const {stdout, code} = await execa(
const {stdout, exitCode} = await execa(
cli,
[
'--verify-conditions',
@@ -490,7 +490,7 @@ test('Pass options via CLI arguments', async t => {
{env, cwd}
);
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
t.is(exitCode, 0);
// Verify package.json and has been updated
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
@@ -581,14 +581,14 @@ test('Log unexpected errors from plugins and exit with 1', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release');
const {stderr, code} = await execa(cli, [], {env, cwd, reject: false});
const {stderr, exitCode} = await execa(cli, [], {env, cwd, reject: false});
// Verify the type and message are logged
t.regex(stderr, /Error: a/);
// Verify the the stacktrace is logged
t.regex(stderr, new RegExp(pluginError));
// Verify the Error properties are logged
t.regex(stderr, /errorProperty: 'errorProperty'/);
t.is(code, 1);
t.is(exitCode, 1);
});
test('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
@@ -608,10 +608,10 @@ test('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release');
const {stderr, code} = await execa(cli, [], {env, cwd, reject: false});
const {stderr, exitCode} = await execa(cli, [], {env, cwd, reject: false});
// Verify the type and message are logged
t.regex(stderr, /EINHERITED Inherited error/);
t.is(code, 1);
t.is(exitCode, 1);
});
test('Exit with 1 if missing permission to push to the remote repository', async t => {
@@ -626,14 +626,14 @@ test('Exit with 1 if missing permission to push to the remote repository', async
await gitCommits(['feat: Initial commit'], {cwd});
await gitPush('origin', 'master', {cwd});
t.log('$ semantic-release');
const {stderr, code} = await execa(
const {stderr, exitCode} = await execa(
cli,
['--repository-url', 'http://user:wrong_pass@localhost:2080/git/unauthorized.git'],
{env: {...env, GH_TOKEN: 'user:wrong_pass'}, cwd, reject: false}
);
// Verify the type and message are logged
t.regex(stderr, /EGITNOPERMISSION/);
t.is(code, 1);
t.is(exitCode, 1);
});
test('Hide sensitive environment variable values from the logs', async t => {
+1 -1
View File
@@ -273,6 +273,6 @@ test('Throws an error if the plugin is not found', t => {
Error
);
t.is(error.message, "Cannot find module 'non-existing-path'");
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
});
+18 -3
View File
@@ -47,7 +47,12 @@ test('Execute each function in series passing the "lastResult" and "result" to "
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
t.deepEqual(result, [1, 2, 3, 4]);
t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]);
t.deepEqual(getNextInput.args, [
[5, 1],
[5, 2],
[5, 3],
[5, 4],
]);
});
test('Execute each function in series calling "transform" to modify the results', async t => {
@@ -61,7 +66,12 @@ test('Execute each function in series calling "transform" to modify the results'
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Execute each function in series calling "transform" to modify the results with "settleAll"', async t => {
@@ -75,7 +85,12 @@ test('Execute each function in series calling "transform" to modify the results
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Stop execution and throw error if a step rejects', async t => {
+10 -4
View File
@@ -14,6 +14,7 @@ import {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
} from '../lib/utils';
test('extractErrors', t => {
@@ -173,8 +174,13 @@ test('getRange', t => {
test('makeTag', t => {
t.is(makeTag(`v\${version}`, '1.0.0'), 'v1.0.0');
t.is(makeTag(`v\${version}`, '1.0.0', false), 'v1.0.0');
t.is(makeTag(`v\${version}`, '1.0.0', null), 'v1.0.0');
t.is(makeTag(`v\${version}`, '1.0.0', 'next'), 'v1.0.0@next');
t.is(makeTag(`v\${version}@test`, '1.0.0', 'next'), 'v1.0.0@next@test');
});
test('isSameChannel', t => {
t.true(isSameChannel('next', 'next'));
t.true(isSameChannel(null, undefined));
t.true(isSameChannel(false, undefined));
t.true(isSameChannel('', false));
t.false(isSameChannel('next', false));
});