Compare commits

..
170 Commits
Author SHA1 Message Date
Gregor Martynus e72d201afe wip esm adapt lodash 2021-07-30 13:57:35 -07:00
Gregor Martynus ab7a428102 WIP esm - adapt sinon 2021-07-30 13:57:35 -07:00
Gregor Martynus ce263c5f4a WIP convert CJS to ESM using https://github.com/homer0/cjs2esm 2021-07-30 13:57:35 -07:00
Matt Travi d9605a5e45 docs(node-version): updated documentation to reference the new minimum 2021-07-30 15:47:49 -05:00
Matt Travi e1c00e2531 feat(node-version): raised the minimum required version to v14.17
for #2055

BREAKING CHANGE: the minimum required version of node is now v14.17
2021-07-30 15:18:22 -05:00
88e423b828 chore(deps): update dependency p-retry to v4.6.1 (#2042)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-07-19 13:17:04 -07:00
Oscar DominguezandGitHub a4f0ff70fe ci(workflow): add 'npm' cache for actions/setup-node in .github/workflows (#2038) 2021-07-19 10:39:55 -07:00
ee3068eb6a chore(deps): update dependency nock to v13.1.1 (#2034)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-07-18 10:06:40 -07:00
Hugh RawlinsonandGitHub 04b6046e1f docs(branches): minor typo fixes (#2033) 2021-07-17 14:53:43 -07:00
fa847e2b87 chore(deps): update dependency p-retry to v4.6.0 (#2004)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-07-08 21:39:19 +00:00
Kyle TandGitHub 413ffd2a4b docs(circleci): Update CircleCI recipes config (#2018)
Updates the included CircleCI config file example.

* Now uses 2.1 style config
* Uses Node Orb
* Use pre-defined node/test job
* Uses Matrix jobs to test multiple node versions.
* Updated Node versions
* Using new CircleCI docker images (from node executor)
* Updated language/terminology in description
2021-07-08 13:14:29 -07:00
143c8981bf chore(deps): update dependency js-yaml to v4 (#1752)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Matt Travi <programmer@travi.org>
2021-07-02 00:07:46 -05:00
Moritz WiesingerandGitHub cf000687c4 docs(recipes): fix some typos (#1999)
Signed-off-by: Moritz Wiesinger <moritz.wiesinger@dynatrace.com>
2021-07-01 09:17:34 -07:00
Fernando RojoandGitHub 1463eb42cb docs(plugins): clarify order of execution (#1989) 2021-06-25 14:06:57 -05:00
AxieumandGitHub 80e1665037 fix: corrected spelling of invalid plugin config error (#1979) 2021-06-15 09:52:19 -05:00
37765ef36d chore(deps): update dependency dockerode to v3.3.0 (#1977)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 08:56:45 -07:00
a7a1e6d28f chore(deps): update dependency sinon to v11 (#1976)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-11 13:38:45 +00:00
59bf862d4a chore(deps): update dependency nock to v13.1.0 (#1975)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-10 12:45:53 -07:00
78a63a7de0 chore(deps): update dependency trim-newlines to 3.0.1 [security] (#1974)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-08 22:31:00 -05:00
61f94c00ab build(deps): bump normalize-url from 4.5.0 to 4.5.1 (#1973)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-08 22:03:00 -05:00
Justin DietzandGitHub 7452eaf2cd docs: adds updated docker plugin that supports major, minor tagging (#1963) 2021-06-03 09:31:49 -07:00
c229888cc0 chore(deps): update dependency fs-extra to v10 (#1955)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-06-01 22:14:25 +00:00
HonkingGooseandGitHub 29d34788ff docs(README): improve sentence (#1962) 2021-06-01 13:48:53 -07:00
Aki MäkinenandGitHub fe0ac417cf docs(plugin developer guide): correction, additional details / advanced (#1961)
A correction to the earlier documentation: the "nextRelease" object is NOT similar to the
lastRelease and there are some differences. Because of this, the object is now documented as a
separate entry. Added also information on how the error handling should be done so that
semantic-release would be able to handle errors from plugins properly and call "fail" lifecycle
plugins. Included some more advanced information on how the analyzeCommits-lifecycle plugins work
and how the result of the initial analyzer can be overridden. This is an edge case, but could happen
and is good to understand.
2021-05-31 10:15:47 -07:00
3edc917c7c chore(deps): update dependency tempy to v1.0.1 (#1946)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-27 02:54:20 +00:00
8bc98c33c9 chore(deps): update dependency browserslist to 4.16.5 [security] (#1954)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-26 20:46:59 -05:00
Josh SullivanandGitHub 6427210b83 docs(workflow-configurator): fix single typo (#1892) 2021-05-26 10:57:54 -07:00
4c1c992869 chore(deps): update dependency sinon to v10 (#1941)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-22 01:06:53 +00:00
a3a9907368 chore(deps): update dependency codecov to v3.8.2 (#1940)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-05-21 23:16:45 +00:00
7fbab56a98 docs: add semantic-release-telegram to the list of community plugins (#1945)
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2021-05-21 14:30:44 -07:00
Dmytro PustovitandGitHub 7dae2a9f43 docs: add heroku to list of community plugins (#1920) 2021-05-21 14:29:28 -07:00
Ryan SonshineandGitHub 13f6a725a1 docs(plugins-list): add semantic-release-codeartifact (#1939) 2021-05-17 07:50:18 -07:00
Gregor MartynusandGitHub 55194c106e fix: bump minimal version of lodash to address CVE-2021-23337 (#1931) 2021-05-12 11:37:06 -07:00
mhagnumdwandGitHub e24ef71e33 docs(distribution-channels): fix branch name (#1925) 2021-05-09 22:40:32 -05:00
Aki MäkinenandGitHub 0435e35fee docs(Plugin Developer Guide): More detailed lifecycle list and context descriptions (#1880)
* docs(Plugin Developer Guide): More detailed lifecycle list and context descriptions

This commit will improve the following in the Plugin developer guide:
  * Lifecyle list more complete (previous was missing some lifecycles)
  * Context object has been now described in more detail per lifecycle
  * Some readability/maintainability fixes to the markdown file itself
    to make it easier to edit and read in an editor.

* docs(Plugin Developer Guide): Added missing context field to analyzeCommits lifecycle documentation

analyzeCommits lifecycle context was missing documentation for "commits" list.

* docs(developer-guide): remove line breaks added in an earlier commit

As per requested in the code review, the line breaks that were added in an earlier commit are now
removed. While this does make reading the text just slightly more difficult (to see the whole
picture), the benefit of making code reviews easier does outweight the admittedly small benefit.
2021-04-25 15:14:03 -07:00
1d1b52a7dc chore(deps): update dependency p-retry to v4.5.0 (#1885)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-04-10 14:57:32 +00:00
Aki MäkinenandGitHub 72522c3570 docs(Plugin Developer Guide): Update lifecycle list (#1874)
The old list was missing analyzeCommits lifecycle and the verification lifecycle was incorrectly listed as "verify", when it should currently be "verifyConditions" in order to properly load.
2021-04-07 14:47:41 -07:00
Oscar DominguezandGitHub 5489489dda docs(github-actions): example suggests using ubuntu-latest (#1867)
instead of a concrete version of Ubuntu
2021-03-31 10:39:08 -07:00
Mohammed ragab aliandGitHub 12337175c6 docs: Add article to explain semantic release (#1856) 2021-03-30 10:32:07 -07:00
8fa4f9a750 chore(deps): lock file maintenance (#1861)
* chore(deps): lock file maintenance

* build(package): lock file

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2021-03-30 10:12:16 -07:00
f668935993 chore(deps): update dependency got to v11.8.2 (#1858)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-03-24 15:42:59 -07:00
0f3c4148c7 chore(deps): lock file maintenance (#1848)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-03-24 19:52:13 +00:00
Kristian HeljasandGitHub 2e4b901c4f docs: use consistent version number for tag recovery (#1857) 2021-03-24 09:19:06 -07:00
971a5e0d16 chore(deps): update dependency nock to v13.0.11 (#1844)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-03-11 15:09:05 -08:00
44b3344b91 fix(deps): update dependency hosted-git-info to v4 (#1838)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-03-11 12:01:43 -08:00
renovate[bot]andGitHub 046a845a9e chore(deps): lock file maintenance (#1835) 2021-03-08 10:23:39 -08:00
LukasandGitHub 48def0c4cc docs: fix links to gitlab releases documentation (#1834) 2021-03-07 12:44:08 -08:00
2b6c9ba093 docs: update documentation for addNote function (#1833)
Co-Authored-By: Virgil <virgilwashere@users.noreply.github.com>
2021-03-04 16:19:31 -08:00
thislooksfunandGitHub 07f12b9df2 fix(deps): peer dependecy error with marked-terminal (#1829) 2021-03-03 12:09:32 -08:00
2272ce3059 chore(deps): lock file maintenance (#1826)
* chore(deps): lock file maintenance

* build(package): lock file

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2021-03-01 10:07:51 -08:00
NickandGitHub 3ecc196d8a feat(config): Use cosmiconfig defaults to support .cjs config files (#1815)
This change adds support for `*.cjs` config files by removing the
explicit use of `searchPlaces` options and relying and the default
search places generated by `cosmicconfig`.  As per the docs for
[`cosmicconfig`][cc], the defaults include all the extensions/formats
previously supported by semantic-release in addition to the new .cjs
variants.

Resolves [#1814][issue].

[issue]: https://github.com/semantic-release/semantic-release/issues/1814
[cc]: https://github.com/davidtheclark/cosmiconfig#searchplaces
2021-02-25 21:04:04 -06:00
Micael JarniacandGitHub acf8bc4d21 docs: fix missing whitespace (#1821) 2021-02-24 16:20:35 -08:00
Kiran KoiralaandGitHub 67dfb676bc docs(README): change link to Angular's Contribution guidelines instead of Angular.js's Developers guide (#1817) 2021-02-23 10:42:30 -08:00
f25c3527df chore(deps): lock file maintenance (#1813)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-22 06:51:31 +00:00
6013a5633e chore(deps): update dependency p-retry to v4.4.0 (#1803)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-15 19:43:36 +00:00
d2782484c8 chore(deps): lock file maintenance (#1800)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-15 04:18:03 +00:00
34e5ddad12 chore(deps): update dependency js-yaml to v3.14.1 (#1797)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-13 10:17:05 +00:00
mcnesiumandGitHub a9be97b56b fix: update link to GitLab CI introduction (#1794) 2021-02-12 13:45:36 -08:00
Alexis SaettlerandGitHub 85bc213f04 docs: add semantic-release-github-pullrequest to list of community plugins (#1790) 2021-02-11 09:09:58 -08:00
Matt TraviandGitHub 8e3a6cbef2 docs(discussions): updated spectrum references to point to discussions instead (#1782)
for #1764
2021-02-09 08:22:21 -08:00
a2eaed086f fix(deps): update dependency marked to v2
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-08 07:45:59 -08:00
b22e591c96 chore(deps): lock file maintenance (#1779)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-08 02:04:34 +00:00
Matt TraviandGitHub fc32afa776 build(engine-strict): strictly enforced node version compatibility for dependencies (#1763) 2021-02-04 09:26:18 -08:00
8ea81ae6c9 chore(deps): update dependency delay to v5 (#1771)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-01 10:01:31 -08:00
e4002c8bd7 chore(deps): lock file maintenance (#1770)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-01 04:03:39 +00:00
d738dd0fb7 chore(deps): lock file maintenance (#1769)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-01 01:56:15 +00:00
4c7222d648 chore(deps): update dependency delay to v4.4.1 (#1768)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-30 10:47:21 +00:00
54bf761f7e chore(deps): update dependency nock to v13.0.7 (#1767)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-30 06:05:46 +00:00
ea5e813192 chore(deps): lock file maintenance (#1761)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-25 23:26:59 +00:00
Gregor Martynus a7f008253c style: prettier 2021-01-25 13:48:03 -08:00
Gregor Martynus ece7275743 test: bump retries in gitbox.createRepo() helper, see https://github.com/semantic-release/semantic-release/pull/1761\#issuecomment-767122221 2021-01-25 13:27:20 -08:00
16a9fb00e9 chore(deps): update dependency sinon to v9.2.4 (#1760)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-23 13:28:47 +00:00
854d643bed chore(deps): update dependency p-retry to v4.3.0 (#1759)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-23 11:16:17 +00:00
969e460948 fix(deps): update dependency get-stream to v6 (#1748)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-22 12:43:10 -08:00
ab227451fa docs: clarify renovate/** branches (#1757)
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2021-01-22 11:44:54 -08:00
0f37fc3aa6 chore(deps): update dependency tempy to v1 (#1755)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-21 08:33:15 +00:00
fa7080e0d4 chore(deps): update dependency nock to v13 (#1754)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-21 07:00:55 +00:00
0a86d61a00 chore(deps): update dependency sinon to v9.2.3 (#1750)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-21 04:05:41 +00:00
d53e2066cc chore(deps): update dependency got to v11.8.1 (#1749)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-21 03:06:00 +00:00
47450fb3b1 chore(deps): update dependency fs-extra to v9.1.0 (#1747)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 17:04:51 -08:00
fb5db83adc chore(deps): update dependency ava to v3.15.0 (#1746)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 17:04:39 -08:00
8262832cda fix(deps): update dependency execa to v5 (#1745)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 17:00:27 -08:00
cc74a4c890 fix(deps): update dependency cosmiconfig to v7 (#1742)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 16:59:32 -08:00
a3c49428e3 chore(deps): pin dependencies (#1744)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 15:42:59 -08:00
d95d80a0aa chore(deps): update actions/setup-node action to v2 (#1741)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-01-20 14:19:25 -08:00
b5aa853b22 ci: replace travis with actions, setup renovate (#1699)
Co-authored-by: Josh Soref <jsoref@users.noreply.github.com>
2021-01-20 14:07:16 -08:00
Josh SorefandGitHub 32a2c91d7c fix: pin initial branch to master (#1740) 2021-01-20 11:20:48 -08:00
Roland WeissandGitHub d2ec8931bc fix(deps): update to yargs 16.2.0 (#1739)
Signed-off-by: Roland Weiss <roland.weiss@mobi.ch>
2021-01-19 08:46:14 -08:00
Thomas ZimmermannandGitHub 9f57b84aaa fix: add logging for top-level errors (#1736) 2021-01-15 14:31:07 -08:00
Vegard ØklandandGitHub c5719101cc docs: clarify node vs npm in FAQ (#1731) thanks @vegardok 2021-01-15 11:50:33 -08:00
Mo SattlerandGitHub 7581aa8584 docs: fix a typo in github-actions.md (#1735) 2021-01-15 09:35:05 -06:00
Dominykas BlyžėandGitHub 21c151f167 fix: normalize ci: false into noCi: true after configs get merged (#1732) thanks @dominykas
This makes sure that options.ci is respected even when set inside a shareable config
2021-01-13 07:38:57 -08:00
Max DuvalandGitHub da75a9c60a docs: clean and strip SVG logo (#1723) thanks @mxdvl 2021-01-04 10:48:15 -08:00
cherryblossom000andGitHub e211499735 docs(plugins): document addChannel step (#1721)
Document the `addChannel` step defined in
https://github.com/semantic-release/semantic-release/blob/e35e5bb73f3628906553709ac0f5beb8f565c61a/lib/definitions/plugins.js#L83-L84,
which is used in @semantic-release/npm.
2020-12-29 12:33:12 -08:00
Ryan LingandGitHub 52238cbccc fix(deps): Require find-versions ^4.0.0 (#1722)
This new version includes a fix for a ReDoS vulnerability in
`semver-regex` that is flagged by some source composition analysis tools
like Snyk:

https://snyk.io/vuln/SNYK-JS-SEMVERREGEX-1047770

It's a major version because it drops support for Node.js 6.x. This
doesn't affect us since we already require Node.js >=10.18.
2020-12-29 12:15:11 -08:00
Jonas BromsandGitHub af596a9443 docs: semantic-release SVG logo (#1715) thanks @bromso
I recreated the logo and used this image as a reference: https://gblobscdn.gitbook.com/spaces%2F-LGsE7zdvzHI5cG-XV6p%2Favatar.png
2020-12-16 14:00:30 -08:00
Mihai PlasoianuandGitHub 6c7e4be68a docs: add semantic-release-helm plugin (#1713) 2020-12-09 09:22:32 -08:00
abichingerandGitHub c177d4b15a docs: add semantic-release-pypi plugin (#1707) 2020-12-02 09:43:00 -08:00
Geoffroy EmpainandGitHub eb708236df docs: add semantic-release-license-plugin (#1701)
This adds a semantic-release-plugin which we use to update our license files (copyright dates and more) based on their type.
2020-11-25 09:25:48 -08:00
AlmenonandGitHub 885d87ac0f feat(docs): note that publish token is required (#1700)
see https://github.com/semantic-release/npm/issues/277
2020-11-22 10:38:15 -08:00
Dmitriy DekhanovandGitHub f8f8fbcac4 fix: escape uri encoded symbols (#1697) 2020-11-19 11:55:09 -08:00
Matt TraviandGitHub c8d38b6258 style: removed line breaks to align with xo rule (#1689) 2020-11-16 11:32:04 -08:00
Matt TraviandMatt Travi ca90b34c4a fix: mask secrets when characters get uri encoded 2020-11-16 10:36:40 -06:00
Amanda MitchellandGitHub 63fa143023 docs(plugins): add listing for new plugin (#1686) 2020-11-12 14:16:15 -08:00
Arthur ChaloinandGitHub 2bf377194e fix: use valid git credentials when multiple are provided (#1669) 2020-10-29 11:09:19 -07:00
Arthur ChaloinandGitHub 77a75f072b fix: don't parse port as part of the path in repository URLs (#1671) 2020-10-28 15:53:03 -07:00
Guillaume GautreauandGitHub d74ffef788 docs: add npm-deprecate-old-versions in plugins list (#1667) 2020-10-23 14:26:20 -07:00
Matt Travi 3abcbaf256 Revert "feat: throw an Error if package.json has duplicate "repository" key (#1656)"
This reverts commit b8fb35c7e1.

fixes #1657
2020-10-12 08:21:24 -05:00
Gregor MartynusandGitHub b8fb35c7e1 feat: throw an Error if package.json has duplicate "repository" key (#1656) 2020-10-11 14:59:51 -07:00
Graeme CasselsandGitHub 18e35b28ad docs: reorder default plugins list (#1650)
Reorder the list default plugins from alphabetical to order of execution.
2020-10-04 11:52:17 -07:00
Micael JarniacandGitHub e35e5bb73f docs(contributing): fix commit message examples (#1648)
It had the backticks (`\``) around the messages, but I think they were unintentional, probably a leftover from copy-pasting.
2020-10-02 15:12:20 -07:00
Gregor MartynusandGitHub 311c46580f docs(README): welcome @travi, add alumni section 2020-09-25 15:31:11 -07:00
b4c5d0a436 fix: add logging for when ssh falls back to http (#1639)
Co-authored-by: thomas williams <thomas.williams@thetradedesk.com>
2020-09-17 10:15:04 -07:00
Micael JarniacandGitHub c982249f87 docs(contributing): typo fix (#1638) 2020-09-16 11:39:04 -07:00
Joep KockelkornandGitHub 9635f50da6 docs: improve github actions recipe on git plugin (#1626) 2020-09-06 10:38:29 -07:00
ylemkimonandGitHub d036a899d5 ci(docs): use actions/checkout@v2 (#1620) 2020-08-17 16:26:13 -07:00
AbdelRahman WahdanandGitHub 9303d1dba0 docs(resources.md): added more sematnic release article (#1610) 2020-07-30 17:17:36 -07:00
kopalandGitHub b72cdb331b docs(configuration.md): Updated documentation for dry-run feature of semantic Release (#1607) 2020-07-29 15:25:32 -07:00
Ahmed HassaneinandGitHub ee44ee80c8 docs(github-actions): suggest action_dispatch as trigger (#1605) 2020-07-28 06:57:05 -07:00
Rylan CollinsandGitHub b24d2474b1 docs: add semantic-release-rubygem to community plugins (#1602) 2020-07-22 16:35:05 -07:00
Gavin StaniforthandGitHub 6d118c6c14 docs: be clear about what module of semantic-release handles updating the package.json (#1601) 2020-07-12 12:31:40 -07:00
Shun KakinokiandMatt Travi b5c9dea215 docs: update github documentation to docs.github.com 2020-07-09 22:44:15 -05:00
kopalandGitHub 1405b94296 docs: added recipe for Jenkins CI configuration (#1) (#1591) 2020-06-30 14:54:33 -07:00
Emmanuel OgbiziandGitHub 0f0c650b41 fix: use correct ci branch context (#1521) 2020-06-25 09:30:12 -07:00
Sven LiebigandGitHub a4658016d9 feat(bitbucket-basic-auth): support for bitbucket server basic auth (#1578) 2020-06-22 11:26:24 -07:00
Daniel WirtzandGitHub 6d48663a77 build(gitattributes): eol=lf for Windows users (#1575) 2020-06-15 09:16:33 -07:00
Nicholas ShineandGitHub eed1d3c8cb fix: prevent false positive secret replacement for Golang projects (#1562) 2020-05-24 11:53:00 -07:00
Daniel TschinderandGitHub 5f3a8bb14e docs: Recommend using npx instead of installing globally (#1563) 2020-05-24 10:20:58 -07:00
Lucas ShadlerandGitHub 0ef52e7a5f docs: adjust minor typos (#1554) 2020-05-19 14:15:26 -07:00
Aaron BatiloandGitHub a62bc7cd85 docs(GitHub Actions): Add alternative trigger options (#1471)
These options work the same as the curl command, but use a GitHub App so there's no need to provision and manage a Personal Access Token in order to trigger the Semantic Release action.
2020-05-06 11:50:31 -07:00
solidnerdandGregor Martynus 22321cff1c docs: update recipe for gitlab-ci with Node.js >=10.18
Signed-off-by: solidnerd <niclas@mietz.io>
2020-04-22 15:15:05 -07:00
greenkeeper[bot]andGitHub d64db31e76 fix(package): update marked to version 1.0.0 (#1534) 2020-04-21 13:04:21 -07:00
greenkeeper[bot]andGitHub 2322a7028f build(deps): update got to version 11.0.0 (#1533) 2020-04-20 10:17:39 -07:00
Gregor MartynusandGitHub 431d571a7b fix: adapt for semver to version 7.3.2 (part II) (#1530) 2020-04-16 14:42:25 -07:00
Gregor Martynus 0363790b8a fix: adapt for semver to version 7.3.2
see https://github.com/npm/node-semver/issues/322
2020-04-16 13:54:47 -07:00
greenkeeper[bot]andGitHub 93cb76653d chore(package): update semver to version 7.3.2 2020-04-14 17:46:03 +00:00
Gregor Martynus caa3526caa style: xo 2020-04-08 15:13:33 -07:00
greenkeeper[bot]andGitHub ed5f26d10b chore(package): update xo to version 0.29.0 2020-04-08 21:14:42 +00:00
Justin PoehneltandGitHub ff1d53df58 docs: add @google/semantic-release-replace-plugin to plugin list (#1509) 2020-04-02 16:23:31 -07:00
greenkeeper[bot]andPierre Vanduynslager f899fa9a7c chore(package): update xo to version 0.28.1
Closes #1488
2020-03-26 23:55:26 -04:00
greenkeeper[bot]andGregor Martynus b5488ae7a5 chore(package): update fs-extra to version 9.0.0 2020-03-19 11:31:14 -07:00
greenkeeper[bot]andPierre Vanduynslager da5d1cf9c6 chore(package): update tempy to version 0.5.0 2020-03-09 11:22:55 -04:00
Gaurav NelsonandGitHub da5d06cf78 docs: fix formatting for GitBook 2020-02-27 02:52:32 -05:00
greenkeeper[bot]andPierre Vanduynslager b85adbdb8f chore(package): update xo to version 0.27.0 2020-02-25 17:55:12 -05:00
greenkeeper[bot]andPierre Vanduynslager bce633a41e chore(package): update sinon to version 9.0.0 2020-02-19 18:08:22 -05:00
Pierre Vanduynslager 55be0ba2b1 fix: add repositoryUrl in logs 2020-02-17 14:09:01 -05:00
Raphaël HuchetandGitHub ec516a34bf docs(configuration): fix CLI usage with --branches option (#1465) 2020-02-17 10:05:33 -08:00
greenkeeper[bot]andGregor Martynus 5f89e6bfc0 chore(package): update nock to version 12.0.0 2020-02-16 13:54:44 -08:00
greenkeeper[bot]andPierre Vanduynslager 195e5b18c7 chore(package): update xo to version 0.26.0 2020-02-12 20:09:31 -05:00
Pierre Vanduynslager e7bede1866 fix: pass a branch name to getGitAuthUrl 2020-02-12 19:56:12 -05:00
greenkeeper[bot]andPierre Vanduynslager 8426b4203e chore(package): update tempy to version 0.4.0 2020-02-12 16:09:26 -05:00
Gregor MartynusandGitHub 804fc2a943 docs(Troubleshooting): release not found in prereleases branch (e.g. beta) after rebase on master) (#1444) 2020-02-08 11:03:48 -08:00
greenkeeper[bot]andPierre Vanduynslager 389e3316e9 chore(package): update got to version 10.5.2 2020-02-07 19:00:38 -05:00
Pierre Vanduynslager a93c96fec9 revert: fix: allow plugins to set environment variables to be used by other plugins
This reverts commit 68f7e928f9.
2020-02-07 17:50:14 -05:00
Pierre VanduynslagerandGregor Martynus 68f7e928f9 fix: allow plugins to set environment variables to be used by other plugins 2020-02-07 13:51:20 -08:00
Gregor MartynusandGitHub 8ce2d6e834 fix(package): update marked-terminal to version 4.0.0 2020-01-31 11:14:54 -05:00
Pierre Vanduynslager 45695b9183 fix(package): update @semantic-release/commit-analyzer to version 8.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager 3c7b114eed fix(package): update @semantic-release/release-notes-generator to version 9.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager f2b5826c0c fix(package): update @semantic-release/npm to version 7.0.0 2020-01-28 11:31:01 -05:00
greenkeeper[bot]andPierre Vanduynslager c48bd3ac36 fix(package): update @semantic-release/github to version 7.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager bc97537c97 test: fix copy/paste typo in test titles 2020-01-28 10:33:24 -05:00
Mario LubenkaandPierre Vanduynslager c7e461dc1c docs: add @saithodev/semantic-release-gitea to community plugins 2020-01-27 14:23:38 -05:00
greenkeeper[bot]andPierre Vanduynslager ef1b8a0b91 chore(package): update ava to version 3.1.0 2020-01-27 13:52:59 -05:00
Pierre Vanduynslager bec57cd2ed chore: require Node.js >=10.18
BREAKING CHANGE: Require Node.js >= 10.18
2020-01-27 13:52:59 -05:00
Jozef CipaandPierre Vanduynslager c6b10766a7 fix: correct error when remote repository has no branches 2020-01-27 11:59:38 -05:00
Pierre Vanduynslager b54b20d412 fix: use --no-verify when testing the Git permissions 2020-01-22 13:08:59 -05:00
DanielHabenichtandGregor Martynus 88fe81915b docs: fix typo
just a little type fist -> first
2020-01-21 15:34:01 -08:00
Michaël De BoeyandGregor Martynus 559c152647 docs(README): update minimal required Node version / FAQ link (#1422) 2020-01-20 12:06:39 -08:00
Prokop SimekandGregor Martynus 31e78767d9 docs(travis): build on all branches by default
If you want to have more prereleases than default so you have to enable deploy on all branches. ref.: https://docs.travis-ci.com/user/deployment#examples-of-conditional-deployment
2020-01-20 08:40:16 -08:00
Daniel BasilioandGregor Martynus 8c0490d057 docs: replacing firefox plugin in the list (#1416) 2020-01-15 20:02:00 -08:00
105 changed files with 19428 additions and 1307 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+23
View File
@@ -0,0 +1,23 @@
name: Release
on:
push:
branches:
- master
- next
- beta
- "*.x" # maintenance releases branches
jobs:
release:
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
cache: npm
- run: npm ci
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }}
+50
View File
@@ -0,0 +1,50 @@
name: Test
on:
push:
branches:
- master
# renovate/** branches are generated by https://github.com/apps/renovate
- renovate/**
pull_request:
types:
- opened
- synchronize
jobs:
test_matrix:
strategy:
matrix:
node-version:
- 14.17
- 16
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: git config --global user.name github-actions
- run: git config --global user.email github-actions@github.com
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Ensure dependencies are compatible with the version of node
run: echo 'engine-strict=true' >> .npmrc
- run: npm ci
- run: npm run test:ci
# separate job to set as required in branch protection,
# as the build names above change each time Node versions change
test:
runs-on: ubuntu-latest
needs: test_matrix
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
cache: npm
- run: npm ci
- run: npm run lint
-4
View File
@@ -125,10 +125,6 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
# Lockfiles
package-lock.json
yarn.lock
# Gitbook
_book
-1
View File
@@ -1 +0,0 @@
package-lock=false
-11
View File
@@ -1,11 +0,0 @@
version: ~> 1.0
services:
- docker
import:
- .travis/node.yml
- .travis/node-versions.yml
- .travis/semantic-release.yml
- .travis/greenkeeper.yml
- .travis/codecov.yml
-2
View File
@@ -1,2 +0,0 @@
after_success:
- npm run codecov
-3
View File
@@ -1,3 +0,0 @@
branches:
only:
- /^greenkeeper.*$/
-3
View File
@@ -1,3 +0,0 @@
node_js:
- 12
- 10.13
-11
View File
@@ -1,11 +0,0 @@
language: node_js
cache:
npm: false
# Retry install on fail to avoid failing a build on network/disk/external errors
install:
- travis_retry npm install
script:
- npm run test
-15
View File
@@ -1,15 +0,0 @@
branches:
only:
- master
- next
- beta
- /^\d+\.(\d+|x)(\.x)?$/
jobs:
include:
- stage: release
node_js: lts/*
install:
- travis_retry npm install
script:
- npm run semantic-release
-1
View File
@@ -1 +0,0 @@
--install.no-lockfile true
+5 -5
View File
@@ -107,11 +107,11 @@ To ensure consistency and quality, all documentation modifications must:
- a third-party product/brand/service, i.e. Integrate with [GitHub](https://github.com)
- an external concept or feature, i.e. Create a [GitHub release](https://help.github.com/articles/creating-releases)
- a package or module, i.e. The [`@semantic-release/github`](https://github.com/semantic-release/github) module
- Use the the [single backtick `code` quoting](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) for:
- Use the [single backtick `code` quoting](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) for:
- commands inside sentences, i.e. the `semantic-release` command
- programming language keywords, i.e. `function`, `async`, `String`
- packages or modules, i.e. The [`@semantic-release/github`](https://github.com/semantic-release/github) module
- Use the the [triple backtick `code` formatting](https://help.github.com/articles/creating-and-highlighting-code-blocks) for:
- Use the [triple backtick `code` formatting](https://help.github.com/articles/creating-and-highlighting-code-blocks) for:
- code examples
- configuration examples
- sequence of command lines
@@ -183,17 +183,17 @@ The footer should contain any information about **Breaking Changes** and is also
#### Examples
```commit
`fix(pencil): stop graphite breaking when too much pressure applied`
fix(pencil): stop graphite breaking when too much pressure applied
```
```commit
`feat(pencil): add 'graphiteWidth' option`
feat(pencil): add 'graphiteWidth' option
Fix #42
```
```commit
perf(pencil): remove graphiteWidth option`
perf(pencil): remove graphiteWidth option
BREAKING CHANGE: The graphiteWidth option has been removed.
+21 -19
View File
@@ -1,17 +1,11 @@
<h1 align="center" style="border-bottom: none;">📦🚀 semantic-release</h1>
<h3 align="center">Fully automated version management and package publishing</h3>
<p align="center">
<a href="https://spectrum.chat/semantic-release">
<img alt="Join the community on Spectrum" src="https://withspectrum.github.io/badge/badge.svg">
<a href="https://github.com/semantic-release/semantic-release/discussions">
<img alt="Join the community on GitHub Discussions" src="https://img.shields.io/badge/Join%20the%20community-on%20GitHub%20Discussions-blue">
</a>
<a href="https://travis-ci.org/semantic-release/semantic-release">
<img alt="Travis" src="https://img.shields.io/travis/semantic-release/semantic-release/master.svg">
</a>
<a href="https://codecov.io/gh/semantic-release/semantic-release">
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/semantic-release/semantic-release/master.svg">
</a>
<a href="https://greenkeeper.io">
<img alt="Greenkeeper" src="https://badges.greenkeeper.io/semantic-release/semantic-release.svg">
<a href="https://github.com/semantic-release/semantic-release/actions?query=workflow%3ATest+branch%3Amaster">
<img alt="Build states" src="https://github.com/semantic-release/semantic-release/workflows/Test/badge.svg">
</a>
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
@@ -54,14 +48,14 @@ This removes the immediate connection between human emotions and version numbers
**semantic-release** uses the commit messages to determine the type of changes in the codebase. Following formalized conventions for commit messages, **semantic-release** automatically determines the next [semantic version](https://semver.org) number, generates a changelog and publishes the release.
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.
By default **semantic-release** uses [Angular Commit Message Conventions](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format). 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) 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:
The table below shows which commit message gets you which release type when `semantic-release` runs (using the default configuration):
| Commit message | Release type |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- |
| `fix(pencil): stop graphite breaking when too much pressure applied` | Patch Release |
| `feat(pencil): add 'graphiteWidth' option` | ~~Minor~~ Feature Release |
| `perf(pencil): remove graphiteWidth option`<br><br>`BREAKING CHANGE: The graphiteWidth option has been removed.`<br>`The default graphite width of 10mm is always used for performance reasons.` | ~~Major~~ Breaking Release |
@@ -75,6 +69,7 @@ Here is an example of the release type that will be done based on a commit messa
For each new commits added to one of the release branches (for example `master`, `next`, `beta`), with `git push` or by merging a pull request or merging from another branch, a CI build is triggered and runs the `semantic-release` command to make a release if there are codebase changes since the last release that affect the package functionalities.
**semantic-release** offers various ways to control the timing, the content and the audience of published releases. See example workflows in the following recipes:
- [Using distribution channels](docs/recipes/distribution-channels.md#publishing-on-distribution-channels)
- [Maintenance releases](docs/recipes/maintenance-releases.md#publishing-maintenance-releases)
- [Pre-releases](docs/recipes/pre-releases.md#publishing-pre-releases)
@@ -84,7 +79,7 @@ For each new commits added to one of the release branches (for example `master`,
After running the tests, the command `semantic-release` will execute the following steps:
| Step | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Verify Conditions | Verify all the conditions to proceed with the release. |
| Get last release | Obtain the commit corresponding to the last release by analyzing [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). |
| Analyze commits | Determine the type of release based on the commits added since the last release. |
@@ -98,10 +93,11 @@ After running the tests, the command `semantic-release` will execute the followi
## 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
- [Node.js](https://nodejs.org) version [14.17 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--1417) installed in your Continuous Integration environment
## Documentation
@@ -135,7 +131,7 @@ In order to use **semantic-release** you need:
## Get help
- [Stack Overflow](https://stackoverflow.com/questions/tagged/semantic-release)
- [Spectrum community](https://spectrum.chat/semantic-release)
- [GitHub Discussions](https://github.com/semantic-release/semantic-release/discussions)
- [Twitter](https://twitter.com/SemanticRelease)
## Badge
@@ -150,9 +146,15 @@ Let people know that your package is published using **semantic-release** by inc
## Team
| [![Stephan Bönnemann](https://github.com/boennemann.png?size=100)](https://github.com/boennemann) | [![Rolf Erik Lekang](https://github.com/relekang.png?size=100)](https://github.com/relekang) | [![Johannes Jörg Schmidt](https://github.com/jo.png?size=100)](https://github.com/jo) | [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | [![Pierre Vanduynslager](https://github.com/finnp.png?size=100)](https://github.com/finnp) | [![Pierre Vanduynslager](https://github.com/pvdlg.png?size=100)](https://github.com/pvdlg) | [![Christoph Witzko](https://github.com/christophwitzko.png?size=100)](https://github.com/christophwitzko) |
|---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| [Stephan Bönnemann](https://github.com/boennemann) | [Rolf Erik Lekang](https://github.com/relekang) | [Johannes Jörg Schmidt](https://github.com/jo) | [Gregor Martynus](https://github.com/gr2m) | [Finn Pauls](https://github.com/finnp) | [Pierre Vanduynslager](https://github.com/pvdlg) | [Christoph Witzko](https://github.com/christophwitzko) |
| [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | [![Pierre Vanduynslager](https://github.com/pvdlg.png?size=100)](https://github.com/pvdlg) | [![Matt Travi](https://github.com/travi.png?size=100)](https://github.com/travi) |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| [Gregor Martynus](https://github.com/gr2m) | [Pierre Vanduynslager](https://github.com/pvdlg) | [Matt Travi](https://github.com/travi) |
## Alumni
| [![Stephan Bönnemann](https://github.com/boennemann.png?size=100)](https://github.com/boennemann) | [![Rolf Erik Lekang](https://github.com/relekang.png?size=100)](https://github.com/relekang) | [![Johannes Jörg Schmidt](https://github.com/jo.png?size=100)](https://github.com/jo) | [![Finn Pauls](https://github.com/finnp.png?size=100)](https://github.com/finnp) | [![Christoph Witzko](https://github.com/christophwitzko.png?size=100)](https://github.com/christophwitzko) |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [Stephan Bönnemann](https://github.com/boennemann) | [Rolf Erik Lekang](https://github.com/relekang) | [Johannes Jörg Schmidt](https://github.com/jo) | [Finn Pauls](https://github.com/finnp) | [Christoph Witzko](https://github.com/christophwitzko) |
<p align="center">
<img alt="Kill all humans" src="media/bender.png">
+11 -15
View File
@@ -1,16 +1,11 @@
#!/usr/bin/env node
#!/usr/bin/env node// Bad news: We have to write plain ES5 in this file
import semver from 'semver';
// Bad news: We have to write plain ES5 in this file
// Good news: It's the only file of the entire project
import execa from 'execa';
import findVersions from 'find-versions';
import pkg from '../package.json';
/* eslint-disable no-var */
var semver = require('semver');
var execa = require('execa');
var findVersions = require('find-versions');
var pkg = require('../package.json');
var MIN_GIT_VERSION = '2.7.1';
const MIN_GIT_VERSION = '2.7.1';
if (!semver.satisfies(process.version, pkg.engines.node)) {
console.error(
@@ -23,13 +18,13 @@ See https://github.com/semantic-release/semantic-release/blob/master/docs/suppor
execa('git', ['--version'])
.then(({stdout}) => {
var gitVersion = findVersions(stdout)[0];
const gitVersion = findVersions(stdout)[0];
if (semver.lt(gitVersion, MIN_GIT_VERSION)) {
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. Found ${gitVersion}.`);
process.exit(1);
}
})
.catch(error => {
.catch((error) => {
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. No git binary found.`);
console.error(error);
process.exit(1);
@@ -37,9 +32,10 @@ execa('git', ['--version'])
// Node 10+ from this point on
require('../cli')()
.then(exitCode => {
.then((exitCode) => {
process.exitCode = exitCode;
})
.catch(() => {
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
+16 -13
View File
@@ -1,19 +1,22 @@
const {argv, env, stderr} = require('process'); // eslint-disable-line node/prefer-global/process
const util = require('util');
const hideSensitive = require('./lib/hide-sensitive');
import util from 'util';
import hideSensitive from './lib/hide-sensitive';
import yargs from 'yargs';
import debug from 'debug';
import run from '.';
const stringList = {
type: 'string',
array: true,
coerce: values =>
coerce: (values) =>
values.length === 1 && values[0].trim() === 'false'
? []
: values.reduce((values, value) => values.concat(value.split(',').map(value => value.trim())), []),
: values.reduce((values, value) => values.concat(value.split(',').map((value) => value.trim())), []),
};
module.exports = async () => {
const cli = require('yargs')
.command('$0', 'Run automated package publishing', yargs => {
export default async () => {
const cli = yargs
.command('$0', 'Run automated package publishing', (yargs) => {
yargs.demandCommand(0, 0).usage(`Run automated package publishing
Usage:
@@ -41,22 +44,22 @@ Usage:
.exitProcess(false);
try {
const {help, version, ...opts} = cli.parse(argv.slice(2));
const {help, version, ...options} = cli.parse(process.argv.slice(2));
if (Boolean(help) || Boolean(version)) {
return 0;
}
if (opts.debug) {
if (options.debug) {
// Debug must be enabled before other requires in order to work
require('debug').enable('semantic-release:*');
debug.enable('semantic-release:*');
}
await require('.')(opts);
await run(options);
return 0;
} catch (error) {
if (error.name !== 'YError') {
stderr.write(hideSensitive(env)(util.inspect(error, {colors: true})));
process.stderr.write(hideSensitive(process.env)(util.inspect(error, {colors: true})));
}
return 1;
+1 -1
View File
@@ -1,7 +1,7 @@
# semantic-release documentation
- [Usage](usage/README.md) - **semantic-release** installation and configuration
- [Extending](extending/README.md)- Extending **semantic-release** with plugins and shareable configurations
- [Extending](extending/README.md) - Extending **semantic-release** with plugins and shareable configurations
- [Recipes](recipes/README.md) - Community written recipes for common **semantic-release** use-cases
- [Developer Guide](developer-guide/README.md) - The essentials of writing a **semantic-release** plugin or shareable configurations
- [Support](support/README.md) - FAQ and troubleshooting
+166 -6
View File
@@ -1,14 +1,18 @@
# 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:
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 `verifyConditions` 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`
- `verifyConditions`
- `analyzeCommits`
- `verifyRelease`
- `generateNotes`
- `addChannel`
- `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.
`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 `verifyConditions` and `success` step, the `main` file for your object would need to `export` an object with `verifyConditions` and `success` functions.
In addition to the lifecycle methods, each lifecyle is passed two objects:
@@ -93,7 +97,136 @@ if (message.length) {
}
```
## Supporting Environment Variables
## Context
### Common context keys
* `stdout`
* `stderr`
* `logger`
### Context object keys by lifecycle
#### verifyConditions
Initially the context object contains the following keys (`verifyConditions` lifecycle):
* `cwd`
* Current working directory
* `env`
* Environment variables
* `envCi`
* Information about CI environment
* Contains (at least) the following keys:
* `isCi`
* Boolean, true if the environment is a CI environment
* `commit`
* Commit hash
* `branch`
* Current branch
* `options`
* Options passed to `semantic-release` via CLI, configuration files etc.
* `branch`
* Information on the current branch
* Object keys:
* `channel`
* `tags`
* `type`
* `name`
* `range`
* `accept`
* `main`
* `branches`
* Information on branches
* List of branch objects (see above)
#### analyzeCommits
Compared to the verifyConditions, `analyzeCommits` lifecycle context has keys
* `commits` (List)
* List of commits taken into account when determining the new version.
* Keys:
* `commit` (Object)
* Keys:
* `long` (String, Commit hash)
* `short` (String, Commit hash)
* `tree` (Object)
* Keys:
* `long` (String, Commit hash)
* `short` (String, Commit hash)
* `author` (Object)
* Keys:
* `name` (String)
* `email` (String)
* `date` (String, ISO 8601 timestamp)
* `committer` (Object)
* Keys:
* `name` (String)
* `email` (String)
* `date` (String, ISO 8601 timestamp)
* `subject` (String, Commit message subject)
* `body` (String, Commit message body)
* `hash` (String, Commit hash)
* `committerDate` (String, ISO 8601 timestamp)
* `message` (String)
* `gitTags` (String, List of git tags)
* `releases` (List)
* `lastRelease` (Object)
* Keys
* `version` (String)
* `gitTag` (String)
* `channels` (List)
* `gitHead` (String, Commit hash)
* `name` (String)
#### verifyRelease
Additional keys:
* `nextRelease` (Object)
* `type` (String)
* `channel` (String)
* `gitHead` (String, Git hash)
* `version` (String, version without `v`)
* `gitTag` (String, version with `v`)
* `name` (String)
#### generateNotes
No new content in the context.
#### addChannel
*This is run only if there are releases that have been merged from a higher branch but not added on the channel of the current branch.*
Context content is similar to lifecycle `verifyRelease`.
#### prepare
Only change is that `generateNotes` has populated `nextRelease.notes`.
#### publish
No new content in the context.
#### success
Lifecycles `success` and `fail` are mutually exclusive, only one of them will be run.
Additional keys:
* `releases`
* Populated by `publish` lifecycle
#### fail
Lifecycles `success` and `fail` are mutually exclusive, only one of them will be run.
Additional keys:
* `errors`
### 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:
@@ -101,6 +234,33 @@ Similar to `options`, environment variables exist to allow users to pass tokens
const { env } = context;
if (env.GITHUB_TOKEN) {
//...
//...
}
```
```
## Execution order
For the lifecycles, the list at the top of the readme contains the order. If there are multiple plugins for the same lifecycle, then the order of the plugins determines the order in which they are executed.
## Handling errors
In order to be able to detect and handle errors properly, the errors thrown from the must be of type [SemanticReleaseError](https://github.com/semantic-release/error) or extend it as described in the package readme. This way the errors are handled properly and plugins using the `fail` lifecycle receive the errors correctly. For any other types of errors the internal error handling does nothing, lets them through up until the final catch and does not call any `fail` plugins.
## Advanced
Knowledge that might be useful for plugin developers.
### Multiple analyzeCommits plugins
While it may be trivial that multiple analyzeCommits (or any lifecycle plugins) can be defined, it is not that self-evident that the plugins executed AFTER the first one (for example, the default one: `commit-analyzer`) can change the result. This way it is possible to create more advanced rules or situations, e.g. if none of the commits would result in new release, then a default can be defined.
The commit must be a known release type, for example the commit-analyzer has the following default types:
* major
* premajor
* minor
* preminor
* patch
* prepatch
* prerelease
If the analyzeCommits-lifecycle plugin does not return anything, then the earlier result is used, but if it returns a supported string value, then that overrides the previous result.
+50 -5
View File
@@ -20,7 +20,7 @@
- `publish`: Publish the package on the npm registry
- [@semantic-release/gitlab](https://github.com/semantic-release/gitlab)
- `verifyConditions`: Verify the presence and the validity of the GitLab authentication and release configuration
- `publish`: Publish a [GitLab release](https://docs.gitlab.com/ce/workflow/releases.html)
- `publish`: Publish a [GitLab release](https://docs.gitlab.com/ee/user/project/releases/)
- [@semantic-release/git](https://github.com/semantic-release/git)
- `verifyConditions`: Verify the presence and the validity of the Git authentication and release configuration
- `prepare`: Push a release commit and tag, including configurable files
@@ -52,6 +52,9 @@
- [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
- [@semantic-release-plus/docker](https://github.com/semantic-release-plus/semantic-release-plus/tree/master/packages/plugins/docker)
- `verifyConditions`: Verify that all needed configuration is present and login to the configured docker registry.
- `publish`: Tag the image specified by `name` with the new version, push it to the configured docker registry and update the `latest`, `major`, `minor` tags based on the configuration settings.
- [semantic-release-gcr](https://github.com/carlos-cubas/semantic-release-gcr)
- `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
@@ -65,10 +68,10 @@
- `verifyConditions`: Verify the presence of the authentication (set via environment variables)
- `prepare`: Write the correct version to the `manifest.json` and creates a zip file of the whole dist folder
- `publish`: Uploads the generated zip file to the webstore, and publish the item
- [semantic-release-firefox](https://github.com/felixfbecker/semantic-release-firefox)
- `verifyConditions`: Verify the presence of the authentication (set via environment variables)
- `prepare`: Write the correct version to the `manifest.json`, creates a `xpi` file of the dist folder and a zip of the sources
- `publish`: Submit the generated archives to the webstore for review, and publish the item including release notes
- [semantic-release-firefox-add-on](https://github.com/tophat/semantic-release-firefox-add-on)
- `verifyConditions`: Verify that all required options are present and authentication is set via environment variables
- `prepare`: Write the correct version to the `manifest.json`
- `publish`: Creates an unsigned `.xpi` file, and submits it to the Mozilla Add On store for signing. Once the package is signed, downloads the signed `.xpi` to a local directory
- [semantic-release-gerrit](https://github.com/pascalMN/semantic-release-gerrit)
- `generateNotes`: Generate release notes with Gerrit reviews URL
- [semantic-release-expo](https://github.com/bycedric/semantic-release-expo)
@@ -91,7 +94,49 @@
- [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.
- [semantic-release-github-pullrequest](https://github.com/asbiin/semantic-release-github-pullrequest)
- `verifyConditions`: Verify the presence and the validity of the GitHub authentication and other configuration.
- `publish`: Create a branch to upload all assets and create the pull request on the base branch on GitHub.
- [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).
- [@saithodev/semantic-release-gitea](https://github.com/saitho/semantic-release-gitea)
- `verifyConditions`: Verify the presence and the validity of the authentication and the assets option configuration.
- `publish`: Publish a Gitea release, optionally uploading file assets.
- `addChannel`: Update a Gitea release's pre-release field.
- [@google/semantic-release-replace-plugin](https://github.com/google/semantic-release-replace-plugin)
- `prepare`: Replace version strings in files using regex and glob.
- [semantic-release-rubygem](https://github.com/Gusto/semantic-release-rubygem)
- `verifyConditions`: Locate and validate a `.gemspec` file, locate and validate a `lib/**/version.rb` file, verify the presence of the `GEM_HOST_API_KEY` environment variable, and create a credentials file with the API key.
- `prepare`: Update the version in the `lib/**/version.rb` version file and [build](https://guides.rubygems.org/command-reference/#gem-build) the gem.
- `publish`: [Push the Ruby gem](https://guides.rubygems.org/command-reference/#gem-push) to the gem server.
- [semantic-release-npm-deprecate-old-versions](https://github.com/ghusse/semantic-release-npm-deprecate-old-versions)
- `verifyConditions`: Validates configuration.
- `publish`: Deprecates old versions, based on the declaration of supported versions in the config.
- [amanda-mitchell/semantic-release-npm-multiple](https://github.com/amanda-mitchell/semantic-release-npm-multiple)
- **Note**: this is a thin wrapper around the built-in npm plugin that can target multiple registries
- `verifyConditions`: Verify the presence and the validity of the npm authentication and release configuration for multiple registries
- `prepare`: Update the package.json version and create the npm package tarball
- `publish`: Publish the package on the npm registry for multiple registries
- [semantic-release-license](https://github.com/cbhq/semantic-release-license) Automatically update dates and more in your license file for new releases.
- `verifyConditions`: Verify the presence of a license file
- `prepare`: Update the license file based on its type
- [semantic-release-pypi](https://github.com/abichinger/semantic-release-pypi)
- `verifyConditions`: Verify the environment variable ```PYPI_TOKEN``` and installation of build tools
- `prepare`: Update the version in ```setup.cfg``` and create the distribution packages
- `publish`: Publish the python package to a repository (default: pypi)
- [semantic-release-helm](https://github.com/m1pl/semantic-release-helm)
- `verifyConditions`: Validate configuration and (if present) credentials
- `prepare`: Update version and appVersion in ```Chart.yaml```
- `publish`: Publish the chart to a registry (if configured)
- [semantic-release-codeartifact](https://github.com/ryansonshine/semantic-release-codeartifact)
- `verifyConditions`: Validate configuration, get AWS CodeArtifact authentication and repository, validate `publishConfig` or `.npmrc` (if they exist), then pass the configuration to the associated plugins.
- [semantic-release-telegram](https://github.com/pustovitDmytro/semantic-release-telegram)
- `verifyConditions`: Validate configuration and verify ```TELEGRAM_BOT_ID``` and ```TELEGRAM_BOT_TOKEN```
- `success`: Publish a message about the successful release to a telegram chat
- `fail`: publish a message about failure to a telegram chat
- [semantic-release-heroku](https://github.com/pustovitDmytro/semantic-release-heroku)
- `verifyConditions`: Validate configuration and verify ```HEROKU_API_KEY```
- `prepare`: Update the package.json version and create release tarball
- `publish`: Publish version to heroku
+1
View File
@@ -5,6 +5,7 @@
- [Travis CI](travis.md)
- [GitLab CI](gitlab-ci.md)
- [GitHub Actions](github-actions.md)
- [Jenkins CI](jenkins-ci.md)
## Git hosted services
- [Git authentication with SSH keys](git-auth-ssh-keys.md)
+17 -24
View File
@@ -10,46 +10,39 @@ Alternatively, the default `NPM_TOKEN` and `GH_TOKEN` can be easily [setup with
### `.circleci/config.yml` configuration for multiple Node jobs
This example is a minimal configuration for **semantic-release** with a build running Node 6 and 8. See [CircleCI documentation](https://circleci.com/docs/2.0) for additional configuration options.
This example is a minimal configuration for **semantic-release** with tests running against Node 16 and 14. See [CircleCI documentation](https://circleci.com/docs/2.0) for additional configuration options.
This example create the workflows `test_node_4`, `test_node_6`, `test_node_8` and `release`. The release workflows will [run `semantic-release` only after the all the `test_node_*` are successful](../usage/ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded).
In this example, the [`circleci/node`](https://circleci.com/developer/orbs/orb/circleci/node) orb is imported (Which makes some node operations easier), then a `release` job is defined which will run `semantic-release`.
To run our `release` job, we have created a workflow named `test_and_release` which will run two jobs, `node/test`, which comes from the node orb and will test our application, and our release job. Here, we are actually making use of [matrix jobs](https://circleci.com/blog/circleci-matrix-jobs/) so that our single `node/test` job will actually be executed twice, once for Node version 16, and once for version 14. Finally, we call our release job with a `requires` parameter so that `release` will only run after `node/test` has successfully tested against v14 and v16.
```yaml
version: 2
version: 2.1
orbs:
node: circleci/node@4.5
jobs:
test_node_6:
docker:
- image: circleci/node:6
steps:
# Configure your test steps here (checkout, npm install, cache management, tests etc...)
test_node_8:
docker:
- image: circleci/node:8
steps:
# Configure your test steps here (checkout, npm install, cache management, tests etc...)
release:
docker:
- image: circleci/node:8
executor: node/default
steps:
- checkout
- run: npm install
- node/install-packages # Install and automatically cache packages
# Run optional required steps before releasing
# - run: npm run build-script
- run: npx semantic-release
workflows:
version: 2
test_and_release:
# Run the test jobs first, then the release only when all the test jobs are successful
jobs:
- test_node_6
- test_node_8
- node/test:
matrix:
parameters:
version:
- 16.1.0
- 14.17.0
- release:
requires:
- test_node_6
- test_node_8
- node/test
```
### `package.json` configuration for multiple Node jobs
@@ -59,7 +52,7 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
```json
{
"devDependencies": {
"semantic-release": "^15.0.0"
"semantic-release": "^18.0.0"
}
}
```
+1 -1
View File
@@ -97,7 +97,7 @@ The Git history of the repository is now:
After a period of feedback from our users using the `@next` dist-tag we feel confident to make our big feature available to all users. To do so we merge the `next` branch into `master`. There should be no conflict as `next` is strictly ahead of `master`.
Once the merge commit is pushed to `next`, **semantic-release** will add the version `2.1.0` to the dist-tag `@latest` so all users will receive it when installing out module with `npm install example-module`.
Once the merge commit is pushed to `master`, **semantic-release** will add the version `2.1.0` to the dist-tag `@latest` so all users will receive it when installing out module with `npm install example-module`.
The Git history of the repository is now:
+30 -7
View File
@@ -4,17 +4,18 @@
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.
In this example a publish type [`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).
**Note**: The publish pipeline must run on [Node version >= 14.17](../support/FAQ.md#why-does-semantic-release-require-node-version--1417).
### `.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.
The following is a minimal configuration for [`semantic-release`](https://github.com/semantic-release/semantic-release) with a build running on Node 14.17 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
@@ -25,14 +26,16 @@ on:
jobs:
release:
name: Release
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
node-version: '14.17'
- name: Install dependencies
run: npm ci
- name: Release
@@ -48,9 +51,23 @@ To keep `package.json` updated in the `master` branch, [`@semantic-release/git`]
**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.
If the risk is acceptable, some extra configuration is needed. The [actions/checkout `persist-credentials`](https://github.com/marketplace/actions/checkout#usage) option needs to be `false`, otherwise the generated `GITHUB_TOKEN` will interfere with the custom one. Example:
```yaml
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false # <--- this
```
## 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.:
### Using GUI:
You can use [Manual Triggers](https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/) for GitHub Actions.
### Using HTTP:
Use [`repository_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#repository_dispatch) event to have control on when to generate a release by making an HTTP request, e.g.:
```yaml
name: Release
@@ -66,3 +83,9 @@ To trigger a release, call (with a [Personal Access Tokens](https://help.github.
```
$ 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" }'
```
### Using 3rd party apps:
If you'd like to use a GitHub app to manage this instead of creating a personal access token, you could consider using a project like:
* [Actions Panel](https://www.actionspanel.app/) - A declaratively configured way for triggering GitHub Actions
* [Action Button](https://github-action-button.web.app/#details) - A simple badge based mechanism for triggering GitHub Actions
+42 -7
View File
@@ -10,11 +10,11 @@ The [Authentication](../usage/ci-configuration.md#authentication) environment va
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 >= 10.13 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
**Note**: The publish pipeline must run a [Node >= 14.17 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1417).
### `.gitlab-ci.yml` configuration for Node projects
This example is a minimal configuration for **semantic-release** with a build running Node 6 and 8. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
This example is a minimal configuration for **semantic-release** with a build running Node 10 and 12. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
**Note**: The`semantic-release` execution command varies depending if you are using a [local](../usage/installation.md#local-installation) or [global](../usage/installation.md#global-installation) **semantic-release** installation.
@@ -27,25 +27,60 @@ stages:
before_script:
- npm install
node:6:
image: node:6
node:10:
image: node:10
stage: test
script:
- npm test
node:8:
image: node:8
node:12:
image: node:12
stage: test
script:
- npm test
publish:
image: node:8
image: node:12
stage: release
script:
- npx semantic-release
```
### `.gitlab-ci.yml` configuration for all projects
This example is a minimal configuration for **semantic-release** with a build running Node 10 and 12. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
**Note**: The`semantic-release` execution command varies depending if you are using a [local](../usage/installation.md#local-installation) or [global](../usage/installation.md#global-installation) **semantic-release** installation.
```yaml
# The release pipeline will run only on the master branch a commit is triggered
stages:
- release
release:
image: node:10-buster-slim
stage: release
before_script:
- apt-get update && apt-get install -y --no-install-recommends git-core ca-certificates
- npm install -g semantic-release @semantic-release/gitlab
script:
- semantic-release
only:
- master
release:
image: node:12-buster-slim
stage: release
before_script:
- apt-get update && apt-get install -y --no-install-recommends git-core ca-certificates
- npm install -g semantic-release @semantic-release/gitlab
script:
- semantic-release
only:
- master
```
### `package.json` configuration
A `package.json` is required only for [local](../usage/installation.md#local-installation) **semantic-release** installation.
+61
View File
@@ -0,0 +1,61 @@
# Using semantic-release with [Jenkins CI](https://www.jenkins.io/doc/book/pipeline/)
## Environment variables
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured in [Jenkins Project Settings](https://www.jenkins.io/doc/pipeline/tour/environment/)..
Alternatively, the default `NPM_TOKEN` and `GH_TOKEN` can be easily [setup with semantic-release-cli](../usage/getting-started.md#getting-started).
## Node.js project configuration
### `Jenkinsfile (Declarative Pipeline)` configuration for a Node.js job
**Note**: The publish pipeline must run a [Node >= 14.17 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1417).
This example is a minimal configuration for **semantic-release** with a build running Node 14.17. See [Jenkins documentation](https://www.jenkins.io/doc/) for additional configuration options.
The`semantic-release` execution command varies depending if you are using a [local](../usage/installation.md#local-installation) or [global](../usage/installation.md#global-installation) **semantic-release** installation.
```yaml
// The release stage in the pipeline will run only if the test stage in the pipeline is successful
pipeline {
agent any
environment {
GH_TOKEN = credentials('some-id')
}
stages {
stage('Test') {
steps {
sh '''
# Configure your test steps here (checkout, npm install, tests etc)
npm install
npm test
'''
}
}
stage('Release') {
tools {
nodejs "node 14.17"
}
steps {
sh '''
# Run optional required steps before releasing
npx semantic-release
'''
}
}
}
}
```
### `package.json` configuration for a Node job
A `package.json` is required only for [local](../usage/installation.md#local-installation) **semantic-release** installation.
```json
{
"devDependencies": {
"semantic-release": "^18.0.0"
}
}
```
+2 -2
View File
@@ -33,7 +33,7 @@ The Git history of the repository is now:
One of our users request a new feature, however they cannot migrate to Node.js 8 or higher due to corporate policies.
If we were to push that feature on `master` and release it, the new version would require Node.js 8 or higher as the release would also contains the commit `feat: drop Node.js 6 support \n\n BREAKING CHANGE: Node.js >= 8 required`.
If we were to push that feature on `master` and release it, the new version would require Node.js 8 or higher as the release would also contain the commit `feat: drop Node.js 6 support \n\n BREAKING CHANGE: Node.js >= 8 required`.
Instead, we create the branch `1.x` from the tag `v1.0.0` with the command `git checkout -b 1.x v1.0.0` and we commit that feature with the message `feat: a feature` to the branch `1.x`. When pushing that commit, **semantic-release** will release the version `1.1.0` on the dist-tag `@release-1.x` so users who can't migrate to Node.js 8 or higher can benefit from it.
@@ -48,7 +48,7 @@ The Git history of the repository is now:
## Releasing a bug fix for version 1.0.x users
Another user currently using version `1.0.0` reports a bug. They cannot migrate to Node.js 8 or higher and they also cannot migrate to `1.1.0` as they do not use the feature developed in `feat: a feature` and their corporate policies require to go through a costly quality insurance process for each `minor` upgrades.
Another user currently using version `1.0.0` reports a bug. They cannot migrate to Node.js 8 or higher and they also cannot migrate to `1.1.0` as they do not use the feature developed in `feat: a feature` and their corporate policies require to go through a costly quality assurance process for each `minor` upgrades.
In order to deliver the bug fix in a `patch` release, we create the branch `1.0.x` from the tag `v1.0.0` with the command `git checkout -b 1.0.x v1.0.0` and we commit that fix with the message `fix: a fix` to the branch `1.0.x`. When pushing that commit, **semantic-release** will release the version `1.0.1` on the dist-tag `@release-1.0.x` so users who can't migrate to `1.1.x` or `2.x` can benefit from it.
+1 -1
View File
@@ -169,7 +169,7 @@ The Git history of the repository is now:
We can now start to work on a new future major release, version `4.0.0`, on the `@beta` distribution channel.
To do so we fist need to update the `beta` branch with all the changes from `master` (the commits `fix: a fix`). As `beta` and `master` branches have diverged, this merge might require to resolve conflicts.
To do so we first need to update the `beta` branch with all the changes from `master` (the commits `fix: a fix`). As `beta` and `master` branches have diverged, this merge might require to resolve conflicts.
We can now commit our new feature on `beta`. When pushing that commit, **semantic-release** will publish the pre-release version `3.1.0-beta.1` on the dist-tag `@beta`. That allow us to run integration tests by installing our module with `npm install example-module@beta`. Other users installing with `npm install example-module` will still receive the version `3.0.0`.
+6 -4
View File
@@ -10,7 +10,7 @@ Alternatively, the default `NPM_TOKEN` and `GH_TOKEN` can be easily [setup with
### `.travis.yml` configuration for multiple Node.js jobs
This example is a minimal configuration for **semantic-release** with a build running Node 6 and 8. See [Travis - Customizing the Build](https://docs.travis-ci.com/user/customizing-the-build) for additional configuration options.
This example is a minimal configuration for **semantic-release** with a build running Node 14 and 16. See [Travis - Customizing the Build](https://docs.travis-ci.com/user/customizing-the-build) for additional configuration options.
This example creates a `release` [build stage](https://docs.travis-ci.com/user/build-stages) that [runs `semantic-release` only after all test jobs are successful](../usage/ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded).
@@ -24,8 +24,8 @@ It's recommended to run the `semantic-release` command in the [Travis `deploy` s
language: node_js
node_js:
- 8
- 6
- 14
- 16
jobs:
include:
@@ -48,7 +48,7 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
```json
{
"devDependencies": {
"semantic-release": "^15.0.0"
"semantic-release": "^18.0.0"
}
}
```
@@ -92,4 +92,6 @@ jobs:
# Use nvm to install and use the Node LTS version (nvm is installed on all Travis images)
- nvm install lts/*
- npx semantic-release
on:
all_branches: true
```
+7 -35
View File
@@ -2,12 +2,14 @@
## Why is the `package.json`s version not updated in my repository?
**semantic-release** takes care of updating the `package.json`s version before publishing to [npm](https://www.npmjs.com).
[`@semantic-release/npm`](https://github.com/semantic-release/npm) takes care of updating the `package.json`s version before publishing to [npm](https://www.npmjs.com).
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.
If you wish to only update the `package.json` and push via Git you can set the project to `"private": true,` within your `package.json` to prevent publishing to [the npm registry](https://www.npmjs.com).
## How can I use a npm build script that requires the `package.json`s version ?
The `package.json`s version will be updated by the `semantic-release` command just before publishing to [npm](https://www.npmjs.com), therefore it won't be available for scripts ran before the `semantic-release` command.
@@ -36,44 +38,14 @@ If using npm hook scripts is not possible, and alternative solution is to [`@sem
Yes with the [dry-run options](../usage/configuration.md#dryrun) which prints to the console the next version to be published and the release notes.
## 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 >= 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:
```bash
$ 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 >= 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:
```bash
$ nvm install 8 && yarn global add semantic-release && semantic-release
```
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
As `semantic-release` is recommended to be executed with [`npx`](https://www.npmjs.com/package/npx) an alternative is required for usage with Yarn. Even though it is possible to install npx with Yarn, it's not recommended. Yarn and npx would be using different cache locations.
For [local installation](../usage/installation.md#local-installation) replace
`npx semantic-release` with `yarn run semantic-release`.
For [global installation](../usage/installation.md#global-installation) replace
`npx semantic-release` with `yarn global add semantic-release && semantic-release`.
## Can I use semantic-release to publish non-JavaScript packages?
Yes, **semantic-release** is a Node CLI application but it can be used to publish any type of packages.
Yes, **semantic-release** is a Node CLI application, but it can be used to publish any type of packages.
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 >= 10.13](#why-does-semantic-release-require-node-version--1013) to execute the `semantic-release` command
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 14.17](#why-does-semantic-release-require-node-version--1417) to execute the `semantic-release` command
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
@@ -232,9 +204,9 @@ 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 >= 10.13?
## Why does semantic-release require Node version >= 14.17?
**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**.
**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 14.17 or higher**.
See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions.
+1 -1
View File
@@ -1,6 +1,6 @@
# Node Support Policy
We only support [Long-Term Support](https://github.com/nodejs/Release) versions of Node starting with [Node 8.9.0 (LTS)](https://nodejs.org/en/blog/release/v8.9.0).
We only support [Long-Term Support](https://github.com/nodejs/Release) versions of Node starting with [Node 14.17.0 (LTS)](https://nodejs.org/en/blog/release/v14.17.0).
We specifically limit our support to LTS versions of Node, not because this package won't work on other versions, but because we have a limited amount of time, and supporting LTS offers the greatest return on that investment.
+10 -8
View File
@@ -1,16 +1,17 @@
# 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 10 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 14.17 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.
**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 14.17 or higher.
See our [Node Support Policy](node-support-policy.md) for our long-term promise regarding Node version support.
## Recommended solution
### Run at least one CI job with Node >= 10.13
### Run at least one CI job with Node >= 14.17
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.
The recommended approach is to run the `semantic-release` command from a CI job running on Node 14.17 or higher.
This can either be a job used by your project to test on Node >= 14.17 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.
@@ -18,18 +19,19 @@ See [CI configuration](../usage/ci-configuration.md) and [CI configuration recip
### Use `npx`
`npx` is included with npm >= 5.2 and can be used to download the latest [Node 8 package published on npm](https://www.npmjs.com/package/node). Use it to execute the `semantic-release` command.
`npx` is included with npm >= 5.2 and can be used to download the latest [Node 14 package published on npm](https://www.npmjs.com/package/node).
Use it to execute the `semantic-release` command.
```bash
$ npx -p node@8 -c "npx semantic-release"
$ npx -p node@14 -c "npx semantic-release"
```
**Note:**: See [What is npx](./FAQ.md#what-is-npx) for more details.
### Use `nvm`
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can use it to switch to Node 8 before running the `semantic-release` command.
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can use it to switch to Node 14.17 before running the `semantic-release` command.
```bash
$ nvm install 8 && npx semantic-release
$ nvm install 14.17 && npx semantic-release
```
+2
View File
@@ -14,6 +14,8 @@
- ["Introduction to SemVer" - Irina Gebauer](https://blog.greenkeeper.io/introduction-to-semver-d272990c44f2)
- ["Introduction to Semantic Release" - liv](https://blog.greenkeeper.io/introduction-to-semantic-release-33f73b117c8)
- ["Series - Semantic Release Automation" - Abdelrahman Wahdan](https://dev.to/abdelrahmanahmed/semantic-release-and-how-to-automate-it-part-1-4pa2)
- ["Explain semantic release and how to use it on GitLab pipeline"](https://regoo707.medium.com/auto-bump-apps-versions-and-releases-using-gitlab-pipeline-e32f1d7fa3ee)
## Tutorials
+13
View File
@@ -54,3 +54,16 @@ $ git branch --contains <tag name>
$ git tag -d <tag name>
$ git push origin :refs/tags/<tag name>
```
## release not found release branch after `git push --force`
**semantic-release** is using both [git tags](https://git-scm.com/docs/git-tag) and [git notes](https://git-scm.com/docs/git-notes) to store information about which releases happened in which branch.
After a git history rewrite due to a `git push --force`, the git tags and notes referencing the commits that were rewritten are lost.
To recover from that situation, do the following:
1. Delete the tag(s) for the release(s) that have been lost from the git history. You can delete each tag from remote using `git push origin :[TAG NAME]`, e.g. `git push origin :v2.0.0-beta.1`. You can delete tags locally using `git tag -d [TAG NAME]`, e.g. `git tag -d v2.0.0-beta.1`.
2. Re-create the tags locally: `git tag [TAG NAME] [COMMIT HASH]`, where `[COMMIT HASH]` is the new commit that created the release for the lost tag. E.g. `git tag v2.0.0-beta.1 abcdef0`
3. Re-create the git notes for each release tag, e.g. `git notes --ref semantic-release add -f -m '{"channels":["beta"]}' v2.0.0-beta.1`. If the release was also published in the default channel (usually `master`), then set the first channel to `null`, e.g. `git notes --ref semantic-release add -f -m '{"channels":[null, "beta"]}'
4. Push the local notes: `git push --force origin refs/notes/semantic-release`. The `--force` is needed after the rebase. Be careful.
+8 -7
View File
@@ -8,7 +8,7 @@ Here is a few example of the CI services that can be used to achieve this:
- [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)
- [GitLab Pipelines](https://docs.gitlab.com/ce/ci/introduction/)
- [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).
@@ -21,12 +21,13 @@ See [CI configuration recipes](../recipes/README.md#ci-configurations) for more
**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 |
|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
| `BB_TOKEN` or `BITBUCKET_TOKEN` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html). |
| `GIT_CREDENTIALS` | [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) Git username and password in the format `<username>:<password>`. The username and password must each be individually URL encoded, not the `:` separating them. |
| Variable | Description |
|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
| `BB_TOKEN` or `BITBUCKET_TOKEN` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html). |
| `BB_TOKEN_BASIC_AUTH` or `BITBUCKET_TOKEN_BASIC_AUTH` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html) with basic auth support. For clearification `user:token` has to be the value of this env. |
| `GIT_CREDENTIALS` | [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) Git username and password in the format `<username>:<password>`. The username and password must each be individually URL encoded, not the `:` separating them. |
Alternatively the Git authentication can be set up via [SSH keys](../recipes/git-auth-ssh-keys.md).
+5 -3
View File
@@ -38,7 +38,7 @@ The following three examples are the same.
- Via CLI argument:
```bash
$ semantic-release --branch next
$ semantic-release --branches next
```
**Note**: CLI arguments take precedence over options configured in the configuration file.
@@ -74,7 +74,7 @@ The branches on which releases should happen. By default **semantic-release** wi
**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).
**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://docs.github.com/github/administering-a-repository/about-protected-branches).
See [Workflow configuration](workflow-configuration.md#workflow-configuration) for more details.
@@ -116,7 +116,9 @@ Type: `Boolean`<br>
Default: `false` if running in a CI environment, `true` otherwise<br>
CLI arguments: `-d`, `--dry-run`
Dry-run mode, skip publishing, print next version and release notes.
The objective of the dry-run mode is to get a preview of the pending release. Dry-run mode skips the following steps: prepare, publish, success and fail. In addition to this it prints the next version and release notes to the console.
**Note**: The Dry-run mode verifies the repository push permission, even though nothing will be pushed. The verification is done to help user to figure out potential configuration issues.
### ci
+2 -3
View File
@@ -1,6 +1,7 @@
# Getting started
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
@@ -9,10 +10,8 @@ In order to use **semantic-release** you must follow these steps:
Alternatively those steps can be easily done with the [**semantic-release** interactive CLI](https://github.com/semantic-release/cli):
```bash
npm install -g semantic-release-cli
cd your-module
semantic-release-cli setup
npx semantic-release-cli setup
```
![dialogue](../../media/semantic-release-cli.png)
+2 -1
View File
@@ -24,6 +24,7 @@ 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`). 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@18`).
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.
+8 -3
View File
@@ -12,21 +12,24 @@ A plugin is a npm module that can implement one or more of the following steps:
| `generateNotes` | No | Responsible for generating the content of the release note. If multiple plugins with a `generateNotes` step are defined, the release notes will be the result of the concatenation of each plugin output. |
| `prepare` | No | Responsible for preparing the release, for example creating or updating files such as `package.json`, `CHANGELOG.md`, documentation or compiled assets and pushing a commit. |
| `publish` | No | Responsible for publishing the release. |
| `addChannel` | No | Responsible for adding a release channel (e.g. adding an npm dist-tag to a release). |
| `success` | No | Responsible for notifying of a new release. |
| `fail` | No | Responsible for notifying of a failed release. |
Release steps will run in that order. At each step, **semantic-release** will run every plugin in the [`plugins` array](#plugins-declaration-and-execution-order), as long as the plugin implements the step.
**Note:** If no plugin with a `analyzeCommits` step is defined `@semantic-release/commit-analyzer` will be used.
## Plugins installation
### Default plugins
These four plugins are already part of **semantic-release** and don't have to be installed separately:
These four plugins are already part of **semantic-release** and are listed in order of execution. They do not have to be installed separately:
```
"@semantic-release/commit-analyzer"
"@semantic-release/github"
"@semantic-release/npm"
"@semantic-release/release-notes-generator"
"@semantic-release/npm"
"@semantic-release/github"
```
### Additional plugins
@@ -69,6 +72,8 @@ With this configuration **semantic-release** will:
- execute the `prepare` implementation of `@semantic-release/npm` then `@semantic-release/git`
- execute the `publish` implementation of `@semantic-release/npm`
Order is first determined by release steps (such as `verifyConditions``anayzeCommits`). At each release step, plugins are executed in the order in which they are defined.
## Plugin options configuration
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.
+4 -4
View File
@@ -13,7 +13,7 @@ Each branch can be defined either as a string, a [glob](https://github.com/micro
A branch can defined as one of three types:
- [release](#release-branches): to make releases on top of the last version released
- [maintenance](#maintenance-branches): to make release on top of an old release
- [maintenance](#maintenance-branches): to make releases on top of an old release
- [pre-release](#pre-release-branches): to make pre-releases
The type of the branch is automatically determined based on naming convention and/or [properties](#branches-properties).
@@ -25,7 +25,7 @@ The type of the branch is automatically determined based on naming convention an
| `name` | All | **Required.** The Git branch holding the commits to analyze and the code to release. See [name](#name). | - The value itself if defined as a `String` or the matching branches name if defined as a glob. |
| `channel` | All | The distribution channel on which to publish releases from this branch. Set to `false` to force the default distribution channel instead of using the default. See [channel](#channel). | `undefined` for the first release branch, the value of `name` for subsequent ones. |
| `range` | [maintenance](#maintenance-branches) only | **Required unless `name` is formatted like `N.N.x` or `N.x` (`N` is a number).** The range of [semantic versions](https://semver.org) to support on this branch. See [range](#range). | The value of `name`. |
| `prerelease` | [pre-release](#pre-release-branches) only | **Required.** The pre-release detonation to append to [semantic versions](https://semver.org) released from this branch. See [prerelease](#prerelease). | - |
| `prerelease` | [pre-release](#pre-release-branches) only | **Required.** The pre-release denotation to append to [semantic versions](https://semver.org) released from this branch. See [prerelease](#prerelease). | - |
### name
@@ -140,7 +140,7 @@ A maintenance branch is characterized by a range which defines the versions that
Maintenance branches are always considered lower than [release branches](#release-branches) and similarly to them, when a commit that would create a version conflict is pushed, **semantic-release** will not perform the release and will throw an `EINVALIDNEXTVERSION` error, listing the problematic commits and the valid branches on which to move them.
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release or maintenance branch to another maintenance branch](#merging-into-a-maintenance-branch), however only version version within the branch `range` can be merged. Ia merged version is outside the maintenance branch `range` **semantic-release** will not add to the corresponding channel and will throw an `EINVALIDMAINTENANCEMERGE` error.
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release or maintenance branch to another maintenance branch](#merging-into-a-maintenance-branch), however only versions within the branch `range` can be merged. If a merged version is outside the maintenance branch `range`, **semantic-release** will not add to the corresponding channel and will throw an `EINVALIDMAINTENANCEMERGE` error.
See [publishing maintenance releases recipe](../recipes/maintenance-releases.md) for a detailed example.
@@ -162,7 +162,7 @@ With the configuration `"branches": ["1.0.x", "1.x", "master"]`, if the last rel
### Pre-release branches
A pre-release branch is a type of branch used by **semantic-release** that allows to publish releases with a [pre-release version](https://semver.org/#spec-item-9).
Using a pre-release version allow to publish multiple releases with the same version. Those release will be differentiated via there identifiers (in `1.0.0-alpha.1` the identifier is `alpha.1`).
Using a pre-release version allow to publish multiple releases with the same version. Those release will be differentiated via their identifiers (in `1.0.0-alpha.1` the identifier is `alpha.1`).
This is useful when you need to work on a future major release that will include many breaking changes but you do not want to increment the version number for each breaking change commit.
A pre-release branch is characterized by the `prerelease` property that defines the static part of the version released (in `1.0.0-alpha.1` the static part fo the identifier is `alpha`). The [`prerelease`](#prerelease) value of each pre-release branch must be unique across the project.
+35 -29
View File
@@ -1,32 +1,36 @@
const {pick} = require('lodash');
const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const semver = require('semver');
const AggregateError = require('aggregate-error');
const pkg = require('./package.json');
const hideSensitive = require('./lib/hide-sensitive');
const getConfig = require('./lib/get-config');
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 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, pushNotes, getTagHead, addNote} = require('./lib/git');
const getError = require('./lib/get-error');
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
import lodash from 'lodash'
const {pick} = lodash
import marked from 'marked';
import TerminalRenderer from 'marked-terminal';
import envCi from 'env-ci';
import hookStd from 'hook-std';
import semver from 'semver';
import AggregateError from 'aggregate-error';
import pkg from './package.json';
import hideSensitive from './lib/hide-sensitive';
import getConfig from './lib/get-config';
import verify from './lib/verify';
import getNextVersion from './lib/get-next-version';
import getCommits from './lib/get-commits';
import getLastRelease from './lib/get-last-release';
import getReleaseToAdd from './lib/get-release-to-add';
import {extractErrors, makeTag} from './lib/utils';
import getGitAuthUrl from './lib/get-git-auth-url';
import getBranches from './lib/branches';
import getLogger from './lib/get-logger';
import {verifyAuth, isBranchUpToDate, getGitHead, tag, push, pushNotes, getTagHead, addNote} from './lib/git';
import getError from './lib/get-error';
import {COMMIT_NAME, COMMIT_EMAIL} from './lib/definitions/constants';
marked.setOptions({renderer: new TerminalRenderer()});
/* eslint complexity: off */
async function run(context, plugins) {
const {cwd, env, options, logger} = context;
const {isCi, branch: ciBranch, isPr} = context.envCi;
const {isCi, branch, prBranch, isPr} = context.envCi;
const ciBranch = isPr ? prBranch : branch;
if (!isCi && !options.dryRun && !options.noCi) {
logger.warn('This run was not triggered in a known CI environment, running in dry-run mode.');
@@ -52,7 +56,7 @@ async function run(context, plugins) {
// Verify config
await verify(context);
options.repositoryUrl = await getGitAuthUrl(context);
options.repositoryUrl = await getGitAuthUrl({...context, branch: {name: ciBranch}});
context.branches = await getBranches(options.repositoryUrl, ciBranch, context);
context.branch = context.branches.find(({name}) => name === ciBranch);
@@ -66,7 +70,9 @@ async function run(context, plugins) {
}
logger[options.dryRun ? 'warn' : 'success'](
`Run automated release from branch ${ciBranch}${options.dryRun ? ' in dry-run mode' : ''}`
`Run automated release from branch ${ciBranch} on repository ${options.repositoryUrl}${
options.dryRun ? ' in dry-run mode' : ''
}`
);
try {
@@ -212,7 +218,7 @@ async function run(context, plugins) {
}
function logErrors({logger, stderr}, err) {
const errors = extractErrors(err).sort(error => (error.semanticRelease ? -1 : 0));
const errors = extractErrors(err).sort((error) => (error.semanticRelease ? -1 : 0));
for (const error of errors) {
if (error.semanticRelease) {
logger.error(`${error.code} ${error.message}`);
@@ -226,7 +232,7 @@ function logErrors({logger, stderr}, err) {
}
async function callFail(context, plugins, err) {
const errors = extractErrors(err).filter(err => err.semanticRelease);
const errors = extractErrors(err).filter((err) => err.semanticRelease);
if (errors.length > 0) {
try {
await plugins.fail({...context, errors});
@@ -236,7 +242,7 @@ async function callFail(context, plugins, err) {
}
}
module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => {
export default async (cliOptions = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => {
const {unhook} = hookStd(
{silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean)},
hideSensitive(env)
@@ -251,7 +257,7 @@ module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdo
context.logger = getLogger(context);
context.logger.log(`Running ${pkg.name} version ${pkg.version}`);
try {
const {plugins, options} = await getConfig(context, opts);
const {plugins, options} = await getConfig(context, cliOptions);
context.options = options;
try {
const result = await run(context, plugins);
+7 -6
View File
@@ -1,16 +1,17 @@
const {isString, remove, omit, mapValues, template} = require('lodash');
const micromatch = require('micromatch');
const {getBranches} = require('../git');
import lodash from 'lodash'
const {isString, remove, omit, mapValues, template} = lodash
import micromatch from 'micromatch';
import {getBranches} from '../git.js';
module.exports = async (repositoryUrl, {cwd}, branches) => {
export default async (repositoryUrl, {cwd}, branches) => {
const gitBranches = await getBranches(repositoryUrl, {cwd});
return branches.reduce(
(branches, branch) => [
...branches,
...remove(gitBranches, name => micromatch(gitBranches, branch.name).includes(name)).map(name => ({
...remove(gitBranches, (name) => micromatch(gitBranches, branch.name).includes(name)).map((name) => ({
name,
...mapValues(omit(branch, 'name'), value => (isString(value) ? template(value)({name}) : value)),
...mapValues(omit(branch, 'name'), (value) => (isString(value) ? template(value)({name}) : value)),
})),
],
[]
+8 -6
View File
@@ -1,10 +1,12 @@
const {template, escapeRegExp} = require('lodash');
const semver = require('semver');
const pReduce = require('p-reduce');
const debug = require('debug')('semantic-release:get-tags');
const {getTags, getNote} = require('../../lib/git');
import lodash from 'lodash'
const {template, escapeRegExp} = lodash
import semver from 'semver';
import pReduce from 'p-reduce';
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:get-tags');
import {getTags, getNote} from '../../lib/git.js';
module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
export default 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,
+16 -15
View File
@@ -1,20 +1,21 @@
const {isString, isRegExp} = require('lodash');
const AggregateError = require('aggregate-error');
const pEachSeries = require('p-each-series');
const DEFINITIONS = require('../definitions/branches');
const getError = require('../get-error');
const {fetch, fetchNotes, verifyBranchName} = require('../git');
const expand = require('./expand');
const getTags = require('./get-tags');
const normalize = require('./normalize');
import lodash from 'lodash'
const {isString, isRegExp} = lodash
import AggregateError from 'aggregate-error';
import pEachSeries from 'p-each-series';
import DEFINITIONS from '../definitions/branches.js';
import getError from '../get-error.js';
import {fetch, fetchNotes, verifyBranchName} from '../git.js';
import expand from './expand.js';
import getTags from './get-tags.js';
import normalize from './normalize.js';
module.exports = async (repositoryUrl, ciBranch, context) => {
export default 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))
context.options.branches.map((branch) => (isString(branch) || isRegExp(branch) ? {name: branch} : branch))
);
await pEachSeries(remoteBranches, async ({name}) => {
@@ -32,7 +33,7 @@ module.exports = async (repositoryUrl, ciBranch, context) => {
);
const result = Object.entries(DEFINITIONS).reduce((result, [type, {branchesValidator, branchValidator}]) => {
branchesByType[type].forEach(branch => {
branchesByType[type].forEach((branch) => {
if (branchValidator && !branchValidator(branch)) {
errors.push(getError(`E${type.toUpperCase()}BRANCH`, {branch}));
}
@@ -48,15 +49,15 @@ module.exports = async (repositoryUrl, ciBranch, context) => {
}, {});
const duplicates = [...branches]
.map(branch => branch.name)
.map((branch) => branch.name)
.sort()
.filter((_, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
.filter((_, idx, array) => array[idx] === array[idx + 1] && array[idx] !== array[idx - 1]);
if (duplicates.length > 0) {
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
}
await pEachSeries(branches, async branch => {
await pEachSeries(branches, async (branch) => {
if (!(await verifyBranchName(branch.name))) {
errors.push(getError('EINVALIDBRANCHNAME', branch));
}
+15 -6
View File
@@ -1,7 +1,9 @@
const {sortBy, isNil} = require('lodash');
const semverDiff = require('semver-diff');
const {FIRST_RELEASE, RELEASE_TYPE} = require('../definitions/constants');
const {
import lodash from 'lodash'
const {sortBy, isNil} = lodash
import semverDiff from 'semver-diff';
import {FIRST_RELEASE, RELEASE_TYPE} from '../definitions/constants.js';
import {
tagsToVersions,
isMajorRange,
getUpperBound,
@@ -11,7 +13,7 @@ const {
getLatestVersion,
getFirstVersion,
getRange,
} = require('../utils');
} from '../utils.js';
function maintenance({maintenance, release}) {
return sortBy(
@@ -103,4 +105,11 @@ function prerelease({prerelease}) {
});
}
module.exports = {maintenance, release, prerelease};
const exported = {
maintenance,
release,
prerelease,
};
export default exported;
export {maintenance, release, prerelease};
+16 -8
View File
@@ -1,23 +1,31 @@
const {isNil, uniqBy} = require('lodash');
const semver = require('semver');
const {isMaintenanceRange} = require('../utils');
import lodash from 'lodash'
const {isNil, uniqBy} = lodash
import semver from 'semver';
import {isMaintenanceRange} from '../utils.js';
const maintenance = {
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,
branchesValidator: (branches) => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
};
const 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,
branchesValidator: (branches) => uniqBy(branches, 'prerelease').length === branches.length,
};
const release = {
filter: branch => !maintenance.filter(branch) && !prerelease.filter(branch),
branchesValidator: branches => branches.length <= 3 && branches.length > 0,
filter: (branch) => !maintenance.filter(branch) && !prerelease.filter(branch),
branchesValidator: (branches) => branches.length <= 3 && branches.length > 0,
};
module.exports = {maintenance, prerelease, release};
const exported = {
maintenance,
prerelease,
release,
};
export default exported;
export {maintenance, prerelease, release};
+14 -1
View File
@@ -16,7 +16,20 @@ const SECRET_MIN_SIZE = 5;
const GIT_NOTE_REF = 'semantic-release';
module.exports = {
const exported = {
RELEASE_TYPE,
FIRST_RELEASE,
FIRSTPRERELEASE,
COMMIT_NAME,
COMMIT_EMAIL,
RELEASE_NOTES_SEPARATOR,
SECRET_REPLACEMENT,
SECRET_MIN_SIZE,
GIT_NOTE_REF,
};
export default exported;
export {
RELEASE_TYPE,
FIRST_RELEASE,
FIRSTPRERELEASE,
+61 -11
View File
@@ -1,15 +1,17 @@
const {inspect} = require('util');
const {toLower, isString, trim} = require('lodash');
const pkg = require('../../package.json');
const {RELEASE_TYPE} = require('./constants');
import {inspect} from 'util';
import lodash from 'lodash'
const {toLower, isString, trim} = lodash
import pkg from '../../package.json';
import {RELEASE_TYPE} from './constants.js';
const [homepage] = pkg.homepage.split('#');
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
const linkify = file => `${homepage}/blob/master/${file}`;
const wordsList = words =>
const stringify = (object) =>
isString(object) ? object : inspect(object, {breakLength: Infinity, depth: 2, maxArrayLength: 5});
const linkify = (file) => `${homepage}/blob/master/${file}`;
const wordsList = (words) =>
`${words.slice(0, -1).join(', ')}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;
module.exports = {
const exported = {
ENOGITREPO: ({cwd}) => ({
message: 'Not running from a git repository.',
details: `The \`semantic-release\` command must be executed from a Git repository.
@@ -18,6 +20,7 @@ The current working directory is \`${cwd}\`.
Please verify your CI configuration to make sure the \`semantic-release\` command is executed from the root of the cloned repository.`,
}),
ENOREPOURL: () => ({
message: 'The `repositoryUrl` option is required.',
details: `The [repositoryUrl option](${linkify(
@@ -28,6 +31,7 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
'docs/usage/configuration.md'
)}).`,
}),
EGITNOPERMISSION: ({options: {repositoryUrl}, branch: {name}}) => ({
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}\`.
@@ -39,6 +43,7 @@ This can be caused by:
'docs/usage/ci-configuration.md#authentication'
)})`,
}),
EINVALIDTAGFORMAT: ({options: {tagFormat}}) => ({
message: 'Invalid `tagFormat` option.',
details: `The [tagFormat](${linkify(
@@ -47,6 +52,7 @@ This can be caused by:
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
}),
ETAGNOVERSION: ({options: {tagFormat}}) => ({
message: 'Invalid `tagFormat` option.',
details: `The [tagFormat](${linkify(
@@ -55,22 +61,25 @@ Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
}),
EPLUGINCONF: ({type, required, pluginConf}) => ({
message: `The \`${type}\` plugin configuration is invalid.`,
details: `The [${type} plugin configuration](${linkify(`docs/usage/plugins.md#${toLower(type)}-plugin`)}) ${
required ? 'is required and ' : ''
} must be a single or an array of plugins definition. A plugin definition is an npm module name, optionnaly wrapped in an array with an object.
} must be a single or an array of plugins definition. A plugin definition is an npm module name, optionally wrapped in an array with an object.
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
}),
EPLUGINSCONF: ({plugin}) => ({
message: 'The `plugins` configuration is invalid.',
details: `The [plugins](${linkify(
'docs/usage/configuration.md#plugins'
)}) option must be an array of plugin definions. A plugin definition is an npm module name, optionnaly wrapped in an array with an object.
)}) option must be an array of plugin definions. A plugin definition is an npm module name, optionally wrapped in an array with an object.
The invalid configuration is \`${stringify(plugin)}\`.`,
}),
EPLUGIN: ({pluginName, type}) => ({
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
details: `A valid \`${type}\` **semantic-release** plugin must be a function or an object with a function in the property \`${type}\`.
@@ -81,10 +90,11 @@ Please refer to the \`${pluginName}\` and [semantic-release plugins configuratio
'docs/usage/plugins.md'
)}) documentation for more details.`,
}),
EANALYZECOMMITSOUTPUT: ({result, pluginName}) => ({
message: 'The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.',
details: `The \`analyzeCommits\` plugin must return a valid [semver](https://semver.org) release type. The valid values are: ${RELEASE_TYPE.map(
type => `\`${type}\``
(type) => `\`${type}\``
).join(', ')}.
The \`analyzeCommits\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
@@ -97,6 +107,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
'docs/developer-guide/plugin.md'
)})`,
}),
EGENERATENOTESOUTPUT: ({result, pluginName}) => ({
message: 'The `generateNotes` plugin returned an invalid value. It must return a `String`.',
details: `The \`generateNotes\` plugin must return a \`String\`.
@@ -111,6 +122,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
'docs/developer-guide/plugin.md'
)})`,
}),
EPUBLISHOUTPUT: ({result, pluginName}) => ({
message: 'A `publish` plugin returned an invalid value. It must return an `Object`.',
details: `The \`publish\` plugins must return an \`Object\`.
@@ -125,6 +137,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
'docs/developer-guide/plugin.md'
)})`,
}),
EADDCHANNELOUTPUT: ({result, pluginName}) => ({
message: 'A `addChannel` plugin returned an invalid value. It must return an `Object`.',
details: `The \`addChannel\` plugins must return an \`Object\`.
@@ -139,6 +152,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
'docs/developer-guide/plugin.md'
)})`,
}),
EINVALIDBRANCH: ({branch}) => ({
message: 'A branch is invalid in the `branches` configuration.',
details: `Each branch in the [branches configuration](${linkify(
@@ -147,6 +161,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
}),
EINVALIDBRANCHNAME: ({branch}) => ({
message: 'A branch name is invalid in the `branches` configuration.',
details: `Each branch in the [branches configuration](${linkify(
@@ -155,6 +170,7 @@ Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
}),
EDUPLICATEBRANCHES: ({duplicates}) => ({
message: 'The `branches` configuration has duplicate branches.',
details: `Each branch in the [branches configuration](${linkify(
@@ -163,6 +179,7 @@ Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
Your configuration contains duplicates for the following branch names: \`${stringify(duplicates)}\`.`,
}),
EMAINTENANCEBRANCH: ({branch}) => ({
message: 'A maintenance branch is invalid in the `branches` configuration.',
details: `Each maintenance branch in the [branches configuration](${linkify(
@@ -171,6 +188,7 @@ Your configuration contains duplicates for the following branch names: \`${strin
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
}),
EMAINTENANCEBRANCHES: ({branches}) => ({
message: 'The maintenance branches are invalid in the `branches` configuration.',
details: `Each maintenance branch in the [branches configuration](${linkify(
@@ -179,6 +197,7 @@ Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
}),
ERELEASEBRANCHES: ({branches}) => ({
message: 'The release branches are invalid in the `branches` configuration.',
details: `A minimum of 1 and a maximum of 3 release branches are required in the [branches configuration](${linkify(
@@ -189,6 +208,7 @@ This may occur if your repository does not have a release branch, such as \`mast
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
}),
EPRERELEASEBRANCH: ({branch}) => ({
message: 'A pre-release branch configuration is invalid in the `branches` configuration.',
details: `Each pre-release branch in the [branches configuration](${linkify(
@@ -197,6 +217,7 @@ Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
}),
EPRERELEASEBRANCHES: ({branches}) => ({
message: 'The pre-release branches are invalid in the `branches` configuration.',
details: `Each pre-release branch in the [branches configuration](${linkify(
@@ -205,6 +226,7 @@ Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
}),
EINVALIDNEXTVERSION: ({nextRelease: {version}, branch: {name, range}, commits, validBranches}) => ({
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
details: `Based on the releases published on other branches, only versions within the range \`${range}\` can be published from branch \`${name}\`.
@@ -220,6 +242,7 @@ A valid branch could be ${wordsList(validBranches.map(({name}) => `\`${name}\``)
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
}),
EINVALIDMAINTENANCEMERGE: ({nextRelease: {channel, gitTag, version}, branch: {mergeRange, name}}) => ({
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
details: `Only releases within the range \`${mergeRange}\` can be merged into the maintenance branch \`${name}\` and published to the \`${channel}\` distribution channel.
@@ -229,3 +252,30 @@ The branch \`${name}\` head should be [reset](https://git-scm.com/docs/git-reset
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
}),
};
export default exported;
export const {
ENOGITREPO,
ENOREPOURL,
EGITNOPERMISSION,
EINVALIDTAGFORMAT,
ETAGNOVERSION,
EPLUGINCONF,
EPLUGINSCONF,
EPLUGIN,
EANALYZECOMMITSOUTPUT,
EGENERATENOTESOUTPUT,
EPUBLISHOUTPUT,
EADDCHANNELOUTPUT,
EINVALIDBRANCH,
EINVALIDBRANCHNAME,
EDUPLICATEBRANCHES,
EMAINTENANCEBRANCH,
EMAINTENANCEBRANCHES,
ERELEASEBRANCHES,
EPRERELEASEBRANCH,
EPRERELEASEBRANCHES,
EINVALIDNEXTVERSION,
EINVALIDMAINTENANCEMERGE,
} = exported;
+36 -14
View File
@@ -1,27 +1,28 @@
/* eslint require-atomic-updates: off */
import lodash from 'lodash'
const {isString, isPlainObject} = lodash
const {isString, isPlainObject} = require('lodash');
const {getGitHead} = require('../git');
const hideSensitive = require('../hide-sensitive');
const {hideSensitiveValues} = require('../utils');
const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
import {getGitHead} from '../git.js';
import hideSensitive from '../hide-sensitive.js';
import {hideSensitiveValues} from '../utils.js';
import {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} from './constants.js';
module.exports = {
const exported = {
verifyConditions: {
required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}),
},
analyzeCommits: {
default: ['@semantic-release/commit-analyzer'],
required: true,
dryRun: true,
outputValidator: output => !output || RELEASE_TYPE.includes(output),
outputValidator: (output) => !output || RELEASE_TYPE.includes(output),
preprocess: ({commits, ...inputs}) => ({
...inputs,
commits: commits.filter(commit => !/\[skip\s+release\]|\[release\s+skip\]/i.test(commit.message)),
commits: commits.filter((commit) => !/\[skip\s+release]|\[release\s+skip]/i.test(commit.message)),
}),
postprocess: results =>
postprocess: (results) =>
RELEASE_TYPE[
results.reduce((highest, result) => {
const typeIndex = RELEASE_TYPE.indexOf(result);
@@ -29,15 +30,17 @@ module.exports = {
}, -1)
],
},
verifyRelease: {
required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}),
},
generateNotes: {
required: false,
dryRun: true,
outputValidator: output => !output || isString(output),
outputValidator: (output) => !output || isString(output),
pipelineConfig: () => ({
getNextInput: ({nextRelease, ...context}, notes) => ({
...context,
@@ -49,11 +52,12 @@ module.exports = {
}),
postprocess: (results, {env}) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
},
prepare: {
required: false,
dryRun: false,
pipelineConfig: ({generateNotes}) => ({
getNextInput: async context => {
getNextInput: async (context) => {
const newGitHead = await getGitHead({cwd: context.cwd});
// If previous prepare plugin has created a commit (gitHead changed)
if (context.nextRelease.gitHead !== newGitHead) {
@@ -67,10 +71,11 @@ module.exports = {
},
}),
},
publish: {
required: false,
dryRun: false,
outputValidator: output => !output || isPlainObject(output),
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
@@ -80,10 +85,11 @@ module.exports = {
}),
}),
},
addChannel: {
required: false,
dryRun: false,
outputValidator: output => !output || isPlainObject(output),
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
@@ -93,12 +99,14 @@ module.exports = {
}),
}),
},
success: {
required: false,
dryRun: false,
pipelineConfig: () => ({settleAll: true}),
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
},
fail: {
required: false,
dryRun: false,
@@ -106,3 +114,17 @@ module.exports = {
preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}),
},
};
export default exported;
export const {
verifyConditions,
analyzeCommits,
verifyRelease,
generateNotes,
prepare,
publish,
addChannel,
success,
fail,
} = exported;
+4 -3
View File
@@ -1,5 +1,6 @@
const debug = require('debug')('semantic-release:get-commits');
const {getCommits} = require('./git');
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:get-commits');
import {getCommits} from './git.js';
/**
* Retrieve the list of commits on the current branch since the commit sha associated with the last release, or all the commits of the current branch if there is no last released version.
@@ -8,7 +9,7 @@ const {getCommits} = require('./git');
*
* @return {Promise<Array<Object>>} The list of commits on the branch `branch` since the last release.
*/
module.exports = async ({cwd, env, lastRelease: {gitHead: from}, nextRelease: {gitHead: to = 'HEAD'} = {}, logger}) => {
export default async ({cwd, env, lastRelease: {gitHead: from}, nextRelease: {gitHead: to = 'HEAD'} = {}, logger}) => {
if (from) {
debug('Use from: %s', from);
} else {
+25 -31
View File
@@ -1,35 +1,25 @@
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const {cosmiconfig} = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const {validatePlugin, parseConfig} = require('./plugins/utils');
import lodash from 'lodash'
const {castArray, pickBy, isNil, isString, isPlainObject} = lodash
import readPkgUp from 'read-pkg-up';
import {cosmiconfig} from 'cosmiconfig';
import resolveFrom from 'resolve-from';
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:config');
import {repoUrl} from './git.js';
import PLUGINS_DEFINITIONS from './definitions/plugins.js';
import plugins from './plugins';
import {validatePlugin, parseConfig} from './plugins/utils.js';
const CONFIG_NAME = 'release';
const CONFIG_FILES = [
'package.json',
`.${CONFIG_NAME}rc`,
`.${CONFIG_NAME}rc.json`,
`.${CONFIG_NAME}rc.yaml`,
`.${CONFIG_NAME}rc.yml`,
`.${CONFIG_NAME}rc.js`,
`${CONFIG_NAME}.config.js`,
];
module.exports = async (context, opts) => {
export default async (context, cliOptions) => {
const {cwd, env} = context;
const {config, filepath} = (await cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search(cwd)) || {};
const {config, filepath} = (await cosmiconfig(CONFIG_NAME).search(cwd)) || {};
debug('load config from: %s', filepath);
// Merge config file options and CLI/API options
let options = {...config, ...opts};
if (options.ci === false) {
options.noCi = true;
}
let options = {...config, ...cliOptions};
const pluginsPath = {};
let extendPaths;
@@ -38,14 +28,14 @@ module.exports = async (context, opts) => {
// If `extends` is defined, load and merge each shareable config with `options`
options = {
...castArray(extendPaths).reduce((result, extendPath) => {
const extendsOpts = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
const extendsOptions = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
// For each plugin defined in a shareable config, save in `pluginsPath` the extendable config path,
// so those plugin will be loaded relatively to the config file
Object.entries(extendsOpts)
Object.entries(extendsOptions)
.filter(([, value]) => Boolean(value))
.reduce((pluginsPath, [option, value]) => {
castArray(value).forEach(plugin => {
castArray(value).forEach((plugin) => {
if (option === 'plugins' && validatePlugin(plugin)) {
pluginsPath[parseConfig(plugin)[0]] = extendPath;
} else if (
@@ -58,7 +48,7 @@ module.exports = async (context, opts) => {
return pluginsPath;
}, pluginsPath);
return {...result, ...extendsOpts};
return {...result, ...extendsOptions};
}, {}),
...options,
};
@@ -83,16 +73,20 @@ module.exports = async (context, opts) => {
'@semantic-release/github',
],
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, option => !isNil(option)),
...pickBy(options, (option) => !isNil(option)),
...(options.branches ? {branches: castArray(options.branches)} : {}),
};
if (options.ci === false) {
options.noCi = true;
}
debug('options values: %O', options);
return {options, plugins: await plugins({...context, options}, pluginsPath)};
};
async function pkgRepoUrl(opts) {
const {packageJson} = (await readPkgUp(opts)) || {};
async function pkgRepoUrl(options) {
const {packageJson} = (await readPkgUp(options)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}
+3 -3
View File
@@ -1,7 +1,7 @@
const SemanticReleaseError = require('@semantic-release/error');
const ERROR_DEFINITIONS = require('./definitions/errors');
import SemanticReleaseError from '@semantic-release/error';
import ERROR_DEFINITIONS from './definitions/errors.js';
module.exports = (code, ctx = {}) => {
export default (code, ctx = {}) => {
const {message, details} = ERROR_DEFINITIONS[code](ctx);
return new SemanticReleaseError(message, code, details);
};
+79 -21
View File
@@ -1,7 +1,52 @@
const {parse, format} = require('url'); // eslint-disable-line node/no-deprecated-api
const {isNil} = require('lodash');
const hostedGitInfo = require('hosted-git-info');
const {verifyAuth} = require('./git');
import {format} from 'url';
import lodash from 'lodash'
const {isNil} = lodash
import hostedGitInfo from 'hosted-git-info';
import {verifyAuth} from './git.js';
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:get-git-auth-url');
/**
* Machinery to format a repository URL with the given credentials
*
* @param {String} protocol URL protocol (which should not be present in repositoryUrl)
* @param {String} repositoryUrl User-given repository URL
* @param {String} gitCredentials The basic auth part of the URL
*
* @return {String} The formatted Git repository URL.
*/
function formatAuthUrl(protocol, repositoryUrl, gitCredentials) {
const [match, auth, host, basePort, path] =
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<port>\d+)?:?\/?(?<path>.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = new URL(
match ? `ssh://${auth ? `${auth}@` : ''}${host}${basePort ? `:${basePort}` : ''}/${path}` : repositoryUrl
);
return format({
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
}
/**
* Verify authUrl by calling git.verifyAuth, but don't throw on failure
*
* @param {Object} context semantic-release context.
* @param {String} authUrl Repository URL to verify
*
* @return {String} The authUrl as is if the connection was successfull, null otherwise
*/
async function ensureValidAuthUrl({cwd, env, branch}, authUrl) {
try {
await verifyAuth(authUrl, branch.name, {cwd, env});
return authUrl;
} catch (error) {
debug(error);
return null;
}
}
/**
* Determine the the git repository URL to use to push, either:
@@ -14,7 +59,8 @@ const {verifyAuth} = require('./git');
*
* @return {String} The formatted Git repository URL.
*/
module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
export default async (context) => {
const {cwd, env, branch} = context;
const GIT_TOKENS = {
GIT_CREDENTIALS: undefined,
GH_TOKEN: undefined,
@@ -25,10 +71,13 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
GITLAB_TOKEN: 'gitlab-ci-token:',
BB_TOKEN: 'x-token-auth:',
BITBUCKET_TOKEN: 'x-token-auth:',
BB_TOKEN_BASIC_AUTH: '',
BITBUCKET_TOKEN_BASIC_AUTH: '',
};
let {repositoryUrl} = context.options;
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
const {protocol, ...parsed} = parse(repositoryUrl);
const {protocol, ...parsed} = new URL(repositoryUrl);
if (info && info.getDefaultRepresentation() === 'shortcut') {
// Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
@@ -40,25 +89,34 @@ 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 {
debug('Verifying ssh auth by attempting to push to %s', repositoryUrl);
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
} catch (_) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
debug('SSH key auth failed, falling back to https.');
const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(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] =
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = parse(
match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl
);
// Skip verification if there is no ambiguity on which env var to use for authentication
if (envVars.length === 1) {
const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`;
return formatAuthUrl(protocol, repositoryUrl, gitCredentials);
}
return format({
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
if (envVars.length > 1) {
debug(`Found ${envVars.length} credentials in environment, trying all of them`);
const candidateRepositoryUrls = [];
for (const envVar of envVars) {
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`;
const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials);
candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl));
}
const validRepositoryUrls = await Promise.all(candidateRepositoryUrls);
const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null);
if (chosenAuthUrlIndex > -1) {
debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`);
return validRepositoryUrls[chosenAuthUrlIndex];
}
}
}
+7 -6
View File
@@ -1,6 +1,7 @@
const {isUndefined} = require('lodash');
const semver = require('semver');
const {makeTag, isSameChannel} = require('./utils');
import lodash from 'lodash'
const {isUndefined} = lodash
import semver from 'semver';
import {makeTag, isSameChannel} from './utils.js';
/**
* Last release.
@@ -26,11 +27,11 @@ const {makeTag, isSameChannel} = require('./utils');
*
* @return {LastRelease} The last tagged release or empty object if none is found.
*/
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
export default ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
(tag) =>
((branch.type === 'prerelease' && tag.channels.some((channel) => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
+3 -3
View File
@@ -1,7 +1,7 @@
const {Signale} = require('signale');
const figures = require('figures');
import {Signale} from 'signale';
import figures from 'figures';
module.exports = ({stdout, stderr}) =>
export default ({stdout, stderr}) =>
new Signale({
config: {displayTimestamp: true, underlineMessage: false, displayLabel: false},
disabled: false,
+5 -5
View File
@@ -1,8 +1,8 @@
const semver = require('semver');
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
const {isSameChannel, getLatestVersion, tagsToVersions, highest} = require('./utils');
import semver from 'semver';
import {FIRST_RELEASE, FIRSTPRERELEASE} from './definitions/constants.js';
import {isSameChannel, getLatestVersion, tagsToVersions, highest} from './utils.js';
module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
export default ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
let version;
if (lastRelease.version) {
const {major, minor, patch} = semver.parse(lastRelease.version);
@@ -10,7 +10,7 @@ module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) =
if (branch.type === 'prerelease') {
if (
semver.prerelease(lastRelease.version) &&
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
lastRelease.channels.some((lastReleaseChannel) => isSameChannel(lastReleaseChannel, channel))
) {
version = highest(
semver.inc(lastRelease.version, 'prerelease'),
+7 -6
View File
@@ -1,8 +1,9 @@
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');
import lodash from 'lodash'
const {uniqBy, intersection} = lodash
import semver from 'semver';
import semverDiff from 'semver-diff';
import getLastRelease from './get-last-release.js';
import {makeTag, getLowerBound} from './utils.js';
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
@@ -11,7 +12,7 @@ const {makeTag, getLowerBound} = require('./utils');
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
export default (context) => {
const {
branch,
branches,
+76 -52
View File
@@ -1,8 +1,9 @@
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');
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import execa from 'execa';
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:git');
import {GIT_NOTE_REF} from './definitions/constants.js';
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
@@ -14,8 +15,8 @@ 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) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
async function getTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
}
/**
@@ -27,10 +28,10 @@ async function getTagHead(tagName, execaOpts) {
* @return {Array<String>} List of git tags.
* @throws {Error} If the `git` command fails.
*/
async function getTags(branch, execaOpts) {
return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout
async function getTags(branch, execaOptions) {
return (await execa('git', ['tag', '--merged', branch], execaOptions)).stdout
.split('\n')
.map(tag => tag.trim())
.map((tag) => tag.trim())
.filter(Boolean);
}
@@ -42,12 +43,12 @@ async function getTags(branch, execaOpts) {
* @param {Object} [execaOpts] Options to pass to `execa`.
* @return {Promise<Array<Object>>} The list of commits between `from` and `to`.
*/
async function getCommits(from, to, execaOpts) {
async function getCommits(from, to, execaOptions) {
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
{cwd: execaOptions.cwd, env: {...process.env, ...execaOptions.env}}
)
)
).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
@@ -62,11 +63,11 @@ async function getCommits(from, to, execaOpts) {
* @return {Array<String>} List of git branches.
* @throws {Error} If the `git` command fails.
*/
async function getBranches(repositoryUrl, execaOpts) {
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOpts)).stdout
async function getBranches(repositoryUrl, execaOptions) {
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOptions)).stdout
.split('\n')
.map(branch => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1])
.filter(Boolean);
.filter(Boolean)
.map((branch) => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1]);
}
/**
@@ -77,9 +78,9 @@ async function getBranches(repositoryUrl, execaOpts) {
*
* @return {Boolean} `true` if the reference exists, falsy otherwise.
*/
async function isRefExists(ref, execaOpts) {
async function isRefExists(ref, execaOptions) {
try {
return (await execa('git', ['rev-parse', '--verify', ref], execaOpts)).exitCode === 0;
return (await execa('git', ['rev-parse', '--verify', ref], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -99,9 +100,9 @@ async function isRefExists(ref, execaOpts) {
* @param {String} branch The repository branch to fetch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
async function fetch(repositoryUrl, branch, ciBranch, execaOptions) {
const isDetachedHead =
(await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {...execaOpts, reject: false})).stdout === 'HEAD';
(await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {...execaOptions, reject: false})).stdout === 'HEAD';
try {
await execa(
@@ -114,7 +115,7 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
execaOptions
);
} catch (_) {
await execa(
@@ -126,7 +127,7 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
execaOptions
);
}
}
@@ -137,16 +138,16 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetchNotes(repositoryUrl, execaOpts) {
async function fetchNotes(repositoryUrl, execaOptions) {
try {
await execa(
'git',
['fetch', '--unshallow', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
execaOpts
execaOptions
);
} catch (_) {
await execa('git', ['fetch', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
...execaOpts,
...execaOptions,
reject: false,
});
}
@@ -159,8 +160,8 @@ async function fetchNotes(repositoryUrl, execaOpts) {
*
* @return {String} the sha of the HEAD commit.
*/
async function getGitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
async function getGitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
}
/**
@@ -170,9 +171,9 @@ async function getGitHead(execaOpts) {
*
* @return {string} The value of the remote git URL.
*/
async function repoUrl(execaOpts) {
async function repoUrl(execaOptions) {
try {
return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOpts)).stdout;
return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOptions)).stdout;
} catch (error) {
debug(error);
}
@@ -185,9 +186,9 @@ async function repoUrl(execaOpts) {
*
* @return {Boolean} `true` if the current working directory is in a git repository, falsy otherwise.
*/
async function isGitRepo(execaOpts) {
async function isGitRepo(execaOptions) {
try {
return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).exitCode === 0;
return (await execa('git', ['rev-parse', '--git-dir'], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -202,9 +203,9 @@ async function isGitRepo(execaOpts) {
*
* @throws {Error} if not authorized to push.
*/
async function verifyAuth(repositoryUrl, branch, execaOpts) {
async function verifyAuth(repositoryUrl, branch, execaOptions) {
try {
await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`], execaOpts);
await execa('git', ['push', '--dry-run', '--no-verify', repositoryUrl, `HEAD:${branch}`], execaOptions);
} catch (error) {
debug(error);
throw error;
@@ -220,8 +221,8 @@ async function verifyAuth(repositoryUrl, branch, execaOpts) {
*
* @throws {Error} if the tag creation failed.
*/
async function tag(tagName, ref, execaOpts) {
await execa('git', ['tag', tagName, ref], execaOpts);
async function tag(tagName, ref, execaOptions) {
await execa('git', ['tag', tagName, ref], execaOptions);
}
/**
@@ -232,8 +233,8 @@ async function tag(tagName, ref, execaOpts) {
*
* @throws {Error} if the push failed.
*/
async function push(repositoryUrl, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
async function push(repositoryUrl, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl], execaOptions);
}
/**
@@ -244,8 +245,8 @@ async function push(repositoryUrl, execaOpts) {
*
* @throws {Error} if the push failed.
*/
async function pushNotes(repositoryUrl, execaOpts) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOpts);
async function pushNotes(repositoryUrl, execaOptions) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOptions);
}
/**
@@ -256,9 +257,9 @@ async function pushNotes(repositoryUrl, execaOpts) {
*
* @return {Boolean} `true` if valid, falsy otherwise.
*/
async function verifyTagName(tagName, execaOpts) {
async function verifyTagName(tagName, execaOptions) {
try {
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).exitCode === 0;
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -272,9 +273,9 @@ async function verifyTagName(tagName, execaOpts) {
*
* @return {Boolean} `true` if valid, falsy otherwise.
*/
async function verifyBranchName(branch, execaOpts) {
async function verifyBranchName(branch, execaOptions) {
try {
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOpts)).exitCode === 0;
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -289,10 +290,10 @@ async function verifyBranchName(branch, execaOpts) {
*
* @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(repositoryUrl, branch, execaOpts) {
async function isBranchUpToDate(repositoryUrl, branch, execaOptions) {
return (
(await getGitHead(execaOpts)) ===
(await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts)).stdout.match(/^(?<ref>\w+)?/)[1]
(await getGitHead(execaOptions)) ===
(await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOptions)).stdout.match(/^(?<ref>\w+)?/)[1]
);
}
@@ -304,9 +305,9 @@ async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
*
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
*/
async function getNote(ref, execaOpts) {
async function getNote(ref, execaOptions) {
try {
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout);
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout);
} catch (error) {
if (error.exitCode === 1) {
return {};
@@ -318,17 +319,40 @@ async function getNote(ref, execaOpts) {
}
/**
* Get and parse the JSON note of a given reference.
* Add JSON note to 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);
async function addNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOptions);
}
module.exports = {
const exported = {
getTagHead,
getTags,
getCommits,
getBranches,
isRefExists,
fetch,
fetchNotes,
getGitHead,
repoUrl,
isGitRepo,
verifyAuth,
tag,
push,
pushNotes,
verifyTagName,
isBranchUpToDate,
verifyBranchName,
getNote,
addNote,
};
export default exported;
export {
getTagHead,
getTags,
getCommits,
+17 -8
View File
@@ -1,12 +1,21 @@
const {escapeRegExp, size, isString} = require('lodash');
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('./definitions/constants');
import lodash from 'lodash'
const {escapeRegExp, size, isString} = lodash
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from './definitions/constants.js';
module.exports = env => {
const toReplace = Object.keys(env).filter(
envVar => /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE
export default (env) => {
const toReplace = Object.keys(env).filter((envVar) => {
// https://github.com/semantic-release/semantic-release/issues/1558
if (envVar === 'GOPRIVATE') {
return false;
}
return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
});
const regexp = new RegExp(
toReplace.map((envVar) => `${escapeRegExp(env[envVar])}|${escapeRegExp(encodeURI(env[envVar]))}`).join('|'),
'g'
);
const regexp = new RegExp(toReplace.map(envVar => escapeRegExp(env[envVar])).join('|'), 'g');
return output =>
return (output) =>
output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
};
+15 -14
View File
@@ -1,12 +1,13 @@
const {identity, isPlainObject, omit, castArray, isNil, isString} = require('lodash');
const AggregateError = require('aggregate-error');
const getError = require('../get-error');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
const {validatePlugin, validateStep, loadPlugin, parseConfig} = require('./utils');
const pipeline = require('./pipeline');
const normalize = require('./normalize');
import lodash from 'lodash'
const {identity, isPlainObject, omit, castArray, isNil, isString} = lodash
import AggregateError from 'aggregate-error';
import getError from '../get-error.js';
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
import {validatePlugin, validateStep, loadPlugin, parseConfig} from './utils.js';
import pipeline from './pipeline.js';
import normalize from './normalize.js';
module.exports = (context, pluginsPath) => {
export default (context, pluginsPath) => {
let {options, logger} = context;
const errors = [];
@@ -46,14 +47,14 @@ module.exports = (context, pluginsPath) => {
const pluginsConf = Object.entries(PLUGINS_DEFINITIONS).reduce(
(pluginsConf, [type, {required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]) => {
let pluginOpts;
let pluginOptions;
if (isNil(options[type]) && def) {
pluginOpts = def;
pluginOptions = def;
} else {
// If an object is passed and the path is missing, merge it with step options
if (isPlainObject(options[type]) && !options[type].path) {
options[type] = castArray(plugins[type]).map(plugin =>
options[type] = castArray(plugins[type]).map((plugin) =>
plugin ? [plugin[0], Object.assign(plugin[1], options[type])] : plugin
);
}
@@ -63,10 +64,10 @@ module.exports = (context, pluginsPath) => {
return pluginsConf;
}
pluginOpts = options[type];
pluginOptions = options[type];
}
const steps = castArray(pluginOpts).map(pluginOpt =>
const steps = castArray(pluginOptions).map((pluginOpt) =>
normalize(
{...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), 'plugins')},
type,
@@ -75,7 +76,7 @@ module.exports = (context, pluginsPath) => {
)
);
pluginsConf[type] = async input =>
pluginsConf[type] = async (input) =>
postprocess(
await pipeline(steps, pipelineConfig && pipelineConfig(pluginsConf, logger))(await preprocess(input)),
input
+11 -9
View File
@@ -1,11 +1,13 @@
const {isPlainObject, isFunction, noop, cloneDeep, omit} = require('lodash');
const debug = require('debug')('semantic-release:plugins');
const getError = require('../get-error');
const {extractErrors} = require('../utils');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
const {loadPlugin, parseConfig} = require('./utils');
import lodash from 'lodash'
const {isPlainObject, isFunction, noop, cloneDeep, omit} = lodash
import debugFactory from 'debug';
const debug = debugFactory('semantic-release:plugins');
import getError from '../get-error.js';
import {extractErrors} from '../utils.js';
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
import {loadPlugin, parseConfig} from './utils.js';
module.exports = (context, type, pluginOpt, pluginsPath) => {
export default (context, type, pluginOpt, pluginsPath) => {
const {stdout, stderr, options, logger} = context;
if (!pluginOpt) {
return noop;
@@ -26,7 +28,7 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
throw getError('EPLUGIN', {type, pluginName});
}
const validator = async input => {
const validator = async (input) => {
const {dryRun, outputValidator} = PLUGINS_DEFINITIONS[type] || {};
try {
if (!input.options.dryRun || dryRun) {
@@ -48,7 +50,7 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
logger.warn(`Skip step "${type}" of plugin "${pluginName}" in dry-run mode`);
} catch (error) {
logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
extractErrors(error).forEach(err => Object.assign(err, {pluginName}));
extractErrors(error).forEach((err) => Object.assign(err, {pluginName}));
throw error;
}
};
+6 -5
View File
@@ -1,7 +1,8 @@
const {identity} = require('lodash');
const pReduce = require('p-reduce');
const AggregateError = require('aggregate-error');
const {extractErrors} = require('../utils');
import lodash from 'lodash'
const {identity} = lodash
import pReduce from 'p-reduce';
import AggregateError from 'aggregate-error';
import {extractErrors} from '../utils.js';
/**
* A Function that execute a list of function sequencially. If at least one Function ins the pipeline throws an Error or rejects, the pipeline function rejects as well.
@@ -25,7 +26,7 @@ const {extractErrors} = require('../utils');
*
* @return {Pipeline} A Function that execute the `steps` sequencially
*/
module.exports = (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async input => {
export default (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async (input) => {
const results = [];
const errors = [];
await pReduce(
+15 -6
View File
@@ -1,9 +1,10 @@
const {dirname} = require('path');
const {isString, isFunction, castArray, isArray, isPlainObject, isNil} = require('lodash');
const resolveFrom = require('resolve-from');
import {dirname} from 'path';
import lodash from 'lodash'
const {isString, isFunction, castArray, isArray, isPlainObject, isNil} = lodash
import resolveFrom from 'resolve-from';
const validateSteps = conf => {
return conf.every(conf => {
const validateSteps = (conf) => {
return conf.every((conf) => {
if (
isArray(conf) &&
(conf.length === 1 || conf.length === 2) &&
@@ -65,4 +66,12 @@ function parseConfig(plugin) {
return [path, config || {}];
}
module.exports = {validatePlugin, validateStep, loadPlugin, parseConfig};
const exported = {
validatePlugin,
validateStep,
loadPlugin,
parseConfig,
};
export default exported;
export {validatePlugin, validateStep, loadPlugin, parseConfig};
+39 -14
View File
@@ -1,6 +1,7 @@
const {isFunction, union, template} = require('lodash');
const semver = require('semver');
const hideSensitive = require('./hide-sensitive');
import lodash from 'lodash'
const {isFunction, union, template} = lodash
import semver from 'semver';
import hideSensitive from './hide-sensitive.js';
function extractErrors(err) {
return err && isFunction(err[Symbol.iterator]) ? [...err] : [err];
@@ -8,13 +9,13 @@ function extractErrors(err) {
function hideSensitiveValues(env, objs) {
const hideFunction = hideSensitive(env);
return objs.map(obj => {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop]) {
obj[prop] = hideFunction(obj[prop]);
return objs.map((object) => {
Object.getOwnPropertyNames(object).forEach((prop) => {
if (object[prop]) {
object[prop] = hideFunction(object[prop]);
}
});
return obj;
return object;
});
}
@@ -31,9 +32,14 @@ function isMaintenanceRange(range) {
}
function getUpperBound(range) {
return semver.valid(range)
const result = semver.valid(range)
? range
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+)$/) || [])[1];
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+(-\d+)?)$/) || [])[1];
return result
? // https://github.com/npm/node-semver/issues/322
result.replace(/-\d+$/, '')
: result;
}
function getLowerBound(range) {
@@ -49,17 +55,17 @@ function lowest(version1, version2) {
}
function getLatestVersion(versions, {withPrerelease} = {}) {
return versions.filter(version => withPrerelease || !semver.prerelease(version)).sort(semver.rcompare)[0];
return versions.filter((version) => withPrerelease || !semver.prerelease(version)).sort(semver.rcompare)[0];
}
function getEarliestVersion(versions, {withPrerelease} = {}) {
return versions.filter(version => withPrerelease || !semver.prerelease(version)).sort(semver.compare)[0];
return versions.filter((version) => withPrerelease || !semver.prerelease(version)).sort(semver.compare)[0];
}
function getFirstVersion(versions, lowerBranches) {
const lowerVersion = union(...lowerBranches.map(({tags}) => tagsToVersions(tags))).sort(semver.rcompare);
if (lowerVersion[0]) {
return versions.sort(semver.compare).find(version => semver.gt(version, lowerVersion[0]));
return versions.sort(semver.compare).find((version) => semver.gt(version, lowerVersion[0]));
}
return getEarliestVersion(versions);
@@ -77,7 +83,26 @@ function isSameChannel(channel, otherChannel) {
return channel === otherChannel || (!channel && !otherChannel);
}
module.exports = {
const exported = {
extractErrors,
hideSensitiveValues,
tagsToVersions,
isMajorRange,
isMaintenanceRange,
getUpperBound,
getLowerBound,
highest,
lowest,
getLatestVersion,
getEarliestVersion,
getFirstVersion,
getRange,
makeTag,
isSameChannel,
};
export default exported;
export {
extractErrors,
hideSensitiveValues,
tagsToVersions,
+7 -6
View File
@@ -1,9 +1,10 @@
const {template, isString, isPlainObject} = require('lodash');
const AggregateError = require('aggregate-error');
const {isGitRepo, verifyTagName} = require('./git');
const getError = require('./get-error');
import lodash from 'lodash'
const {template, isString, isPlainObject} = lodash
import AggregateError from 'aggregate-error';
import {isGitRepo, verifyTagName} from './git.js';
import getError from './get-error.js';
module.exports = async context => {
export default async (context) => {
const {
cwd,
env,
@@ -29,7 +30,7 @@ module.exports = async context => {
errors.push(getError('ETAGNOVERSION', context));
}
branches.forEach(branch => {
branches.forEach((branch) => {
if (
!((isString(branch) && branch.trim()) || (isPlainObject(branch) && isString(branch.name) && branch.name.trim()))
) {
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="15 0 500 500" width="500px" height="500px">
<path fill="#494949" fill-rule="nonzero" d="M265 300a50 50 0 110-100 50 50 0 010 100zm0-15a35 35 0 100-70 35 35 0 000 70zM189 65v36c2 11 21 55 35 81-18-13-58-43-69-58a92 92 0 01-17-29l-71 40v86c9 5 23 15 31 18 11 4 59 9 88 10-21 9-66 29-85 31-16 3-27 1-34 0v85l69 39c9-4 24-12 31-17 9-7 37-46 52-71-2 22-8 71-15 89-6 15-13 23-17 29l79 45 73-42c1-9 2-28 0-38-1-11-21-55-34-81 18 14 57 44 69 59 11 13 15 24 17 30l74-42v-84c-8-5-24-15-34-19s-58-9-87-10c21-10 66-29 85-31 18-3 29-1 36 1v-87l-70-40-31 19c-9 7-37 46-53 70 3-22 9-71 16-89 6-15 13-24 18-29l-79-45-77 44zm77-65l217 125v250L266 500 49 375V125L266 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 715 B

+17214
View File
File diff suppressed because it is too large Load Diff
+49 -39
View File
@@ -1,5 +1,6 @@
{
"name": "semantic-release",
"type": "module",
"description": "Automated semver compliant package publishing",
"version": "0.0.0-development",
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
@@ -7,9 +8,7 @@
"files": [
"test/**/*.test.js"
],
"helpers": [
"test/helpers/**/*"
]
"timeout": "2m"
},
"bin": {
"semantic-release": "bin/semantic-release.js"
@@ -22,57 +21,58 @@
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "^7.0.0",
"@semantic-release/commit-analyzer": "^8.0.0",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "^6.0.0",
"@semantic-release/npm": "^6.0.0",
"@semantic-release/release-notes-generator": "^7.1.2",
"@semantic-release/github": "^7.0.0",
"@semantic-release/npm": "^7.0.0",
"@semantic-release/release-notes-generator": "^9.0.0",
"aggregate-error": "^3.0.0",
"cosmiconfig": "^6.0.0",
"cosmiconfig": "^7.0.0",
"debug": "^4.0.0",
"env-ci": "^5.0.0",
"execa": "^4.0.0",
"execa": "^5.0.0",
"figures": "^3.0.0",
"find-versions": "^3.0.0",
"get-stream": "^5.0.0",
"find-versions": "^4.0.0",
"get-stream": "^6.0.0",
"git-log-parser": "^1.2.0",
"hook-std": "^2.0.0",
"hosted-git-info": "^3.0.0",
"lodash": "^4.17.15",
"marked": "^0.8.0",
"marked-terminal": "^3.2.0",
"hosted-git-info": "^4.0.0",
"lodash": "^4.17.21",
"marked": "^2.0.0",
"marked-terminal": "^4.1.1",
"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": "^7.1.1",
"semver": "^7.3.2",
"semver-diff": "^3.1.1",
"signale": "^1.2.1",
"yargs": "^15.0.1"
"yargs": "^16.2.0"
},
"devDependencies": {
"ava": "^2.0.0",
"clear-module": "^4.0.0",
"codecov": "^3.0.0",
"delay": "^4.0.0",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^10.2.1",
"js-yaml": "^3.10.0",
"mockserver-client": "^5.1.1",
"nock": "^11.1.0",
"nyc": "^15.0.0",
"p-retry": "^4.0.0",
"proxyquire": "^2.0.0",
"sinon": "^8.0.4",
"stream-buffers": "^3.0.2",
"tempy": "^0.3.0",
"xo": "^0.25.0"
"ava": "3.15.0",
"cjs2esm": "^2.0.0",
"clear-module": "4.1.1",
"codecov": "3.8.2",
"delay": "5.0.0",
"dockerode": "3.3.0",
"file-url": "3.0.0",
"fs-extra": "10.0.0",
"got": "11.8.2",
"js-yaml": "4.1.0",
"mockserver-client": "5.11.2",
"nock": "13.1.1",
"nyc": "15.1.0",
"p-retry": "4.6.1",
"proxyquire": "2.1.3",
"sinon": "11.1.1",
"stream-buffers": "3.0.2",
"tempy": "1.0.1",
"xo": "0.29.1"
},
"engines": {
"node": ">=10.13"
"node": ">=14.17"
},
"files": [
"bin",
@@ -94,7 +94,7 @@
"version"
],
"license": "MIT",
"main": "index.js",
"exports": "./index.js",
"nyc": {
"include": [
"lib/**/*.js",
@@ -124,10 +124,20 @@
"lint": "xo",
"pretest": "npm run lint",
"semantic-release": "./bin/semantic-release.js",
"test": "nyc ava -v"
"test": "nyc ava -v",
"test:ci": "nyc ava -v"
},
"xo": {
"prettier": true,
"space": true
"space": true,
"rules": {
"unicorn/string-content": "off",
"import/extensions": "off"
}
},
"renovate": {
"extends": [
"github>semantic-release/.github"
]
}
}
+6 -5
View File
@@ -1,5 +1,6 @@
import test from 'ava';
import {union} from 'lodash';
import lodash from 'lodash'
const {union} = lodash
import semver from 'semver';
import proxyquire from 'proxyquire';
@@ -12,7 +13,7 @@ const merge = (branches, source, target, tag) => {
);
};
test('Enforce ranges with branching release workflow', async t => {
test('Enforce ranges with branching release workflow', async (t) => {
const branches = [
{name: '1.x', tags: []},
{name: '1.0.x', tags: []},
@@ -189,7 +190,7 @@ test('Enforce ranges with branching release workflow', async t => {
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
});
test('Throw SemanticReleaseError for invalid configurations', async t => {
test('Throw SemanticReleaseError for invalid configurations', async (t) => {
const branches = [
{name: '123', range: '123', tags: []},
{name: '1.x', tags: []},
@@ -224,7 +225,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
t.truthy(errors[4].details);
});
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
test('Throw a SemanticReleaseError if there is duplicate branches', async (t) => {
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
@@ -239,7 +240,7 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
test('Throw a SemanticReleaseError for each invalid branch name', async (t) => {
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
+3 -3
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import expand from '../../lib/branches/expand';
import {gitRepo, gitCommits, gitCheckout, gitPush} from '../helpers/git-utils';
import expand from '../../lib/branches/expand.js';
import {gitRepo, gitCommits, gitCheckout, gitPush} from '../helpers/git-utils.js';
test('Expand branches defined with globs', async t => {
test('Expand branches defined with globs', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
+7 -7
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} from '../helpers/git-utils';
import getTags from '../../lib/branches/get-tags.js';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} from '../helpers/git-utils.js';
test('Get the valid tags', async t => {
test('Get the valid tags', async (t) => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
@@ -28,7 +28,7 @@ test('Get the valid tags', async t => {
]);
});
test('Get the valid tags from multiple branches', async t => {
test('Get the valid tags from multiple branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -71,7 +71,7 @@ test('Get the valid tags from multiple branches', async t => {
]);
});
test('Return branches with and empty tags array if no valid tag is found', async t => {
test('Return branches with and empty tags array if no valid tag is found', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
@@ -85,7 +85,7 @@ test('Return branches with and empty tags array if no valid tag is found', async
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 => {
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('next', true, {cwd});
@@ -108,7 +108,7 @@ test('Return branches with and empty tags array if no valid tag is found in hist
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
+14 -14
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import normalize from '../../lib/branches/normalize';
import normalize from '../../lib/branches/normalize.js';
const toTags = versions => versions.map(version => ({version}));
const toTags = (versions) => versions.map((version) => ({version}));
test('Maintenance branches - initial state', t => {
test('Maintenance branches - initial state', (t) => {
const maintenance = [
{name: '1.x', channel: '1.x', tags: []},
{name: '1.1.x', tags: []},
@@ -48,7 +48,7 @@ test('Maintenance branches - initial state', t => {
);
});
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', t => {
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0'])},
{name: 'name', range: '1.1.x', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1'])},
@@ -110,7 +110,7 @@ test('Maintenance branches - cap range to first release present on default branc
);
});
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', t => {
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0'])},
{name: '2.x.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])},
@@ -147,7 +147,7 @@ test('Maintenance branches - cap range to default branch last release if all rel
);
});
test('Release branches - initial state', t => {
test('Release branches - initial state', (t) => {
const release = [
{name: 'master', tags: []},
{name: 'next', channel: 'next', tags: []},
@@ -187,7 +187,7 @@ test('Release branches - initial state', t => {
);
});
test('Release branches - 3 release branches', t => {
test('Release branches - 3 release branches', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.0.2'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0'])},
@@ -220,7 +220,7 @@ test('Release branches - 3 release branches', t => {
);
});
test('Release branches - 2 release branches', t => {
test('Release branches - 2 release branches', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
@@ -251,7 +251,7 @@ test('Release branches - 2 release branches', t => {
);
});
test('Release branches - 1 release branches', t => {
test('Release branches - 1 release branches', (t) => {
const release = [{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0'])}];
t.deepEqual(
@@ -260,7 +260,7 @@ test('Release branches - 1 release branches', t => {
);
});
test('Release branches - cap ranges to first release only present on following branch', t => {
test('Release branches - cap ranges to first release only present on following branch', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'])},
@@ -286,7 +286,7 @@ test('Release branches - cap ranges to first release only present on following b
);
});
test('Release branches - Handle missing previous tags in branch history', t => {
test('Release branches - Handle missing previous tags in branch history', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '2.0.0'])},
@@ -317,7 +317,7 @@ test('Release branches - Handle missing previous tags in branch history', t => {
);
});
test('Release branches - limit releases on 2nd and 3rd branch 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'])},
@@ -357,7 +357,7 @@ test('Release branches - limit releases on 2nd and 3rd branch based on 1st branc
);
});
test('Prerelease branches', t => {
test('Prerelease branches', (t) => {
const prerelease = [
{name: 'beta', channel: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: 'preview', tags: []},
@@ -372,7 +372,7 @@ test('Prerelease branches', t => {
);
});
test('Allow to set channel to "false" to prevent default', t => {
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: []},
+41 -42
View File
@@ -1,29 +1,28 @@
import test from 'ava';
import {escapeRegExp} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
import lodash from 'lodash'
const {escapeRegExp} = lodash
const proxyquire = require('proxyquire').noPreserveCache();
import sinon from 'sinon';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants.js';
const requireNoCache = proxyquire.noPreserveCache();
test.beforeEach(t => {
test.beforeEach((t) => {
t.context.logs = '';
t.context.errors = '';
t.context.stdout = stub(process.stdout, 'write').callsFake(val => {
t.context.logs += val.toString();
t.context.stdout = sinon.stubprocess.stdout, 'write').callsFake((value) => {
t.context.logs += value.toString();
});
t.context.stderr = stub(process.stderr, 'write').callsFake(val => {
t.context.errors += val.toString();
t.context.stderr = sinon.stubprocess.stderr, 'write').callsFake((value) => {
t.context.errors += value.toString();
});
});
test.afterEach.always(t => {
test.afterEach.always((t) => {
t.context.stdout.restore();
t.context.stderr.restore();
});
test.serial('Pass options to semantic-release API', async t => {
const run = stub().resolves(true);
test.serial('Pass options to semantic-release API', async (t) => {
const run = sinon.stub).resolves(true);
const argv = [
'',
'',
@@ -65,7 +64,7 @@ test.serial('Pass options to semantic-release API', async t => {
'--debug',
'-d',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -88,8 +87,8 @@ test.serial('Pass options to semantic-release API', async t => {
t.is(exitCode, 0);
});
test.serial('Pass options to semantic-release API with alias arguments', async t => {
const run = stub().resolves(true);
test.serial('Pass options to semantic-release API with alias arguments', async (t) => {
const run = sinon.stub).resolves(true);
const argv = [
'',
'',
@@ -107,7 +106,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
'config2',
'--dry-run',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -121,10 +120,10 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
t.is(exitCode, 0);
});
test.serial('Pass unknown options to semantic-release API', async t => {
const run = stub().resolves(true);
test.serial('Pass unknown options to semantic-release API', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', '--bool', '--first-option', 'value1', '--second-option', 'value2', '--second-option', 'value3'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -135,10 +134,10 @@ test.serial('Pass unknown options to semantic-release API', async t => {
t.is(exitCode, 0);
});
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async t => {
const run = stub().resolves(true);
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', '--publish', 'false'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -147,10 +146,10 @@ test.serial('Pass empty Array to semantic-release API for list option set to "fa
t.is(exitCode, 0);
});
test.serial('Do not set properties in option for which arg is not in command line', async t => {
const run = stub().resolves(true);
test.serial('Do not set properties in option for which arg is not in command line', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', '-b', 'master'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
await cli();
@@ -164,10 +163,10 @@ test.serial('Do not set properties in option for which arg is not in command lin
t.false('e' in run.args[0][0]);
});
test.serial('Display help', async t => {
const run = stub().resolves(true);
test.serial('Display help', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', '--help'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -175,10 +174,10 @@ test.serial('Display help', async t => {
t.is(exitCode, 0);
});
test.serial('Return error exitCode and prints help if called with a command', async t => {
const run = stub().resolves(true);
test.serial('Return error exitCode and prints help if called with a command', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', 'pre'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -187,10 +186,10 @@ test.serial('Return error exitCode and prints help if called with a command', as
t.is(exitCode, 1);
});
test.serial('Return error exitCode if multiple plugin are set for single plugin', async t => {
const run = stub().resolves(true);
test.serial('Return error exitCode if multiple plugin are set for single plugin', async (t) => {
const run = sinon.stub).resolves(true);
const argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -199,10 +198,10 @@ test.serial('Return error exitCode if multiple plugin are set for single plugin'
t.is(exitCode, 1);
});
test.serial('Return error exitCode if semantic-release throw error', async t => {
const run = stub().rejects(new Error('semantic-release error'));
test.serial('Return error exitCode if semantic-release throw error', async (t) => {
const run = sinon.stub).rejects(new Error('semantic-release error'));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -210,11 +209,11 @@ test.serial('Return error exitCode if semantic-release throw error', async t =>
t.is(exitCode, 1);
});
test.serial('Hide sensitive environment variable values from the logs', async t => {
test.serial('Hide sensitive environment variable values from the logs', async (t) => {
const env = {MY_TOKEN: 'secret token'};
const run = stub().rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
const run = sinon.stub).rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const exitCode = await cli();
+9 -9
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {maintenance, prerelease, release} from '../../lib/definitions/branches';
import {maintenance, prerelease, release} from '../../lib/definitions/branches.js';
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(maintenance.filter({name: '1.x.x'}));
t.true(maintenance.filter({name: '1.0.x'}));
t.true(maintenance.filter({name: '1.x'}));
@@ -17,7 +17,7 @@ test('A "maintenance" branch is identified by having a "range" property or a "na
t.false(maintenance.filter({name: 'x.x.x'}));
});
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(maintenance.branchValidator({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.branchValidator({name: 'some-name', range: '1.1.x'}));
@@ -29,14 +29,14 @@ test('A "maintenance" branches must have a "range" property formatted like "N.x"
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
});
test('The "maintenance" branches must have unique ranges', t => {
test('The "maintenance" branches must have unique ranges', (t) => {
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x.x'}]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
});
test('A "prerelease" branch is identified by having a thruthy "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: ''}));
@@ -46,7 +46,7 @@ test('A "prerelease" branch is identified by having a thruthy "prerelease" prope
t.false(prerelease.filter({name: 'some-name'}));
});
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', t => {
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', (t) => {
t.true(prerelease.branchValidator({name: 'beta', prerelease: true}));
t.true(prerelease.branchValidator({name: 'some-name', prerelease: 'beta'}));
@@ -59,13 +59,13 @@ test('A "prerelease" branch must have a valid prerelease detonation in "prerelea
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
});
test('The "prerelease" branches must have unique "prerelease" property', t => {
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: 'alpha'}]));
});
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(release.filter({name: 'some-name'}));
t.false(release.filter({name: '1.x.x'}));
@@ -76,7 +76,7 @@ test('A "release" branch is identified by not havin a "range" or "prerelease" pr
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
});
test('There must be between 1 and 3 release branches', t => {
test('There must be between 1 and 3 release branches', (t) => {
t.true(release.branchesValidator([{name: 'branch1'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}]));
+8 -8
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import plugins from '../../lib/definitions/plugins';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants';
import plugins from '../../lib/definitions/plugins.js';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants.js';
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', (t) => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
t.false(plugins.analyzeCommits.outputValidator(1));
t.false(plugins.analyzeCommits.outputValidator({}));
@@ -12,7 +12,7 @@ test('The "analyzeCommits" plugin output must be either undefined or a valid sem
t.true(plugins.analyzeCommits.outputValidator('major'));
});
test('The "generateNotes" plugin output, if defined, must be a string', t => {
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
t.false(plugins.generateNotes.outputValidator(1));
t.false(plugins.generateNotes.outputValidator({}));
@@ -22,7 +22,7 @@ test('The "generateNotes" plugin output, if defined, must be a string', t => {
t.true(plugins.generateNotes.outputValidator('string'));
});
test('The "publish" plugin output, if defined, must be an object or "false"', t => {
test('The "publish" plugin output, if defined, must be an object or "false"', (t) => {
t.false(plugins.publish.outputValidator(1));
t.false(plugins.publish.outputValidator('string'));
@@ -33,7 +33,7 @@ test('The "publish" plugin output, if defined, must be an object or "false"', t
t.true(plugins.publish.outputValidator(false));
});
test('The "addChannel" plugin output, if defined, must be an object', t => {
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
t.false(plugins.addChannel.outputValidator(1));
t.false(plugins.addChannel.outputValidator('string'));
@@ -43,7 +43,7 @@ test('The "addChannel" plugin output, if defined, must be an object', t => {
t.true(plugins.addChannel.outputValidator(''));
});
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', t => {
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', (t) => {
const env = {MY_TOKEN: 'secret token'};
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(plugins.generateNotes.postprocess(['', 'note'], {env}), 'note');
@@ -63,7 +63,7 @@ test('The "generateNotes" plugins output are concatenated with separator and sen
);
});
test('The "analyzeCommits" plugins output are reduced to the highest release type', t => {
test('The "analyzeCommits" plugins output are reduced to the highest release type', (t) => {
t.is(plugins.analyzeCommits.postprocess(['major', 'minor']), 'major');
t.is(plugins.analyzeCommits.postprocess(['', 'minor']), 'minor');
t.is(plugins.analyzeCommits.postprocess([undefined, 'patch']), 'patch');
+1 -1
View File
@@ -1 +1 @@
module.exports = () => {};
export default () => {};
+5 -1
View File
@@ -1,4 +1,4 @@
module.exports = {
const exported = {
verifyConditions: () => {},
getLastRelease: () => {},
analyzeCommits: () => {},
@@ -6,3 +6,7 @@ module.exports = {
generateNotes: () => {},
publish: () => {},
};
export default exported;
export const {verifyConditions, getLastRelease, analyzeCommits, verifyRelease, generateNotes, publish} = exported;
+2 -2
View File
@@ -1,4 +1,4 @@
const SemanticReleaseError = require('@semantic-release/error');
import SemanticReleaseError from '@semantic-release/error';
class InheritedError extends SemanticReleaseError {
constructor(message, code) {
@@ -9,6 +9,6 @@ class InheritedError extends SemanticReleaseError {
}
}
module.exports = () => {
export default () => {
throw new InheritedError('Inherited error', 'EINHERITED');
};
+1 -1
View File
@@ -1,4 +1,4 @@
module.exports = () => {
export default () => {
const error = new Error('a');
error.errorProperty = 'errorProperty';
throw error;
+2 -2
View File
@@ -1,5 +1,5 @@
const AggregateError = require('aggregate-error');
import AggregateError from 'aggregate-error';
module.exports = () => {
export default () => {
throw new AggregateError([new Error('a'), new Error('b')]);
};
+1 -1
View File
@@ -1 +1 @@
module.exports = (pluginConfig, context) => context;
export default (pluginConfig, context) => context;
+1 -1
View File
@@ -1,4 +1,4 @@
module.exports = (pluginConfig, {env, logger}) => {
export default (pluginConfig, {env, logger}) => {
console.log(`Console: Exposing token ${env.MY_TOKEN}`);
logger.log(`Log: Exposing token ${env.MY_TOKEN}`);
logger.error(`Error: Console token ${env.MY_TOKEN}`);
+1 -1
View File
@@ -1 +1 @@
module.exports = () => {};
export default () => {};
+1 -1
View File
@@ -1 +1 @@
module.exports = (pluginConfig, context) => ({pluginConfig, context});
export default (pluginConfig, context) => ({pluginConfig, context});
+13 -13
View File
@@ -1,16 +1,16 @@
import test from 'ava';
import {stub} from 'sinon';
import getCommits from '../lib/get-commits';
import {gitRepo, gitCommits, gitDetachedHead} from './helpers/git-utils';
import sinon from 'sinon';
import getCommits from '../lib/get-commits.js';
import {gitRepo, gitCommits, gitDetachedHead} from './helpers/git-utils.js';
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.log = sinon.stub();
t.context.error = sinon.stub();
t.context.logger = {log: t.context.log, error: t.context.error};
});
test('Get all commits when there is no last release', async t => {
test('Get all commits when there is no last release', 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
@@ -24,7 +24,7 @@ test('Get all commits when there is no last release', async t => {
t.deepEqual(result, commits);
});
test('Get all commits since gitHead (from lastRelease)', async t => {
test('Get all commits since gitHead (from lastRelease)', 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
@@ -42,7 +42,7 @@ test('Get all commits since gitHead (from lastRelease)', async t => {
t.deepEqual(result, commits.slice(0, 2));
});
test('Get all commits since gitHead (from lastRelease) on a detached head repo', async t => {
test('Get all commits since gitHead (from lastRelease) on a detached head repo', 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
@@ -66,7 +66,7 @@ test('Get all commits since gitHead (from lastRelease) on a detached head repo',
t.truthy(result[0].committer.name);
});
test('Get all commits between lastRelease.gitHead and a shas', async t => {
test('Get all commits between lastRelease.gitHead and a shas', 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
@@ -82,10 +82,10 @@ test('Get all commits between lastRelease.gitHead and a shas', async t => {
// Verify the commits created and retrieved by the module are identical
t.is(result.length, 1);
t.deepEqual(result, commits.slice(1, commits.length - 1));
t.deepEqual(result, commits.slice(1, -1));
});
test('Return empty array if lastRelease.gitHead is the last commit', async t => {
test('Return empty array if lastRelease.gitHead is the last commit', 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
@@ -102,7 +102,7 @@ test('Return empty array if lastRelease.gitHead is the last commit', async t =>
t.deepEqual(result, []);
});
test('Return empty array if there is no commits', async t => {
test('Return empty array if there is no commits', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
+91 -29
View File
@@ -2,11 +2,12 @@ import path from 'path';
import {format} from 'util';
import test from 'ava';
import {writeFile, outputJson} from 'fs-extra';
import {omit} from 'lodash';
import lodash from 'lodash'
const {omit} = lodash
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import sinon from 'sinon';
import yaml from 'js-yaml';
import {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
import {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils.js';
const DEFAULT_PLUGINS = [
'@semantic-release/commit-analyzer',
@@ -15,12 +16,12 @@ const DEFAULT_PLUGINS = [
'@semantic-release/github',
];
test.beforeEach(t => {
t.context.plugins = stub().returns({});
test.beforeEach((t) => {
t.context.plugins = sinon.stub).returns({});
t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins});
});
test('Default values, reading repositoryUrl from package.json', async t => {
test('Default values, reading repositoryUrl from package.json', async (t) => {
const pkg = {repository: 'https://host.null/owner/package.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
@@ -47,7 +48,7 @@ test('Default values, reading repositoryUrl from package.json', async t => {
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
test('Default values, reading repositoryUrl from repo if not set in package.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
// Add remote.origin.url config
@@ -68,7 +69,7 @@ test('Default values, reading repositoryUrl from repo if not set in package.json
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
test('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async (t) => {
const pkg = {repository: 'https://host.null/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -90,7 +91,7 @@ test('Default values, reading repositoryUrl (http url) from package.json if not
t.is(result.tagFormat, `v\${version}`);
});
test('Convert "ci" option to "noCi"', async t => {
test('Convert "ci" option to "noCi"', async (t) => {
const pkg = {repository: 'https://host.null/owner/module.git', release: {ci: false}};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -102,7 +103,7 @@ test('Convert "ci" option to "noCi"', async t => {
t.is(result.noCi, true);
});
test('Read options from package.json', async t => {
test('Read options from package.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -125,7 +126,7 @@ test('Read options from package.json', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.yml', async t => {
test('Read options from .releaserc.yml', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -136,7 +137,7 @@ test('Read options from .releaserc.yml', async t => {
plugins: false,
};
// Create package.json in repository root
await writeFile(path.resolve(cwd, '.releaserc.yml'), yaml.safeDump(options));
await writeFile(path.resolve(cwd, '.releaserc.yml'), yaml.dump(options));
const {options: result} = await t.context.getConfig({cwd});
@@ -147,7 +148,7 @@ test('Read options from .releaserc.yml', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.json', async t => {
test('Read options from .releaserc.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -169,7 +170,7 @@ test('Read options from .releaserc.json', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.js', async t => {
test('Read options from .releaserc.js', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -191,7 +192,29 @@ test('Read options from .releaserc.js', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from release.config.js', async t => {
test('Read options from .releaserc.cjs', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branches: ['test_branch'],
repositoryUrl: 'https://host.null/owner/module.git',
tagFormat: `v\${version}`,
plugins: false,
};
// Create .releaserc.cjs in repository root
await writeFile(path.resolve(cwd, '.releaserc.cjs'), `module.exports = ${JSON.stringify(options)}`);
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from .releaserc.cjs
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from .releaserc.cjs
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from release.config.js', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -213,7 +236,29 @@ test('Read options from release.config.js', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Prioritise CLI/API parameters over file configuration and git repo', async t => {
test('Read options from release.config.cjs', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branches: ['test_branch'],
repositoryUrl: 'https://host.null/owner/module.git',
tagFormat: `v\${version}`,
plugins: false,
};
// Create release.config.cjs in repository root
await writeFile(path.resolve(cwd, 'release.config.cjs'), `module.exports = ${JSON.stringify(options)}`);
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from release.config.cjs
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from release.config.cjs
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Prioritise CLI/API parameters over file configuration and git repo', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -243,7 +288,7 @@ test('Prioritise CLI/API parameters over file configuration and git repo', async
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read configuration from file path in "extends"', async t => {
test('Read configuration from file path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: './shareable.json'};
@@ -274,7 +319,7 @@ test('Read configuration from file path in "extends"', async t => {
});
});
test('Read configuration from module path in "extends"', async t => {
test('Read configuration from module path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: 'shareable'};
@@ -303,7 +348,7 @@ test('Read configuration from module path in "extends"', async t => {
});
});
test('Read configuration from an array of paths in "extends"', async t => {
test('Read configuration from an array of paths in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: ['./shareable1.json', './shareable2.json']};
@@ -342,7 +387,7 @@ test('Read configuration from an array of paths in "extends"', async t => {
});
});
test('Prioritize configuration from config file over "extends"', async t => {
test('Prioritize configuration from config file over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -378,7 +423,7 @@ test('Prioritize configuration from config file over "extends"', async t => {
});
});
test('Prioritize configuration from cli/API options over "extends"', async t => {
test('Prioritize configuration from cli/API options over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const cliOptions = {
@@ -421,7 +466,7 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Allow to unset properties defined in shareable config with "null"', async t => {
test('Allow to unset properties defined in shareable config with "null"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -466,7 +511,7 @@ test('Allow to unset properties defined in shareable config with "null"', async
});
});
test('Allow to unset properties defined in shareable config with "undefined"', async t => {
test('Allow to unset properties defined in shareable config with "undefined"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -502,17 +547,34 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
});
});
test('Throw an Error if one of the shareable config cannot be found', async t => {
test('Throw an Error if one of the shareable config cannot be found', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkhOptions = {extends: ['./shareable1.json', 'non-existing-path']};
const pkgOptions = {extends: ['./shareable1.json', 'non-existing-path']};
const options1 = {analyzeCommits: 'analyzeCommits'};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, 'package.json'), {release: pkhOptions});
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
const error = await t.throwsAsync(t.context.getConfig({cwd}), Error);
await t.throwsAsync(t.context.getConfig({cwd}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
});
});
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
test('Convert "ci" option to "noCi" when set from extended config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: './no-ci.json'};
const options = {
ci: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
await outputJson(path.resolve(cwd, 'no-ci.json'), options);
const {options: result} = await t.context.getConfig({cwd});
t.is(result.ci, false);
t.is(result.noCi, true);
});
+82 -28
View File
@@ -1,10 +1,10 @@
import test from 'ava';
import getAuthUrl from '../lib/get-git-auth-url';
import {gitRepo} from './helpers/git-utils';
import getAuthUrl from '../lib/get-git-auth-url.js';
import {gitRepo} from './helpers/git-utils.js';
const env = {GIT_ASKPASS: 'echo', GIT_TERMINAL_PROMPT: 0};
test('Return the same "git" formatted URL if "gitCredentials" is not defined', async t => {
test('Return the same "git" formatted URL if "gitCredentials" is not defined', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -13,7 +13,7 @@ test('Return the same "git" formatted URL if "gitCredentials" is not defined', a
);
});
test('Return the same "https" formatted URL if "gitCredentials" is not defined', async t => {
test('Return the same "https" formatted URL if "gitCredentials" is not defined', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -27,7 +27,7 @@ test('Return the same "https" formatted URL if "gitCredentials" is not defined',
);
});
test('Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -41,7 +41,7 @@ test('Return the "https" formatted URL if "gitCredentials" is not defined and re
);
});
test('Do not add trailing ".git" if not present in the origian URL', async t => {
test('Do not add trailing ".git" if not present in the origian URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -50,7 +50,7 @@ test('Do not add trailing ".git" if not present in the origian URL', async t =>
);
});
test('Handle "https" URL with group and subgroup', async t => {
test('Handle "https" URL with group and subgroup', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -64,7 +64,7 @@ test('Handle "https" URL with group and subgroup', async t => {
);
});
test('Handle "git" URL with group and subgroup', async t => {
test('Handle "git" URL with group and subgroup', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -78,7 +78,7 @@ test('Handle "git" URL with group and subgroup', async t => {
);
});
test('Convert shorthand URL', async t => {
test('Convert shorthand URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -92,7 +92,7 @@ test('Convert shorthand URL', async t => {
);
});
test('Convert GitLab shorthand URL', async t => {
test('Convert GitLab shorthand URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -106,7 +106,7 @@ test('Convert GitLab shorthand URL', async t => {
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -120,7 +120,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -133,7 +133,33 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user and with a custom port', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'host.null:6666:owner/repo.git'},
}),
'https://user:pass@host.null:6666/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user and with a custom port followed by a slash', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'host.null:6666:/owner/repo.git'},
}),
'https://user:pass@host.null:6666/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -147,7 +173,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -161,7 +187,7 @@ 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 => {
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(
@@ -174,7 +200,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -188,7 +214,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+http" URL', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+http" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -202,7 +228,7 @@ 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 => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "ssh" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -215,7 +241,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -229,7 +255,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -243,7 +269,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITH
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -257,7 +283,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -271,7 +297,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITL
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -285,7 +311,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -299,7 +325,35 @@ 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 => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN_BASIC_AUTH"', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, BB_TOKEN_BASIC_AUTH: 'username:token'},
branch: {name: 'master'},
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
}),
'https://username:token@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN_BASIC_AUTH"', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, BITBUCKET_TOKEN_BASIC_AUTH: 'username:token'},
branch: {name: 'master'},
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
}),
'https://username:token@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -312,7 +366,7 @@ test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async t => {
);
});
test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async t => {
test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -326,7 +380,7 @@ test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async
);
});
test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t => {
test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -340,7 +394,7 @@ test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t
);
});
test('Do not add git credential to repositoryUrl if push is allowed', async t => {
test('Do not add git credential to repositoryUrl if push is allowed', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
t.is(
+5 -5
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import getLastRelease from '../lib/get-last-release';
import getLastRelease from '../lib/get-last-release.js';
test('Get the highest non-prerelease valid tag', t => {
test('Get the highest non-prerelease valid tag', (t) => {
const result = getLastRelease({
branch: {
name: 'master',
@@ -18,7 +18,7 @@ test('Get the highest non-prerelease valid tag', t => {
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 => {
test('Get the highest prerelease valid tag, ignoring other tags from other prerelease channels', (t) => {
const result = getLastRelease({
branch: {
name: 'beta',
@@ -43,7 +43,7 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
});
});
test('Return empty object if no valid tag is found', t => {
test('Return empty object if no valid tag is found', (t) => {
const result = getLastRelease({
branch: {
name: 'master',
@@ -56,7 +56,7 @@ test('Return empty object if no valid tag is found', t => {
t.deepEqual(result, {});
});
test('Get the highest non-prerelease valid tag before a certain version', t => {
test('Get the highest non-prerelease valid tag before a certain version', (t) => {
const result = getLastRelease(
{
branch: {
+2 -2
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import {spy} from 'sinon';
import getLogger from '../lib/get-logger';
import getLogger from '../lib/get-logger.js';
test('Expose "error", "success" and "log" functions', t => {
test('Expose "error", "success" and "log" functions', (t) => {
const stdout = spy();
const stderr = spy();
const logger = getLogger({stdout: {write: stdout}, stderr: {write: stderr}});
+15 -15
View File
@@ -1,14 +1,14 @@
import test from 'ava';
import {stub} from 'sinon';
import getNextVersion from '../lib/get-next-version';
import sinon from 'sinon';
import getNextVersion from '../lib/get-next-version.js';
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.log = sinon.stub);
t.context.logger = {log: t.context.log};
});
test('Increase version for patch release', t => {
test('Increase version for patch release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -20,7 +20,7 @@ test('Increase version for patch release', t => {
);
});
test('Increase version for minor release', t => {
test('Increase version for minor release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -32,7 +32,7 @@ test('Increase version for minor release', t => {
);
});
test('Increase version for major release', t => {
test('Increase version for major release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -44,7 +44,7 @@ test('Increase version for major release', t => {
);
});
test('Return 1.0.0 if there is no previous release', t => {
test('Return 1.0.0 if there is no previous release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: []},
@@ -56,7 +56,7 @@ test('Return 1.0.0 if there is no previous release', t => {
);
});
test('Increase version for patch release on prerelease branch', t => {
test('Increase version for patch release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -106,7 +106,7 @@ test('Increase version for patch release on prerelease branch', t => {
);
});
test('Increase version for minor release on prerelease branch', t => {
test('Increase version for minor release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -156,7 +156,7 @@ test('Increase version for minor release on prerelease branch', t => {
);
});
test('Increase version for major release on prerelease branch', t => {
test('Increase version for major release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -206,7 +206,7 @@ test('Increase version for major release on prerelease branch', t => {
);
});
test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
test('Return 1.0.0 if there is no previous release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta', tags: []},
@@ -218,7 +218,7 @@ test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
);
});
test('Increase version for release on prerelease branch after previous commits were merged to release branch', t => {
test('Increase version for release on prerelease branch after previous commits were merged to release branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -239,7 +239,7 @@ test('Increase version for release on prerelease branch after previous commits w
);
});
test('Increase version for release on prerelease branch based on highest commit type since last regular release', t => {
test('Increase version for release on prerelease branch based on highest commit type since last regular release', (t) => {
t.is(
getNextVersion({
branch: {
@@ -259,7 +259,7 @@ test('Increase version for release on prerelease branch based on highest commit
);
});
test('Increase version for release on prerelease branch when there is no regular releases on other branches', t => {
test('Increase version for release on prerelease branch when there is no regular releases on other branches', (t) => {
t.is(
getNextVersion({
branch: {
+10 -10
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import getReleaseToAdd from '../lib/get-release-to-add';
import getReleaseToAdd from '../lib/get-release-to-add.js';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
@@ -42,7 +42,7 @@ test('Return versions merged from release to maintenance branch, excluding lower
});
});
test('Return versions merged between release branches', t => {
test('Return versions merged between release branches', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -83,7 +83,7 @@ test('Return versions merged between release branches', t => {
});
});
test('Return releases sorted by ascending order', t => {
test('Return releases sorted by ascending order', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -118,7 +118,7 @@ test('Return releases sorted by ascending order', t => {
});
});
test('No lastRelease', t => {
test('No lastRelease', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -149,7 +149,7 @@ test('No lastRelease', t => {
});
});
test('Ignore pre-release versions', t => {
test('Ignore pre-release versions', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -188,7 +188,7 @@ test('Ignore pre-release versions', t => {
});
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
@@ -214,7 +214,7 @@ test('Exclude versions merged from release to maintenance branch if they have th
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
test('Exclude versions merged between release branches if they have the same "channel"', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -236,7 +236,7 @@ test('Exclude versions merged between release branches if they have the same "ch
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
test('Exclude versions merged between release branches if they all have "channel" set to "false"', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -258,7 +258,7 @@ test('Exclude versions merged between release branches if they all have "channel
t.is(result, undefined);
});
test('Exclude versions number less than the latest version already released on that branch', t => {
test('Exclude versions number less than the latest version already released on that branch', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
+48 -40
View File
@@ -1,5 +1,6 @@
import test from 'ava';
import tempy from 'tempy';
import {
getTagHead,
isRefExists,
@@ -16,7 +17,8 @@ import {
getNote,
addNote,
fetchNotes,
} from '../lib/git';
} from '../lib/git.js';
import {
gitRepo,
gitCommits,
@@ -33,9 +35,10 @@ import {
gitAddNote,
gitGetNote,
gitFetch,
} from './helpers/git-utils';
initGit,
} from './helpers/git-utils.js';
test('Get the last commit sha', async t => {
test('Get the last commit sha', 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
@@ -46,14 +49,14 @@ test('Get the last commit sha', async t => {
t.is(result, commits[0].hash);
});
test('Throw error if the last commit sha cannot be found', async t => {
test('Throw error if the last commit sha cannot be found', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
await t.throwsAsync(getGitHead({cwd}), Error);
await t.throwsAsync(getGitHead({cwd}));
});
test('Unshallow and fetch repository', async t => {
test('Unshallow and fetch repository', 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
@@ -70,7 +73,7 @@ test('Unshallow and fetch repository', async t => {
t.is((await gitGetCommits(undefined, {cwd})).length, 2);
});
test('Do not throw error when unshallow a complete repository', async t => {
test('Do not throw error when unshallow a complete repository', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
@@ -83,7 +86,7 @@ test('Do not throw error when unshallow a complete repository', async t => {
await t.notThrowsAsync(fetch(repositoryUrl, 'second-branch', 'master', {cwd}));
});
test('Fetch all tags on a detached head repository', async t => {
test('Fetch all tags on a detached head repository', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -100,7 +103,7 @@ test('Fetch all tags on a detached head repository', async t => {
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 (CircleCI)', async t => {
test('Fetch all tags on a repository with a detached head from branch (CircleCI)', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -125,7 +128,7 @@ test('Fetch all tags on a repository with a detached head from branch (CircleCI)
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v2.0.0'].sort());
});
test('Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)', async t => {
test('Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)', async (t) => {
const {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -153,7 +156,7 @@ test('Fetch all tags on a detached head repository with outdated cached repo (Gi
t.deepEqual((await getTags('master', {cwd: cloneCwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v1.2.0'].sort());
});
test('Verify if a branch exists', async t => {
test('Verify if a branch exists', 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
@@ -168,7 +171,7 @@ test('Verify if a branch exists', async t => {
t.falsy(await isRefExists('next', {cwd}));
});
test('Get all branches', async t => {
test('Get all branches', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
@@ -182,7 +185,12 @@ test('Get all branches', async t => {
t.deepEqual((await getBranches(repositoryUrl, {cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
});
test('Get the commit sha for a given tag', async t => {
test('Return empty array if there are no branches', async (t) => {
const {cwd, repositoryUrl} = await initGit(true);
t.deepEqual(await getBranches(repositoryUrl, {cwd}), []);
});
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
@@ -193,7 +201,7 @@ test('Get the commit sha for a given tag', async t => {
t.is(await getTagHead('v1.0.0', {cwd}), commits[0].hash);
});
test('Return git remote repository url from config', async t => {
test('Return git remote repository url from config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add remote.origin.url config
@@ -202,7 +210,7 @@ test('Return git remote repository url from config', async t => {
t.is(await repoUrl({cwd}), 'git@hostname.com:owner/package.git');
});
test('Return git remote repository url set while cloning', async t => {
test('Return git remote repository url set while cloning', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -212,14 +220,14 @@ test('Return git remote repository url set while cloning', async t => {
t.is(await repoUrl({cwd}), repositoryUrl);
});
test('Return falsy if git repository url is not set', async t => {
test('Return falsy if git repository url is not set', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
t.falsy(await repoUrl({cwd}));
});
test('Add tag on head commit', async t => {
test('Add tag on head commit', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const commits = await gitCommits(['Test commit'], {cwd});
@@ -229,7 +237,7 @@ test('Add tag on head commit', async t => {
await t.is(await gitCommitTag(commits[0].hash, {cwd}), 'tag_name');
});
test('Push tag to remote repository', async t => {
test('Push tag to remote repository', async (t) => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['Test commit'], {cwd});
@@ -240,13 +248,13 @@ test('Push tag to remote repository', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
});
test('Push tag to remote repository with remote branch ahead', async t => {
test('Push tag to remote repository with remote branch ahead', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
const tmpRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Second'], {cwd: tmpRepo});
await gitPush('origin', 'master', {cwd: tmpRepo});
const temporaryRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Second'], {cwd: temporaryRepo});
await gitPush('origin', 'master', {cwd: temporaryRepo});
await tag('tag_name', 'HEAD', {cwd});
await push(repositoryUrl, {cwd});
@@ -254,40 +262,40 @@ test('Push tag to remote repository with remote branch ahead', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
});
test('Return "true" if in a Git repository', async t => {
test('Return "true" if in a Git repository', async (t) => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
t.true(await isGitRepo({cwd}));
});
test('Return falsy if not in a Git repository', async t => {
test('Return falsy if not in a Git repository', async (t) => {
const cwd = tempy.directory();
t.falsy(await isGitRepo({cwd}));
});
test('Return "true" for valid tag names', async t => {
test('Return "true" for valid tag names', async (t) => {
t.true(await verifyTagName('1.0.0'));
t.true(await verifyTagName('v1.0.0'));
t.true(await verifyTagName('tag_name'));
t.true(await verifyTagName('tag/name'));
});
test('Return falsy for invalid tag names', async t => {
test('Return falsy for invalid tag names', async (t) => {
t.falsy(await verifyTagName('?1.0.0'));
t.falsy(await verifyTagName('*1.0.0'));
t.falsy(await verifyTagName('[1.0.0]'));
t.falsy(await verifyTagName('1.0.0..'));
});
test('Throws error if obtaining the tags fails', async t => {
test('Throws error if obtaining the tags fails', async (t) => {
const cwd = tempy.directory();
await t.throwsAsync(getTags('master', {cwd}));
});
test('Return "true" if repository is up to date', async t => {
test('Return "true" if repository is up to date', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
@@ -295,7 +303,7 @@ test('Return "true" if repository is up to date', async t => {
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return falsy if repository is not up to date', async t => {
test('Return falsy if repository is not up to date', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitCommits(['Second'], {cwd});
@@ -303,14 +311,14 @@ test('Return falsy if repository is not up to date', async t => {
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
const tmpRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Third'], {cwd: tmpRepo});
await gitPush('origin', 'master', {cwd: tmpRepo});
const temporaryRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Third'], {cwd: temporaryRepo});
await gitPush('origin', 'master', {cwd: temporaryRepo});
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return falsy if detached head repository is not up to date', async t => {
test('Return falsy if detached head repository is not up to date', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
const [commit] = await gitCommits(['First'], {cwd});
@@ -322,7 +330,7 @@ test('Return falsy if detached head repository is not up to date', async t => {
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Get a commit note', async t => {
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
@@ -333,7 +341,7 @@ test('Get a commit note', async t => {
t.deepEqual(await getNote(commits[0].hash, {cwd}), {note: 'note'});
});
test('Return empty object if there is no commit note', async t => {
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
@@ -342,7 +350,7 @@ test('Return empty object if there is no commit note', async t => {
t.deepEqual(await getNote(commits[0].hash, {cwd}), {});
});
test('Throw error if a commit note in invalid', async t => {
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
@@ -353,7 +361,7 @@ test('Throw error if a commit note in invalid', async t => {
await t.throwsAsync(getNote(commits[0].hash, {cwd}));
});
test('Add a commit note', async t => {
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
@@ -364,7 +372,7 @@ test('Add a commit note', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Overwrite a commit note', async t => {
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
@@ -376,7 +384,7 @@ test('Overwrite a commit note', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note2"}');
});
test('Unshallow and fetch repository with notes', async t => {
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
@@ -395,7 +403,7 @@ test('Unshallow and fetch repository with notes', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Fetch all notes on a detached head repository', async t => {
test('Fetch all notes on a detached head repository', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
+122 -50
View File
@@ -4,10 +4,10 @@ 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';
import {GIT_NOTE_REF} from '../../lib/definitions/constants.js';
/**
* Commit message informations.
* Commit message information.
*
* @typedef {Object} Commit
* @property {String} branch The commit branch.
@@ -15,21 +15,38 @@ import {GIT_NOTE_REF} from '../../lib/definitions/constants';
* @property {String} message The commit message.
*/
/**
* Initialize git repository
* If `withRemote` is `true`, creates a bare repository and initialize it.
* If `withRemote` is `false`, creates a regular repository and initialize it.
*
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
* @return {String} The path of the repository
*/
async function initGit(withRemote) {
const cwd = tempy.directory();
const args = withRemote ? ['--bare', '--initial-branch=master'] : ['--initial-branch=master'];
await execa('git', ['init', ...args], {cwd}).catch(() => {
const args = withRemote ? ['--bare'] : [];
return execa('git', ['init', ...args], {cwd});
});
const repositoryUrl = fileUrl(cwd);
return {cwd, repositoryUrl};
}
/**
* Create a temporary git repository.
* If `withRemote` is `true`, creates a bare repository, initialize it and create a shallow clone. Change the current working directory to the clone root.
* If `withRemote` is `false`, creates a regular repository and initialize it. Change the current working directory to the repository root.
* If `withRemote` is `true`, creates a shallow clone. Change the current working directory to the clone root.
* If `withRemote` is `false`, just change the current working directory to the repository root.
*
*
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
* @param {String} [branch='master'] The branch to initialize.
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
*/
export async function gitRepo(withRemote, branch = 'master') {
let cwd = tempy.directory();
await execa('git', ['init', ...(withRemote ? ['--bare'] : [])], {cwd});
const repositoryUrl = fileUrl(cwd);
async function gitRepo(withRemote, branch = 'master') {
let {cwd, repositoryUrl} = await initGit(withRemote);
if (withRemote) {
await initBareRepo(repositoryUrl, branch);
cwd = await gitShallowClone(repositoryUrl, branch);
@@ -53,7 +70,7 @@ export async function gitRepo(withRemote, branch = 'master') {
* @param {String} repositoryUrl The URL of the bare repository.
* @param {String} [branch='master'] the branch to initialize.
*/
export async function initBareRepo(repositoryUrl, branch = 'master') {
async function initBareRepo(repositoryUrl, branch = 'master') {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
await gitCheckout(branch, true, {cwd});
@@ -69,12 +86,13 @@ 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) {
async function gitCommits(messages, execaOptions) {
await pEachSeries(
messages,
async message => (await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)).stdout
async (message) =>
(await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOptions)).stdout
);
return (await gitGetCommits(undefined, execaOpts)).slice(0, messages.length);
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
}
/**
@@ -85,13 +103,16 @@ export async function gitCommits(messages, execaOpts) {
*
* @return {Array<Object>} The list of parsed commits.
*/
export async function gitGetCommits(from, execaOpts) {
async function gitGetCommits(from, execaOptions) {
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}})
gitLogParser.parse(
{_: `${from ? from + '..' : ''}HEAD`},
{...execaOptions, env: {...process.env, ...execaOptions.env}}
)
)
).map(commit => {
).map((commit) => {
commit.message = commit.message.trim();
commit.gitTags = commit.gitTags.trim();
return commit;
@@ -105,8 +126,8 @@ export async function gitGetCommits(from, execaOpts) {
* @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, execaOpts) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOpts);
async function gitCheckout(branch, create, execaOptions) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOptions);
}
/**
@@ -115,8 +136,8 @@ export async function gitCheckout(branch, create, execaOpts) {
* @param {String} repositoryUrl The repository remote URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitFetch(repositoryUrl, execaOpts) {
await execa('git', ['fetch', repositoryUrl], execaOpts);
async function gitFetch(repositoryUrl, execaOptions) {
await execa('git', ['fetch', repositoryUrl], execaOptions);
}
/**
@@ -126,8 +147,8 @@ export async function gitFetch(repositoryUrl, execaOpts) {
*
* @return {String} The sha of the head commit in the current git repository.
*/
export async function gitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
async function gitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
}
/**
@@ -137,8 +158,8 @@ export async function gitHead(execaOpts) {
* @param {String} [sha] The commit on which to create the tag. If undefined the tag is created on the last commit.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitTagVersion(tagName, sha, execaOpts) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOpts);
async function gitTagVersion(tagName, sha, execaOptions) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOptions);
}
/**
@@ -150,7 +171,7 @@ export async function gitTagVersion(tagName, sha, execaOpts) {
* @param {Number} [depth=1] The number of commit to clone.
* @return {String} The path of the cloned repository.
*/
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
@@ -166,7 +187,7 @@ export async function gitShallowClone(repositoryUrl, branch = 'master', depth =
* @param {Number} head A commit sha of the remote repo that will become the detached head of the new one.
* @return {String} The path of the new repository.
*/
export async function gitDetachedHead(repositoryUrl, head) {
async function gitDetachedHead(repositoryUrl, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
@@ -176,7 +197,7 @@ export async function gitDetachedHead(repositoryUrl, head) {
return cwd;
}
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
@@ -194,8 +215,8 @@ export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
* @param {String} value Config value.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddConfig(name, value, execaOpts) {
await execa('git', ['config', '--add', name, value], execaOpts);
async function gitAddConfig(name, value, execaOptions) {
await execa('git', ['config', '--add', name, value], execaOptions);
}
/**
@@ -206,8 +227,8 @@ export async function gitAddConfig(name, value, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export async function gitTagHead(tagName, execaOpts) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
async function gitTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
}
/**
@@ -219,11 +240,11 @@ export async 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('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOpts)).stdout
async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOptions)).stdout
.split('\n')
.filter(tag => Boolean(tag))
.map(tag => tag.match(/^(?<tag>\S+)/)[1])[0];
.filter((tag) => Boolean(tag))
.map((tag) => tag.match(/^(?<tag>\S+)/)[1])[0];
}
/**
@@ -234,8 +255,8 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
*
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export async function gitCommitTag(gitHead, execaOpts) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts)).stdout;
async function gitCommitTag(gitHead, execaOptions) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOptions)).stdout;
}
/**
@@ -247,8 +268,8 @@ export async function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl, branch, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
async function gitPush(repositoryUrl, branch, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOptions);
}
/**
@@ -257,8 +278,8 @@ export async function gitPush(repositoryUrl, branch, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function merge(ref, execaOpts) {
await execa('git', ['merge', '--no-ff', ref], execaOpts);
async function merge(ref, execaOptions) {
await execa('git', ['merge', '--no-ff', ref], execaOptions);
}
/**
@@ -267,8 +288,8 @@ export async function merge(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function mergeFf(ref, execaOpts) {
await execa('git', ['merge', '--ff', ref], execaOpts);
async function mergeFf(ref, execaOptions) {
await execa('git', ['merge', '--ff', ref], execaOptions);
}
/**
@@ -277,8 +298,8 @@ export async function mergeFf(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], execaOpts);
async function rebase(ref, execaOptions) {
await execa('git', ['rebase', ref], execaOptions);
}
/**
@@ -288,8 +309,8 @@ export async function rebase(ref, execaOpts) {
* @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);
async function gitAddNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOptions);
}
/**
@@ -298,6 +319,57 @@ export async function gitAddNote(note, ref, execaOpts) {
* @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;
async function gitGetNote(ref, execaOptions) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout;
}
const exported = {
initGit,
gitRepo,
initBareRepo,
gitCommits,
gitGetCommits,
gitCheckout,
gitFetch,
gitHead,
gitTagVersion,
gitShallowClone,
gitDetachedHead,
gitDetachedHeadFromBranch,
gitAddConfig,
gitTagHead,
gitRemoteTagHead,
gitCommitTag,
gitPush,
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
};
export default exported;
export {
initGit,
gitRepo,
initBareRepo,
gitCommits,
gitGetCommits,
gitCheckout,
gitFetch,
gitHead,
gitTagVersion,
gitShallowClone,
gitDetachedHead,
gitDetachedHeadFromBranch,
gitAddConfig,
gitTagHead,
gitRemoteTagHead,
gitCommitTag,
gitPush,
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
};
+11 -3
View File
@@ -1,7 +1,7 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import pRetry from 'p-retry';
import {initBareRepo, gitShallowClone} from './git-utils';
import {initBareRepo, gitShallowClone} from './git-utils.js';
const IMAGE = 'pvdlg/docker-gitbox:latest';
const SERVER_PORT = 80;
@@ -63,10 +63,18 @@ async function createRepo(name, branch = 'master', description = `Repository ${n
const authUrl = `http://${gitCredential}@${SERVER_HOST}:${HOST_PORT}/git/${name}.git`;
// Retry as the server might take a few ms to make the repo available push
await pRetry(() => initBareRepo(authUrl, branch), {retries: 3, minTimeout: 500, factor: 2});
await pRetry(() => initBareRepo(authUrl, branch), {retries: 5, minTimeout: 500, factor: 2});
const cwd = await gitShallowClone(authUrl);
return {cwd, repositoryUrl, authUrl};
}
export default {start, stop, gitCredential, createRepo};
const exported = {
start,
stop,
gitCredential,
createRepo,
};
export default exported;
export {start, stop, gitCredential, createRepo};
+10 -1
View File
@@ -100,4 +100,13 @@ function verify(expectation) {
return client.verify(expectation);
}
export default {start, stop, mock, verify, url};
const exported = {
start,
stop,
mock,
verify,
url,
};
export default exported;
export {start, stop, mock, verify, url};
+9 -1
View File
@@ -76,4 +76,12 @@ async function stop() {
await container.remove();
}
export default {start, stop, authEnv, url};
const exported = {
start,
stop,
authEnv,
url,
};
export default exported;
export {start, stop, authEnv, url};
+8 -1
View File
@@ -1,5 +1,12 @@
import execa from 'execa';
export async function npmView(packageName, env) {
async function npmView(packageName, env) {
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
}
const exported = {
npmView,
};
export default exported;
export {npmView};
+38 -11
View File
@@ -1,9 +1,10 @@
import test from 'ava';
import {repeat} from 'lodash';
import hideSensitive from '../lib/hide-sensitive';
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from '../lib/definitions/constants';
import lodash from 'lodash'
const {repeat} = lodash
import hideSensitive from '../lib/hide-sensitive.js';
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from '../lib/definitions/constants.js';
test('Replace multiple sensitive environment variable values', t => {
test('Replace multiple sensitive environment variable values', (t) => {
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: 'secret'};
t.is(
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=${env.SOME_TOKEN}`),
@@ -11,7 +12,7 @@ test('Replace multiple sensitive environment variable values', t => {
);
});
test('Replace multiple occurences of sensitive environment variable values', t => {
test('Replace multiple occurences of sensitive environment variable values', (t) => {
const env = {secretKey: 'secret'};
t.is(
hideSensitive(env)(`https://user:${env.secretKey}@host.com?token=${env.secretKey}`),
@@ -19,7 +20,20 @@ test('Replace multiple occurences of sensitive environment variable values', t =
);
});
test('Escape regexp special characters', t => {
test('Replace sensitive environment variable matching specific regex for "private"', (t) => {
const env = {privateKey: 'secret', GOPRIVATE: 'host.com'};
t.is(hideSensitive(env)(`https://host.com?token=${env.privateKey}`), `https://host.com?token=${SECRET_REPLACEMENT}`);
});
test('Replace url-encoded environment variable', (t) => {
const env = {privateKey: 'secret '};
t.is(
hideSensitive(env)(`https://host.com?token=${encodeURI(env.privateKey)}`),
`https://host.com?token=${SECRET_REPLACEMENT}`
);
});
test('Escape regexp special characters', (t) => {
const env = {SOME_CREDENTIALS: 'p$^{.+}\\w[a-z]o.*rd'};
t.is(
hideSensitive(env)(`https://user:${env.SOME_CREDENTIALS}@host.com`),
@@ -27,15 +41,23 @@ test('Escape regexp special characters', t => {
);
});
test('Accept "undefined" input', t => {
test('Escape regexp special characters in url-encoded environment variable', (t) => {
const env = {SOME_PASSWORD: 'secret password p$^{.+}\\w[a-z]o.*rd)('};
t.is(
hideSensitive(env)(`https://user:${encodeURI(env.SOME_PASSWORD)}@host.com`),
`https://user:${SECRET_REPLACEMENT}@host.com`
);
});
test('Accept "undefined" input', (t) => {
t.is(hideSensitive({})(), undefined);
});
test('Return same string if no environment variable has to be replaced', t => {
test('Return same string if no environment variable has to be replaced', (t) => {
t.is(hideSensitive({})('test'), 'test');
});
test('Exclude empty environment variables from the regexp', t => {
test('Exclude empty environment variables from the regexp', (t) => {
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: ''};
t.is(
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=`),
@@ -43,11 +65,16 @@ test('Exclude empty environment variables from the regexp', t => {
);
});
test('Exclude empty environment variables from the regexp if there is only empty ones', t => {
test('Exclude empty environment variables from the regexp if there is only empty ones', (t) => {
t.is(hideSensitive({SOME_PASSWORD: '', SOME_TOKEN: ' \n '})(`https://host.com?token=`), 'https://host.com?token=');
});
test('Exclude environment variables with value shorter than SECRET_MIN_SIZE from the regexp', t => {
test('Exclude nonsensitive GOPRIVATE environment variable for Golang projects from the regexp', (t) => {
const env = {GOPRIVATE: 'host.com'};
t.is(hideSensitive(env)(`https://host.com?token=`), 'https://host.com?token=');
});
test('Exclude environment variables with value shorter than SECRET_MIN_SIZE from the regexp', (t) => {
const SHORT_TOKEN = repeat('a', SECRET_MIN_SIZE - 1);
const LONG_TOKEN = repeat('b', SECRET_MIN_SIZE);
const env = {SHORT_TOKEN, LONG_TOKEN};
+249 -248
View File
File diff suppressed because it is too large Load Diff
+101 -50
View File
@@ -1,12 +1,15 @@
import path from 'path';
import proxyquire from 'proxyquire';
import test from 'ava';
import {escapeRegExp} from 'lodash';
import proxyquire from 'proxyquire';
import lodash from 'lodash'
const {escapeRegExp} = lodash
import {writeJson, readJson} from 'fs-extra';
import execa from 'execa';
import {WritableStreamBuffer} from 'stream-buffers';
import delay from 'delay';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
import getAuthUrl from '../lib/get-git-auth-url.js';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants.js';
import {
gitHead,
gitTagHead,
@@ -17,28 +20,32 @@ import {
gitCheckout,
merge,
gitGetNote,
} from './helpers/git-utils';
import {npmView} from './helpers/npm-utils';
import gitbox from './helpers/gitbox';
import mockServer from './helpers/mockserver';
import npmRegistry from './helpers/npm-registry';
} from './helpers/git-utils.js';
import {npmView} from './helpers/npm-utils.js';
import gitbox from './helpers/gitbox.js';
import mockServer from './helpers/mockserver.js';
import npmRegistry from './helpers/npm-registry.js';
/* eslint camelcase: ["error", {properties: "never"}] */
const requireNoCache = proxyquire.noPreserveCache();
// Environment variables used with semantic-release cli (similar to what a user would setup)
const {GITHUB_ACTION, GITHUB_TOKEN, ...processEnvWithoutGitHubActionsVariables} = process.env;
const env = {
...processEnvWithoutGitHubActionsVariables,
...npmRegistry.authEnv,
GH_TOKEN: gitbox.gitCredential,
GITHUB_URL: mockServer.url,
TRAVIS: 'true',
CI: 'true',
GH_TOKEN: gitbox.gitCredential,
TRAVIS: 'true',
TRAVIS_BRANCH: 'master',
TRAVIS_PULL_REQUEST: 'false',
GITHUB_API_URL: mockServer.url,
};
// Environment variables used only for the local npm command used to do verification
const testEnv = {
const npmTestEnv = {
...process.env,
...npmRegistry.authEnv,
npm_config_registry: npmRegistry.url,
@@ -58,7 +65,7 @@ test.after.always(async () => {
await Promise.all([gitbox.stop(), npmRegistry.stop(), mockServer.stop()]);
});
test('Release patch, minor and major versions', async t => {
test('Release patch, minor and major versions', async (t) => {
const packageName = 'test-release';
const owner = 'git';
// Create a git repository, set the current working directory at the root of the repo
@@ -73,7 +80,7 @@ test('Release patch, minor and major versions', async t => {
release: {branches: ['master', 'next'], success: false, fail: false},
});
// Create a npm-shrinkwrap.json file
await execa('npm', ['shrinkwrap'], {env: testEnv, cwd});
await execa('npm', ['shrinkwrap'], {env: npmTestEnv, cwd, extendEnv: false});
/* No release */
let verifyMock = await mockServer.mock(
@@ -84,7 +91,7 @@ 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, exitCode} = await execa(cli, [], {env, cwd});
let {stdout, exitCode} = await execa(cli, [], {env, cwd, extendEnv: false});
t.regex(stdout, /There are no relevant changes, so no new version is released/);
t.is(exitCode, 0);
@@ -107,7 +114,7 @@ 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, exitCode} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd, extendEnv: false}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -119,7 +126,7 @@ test('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
let {
'dist-tags': {latest: releasedVersion},
} = await npmView(packageName, testEnv);
} = await npmView(packageName, npmTestEnv);
let head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}`, {cwd}), head);
@@ -148,7 +155,7 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a fix');
await gitCommits(['fix: bar'], {cwd});
t.log('$ semantic-release');
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd, extendEnv: false}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -160,7 +167,7 @@ test('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
({
'dist-tags': {latest: releasedVersion},
} = await npmView(packageName, testEnv));
} = await npmView(packageName, npmTestEnv));
head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}`, {cwd}), head);
@@ -189,7 +196,7 @@ test('Release patch, minor and major versions', async t => {
t.log('Commit a feature');
await gitCommits(['feat: baz'], {cwd});
t.log('$ semantic-release');
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd, extendEnv: false}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -201,7 +208,7 @@ test('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
({
'dist-tags': {latest: releasedVersion},
} = await npmView(packageName, testEnv));
} = await npmView(packageName, npmTestEnv));
head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}`, {cwd}), head);
@@ -232,7 +239,7 @@ 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, exitCode} = await execa(cli, [], {env: {...env, TRAVIS_BRANCH: 'next'}, cwd}));
({stdout, exitCode} = await execa(cli, [], {env: {...env, TRAVIS_BRANCH: 'next'}, cwd, extendEnv: false}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -244,7 +251,7 @@ test('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
({
'dist-tags': {next: releasedVersion},
} = await npmView(packageName, testEnv));
} = await npmView(packageName, npmTestEnv));
head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next"]}');
@@ -282,7 +289,7 @@ test('Release patch, minor and major versions', async t => {
await merge('next', {cwd});
await gitPush('origin', 'master', {cwd});
t.log('$ semantic-release');
({stdout, exitCode} = await execa(cli, [], {env, cwd}));
({stdout, exitCode} = await execa(cli, [], {env, cwd, extendEnv: false}));
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(exitCode, 0);
@@ -292,7 +299,7 @@ test('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
({
'dist-tags': {latest: releasedVersion},
} = await npmView(packageName, testEnv));
} = await npmView(packageName, npmTestEnv));
t.is(releasedVersion, version);
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next",null]}');
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}`, {cwd}));
@@ -304,7 +311,7 @@ test('Release patch, minor and major versions', async t => {
await mockServer.verify(updateReleaseMock);
});
test('Exit with 1 if a plugin is not found', async t => {
test('Exit with 1 if a plugin is not found', async (t) => {
const packageName = 'test-plugin-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
@@ -317,12 +324,12 @@ test('Exit with 1 if a plugin is not found', async t => {
release: {analyzeCommits: 'non-existing-path', success: false, fail: false},
});
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd, extendEnv: false}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
test('Exit with 1 if a shareable config is not found', async t => {
test('Exit with 1 if a shareable config is not found', async (t) => {
const packageName = 'test-config-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
@@ -335,12 +342,12 @@ test('Exit with 1 if a shareable config is not found', async t => {
release: {extends: 'non-existing-path', success: false, fail: false},
});
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd, extendEnv: false}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
test('Exit with 1 if a shareable config reference a not found plugin', async t => {
test('Exit with 1 if a shareable config reference a not found plugin', async (t) => {
const packageName = 'test-config-ref-not-found';
const owner = 'test-repo';
const shareable = {analyzeCommits: 'non-existing-path'};
@@ -356,12 +363,12 @@ test('Exit with 1 if a shareable config reference a not found plugin', async t =
});
await writeJson(path.resolve(cwd, 'shareable.json'), shareable);
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd}));
const {exitCode, stderr} = await t.throwsAsync(execa(cli, [], {env, cwd, extendEnv: false}));
t.is(exitCode, 1);
t.regex(stderr, /Cannot find module/);
});
test('Dry-run', async t => {
test('Dry-run', async (t) => {
const packageName = 'test-dry-run';
const owner = 'git';
// Create a git repository, set the current working directory at the root of the repo
@@ -386,7 +393,7 @@ test('Dry-run', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit'], {cwd});
t.log('$ semantic-release -d');
const {stdout, exitCode} = await execa(cli, ['-d'], {env, cwd});
const {stdout, exitCode} = await execa(cli, ['-d'], {env, cwd, extendEnv: false});
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/);
@@ -397,9 +404,8 @@ test('Dry-run', async t => {
await mockServer.verify(verifyMock);
});
test('Allow local releases with "noCi" option', async t => {
test('Allow local releases with "noCi" option', async (t) => {
const envNoCi = {...env};
delete envNoCi.TRAVIS;
delete envNoCi.CI;
const packageName = 'test-no-ci';
const owner = 'git';
@@ -434,7 +440,7 @@ 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, exitCode} = await execa(cli, ['--no-ci'], {env: envNoCi, cwd});
const {stdout, exitCode} = await execa(cli, ['--no-ci'], {env: envNoCi, cwd, extendEnv: false});
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -443,7 +449,7 @@ test('Allow local releases with "noCi" option', async t => {
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
// Retrieve the published package from the registry and check version and gitHead
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, npmTestEnv);
const head = await gitHead({cwd});
t.is(releasedVersion, version);
@@ -456,7 +462,7 @@ test('Allow local releases with "noCi" option', async t => {
await mockServer.verify(createReleaseMock);
});
test('Pass options via CLI arguments', async t => {
test('Pass options via CLI arguments', async (t) => {
const packageName = 'test-cli';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -487,7 +493,7 @@ test('Pass options via CLI arguments', async t => {
false,
'--debug',
],
{env, cwd}
{env, cwd, extendEnv: false}
);
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(exitCode, 0);
@@ -496,7 +502,7 @@ test('Pass options via CLI arguments', async t => {
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
// Retrieve the published package from the registry and check version and gitHead
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, npmTestEnv);
const head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(releasedGitHead, head);
@@ -505,7 +511,7 @@ test('Pass options via CLI arguments', async t => {
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
});
test('Run via JS API', async t => {
test('Run via JS API', async (t) => {
const semanticRelease = requireNoCache('..', {
'./lib/logger': {log: () => {}, error: () => {}, stdout: () => {}},
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
@@ -552,7 +558,7 @@ test('Run via JS API', async t => {
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
// Retrieve the published package from the registry and check version and gitHead
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, npmTestEnv);
const head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(releasedGitHead, head);
@@ -564,7 +570,7 @@ test('Run via JS API', async t => {
await mockServer.verify(createReleaseMock);
});
test('Log unexpected errors from plugins and exit with 1', async t => {
test('Log unexpected errors from plugins and exit with 1', async (t) => {
const packageName = 'test-unexpected-error';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -581,7 +587,7 @@ 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, exitCode} = await execa(cli, [], {env, cwd, reject: false});
const {stderr, exitCode} = await execa(cli, [], {env, cwd, reject: false, extendEnv: false});
// Verify the type and message are logged
t.regex(stderr, /Error: a/);
// Verify the the stacktrace is logged
@@ -591,7 +597,7 @@ test('Log unexpected errors from plugins and exit with 1', async t => {
t.is(exitCode, 1);
});
test('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
test('Log errors inheriting SemanticReleaseError and exit with 1', async (t) => {
const packageName = 'test-inherited-error';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -608,13 +614,13 @@ 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, exitCode} = await execa(cli, [], {env, cwd, reject: false});
const {stderr, exitCode} = await execa(cli, [], {env, cwd, reject: false, extendEnv: false});
// Verify the type and message are logged
t.regex(stderr, /EINHERITED Inherited error/);
t.is(exitCode, 1);
});
test('Exit with 1 if missing permission to push to the remote repository', async t => {
test('Exit with 1 if missing permission to push to the remote repository', async (t) => {
const packageName = 'unauthorized';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
@@ -629,14 +635,14 @@ test('Exit with 1 if missing permission to push to the remote repository', async
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}
{env: {...env, GH_TOKEN: 'user:wrong_pass'}, cwd, reject: false, extendEnv: false}
);
// Verify the type and message are logged
t.regex(stderr, /EGITNOPERMISSION/);
t.is(exitCode, 1);
});
test('Hide sensitive environment variable values from the logs', async t => {
test('Hide sensitive environment variable values from the logs', async (t) => {
const packageName = 'log-secret';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
@@ -649,10 +655,55 @@ test('Hide sensitive environment variable values from the logs', async t => {
});
t.log('$ semantic-release');
const {stdout, stderr} = await execa(cli, [], {env: {...env, MY_TOKEN: 'secret token'}, cwd, reject: false});
const {stdout, stderr} = await execa(cli, [], {
env: {...env, MY_TOKEN: 'secret token'},
cwd,
reject: false,
extendEnv: false,
});
t.regex(stdout, new RegExp(`Console: Exposing token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.regex(stdout, new RegExp(`Log: Exposing token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.regex(stderr, new RegExp(`Error: Console token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.regex(stderr, new RegExp(`Throw error: Exposing ${escapeRegExp(SECRET_REPLACEMENT)}`));
});
test('Use the valid git credentials when multiple are provided', async (t) => {
const {cwd, authUrl} = await gitbox.createRepo('test-auth');
t.is(
await getAuthUrl({
cwd,
env: {
GITHUB_TOKEN: 'dummy',
GITLAB_TOKEN: 'trash',
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
GIT_ASKPASS: 'echo',
GIT_TERMINAL_PROMPT: 0,
},
branch: {name: 'master'},
options: {repositoryUrl: 'http://toto@localhost:2080/git/test-auth.git'},
}),
authUrl
);
});
test('Use the repository URL as is if none of the given git credentials are valid', async (t) => {
const {cwd} = await gitbox.createRepo('test-invalid-auth');
const dummyUrl = 'http://toto@localhost:2080/git/test-invalid-auth.git';
t.is(
await getAuthUrl({
cwd,
env: {
GITHUB_TOKEN: 'dummy',
GITLAB_TOKEN: 'trash',
GIT_ASKPASS: 'echo',
GIT_TERMINAL_PROMPT: 0,
},
branch: {name: 'master'},
options: {repositoryUrl: dummyUrl},
}),
dummyUrl
);
});
+42 -43
View File
@@ -1,16 +1,17 @@
import test from 'ava';
import {noop} from 'lodash';
import {stub} from 'sinon';
import normalize from '../../lib/plugins/normalize';
import lodash from 'lodash'
const {noop} = lodash
import sinon from 'sinon';
import normalize from '../../lib/plugins/normalize.js';
const cwd = process.cwd();
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.success = stub();
t.context.stderr = {write: stub()};
t.context.log = sinon.stub);
t.context.error = sinon.stub);
t.context.success = sinon.stub);
t.context.stderr = {write: sinon.stub)};
t.context.logger = {
log: t.context.log,
error: t.context.error,
@@ -19,7 +20,7 @@ test.beforeEach(t => {
};
});
test('Normalize and load plugin from string', t => {
test('Normalize and load plugin from string', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
@@ -32,7 +33,7 @@ test('Normalize and load plugin from string', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop"']);
});
test('Normalize and load plugin from object', t => {
test('Normalize and load plugin from object', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'publish',
@@ -45,7 +46,7 @@ test('Normalize and load plugin from object', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop"']);
});
test('Normalize and load plugin from a base file path', t => {
test('Normalize and load plugin from a base file path', (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop', {
'./plugin-noop': './test/fixtures',
});
@@ -57,7 +58,7 @@ test('Normalize and load plugin from a base file path', t => {
]);
});
test('Wrap plugin in a function that add the "pluginName" to the error"', async t => {
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
'./plugin-error': './test/fixtures',
});
@@ -67,7 +68,7 @@ test('Wrap plugin in a function that add the "pluginName" to the error"', async
t.is(error.pluginName, './plugin-error');
});
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async t => {
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
'./plugin-errors': './test/fixtures',
});
@@ -78,7 +79,7 @@ test('Wrap plugin in a function that add the "pluginName" to multiple errors"',
}
});
test('Normalize and load plugin from function', t => {
test('Normalize and load plugin from function', (t) => {
const pluginFunction = () => {};
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
@@ -86,7 +87,7 @@ test('Normalize and load plugin from function', t => {
t.is(typeof plugin, 'function');
});
test('Normalize and load plugin that retuns multiple functions', t => {
test('Normalize and load plugin that retuns multiple functions', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
@@ -98,8 +99,8 @@ test('Normalize and load plugin that retuns multiple functions', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin"']);
});
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async t => {
const analyzeCommits = stub().resolves(2);
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
const analyzeCommits = sinon.stub).resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'analyzeCommits',
@@ -116,8 +117,8 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
t.regex(error.details, /2/);
});
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async t => {
const generateNotes = stub().resolves(2);
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
const generateNotes = sinon.stub).resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'generateNotes',
@@ -134,8 +135,8 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
t.regex(error.details, /2/);
});
test('Wrap "publish" plugin in a function that validate the output of the plugin', async t => {
const publish = stub().resolves(2);
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
const publish = sinon.stub).resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'publish',
@@ -152,8 +153,8 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
t.regex(error.details, /2/);
});
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async t => {
const addChannel = stub().resolves(2);
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
const addChannel = sinon.stub).resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'addChannel',
@@ -170,8 +171,8 @@ test('Wrap "addChannel" plugin in a function that validate the output of the plu
t.regex(error.details, /2/);
});
test('Plugin is called with "pluginConfig" (with object definition) and input', async t => {
const pluginFunction = stub().resolves();
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
const pluginFunction = sinon.stub).resolves();
const pluginConf = {path: pluginFunction, conf: 'confValue'};
const options = {global: 'globalValue'};
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
@@ -185,8 +186,8 @@ test('Plugin is called with "pluginConfig" (with object definition) and input',
);
});
test('Plugin is called with "pluginConfig" (with array definition) and input', async t => {
const pluginFunction = stub().resolves();
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
const pluginFunction = sinon.stub).resolves();
const pluginConf = [pluginFunction, {conf: 'confValue'}];
const options = {global: 'globalValue'};
const plugin = normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
@@ -200,8 +201,8 @@ test('Plugin is called with "pluginConfig" (with array definition) and input', a
);
});
test('Prevent plugins to modify "pluginConfig"', async t => {
const pluginFunction = stub().callsFake(pluginConfig => {
test('Prevent plugins to modify "pluginConfig"', async (t) => {
const pluginFunction = sinon.stub).callsFake((pluginConfig) => {
pluginConfig.conf.subConf = 'otherConf';
});
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
@@ -213,8 +214,8 @@ test('Prevent plugins to modify "pluginConfig"', async t => {
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
});
test('Prevent plugins to modify its input', async t => {
const pluginFunction = stub().callsFake((pluginConfig, options) => {
test('Prevent plugins to modify its input', async (t) => {
const pluginFunction = sinon.stub).callsFake((pluginConfig, options) => {
options.param.subParam = 'otherParam';
});
const input = {param: {subParam: 'originalSubParam'}, options: {}};
@@ -224,13 +225,13 @@ test('Prevent plugins to modify its input', async t => {
t.is(input.param.subParam, 'originalSubParam');
});
test('Return noop if the plugin is not defined', t => {
test('Return noop if the plugin is not defined', (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger});
t.is(plugin, noop);
});
test('Always pass a defined "pluginConfig" for plugin defined with string', async t => {
test('Always pass a defined "pluginConfig" for plugin defined with string', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
@@ -243,7 +244,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Always pass a defined "pluginConfig" for plugin defined with path', async t => {
test('Always pass a defined "pluginConfig" for plugin defined with path', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
@@ -256,7 +257,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with path', async
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Throws an error if the plugin return an object without the expected plugin function', t => {
test('Throws an error if the plugin return an object without the expected plugin function', (t) => {
const error = t.throws(() =>
normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', './test/fixtures/multi-plugin', {})
);
@@ -267,12 +268,10 @@ test('Throws an error if the plugin return an object without the expected plugin
t.truthy(error.details);
});
test('Throws an error if the plugin is not found', t => {
const error = t.throws(
() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}),
Error
);
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
test('Throws an error if the plugin is not found', (t) => {
t.throws(() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
instanceOf: Error,
});
});

Some files were not shown because too many files have changed in this diff Show More