Compare commits

..
114 Commits
Author SHA1 Message Date
70996e0181 fix(deps): update dependency read-pkg-up to v10 (#2862)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-04 20:03:53 -05:00
40dbf5633f ci(action): update actions/checkout action to v3.5.3 (#2852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 07:50:42 +00:00
3159c977fc chore(deps): lock file maintenance (#2849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 03:23:24 +00:00
41e7027b2d ci(action): update actions/upload-artifact action to v3.1.2 (#2851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 02:47:04 +00:00
Tijmen van de MeentandGitHub e4229f9c68 fix: get correct version if prerelease branch shares version with ... (#2416) 2023-06-28 14:38:35 -07:00
9514e20e7b ci(action): update ossf/scorecard-action action to v2.2.0 (#2854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-26 17:57:07 -05:00
5fcdc27e75 ci(action): update github/codeql-action action to v2.20.1 (#2853)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-26 09:26:04 +00:00
1260d3a971 ci(action): pin dependencies (#2850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-26 04:50:50 +00:00
Matt TraviandGitHub 9ebabe1266 ci(ossf-scorecard): added workflow calculating scorecard metrics (#2848) 2023-06-25 21:44:40 -05:00
Matt TraviandGitHub e39ae90ae2 test(integration): properly waited for the image pulls to complete (#2842) 2023-06-25 17:51:31 -07:00
39d2a05014 chore(deps): update dependency sinon to v15.2.0 (#2844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-20 13:15:55 -07:00
45eb11ae9c chore(deps): update dependency c8 to v8 (#2833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-19 11:31:31 -07:00
71e805d90c chore(deps): update dependency ava to v5.3.1 (#2839)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-19 06:33:31 +00:00
9f660ff885 chore(deps): lock file maintenance (#2841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-19 03:58:31 +00:00
01e85df88a chore(deps): update dependency sinon to v15.1.2 (#2832)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-12 20:39:33 +00:00
caa0d50d52 chore(deps): lock file maintenance (#2831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-12 04:32:51 +00:00
452e1fa4b9 fix(deps): update dependency marked to v5
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-09 16:54:36 -07:00
94e773d915 ci(action): update actions/checkout digest to c85c95e
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-09 16:25:38 -07:00
Matt TraviandGitHub 18730e824f Merge pull request #2826 from semantic-release/renovate/major-semantic-release-monorepo 2023-06-09 16:32:02 -05:00
Matt Travi 4bf763f264 test(semantic-release-error): switched instanceof check to the native version
since the check provided by the ava assertion seems to fail beyond the context of the test
2023-06-09 16:14:46 -05:00
renovate[bot]andGitHub d259350c3e fix(deps): update dependency @semantic-release/error to v4 2023-06-07 21:29:33 +00:00
286bd67de5 chore(deps): lock file maintenance (#2825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-05 04:23:37 +00:00
95551a0df8 chore(deps): update dependency ava to v5.3.0 (#2809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-02 15:30:01 -05:00
4e80bab563 chore(deps): lock file maintenance (#2816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-02 15:25:29 -05:00
5145268539 chore(deps): update dependency got to v13 (#2813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-02 15:18:21 -05:00
Matt TraviandGitHub dd352f1044 Merge pull request #2823 from semantic-release/beta 2023-06-02 15:09:44 -05:00
Matt Travi 39410186df fix(deps): updated the beta plugins to stable versions 2023-06-02 14:30:32 -05:00
Matt Travi 4a6b31f57e fix: bump @semantic-release/commit-analyzer to v10.0.0-beta.1 2023-05-29 22:03:54 -05:00
6f19d77e54 fix: bump @semantic-release/github to 9.0.0-beta.2 (#2818)
Co-authored-by: Matt Travi <programmer@travi.org>
2023-05-29 20:47:17 -05:00
6ef4cee971 chore(deps): update dependency c8 to v7.14.0 (#2815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-28 19:00:28 +00:00
18bd0c4904 chore(deps): lock file maintenance (#2797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 07:05:08 +00:00
Matt Travi ab4fe4c3d4 refactor: leverage native async setTimeout rather than delay 2023-05-22 22:25:22 -05:00
42385d0f96 chore(deps): update dependency sinon to v15.1.0 (#2801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-21 15:39:53 -07:00
57db31ad97 chore(deps): update dependency delay to v6 (#2803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-21 20:14:15 +00:00
Matt TraviandGitHub fa133f2a9d ci(release): pinned the version of semantic-release (#2796) 2023-05-12 17:17:04 -05:00
4af49f3c87 chore(deps): update dependency nock to v13.3.1 (#2777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-10 16:01:22 -05:00
Joram van den BoezemandGitHub 0d5e717fa6 docs(extending): add semantic-release-yarn to community plugins (#2790) 2023-05-10 08:40:17 -05:00
Matt Travi ee250a04db docs(gitbook): fixed reference to the installation page 2023-05-09 09:07:50 -05:00
85a31fb612 docs(npx-version): mentioned using a renovate regex manager to update the version (#2786)
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
2023-05-09 08:02:45 -05:00
8826e524e4 chore(deps): update dependency testdouble to v3.18.0 (#2787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-09 06:35:20 +00:00
Liran TalandGitHub f1b0801157 docs: Update README.md with npm package provenance (#2789) 2023-05-08 21:06:39 -05:00
214a376342 ci(action): pin dependencies (#2781)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-06 10:09:56 +00:00
0c6c20ee96 chore(deps): lock file maintenance (#2763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-06 08:06:29 +00:00
420211c806 chore(deps): update dependency prettier to v2.8.8 (#2771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-06 04:19:59 +00:00
Eric KandGitHub 0476ae58a0 docs(extending): add link to semantic-release-major-tag (#2776) 2023-05-05 16:55:09 -05:00
ac40804dc4 fix(deps): update dependency @semantic-release/release-notes-generator to v11 (#2778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-28 17:01:24 -05:00
Matt Travi e046ece3cb ci(signatures): moved the audit step to the non-matrix stage
since older versions of npm dont support `npm audit signatures` and instead simply audit for vulnerabilities
2023-04-28 16:33:03 -05:00
Matt Travi ef998acd4d ci(dependencies): audited signatures and provenance attestations of installed packages 2023-04-21 16:41:27 -05:00
Matt Travi 278d8e6bec docs(gh-actions): captured details about publishing with provenance from an actions workflow 2023-04-21 16:01:08 -05:00
ddf4065b4e chore(deps): update dependency sinon to v15.0.4 (#2769)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 17:40:03 +00:00
4bddb37de2 fix(deps): update dependency env-ci to v9 (#2757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 08:18:03 -05:00
aa90774738 chore(deps): update dependency testdouble to v3.17.2 (#2751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 01:53:55 +00:00
Matt Travi d7e14f6d02 docs(artifactory): removed details about using artifactory with legacy auth
since we no longer support legacy auth in the npm plugin and artifcatory can use modern auth with more recent versions
2023-03-31 15:09:10 -05:00
4a943a5651 chore(deps): pin dependencies (#2744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-27 19:16:38 +00:00
f47a5102d6 chore(deps): lock file maintenance (#2737)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-27 14:50:11 +00:00
05596bce72 chore(deps): update dependency prettier to ^2.8.4 (#2746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-27 08:52:21 +00:00
c6e84efc28 chore(deps): update dependency sinon to v15.0.3 (#2748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-27 03:27:20 +00:00
0cbe8046af chore(deps): update dependency fs-extra to ^11.1.0 (#2745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-25 01:36:14 +00:00
ea32d10fa6 chore(deps): update dependency testdouble to v3.17.1 (#2740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-24 20:51:08 +00:00
Matt TraviandGitHub deb1b7f9cb Merge pull request #2741 from semantic-release/beta
upgraded to next major version of the npm plugin
2023-03-24 14:30:48 -05:00
Matt Travi 2d7f607649 chore(lint): ran prettier against the release workflow file 2023-03-21 22:18:42 -05:00
Matt Travi 91eae117ed ci(release): enabled provenance for publishing to npm 2023-03-21 22:12:37 -05:00
Matt Travi 050412eab8 Merge branch 'master' of github.com:semantic-release/semantic-release into beta 2023-03-21 21:46:58 -05:00
Matt Travi d647433c20 fix(deps): updated to the latest version of the npm plugin 2023-03-21 21:13:25 -05:00
f1d6e65d9a chore(deps): update dependency dockerode to v3.3.5
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 13:36:34 -07:00
c38b53a321 fix(deps): update dependency execa to v7.1.1
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 13:36:13 -07:00
fbede54388 fix(deps): update dependency cosmiconfig to v8.1.2
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 13:33:58 -07:00
6d286cd5d6 chore(deps): update dependency sinon to v15.0.2 (#2730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-14 05:05:34 +00:00
bc6d8b4411 chore(deps): update dependency testdouble to v3.17.0 (#2731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-14 01:22:13 +00:00
9986f6fe20 chore(deps): lock file maintenance
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-06 21:15:28 -08:00
Justin MartinandGitHub 6bd6afd8f7 docs: add new to create WritableStreamBuffer instances (#2724) 2023-03-06 23:10:48 -06:00
00714c0fc8 chore(deps): lock file maintenance (#2684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-04 14:40:56 -06:00
Chris. WebsterandGitHub c71d5d94ce test: Windows support in integration tests (#2722)
* ci: win32 escape slash as doubleslash for test

Pathing in stderr output has double backslash in the filename.  The test
will now use double backslash in the existence test.

Signed-off-by: Chris. Webster <chris@webstech.net>

* ci: avoid Windows pop-up in credential tests

Windows uses a `credential.helper` that will open a pop-up for
credentials if they are missing.  A git config parameter will now be set
to avoid the pop-up.

Signed-off-by: Chris. Webster <chris@webstech.net>

---------

Signed-off-by: Chris. Webster <chris@webstech.net>
2023-03-04 12:37:05 -06:00
Chris. WebsterandGitHub c04e827757 test: update docker config for windows support (#2721
There is legacy support for PortBindings and Binds at the root level of
the config.  Currently (including Windows) these reside under the
HostConfig.  ExposedPorts is now used to identify the externally
available ports.

Signed-off-by: Chris. Webster <chris@webstech.net>
2023-03-03 17:09:19 -06:00
Chris. WebsterandGitHub 6ead75bf73 test: index test should reset testdouble (#2723) 2023-03-03 15:58:42 -06:00
ab3dfe4aba chore(deps): update dependency c8 to v7.13.0 (#2716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-28 23:29:06 -06:00
Matt Travi df6dbf0295 docs(navigation): removed the hash from the configuration usage page 2023-02-28 22:59:01 -06:00
Matt Travi ea8ed80e71 docs(navigation): removed the hash from the ci usage page 2023-02-28 22:57:29 -06:00
Matt Travi e47ea08255 docs(ci-configurations): removed hash from the links 2023-02-28 22:27:15 -06:00
Matt Travi 4e5b181ffb docs(node-version): linked to proper recipe page for ci-configurations 2023-02-28 22:23:21 -06:00
Matt Travi 5acdaabaaf docs(ci-configuration): further fixed the format of links to recipes 2023-02-28 22:15:15 -06:00
Matt Travi 724dc823fa docs(ci-configuration): fixed the format of links to recipes 2023-02-28 22:09:16 -06:00
31d9bfe00a fix(deps): update dependency execa to v7 (#2709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 23:18:19 -06:00
7bbaaf9a48 chore(deps): update dependency ava to v5.2.0 (#2702)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 23:05:07 -06:00
42b67ce994 ci: set test workflow timeout to 10 minutes (#2720)
Co-authored-by: Matt Travi <programmer@travi.org>
2023-02-27 22:17:40 -06:00
Chris. WebsterandGitHub f991a599f5 test(integration): fix package name conflict (#2719) 2023-02-27 22:01:57 -06:00
Matt Travi bef1d48114 fix(deps): updated to the latest beta of the npm plugin, which updates npm to v9
BREAKING CHANGE: the npm plugin has updated the npm dependency to v9
2023-02-17 16:05:27 -06:00
Lars TrieloffandGitHub 1775ed80b9 docs(extending): add link to semantic-release-coralogix (#2710) 2023-02-16 00:45:54 -06:00
1beeb84c90 build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 10:08:15 -08:00
Matt Travi 5d1e5a0dd9 fix(deps): updated to the latest beta of the npm plugin 2023-01-27 16:13:03 -06:00
Matt Travi d3aae0752c Merge branch 'master' of github.com:semantic-release/semantic-release into beta 2023-01-27 16:09:41 -06:00
d170f73e0b feat(plugins): add support for loading ESM plugins (#2688)
Co-authored-by: Matt Travi <programmer@travi.org>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
2023-01-24 23:24:02 -06:00
Chris. WebsterandGitHub 5df624c6e5 fix(windows): fixed issues preventing execution from windows (#2672) 2023-01-23 20:38:37 -06:00
Matt TraviandGitHub 914b0a2642 Revert "chore(deps): update dependency ava to v5.1.1" (#2682)
This reverts commit 3f3b286fb8.
2023-01-22 21:39:16 -06:00
Khaled BadenjkiandGitHub 140e36d60f docs(workflow-configuration): improved sentence structure (#2679) 2023-01-20 13:49:17 -06:00
Anton OvechkinandGitHub e9efbc7a52 docs(pre-releases): Correct @latest version in semantic-release (#2668)
There is a tiny typo in the version mentioned in `Working on another future release` section. It says that installing with `npm install example-module` will still receive version `1.0.0`, but in fact, there was a bug fix made on the default distribution channel one step before.
2023-01-18 03:08:40 +00:00
3f3b286fb8 chore(deps): update dependency ava to v5.1.1
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-15 15:27:52 -08:00
Gregor Martynus 15092ad276 breaking: legacy authentication using NPM_USERNAME and NPM_PASSWORD is no longer supported. Use NPM_TOKEN instead.
BREAKING CHANGE: legacy authentication using `NPM_USERNAME` and `NPM_PASSWORD` is no longer supported. Use `NPM_TOKEN` instead.
2023-01-14 15:52:59 -08:00
Gregor MartynusandGitHub 6a83cb53e5 fix(deps): bump @semantic-release/npm to ^10.0.0-beta.1 (#2670) 2023-01-14 14:14:04 -08:00
4db048423d chore(deps): lock file maintenance
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-14 11:54:27 -08:00
AlexandGitHub be2e1965ae ci(permissions): provided only the necessary permissions to the GITHUB_TOKEN 2023-01-14 08:11:42 -06:00
Gregor Martynus e607e234f3 build(release.yml): set node-version to lts/* 2023-01-13 12:51:39 -08:00
Shane O'GradyandGitHub c7b8e10bd1 docs: update node LTS version for npx command (#2669)
The [lts tag](https://www.npmjs.com/package/node?activeTab=versions) refers to Node.js 16.13.2. To use v18 of Node you need to use the tag `v18-lts`.
2023-01-13 09:24:31 -08:00
ac1371470d chore(deps): update dependency mockserver-client to v5.15.0
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-12 13:51:24 -08:00
7e6d9226fa chore(deps): update dependency nock to v13.3.0 (#2665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-11 01:38:21 +00:00
Ben LimmerandGitHub 63073d64c0 docs: update GH Actions recipe actions to v3 (#2652) 2023-01-10 12:32:29 -08:00
cb45d27726 fix(deps): update dependency semver-diff to v4 (#1915)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-08 00:57:59 -06:00
f914c1ed86 fix(deps): update dependency cosmiconfig to v8
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 15:40:20 -08:00
c4da008e29 fix(deps): update dependency hosted-git-info to v6
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 15:39:59 -08:00
b707475b85 chore(deps): update dependency sinon to v15 (#2653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 11:27:44 +00:00
b9579349ca chore(deps): update dependency fs-extra to v11 (#2650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 08:39:35 +00:00
4b74f005c4 chore(deps): update dependency ava to v5 (#2649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 05:49:16 +00:00
bbb5e5ade6 chore(deps): update dependency testdouble to v3.16.8 (#2646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-07 02:25:38 +00:00
a1e0467f04 chore(deps): update dependency sinon to v14.0.2 (#2645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-06 23:31:08 +00:00
58 changed files with 7922 additions and 6873 deletions
+2
View File
@@ -0,0 +1,2 @@
# style: prettier (#2670)
b06c9bbe4c6be121c5561b356d8c465c1cadffba
+15 -5
View File
@@ -6,18 +6,28 @@ name: Release
- next
- beta
- "*.x"
permissions:
contents: read # for checkout
jobs:
release:
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
cache: npm
node-version: 16
- run: npm ci
- run: npx semantic-release
node-version: lts/*
- run: npm clean-install
- run: npm audit signatures
# pinned version updated automatically by Renovate.
# details at https://semantic-release.gitbook.io/semantic-release/usage/installation#global-installation
- run: npx semantic-release@21.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }}
+36
View File
@@ -0,0 +1,36 @@
name: OpenSSF Scorecard
"on":
schedule:
- cron: 31 2 * * 1
push:
branches:
- master
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@f6e388ebf0efc915c6c5b165b019ee61a6746a38 # v2.20.1
with:
sarif_file: results.sarif
+10 -8
View File
@@ -12,6 +12,9 @@ on:
- opened
- synchronize
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
test_matrix:
strategy:
@@ -19,18 +22,16 @@ jobs:
node-version:
- 18.0.0
- 19
os:
- ubuntu-latest
- windows-latest
runs-on: "${{ matrix.os }}"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
- 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@v3
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
@@ -45,10 +46,11 @@ jobs:
runs-on: ubuntu-latest
needs: test_matrix
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: lts/*
cache: npm
- run: npm clean-install
- run: npm audit signatures
- run: npm run lint
+4
View File
@@ -7,6 +7,9 @@
<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="https://securityscorecards.dev/viewer/?uri=github.com/semantic-release/semantic-release">
<img alt="OpenSSF Scorecard" src="https://api.securityscorecards.dev/projects/github.com/semantic-release/semantic-release/badge">
</a>
<a href="#badge">
<img alt="semantic-release: angular" src="https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release">
</a>
@@ -41,6 +44,7 @@ This removes the immediate connection between human emotions and version numbers
- Avoid potential errors associated with manual releases
- Support any [package managers and languages](docs/recipes/release-workflow/README.md#package-managers-and-languages) via [plugins](docs/usage/plugins.md)
- Simple and reusable configuration via [shareable configurations](docs/usage/shareable-configurations.md)
- Support for [npm package provenance](https://github.com/semantic-release/npm#npm-provenance) that promotes increased supply-chain security via signed attestations on GitHub Actions
## How does it work?
+3 -3
View File
@@ -3,9 +3,9 @@
## Usage
- [Getting started](docs/usage/getting-started.md#getting-started)
- [Installation](docs/usage/installation.md#installation)
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
- [Configuration](docs/usage/configuration.md#configuration)
- [Installation](docs/usage/installation.md)
- [CI Configuration](docs/usage/ci-configuration.md)
- [Configuration](docs/usage/configuration.md)
- [Plugins](docs/usage/plugins.md)
- [Workflow configuration](docs/usage/workflow-configuration.md)
- [Shareable configurations](docs/usage/shareable-configurations.md)
+1 -1
View File
@@ -25,7 +25,7 @@ See https://github.com/semantic-release/semantic-release/blob/master/docs/suppor
execa("git", ["--version"])
.then(({ stdout }) => {
const gitVersion = findVersions(stdout)[0];
const gitVersion = findVersions(stdout, { loose: true })[0];
if (lt(gitVersion, MIN_GIT_VERSION)) {
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. Found ${gitVersion}.`);
process.exit(1);
+3 -3
View File
@@ -6,8 +6,8 @@
const semanticRelease = require("semantic-release");
const { WritableStreamBuffer } = require("stream-buffers");
const stdoutBuffer = WritableStreamBuffer();
const stderrBuffer = WritableStreamBuffer();
const stdoutBuffer = new WritableStreamBuffer();
const stderrBuffer = new WritableStreamBuffer();
try {
const result = await semanticRelease(
@@ -142,7 +142,7 @@ Information related to the last release found:
| gitTag | `String` | The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) associated with the last release. |
| channel | `String` | The distribution channel on which the last release was initially made available (`undefined` for the default distribution channel). |
**Notes**: If no previous release is found, `lastRelease` will be an empty `Object`.
**Note**: If no previous release is found, `lastRelease` will be an empty `Object`.
Example:
+11
View File
@@ -171,3 +171,14 @@
- `verifyConditions` Validate configuration, `Cargo.toml`, and local cargo executable. Also, logs in into `crates.io`.
- `prepare` Write the new version number into `Cargo.toml` file and perform `cargo check` if configured.
- `publish` Publish the Rust crate to `crates.io`
- [semantic-release-coralogix](https://github.com/adobe/semantic-release-coralogix)
- `verifyConditions` Verified that required credentials are provided and API is accessible
- `publish` add a release tag to Coralogix
- [semantic-release-major-tag](https://github.com/doteric/semantic-release-major-tag)
- `success` Create major version tag, for example `v1`.
- [semantic-release-yarn](https://github.com/hongaar/semantic-release-yarn)
- **Note**: this is an alternative to the default `@semantic-release/npm` plugin and adds support for monorepos.
- `verifyConditions` Verify Yarn 2 or higher is installed, verify the presence of a NPM auth token (either in an environment variable or a `.yarnrc.yml` file) and verify the authentication method is valid.
- `prepare` Update the `package.json` version and create the package tarball.
- `addChannel` Add a tag for the release.
- `publish` Publish to the npm registry.
@@ -6,6 +6,11 @@ The [Authentication](../../usage/ci-configuration.md#authentication) environment
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.
## npm provenance
Since GitHub Actions is a [supported provider](https://docs.npmjs.com/generating-provenance-statements#provenance-limitations) for [npm provenance](https://docs.npmjs.com/generating-provenance-statements), it is recommended to enable this to increase supply-chain security for your npm packages.
Find more detail about configuring npm to publish with provenance through semantic-release [in the documentation for our npm plugin](https://github.com/semantic-release/npm#npm-provenance).
## 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.
@@ -23,21 +28,32 @@ on:
push:
branches:
- master
permissions:
contents: read # for checkout
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: "lts/*"
- name: Install dependencies
run: npm ci
run: npm clean-install
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -62,7 +62,7 @@ The Git history of the repository is now:
We now decide to work on another future major release, in parallel of the beta one, which will also be composed of multiple features, some of them being breaking changes.
To implement that workflow we can create the branch `alpha` from the branch `beta` and commit our first feature there. When pushing that commit, **semantic-release** will publish the pre-release version `3.0.0-alpha.1` on the dist-tag `@alpha`. That allow us to run integration tests by installing our module with `npm install example-module@alpha`. Other users installing with `npm install example-module` will still receive the version `1.0.0`.
To implement that workflow we can create the branch `alpha` from the branch `beta` and commit our first feature there. When pushing that commit, **semantic-release** will publish the pre-release version `3.0.0-alpha.1` on the dist-tag `@alpha`. That allow us to run integration tests by installing our module with `npm install example-module@alpha`. Other users installing with `npm install example-module` will still receive the version `1.0.1`.
The Git history of the repository is now:
-8
View File
@@ -158,14 +158,6 @@ Or with the `publishConfig.access` key in your project's `package.json`:
}
```
## Can I use semantic-release to publish a package on Artifactory?
Any npm compatible registry is supported with the [`@semantic-release/npm`](https://github.com/semantic-release/npm) plugin. For Artifactory versions prior to 5.4, the legacy authentication has to be used (with `NPM_USERNAME`, `NPM_PASSWORD` and `NPM_EMAIL` [environment variables](https://github.com/semantic-release/npm#environment-variables)).
See [npm registry authentication](https://github.com/semantic-release/npm#npm-registry-authentication) for more details.
See [Artifactory - npm Registry](https://www.jfrog.com/confluence/display/RTF/Npm+Registry#NpmRegistry-AuthenticatingthenpmClient) documentation for Artifactory configuration.
## Can I manually trigger the release of a specific version?
You can trigger a release by pushing to your Git repository. You deliberately cannot trigger a _specific_ version release, because this is the whole point of semantic-release.
+2 -2
View File
@@ -14,7 +14,7 @@ See our [Node Support Policy](node-support-policy.md) for our long-term promise
The recommended approach is to run the `semantic-release` command from a CI job running on the latest available LTS version of node.
This can either be a job used by your project to test on the latest Node LTS version or a dedicated job for the release steps.
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/release-workflow/README.md#ci-configurations) for more details.
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/ci-configurations/README.md#ci-configurations) for more details.
## Alternative solutions
@@ -24,7 +24,7 @@ See [CI configuration](../usage/ci-configuration.md) and [CI configuration recip
Use it to execute the `semantic-release` command.
```bash
$ npx -p node@lts -c "npx semantic-release"
$ npx -p node@v18-lts -c "npx semantic-release"
```
**Note**: See [What is npx](./FAQ.md#what-is-npx) for more details.
+2 -2
View File
@@ -14,7 +14,7 @@ Here are a few examples of the CI services that can be used to achieve this:
- [Wercker Workflows](http://devcenter.wercker.com/docs/workflows)
- [GoCD Pipelines](https://docs.gocd.org/current/introduction/concepts_in_go.html#pipeline).
See [CI configuration recipes](../recipes/ci-configurations#ci-configurations) for more details.
See [CI configuration recipes](../recipes/ci-configurations/README.md) for more details.
## Authentication
@@ -45,6 +45,6 @@ See each plugin's documentation for the environment variables required.
The authentication token/credentials have to be made available in the CI service via environment variables.
See [CI configuration recipes](../recipes/ci-configurations#ci-configurations) for more details on how to configure environment variables in your CI service.
See [CI configuration recipes](../recipes/ci-configurations/README.md) for more details on how to configure environment variables in your CI service.
**Note**: The environment variables `GH_TOKEN`, `GITHUB_TOKEN`, `GL_TOKEN` and `GITLAB_TOKEN` can be used for both the Git authentication and the API authentication required by [@semantic-release/github](https://github.com/semantic-release/github) and [@semantic-release/gitlab](https://github.com/semantic-release/gitlab).
+24 -5
View File
@@ -24,9 +24,28 @@ 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 `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.
### Notes
**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.
1. If you've globally installed **semantic-release** then we recommend that you set the major **semantic-release** version to install.
For example, by using `npx semantic-release@18`.
This way you control which major version of **semantic-release** is used by your build, and thus avoid breaking the build when there's a new major version of **semantic-release**.
This also means you, or a bot, must upgrade **semantic-release** when a new major version is released.
2. Pinning **semantic-release** to an exact version makes your releases even more deterministic.
But pinning also means you, or a bot, must update to newer versions of **semantic-release** more often.
3. You can use [Renovate's regex manager](https://docs.renovatebot.com/modules/manager/regex/) to get automatic updates for **semantic-release** in either of the above scenarios.
Put this in your Renovate configuration file:
```json
{
"regexManagers": [
{
"description": "Update semantic-release version used by npx",
"fileMatch": ["^\\.github/workflows/[^/]+\\.ya?ml$"],
"matchStrings": ["\\srun: npx semantic-release@(?<currentValue>.*?)\\s"],
"datasourceTemplate": "npm",
"depNameTemplate": "semantic-release"
}
]
}
```
4. `npx` is a tool bundled with `npm@>=5.2.0`. You can use it to install (and run) the **semantic-release** binary.
See [What is npx](../support/FAQ.md#what-is-npx) for more details.
+1 -1
View File
@@ -12,7 +12,7 @@ See [Release workflow recipes](../recipes/release-workflow/README.md#release-wor
The release workflow is configured via the [branches option](./configuration.md#branches) which accepts a single or an array of branch definitions.
Each branch can be defined either as a string, a [glob](https://github.com/micromatch/micromatch#matching-features) or an object. For string and glob definitions each [property](#branches-properties) will be defaulted.
A branch can defined as one of three types:
A branch can be defined as one of three types:
- [release](#release-branches): to make releases on top of the last version released
- [maintenance](#maintenance-branches): to make releases on top of an old release
+7 -7
View File
@@ -1,18 +1,18 @@
import {isString, mapValues, omit, remove, template} from 'lodash-es';
import micromatch from 'micromatch';
import {getBranches} from '../git.js';
import { isString, mapValues, omit, remove, template } from "lodash-es";
import micromatch from "micromatch";
import { getBranches } from "../git.js";
export default async (repositoryUrl, {cwd}, branches) => {
const gitBranches = await getBranches(repositoryUrl, {cwd});
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) => ({
name,
...mapValues(omit(branch, 'name'), (value) => (isString(value) ? template(value)({name}) : value)),
...mapValues(omit(branch, "name"), (value) => (isString(value) ? template(value)({ name }) : value)),
})),
],
[]
);
}
};
+13 -14
View File
@@ -1,36 +1,35 @@
import {escapeRegExp, template} from 'lodash-es';
import semver from 'semver';
import pReduce from 'p-reduce';
import debugTags from 'debug';
import {getNote, getTags} from '../../lib/git.js';
import { escapeRegExp, template } from "lodash-es";
import semver from "semver";
import pReduce from "p-reduce";
import debugTags from "debug";
import { getNote, getTags } from "../../lib/git.js";
const debug = debugTags('semantic-release:get-tags');
const debug = debugTags("semantic-release:get-tags");
export default 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,
// so it's guaranteed to no be present in the `tagFormat`.
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)')}`;
const tagRegexp = `^${escapeRegExp(template(tagFormat)({ version: " " })).replace(" ", "(.+)")}`;
return pReduce(
branches,
async (branches, branch) => {
const branchTags = await pReduce(
await getTags(branch.name, {cwd, env}),
await getTags(branch.name, { cwd, env }),
async (branchTags, tag) => {
const [, version] = tag.match(tagRegexp) || [];
return version && semver.valid(semver.clean(version))
? [...branchTags, {gitTag: tag, version, channels: (await getNote(tag, {cwd, env})).channels || [null]}]
? [...branchTags, { gitTag: tag, version, channels: (await getNote(tag, { cwd, env })).channels || [null] }]
: branchTags;
},
[]
);
debug('found tags for branch %s: %o', branch.name, branchTags);
return [...branches, {...branch, tags: branchTags}];
debug("found tags for branch %s: %o", branch.name, branchTags);
return [...branches, { ...branch, tags: branchTags }];
},
[]
);
}
};
+22 -22
View File
@@ -1,51 +1,51 @@
import {isRegExp, isString} from 'lodash-es';
import AggregateError from 'aggregate-error';
import pEachSeries from 'p-each-series';
import * as 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 * as normalize from './normalize.js';
import { isRegExp, isString } from "lodash-es";
import AggregateError from "aggregate-error";
import pEachSeries from "p-each-series";
import * as 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 * as normalize from "./normalize.js";
export default async (repositoryUrl, ciBranch, context) => {
const {cwd, env} = 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}) => {
await fetch(repositoryUrl, name, ciBranch, {cwd, env});
await pEachSeries(remoteBranches, async ({ name }) => {
await fetch(repositoryUrl, name, ciBranch, { cwd, env });
});
await fetchNotes(repositoryUrl, {cwd, env});
await fetchNotes(repositoryUrl, { cwd, env });
const branches = await getTags(context, remoteBranches);
const errors = [];
const branchesByType = Object.entries(DEFINITIONS).reduce(
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
(branchesByType, [type, {filter}]) => ({[type]: branches.filter(filter), ...branchesByType}),
(branchesByType, [type, { filter }]) => ({ [type]: branches.filter(filter), ...branchesByType }),
{}
);
const result = Object.entries(DEFINITIONS).reduce((result, [type, {branchesValidator, branchValidator}]) => {
const result = Object.entries(DEFINITIONS).reduce((result, [type, { branchesValidator, branchValidator }]) => {
branchesByType[type].forEach((branch) => {
if (branchValidator && !branchValidator(branch)) {
errors.push(getError(`E${type.toUpperCase()}BRANCH`, {branch}));
errors.push(getError(`E${type.toUpperCase()}BRANCH`, { branch }));
}
});
const branchesOfType = normalize[type](branchesByType);
if (!branchesValidator(branchesOfType)) {
errors.push(getError(`E${type.toUpperCase()}BRANCHES`, {branches: branchesOfType}));
errors.push(getError(`E${type.toUpperCase()}BRANCHES`, { branches: branchesOfType }));
}
return {...result, [type]: branchesOfType};
return { ...result, [type]: branchesOfType };
}, {});
const duplicates = [...branches]
@@ -54,12 +54,12 @@ export default async (repositoryUrl, ciBranch, context) => {
.filter((_, idx, array) => array[idx] === array[idx + 1] && array[idx] !== array[idx - 1]);
if (duplicates.length > 0) {
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
errors.push(getError("EDUPLICATEBRANCHES", { duplicates }));
}
await pEachSeries(branches, async (branch) => {
if (!(await verifyBranchName(branch.name))) {
errors.push(getError('EINVALIDBRANCHNAME', branch));
errors.push(getError("EINVALIDBRANCHNAME", branch));
}
});
@@ -68,4 +68,4 @@ export default async (repositoryUrl, ciBranch, context) => {
}
return [...result.maintenance, ...result.release, ...result.prerelease];
}
};
+18 -17
View File
@@ -1,27 +1,28 @@
import {isNil, sortBy} from 'lodash-es';
import semverDiff from 'semver-diff';
import {FIRST_RELEASE, RELEASE_TYPE} from '../definitions/constants.js';
import { isNil, sortBy } from "lodash-es";
import semverDiff from "semver-diff";
import { FIRST_RELEASE, RELEASE_TYPE } from "../definitions/constants.js";
import {
getFirstVersion,
getLatestVersion,
getLowerBound, getRange,
getLowerBound,
getRange,
getUpperBound,
highest,
isMajorRange,
lowest,
tagsToVersions
} from '../utils.js';
tagsToVersions,
} from "../utils.js";
export function maintenance({maintenance, release}) {
export function maintenance({ maintenance, release }) {
return sortBy(
maintenance.map(({name, range, channel, ...rest}) => ({
maintenance.map(({ name, range, channel, ...rest }) => ({
...rest,
name,
range: range || name,
channel: isNil(channel) ? name : channel,
})),
'range'
).map(({name, range, tags, ...rest}, idx, branches) => {
"range"
).map(({ name, range, tags, ...rest }, idx, branches) => {
const versions = tagsToVersions(tags);
// Find the lower bound based on Maintenance branches
const maintenanceMin =
@@ -44,7 +45,7 @@ export function maintenance({maintenance, release}) {
const diff = semverDiff(min, max);
return {
...rest,
type: 'maintenance',
type: "maintenance",
name,
tags,
range: getRange(min, max),
@@ -54,7 +55,7 @@ export function maintenance({maintenance, release}) {
});
}
export function release({release}) {
export function release({ release }) {
if (release.length === 0) {
return release;
}
@@ -62,7 +63,7 @@ export function release({release}) {
// The intial lastVersion is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
let lastVersion = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
return release.map(({name, tags, channel, ...rest}, idx) => {
return release.map(({ name, tags, channel, ...rest }, idx) => {
const versions = tagsToVersions(tags);
// The new lastVersion is the highest version between the current branch last release and the previous branch lastVersion
lastVersion = highest(getLatestVersion(versions), lastVersion);
@@ -79,7 +80,7 @@ export function release({release}) {
...rest,
channel: idx === 0 ? channel : isNil(channel) ? name : channel,
tags,
type: 'release',
type: "release",
name,
range: getRange(lastVersion, bound),
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
@@ -88,13 +89,13 @@ export function release({release}) {
});
}
export function prerelease({prerelease}) {
return prerelease.map(({name, prerelease, channel, tags, ...rest}) => {
export function prerelease({ prerelease }) {
return prerelease.map(({ name, prerelease, channel, tags, ...rest }) => {
const preid = prerelease === true ? name : prerelease;
return {
...rest,
channel: isNil(channel) ? name : channel,
type: 'prerelease',
type: "prerelease",
name,
prerelease: preid,
tags,
+9 -9
View File
@@ -1,18 +1,18 @@
import {isNil, uniqBy} from 'lodash-es';
import semver from 'semver';
import {isMaintenanceRange} from '../utils.js';
import { isNil, uniqBy } from "lodash-es";
import semver from "semver";
import { isMaintenanceRange } from "../utils.js";
export 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,
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,
};
export const prerelease = {
filter: ({prerelease}) => !isNil(prerelease) && prerelease !== false,
branchValidator: ({name, 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,
};
export const release = {
+8 -8
View File
@@ -1,17 +1,17 @@
export const RELEASE_TYPE = ['patch', 'minor', 'major'];
export const RELEASE_TYPE = ["patch", "minor", "major"];
export const FIRST_RELEASE = '1.0.0';
export const FIRST_RELEASE = "1.0.0";
export const FIRSTPRERELEASE = '1';
export const FIRSTPRERELEASE = "1";
export const COMMIT_NAME = 'semantic-release-bot';
export const COMMIT_NAME = "semantic-release-bot";
export const COMMIT_EMAIL = 'semantic-release-bot@martynus.net';
export const COMMIT_EMAIL = "semantic-release-bot@martynus.net";
export const RELEASE_NOTES_SEPARATOR = '\n\n';
export const RELEASE_NOTES_SEPARATOR = "\n\n";
export const SECRET_REPLACEMENT = '[secure]';
export const SECRET_REPLACEMENT = "[secure]";
export const SECRET_MIN_SIZE = 5;
export const GIT_NOTE_REF = 'semantic-release';
export const GIT_NOTE_REF = "semantic-release";
+81 -81
View File
@@ -1,21 +1,21 @@
import {inspect} from 'node:util';
import {createRequire} from 'node:module';
import {isString, toLower, trim} from 'lodash-es';
import {RELEASE_TYPE} from './constants.js';
import { inspect } from "node:util";
import { createRequire } from "node:module";
import { isString, toLower, trim } from "lodash-es";
import { RELEASE_TYPE } from "./constants.js";
const require = createRequire(import.meta.url);
const pkg = require('../../package.json');
const pkg = require("../../package.json");
const [homepage] = pkg.homepage.split('#');
const [homepage] = pkg.homepage.split("#");
const stringify = (object) =>
isString(object) ? object : inspect(object, {breakLength: Infinity, depth: 2, maxArrayLength: 5});
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])}`;
`${words.slice(0, -1).join(", ")}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;
export function ENOGITREPO({cwd}) {
export function ENOGITREPO({ cwd }) {
return {
message: 'Not running from a git repository.',
message: "Not running from a git repository.",
details: `The \`semantic-release\` command must be executed from a Git repository.
The current working directory is \`${cwd}\`.
@@ -26,76 +26,76 @@ Please verify your CI configuration to make sure the \`semantic-release\` comman
export function ENOREPOURL() {
return {
message: 'The `repositoryUrl` option is required.',
message: "The `repositoryUrl` option is required.",
details: `The [repositoryUrl option](${linkify(
'docs/usage/configuration.md#repositoryurl'
"docs/usage/configuration.md#repositoryurl"
)}) cannot be determined from the semantic-release configuration, the \`package.json\` nor the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
Please make sure to add the \`repositoryUrl\` to the [semantic-release configuration] (${linkify(
'docs/usage/configuration.md'
"docs/usage/configuration.md"
)}).`,
};
}
export function EGITNOPERMISSION({options: {repositoryUrl}, branch: {name}}) {
export function EGITNOPERMISSION({ options: { repositoryUrl }, branch: { name } }) {
return {
message: 'Cannot push to the Git repository.',
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}\`.
This can be caused by:
- a misconfiguration of the [repositoryUrl](${linkify('docs/usage/configuration.md#repositoryurl')}) option
- a misconfiguration of the [repositoryUrl](${linkify("docs/usage/configuration.md#repositoryurl")}) option
- the repository being unavailable
- or missing push permission for the user configured via the [Git credentials on your CI environment](${linkify(
'docs/usage/ci-configuration.md#authentication'
)})`,
"docs/usage/ci-configuration.md#authentication"
)})`,
};
}
export function EINVALIDTAGFORMAT({options: {tagFormat}}) {
export function EINVALIDTAGFORMAT({ options: { tagFormat } }) {
return {
message: 'Invalid `tagFormat` option.',
message: "Invalid `tagFormat` option.",
details: `The [tagFormat](${linkify(
'docs/usage/configuration.md#tagformat'
"docs/usage/configuration.md#tagformat"
)}) must compile to a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
};
}
export function ETAGNOVERSION({options: {tagFormat}}) {
export function ETAGNOVERSION({ options: { tagFormat } }) {
return {
message: 'Invalid `tagFormat` option.',
message: "Invalid `tagFormat` option.",
details: `The [tagFormat](${linkify(
'docs/usage/configuration.md#tagformat'
"docs/usage/configuration.md#tagformat"
)}) option must contain the variable \`version\` exactly once.
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
};
}
export function EPLUGINCONF({type, required, pluginConf}) {
export function EPLUGINCONF({ type, required, pluginConf }) {
return {
message: `The \`${type}\` plugin configuration is invalid.`,
details: `The [${type} plugin configuration](${linkify(`docs/usage/plugins.md#${toLower(type)}-plugin`)}) ${
required ? 'is required and ' : ''
required ? "is required and " : ""
} 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)}\`.`,
};
}
export function EPLUGINSCONF({plugin}) {
export function EPLUGINSCONF({ plugin }) {
return {
message: 'The `plugins` configuration is invalid.',
message: "The `plugins` configuration is invalid.",
details: `The [plugins](${linkify(
'docs/usage/configuration.md#plugins'
"docs/usage/configuration.md#plugins"
)}) option must be an array of plugin definitions. A plugin definition is an npm module name, optionally wrapped in an array with an object.
The invalid configuration is \`${stringify(plugin)}\`.`,
};
}
export function EPLUGIN({pluginName, type}) {
export function EPLUGIN({ pluginName, type }) {
return {
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}\`.
@@ -103,17 +103,17 @@ export function EPLUGIN({pluginName, type}) {
The plugin \`${pluginName}\` doesn't have the property \`${type}\` and cannot be used for the \`${type}\` step.
Please refer to the \`${pluginName}\` and [semantic-release plugins configuration](${linkify(
'docs/usage/plugins.md'
"docs/usage/plugins.md"
)}) documentation for more details.`,
};
}
export function EANALYZECOMMITSOUTPUT({result, pluginName}) {
export function EANALYZECOMMITSOUTPUT({ result, pluginName }) {
return {
message: 'The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.',
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}\``
).join(', ')}.
).join(", ")}.
The \`analyzeCommits\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
@@ -121,15 +121,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
- The **semantic-release** version: \`${pkg.version}\`
- The **semantic-release** logs from your CI job
- The value returned by the plugin: \`${stringify(result)}\`
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
'docs/developer-guide/plugin.md'
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
"docs/developer-guide/plugin.md"
)})`,
};
}
export function EGENERATENOTESOUTPUT({result, pluginName}) {
export function EGENERATENOTESOUTPUT({ result, pluginName }) {
return {
message: 'The `generateNotes` plugin returned an invalid value. It must return a `String`.',
message: "The `generateNotes` plugin returned an invalid value. It must return a `String`.",
details: `The \`generateNotes\` plugin must return a \`String\`.
The \`generateNotes\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
@@ -138,15 +138,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
- The **semantic-release** version: \`${pkg.version}\`
- The **semantic-release** logs from your CI job
- The value returned by the plugin: \`${stringify(result)}\`
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
'docs/developer-guide/plugin.md'
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
"docs/developer-guide/plugin.md"
)})`,
};
}
export function EPUBLISHOUTPUT({result, pluginName}) {
export function EPUBLISHOUTPUT({ result, pluginName }) {
return {
message: 'A `publish` plugin returned an invalid value. It must return an `Object`.',
message: "A `publish` plugin returned an invalid value. It must return an `Object`.",
details: `The \`publish\` plugins must return an \`Object\`.
The \`publish\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
@@ -155,15 +155,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
- The **semantic-release** version: \`${pkg.version}\`
- The **semantic-release** logs from your CI job
- The value returned by the plugin: \`${stringify(result)}\`
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
'docs/developer-guide/plugin.md'
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
"docs/developer-guide/plugin.md"
)})`,
};
}
export function EADDCHANNELOUTPUT({result, pluginName}) {
export function EADDCHANNELOUTPUT({ result, pluginName }) {
return {
message: 'A `addChannel` plugin returned an invalid value. It must return an `Object`.',
message: "A `addChannel` plugin returned an invalid value. It must return an `Object`.",
details: `The \`addChannel\` plugins must return an \`Object\`.
The \`addChannel\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
@@ -172,72 +172,72 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
- The **semantic-release** version: \`${pkg.version}\`
- The **semantic-release** logs from your CI job
- The value returned by the plugin: \`${stringify(result)}\`
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
'docs/developer-guide/plugin.md'
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
"docs/developer-guide/plugin.md"
)})`,
};
}
export function EINVALIDBRANCH({branch}) {
export function EINVALIDBRANCH({ branch }) {
return {
message: 'A branch is invalid in the `branches` configuration.',
message: "A branch is invalid in the `branches` configuration.",
details: `Each branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must be either a string, a regexp or an object with a \`name\` property.
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
};
}
export function EINVALIDBRANCHNAME({branch}) {
export function EINVALIDBRANCHNAME({ branch }) {
return {
message: 'A branch name is invalid in the `branches` configuration.',
message: "A branch name is invalid in the `branches` configuration.",
details: `Each branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must be a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
};
}
export function EDUPLICATEBRANCHES({duplicates}) {
export function EDUPLICATEBRANCHES({ duplicates }) {
return {
message: 'The `branches` configuration has duplicate branches.',
message: "The `branches` configuration has duplicate branches.",
details: `Each branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must havea unique name.
Your configuration contains duplicates for the following branch names: \`${stringify(duplicates)}\`.`,
};
}
export function EMAINTENANCEBRANCH({branch}) {
export function EMAINTENANCEBRANCH({ branch }) {
return {
message: 'A maintenance branch is invalid in the `branches` configuration.',
message: "A maintenance branch is invalid in the `branches` configuration.",
details: `Each maintenance branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must have a \`range\` property formatted like \`N.x\`, \`N.x.x\` or \`N.N.x\` (\`N\` is a number).
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
};
}
export function EMAINTENANCEBRANCHES({branches}) {
export function EMAINTENANCEBRANCHES({ branches }) {
return {
message: 'The maintenance branches are invalid in the `branches` configuration.',
message: "The maintenance branches are invalid in the `branches` configuration.",
details: `Each maintenance branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must have a unique \`range\` property.
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
};
}
export function ERELEASEBRANCHES({branches}) {
export function ERELEASEBRANCHES({ branches }) {
return {
message: 'The release branches are invalid in the `branches` configuration.',
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(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}).
This may occur if your repository does not have a release branch, such as \`master\`.
@@ -246,53 +246,53 @@ Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
};
}
export function EPRERELEASEBRANCH({branch}) {
export function EPRERELEASEBRANCH({ branch }) {
return {
message: 'A pre-release branch configuration is invalid in the `branches` configuration.',
message: "A pre-release branch configuration is invalid in the `branches` configuration.",
details: `Each pre-release branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must have a \`prerelease\` property valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
};
}
export function EPRERELEASEBRANCHES({branches}) {
export function EPRERELEASEBRANCHES({ branches }) {
return {
message: 'The pre-release branches are invalid in the `branches` configuration.',
message: "The pre-release branches are invalid in the `branches` configuration.",
details: `Each pre-release branch in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
"docs/usage/configuration.md#branches"
)}) must have a unique \`prerelease\` property. If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
};
}
export function EINVALIDNEXTVERSION({nextRelease: {version}, branch: {name, range}, commits, validBranches}) {
export function EINVALIDNEXTVERSION({ nextRelease: { version }, branch: { name, range }, commits, validBranches }) {
return {
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}\`.
The following commit${commits.length > 1 ? 's are' : ' is'} responsible for the invalid release:
${commits.map(({commit: {short}, subject}) => `- ${subject} (${short})`).join('\n')}
The following commit${commits.length > 1 ? "s are" : " is"} responsible for the invalid release:
${commits.map(({ commit: { short }, subject }) => `- ${subject} (${short})`).join("\n")}
${
commits.length > 1 ? 'Those commits' : 'This commit'
} should be moved to a valid branch with [git merge](https://git-scm.com/docs/git-merge) or [git cherry-pick](https://git-scm.com/docs/git-cherry-pick) and removed from branch \`${name}\` with [git revert](https://git-scm.com/docs/git-revert) or [git reset](https://git-scm.com/docs/git-reset).
commits.length > 1 ? "Those commits" : "This commit"
} should be moved to a valid branch with [git merge](https://git-scm.com/docs/git-merge) or [git cherry-pick](https://git-scm.com/docs/git-cherry-pick) and removed from branch \`${name}\` with [git revert](https://git-scm.com/docs/git-revert) or [git reset](https://git-scm.com/docs/git-reset).
A valid branch could be ${wordsList(validBranches.map(({name}) => `\`${name}\``))}.
A valid branch could be ${wordsList(validBranches.map(({ name }) => `\`${name}\``))}.
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
See the [workflow configuration documentation](${linkify("docs/usage/workflow-configuration.md")}) for more details.`,
};
}
export function EINVALIDMAINTENANCEMERGE({nextRelease: {channel, gitTag, version}, branch: {mergeRange, name}}) {
export function EINVALIDMAINTENANCEMERGE({ nextRelease: { channel, gitTag, version }, branch: { mergeRange, name } }) {
return {
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.
The branch \`${name}\` head should be [reset](https://git-scm.com/docs/git-reset) to a previous commit so the commit with tag \`${gitTag}\` is removed from the branch history.
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
See the [workflow configuration documentation](${linkify("docs/usage/workflow-configuration.md")}) for more details.`,
};
}
+20 -20
View File
@@ -1,23 +1,23 @@
/* eslint require-atomic-updates: off */
import {isPlainObject, isString} from 'lodash-es';
import {getGitHead} from '../git.js';
import hideSensitive from '../hide-sensitive.js';
import {hideSensitiveValues} from '../utils.js';
import {RELEASE_NOTES_SEPARATOR, RELEASE_TYPE} from './constants.js';
import { isPlainObject, isString } from "lodash-es";
import { getGitHead } from "../git.js";
import hideSensitive from "../hide-sensitive.js";
import { hideSensitiveValues } from "../utils.js";
import { RELEASE_NOTES_SEPARATOR, RELEASE_TYPE } from "./constants.js";
export default {
verifyConditions: {
required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}),
pipelineConfig: () => ({ settleAll: true }),
},
analyzeCommits: {
default: ['@semantic-release/commit-analyzer'],
default: ["@semantic-release/commit-analyzer"],
required: true,
dryRun: true,
outputValidator: (output) => !output || RELEASE_TYPE.includes(output),
preprocess: ({commits, ...inputs}) => ({
preprocess: ({ commits, ...inputs }) => ({
...inputs,
commits: commits.filter((commit) => !/\[skip\s+release]|\[release\s+skip]/i.test(commit.message)),
}),
@@ -32,29 +32,29 @@ export default {
verifyRelease: {
required: false,
dryRun: true,
pipelineConfig: () => ({settleAll: true}),
pipelineConfig: () => ({ settleAll: true }),
},
generateNotes: {
required: false,
dryRun: true,
outputValidator: (output) => !output || isString(output),
pipelineConfig: () => ({
getNextInput: ({nextRelease, ...context}, notes) => ({
getNextInput: ({ nextRelease, ...context }, notes) => ({
...context,
nextRelease: {
...nextRelease,
notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`,
notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ""}${notes}`,
},
}),
}),
postprocess: (results, {env}) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
postprocess: (results, { env }) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
},
prepare: {
required: false,
dryRun: false,
pipelineConfig: ({generateNotes}) => ({
pipelineConfig: ({ generateNotes }) => ({
getNextInput: async (context) => {
const newGitHead = await getGitHead({cwd: context.cwd});
const newGitHead = await getGitHead({ cwd: context.cwd });
// If previous prepare plugin has created a commit (gitHead changed)
if (context.nextRelease.gitHead !== newGitHead) {
context.nextRelease.gitHead = newGitHead;
@@ -73,7 +73,7 @@ export default {
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
transform: (release, step, { nextRelease }) => ({
...(release === false ? {} : nextRelease),
...release,
...step,
@@ -86,7 +86,7 @@ export default {
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
transform: (release, step, { nextRelease }) => ({
...(release === false ? {} : nextRelease),
...release,
...step,
@@ -96,13 +96,13 @@ export default {
success: {
required: false,
dryRun: false,
pipelineConfig: () => ({settleAll: true}),
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
pipelineConfig: () => ({ settleAll: true }),
preprocess: ({ releases, env, ...inputs }) => ({ ...inputs, env, releases: hideSensitiveValues(env, releases) }),
},
fail: {
required: false,
dryRun: false,
pipelineConfig: () => ({settleAll: true}),
preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}),
pipelineConfig: () => ({ settleAll: true }),
preprocess: ({ errors, env, ...inputs }) => ({ ...inputs, env, errors: hideSensitiveValues(env, errors) }),
},
};
+5 -1
View File
@@ -30,7 +30,11 @@ 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))) ||
((branch.type === "prerelease" &&
tag.channels.some((channel) => isSameChannel(branch.channel, channel)) &&
semver
.parse(tag.version)
.prerelease.includes(branch.prerelease === true ? branch.name : branch.prerelease)) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
+18 -18
View File
@@ -1,13 +1,13 @@
import {castArray, identity, isNil, isPlainObject, isString, omit} from 'lodash-es';
import AggregateError from 'aggregate-error';
import getError from '../get-error.js';
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
import {loadPlugin, parseConfig, validatePlugin, validateStep} from './utils.js';
import pipeline from './pipeline.js';
import normalize from './normalize.js';
import { castArray, identity, isNil, isPlainObject, isString, omit } from "lodash-es";
import AggregateError from "aggregate-error";
import getError from "../get-error.js";
import PLUGINS_DEFINITIONS from "../definitions/plugins.js";
import { loadPlugin, parseConfig, validatePlugin, validateStep } from "./utils.js";
import pipeline from "./pipeline.js";
import normalize from "./normalize.js";
export default async (context, pluginsPath) => {
let {options, logger} = context;
let { options, logger } = context;
const errors = [];
const plugins = options.plugins
@@ -20,8 +20,8 @@ export default async (context, pluginsPath) => {
if (isPlainObject(plugin)) {
Object.entries(plugin).forEach(([type, func]) => {
if (PLUGINS_DEFINITIONS[type]) {
Reflect.defineProperty(func, 'pluginName', {
value: isPlainObject(name) ? 'Inline plugin' : name,
Reflect.defineProperty(func, "pluginName", {
value: isPlainObject(name) ? "Inline plugin" : name,
writable: false,
enumerable: true,
});
@@ -29,10 +29,10 @@ export default async (context, pluginsPath) => {
}
});
} else {
errors.push(getError('EPLUGINSCONF', {plugin}));
errors.push(getError("EPLUGINSCONF", { plugin }));
}
} else {
errors.push(getError('EPLUGINSCONF', {plugin}));
errors.push(getError("EPLUGINSCONF", { plugin }));
}
return pluginsList;
@@ -43,12 +43,12 @@ export default async (context, pluginsPath) => {
throw new AggregateError(errors);
}
options = {...plugins, ...options};
options = { ...plugins, ...options };
const pluginsConfig = await Object.entries(PLUGINS_DEFINITIONS).reduce(
async (
eventualPluginsConfigAccumulator,
[type, {required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
[type, { required, default: def, pipelineConfig, postprocess = identity, preprocess = identity }]
) => {
let pluginOptions;
const pluginsConfigAccumulator = await eventualPluginsConfigAccumulator;
@@ -63,8 +63,8 @@ export default async (context, pluginsPath) => {
);
}
if (!validateStep({required}, options[type])) {
errors.push(getError('EPLUGINCONF', {type, required, pluginConf: options[type]}));
if (!validateStep({ required }, options[type])) {
errors.push(getError("EPLUGINCONF", { type, required, pluginConf: options[type] }));
return pluginsConfigAccumulator;
}
@@ -74,7 +74,7 @@ export default async (context, pluginsPath) => {
const steps = await Promise.all(
castArray(pluginOptions).map(async (pluginOpt) =>
normalize(
{...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), 'plugins')},
{ ...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), "plugins") },
type,
pluginOpt,
pluginsPath
@@ -100,4 +100,4 @@ export default async (context, pluginsPath) => {
}
return pluginsConfig;
}
};
+17 -17
View File
@@ -1,14 +1,14 @@
import {cloneDeep, isFunction, isPlainObject, noop, omit} from 'lodash-es';
import debugPlugins from 'debug';
import getError from '../get-error.js';
import {extractErrors} from '../utils.js';
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
import {loadPlugin, parseConfig} from './utils.js';
import { cloneDeep, isFunction, isPlainObject, noop, omit } from "lodash-es";
import debugPlugins from "debug";
import getError from "../get-error.js";
import { extractErrors } from "../utils.js";
import PLUGINS_DEFINITIONS from "../definitions/plugins.js";
import { loadPlugin, parseConfig } from "./utils.js";
const debug = debugPlugins('semantic-release:plugins');
const debug = debugPlugins("semantic-release:plugins");
export default async (context, type, pluginOpt, pluginsPath) => {
const {stdout, stderr, options, logger} = context;
const { stdout, stderr, options, logger } = context;
if (!pluginOpt) {
return noop;
}
@@ -21,26 +21,26 @@ export default async (context, type, pluginOpt, pluginsPath) => {
let func;
if (isFunction(plugin)) {
func = plugin.bind(null, cloneDeep({...options, ...config}));
func = plugin.bind(null, cloneDeep({ ...options, ...config }));
} else if (isPlainObject(plugin) && plugin[type] && isFunction(plugin[type])) {
func = plugin[type].bind(null, cloneDeep({...options, ...config}));
func = plugin[type].bind(null, cloneDeep({ ...options, ...config }));
} else {
throw getError('EPLUGIN', {type, pluginName});
throw getError("EPLUGIN", { type, pluginName });
}
const validator = async (input) => {
const {dryRun, outputValidator} = PLUGINS_DEFINITIONS[type] || {};
const { dryRun, outputValidator } = PLUGINS_DEFINITIONS[type] || {};
try {
if (!input.options.dryRun || dryRun) {
logger.log(`Start step "${type}" of plugin "${pluginName}"`);
const result = await func({
...cloneDeep(omit(input, ['stdout', 'stderr', 'logger'])),
...cloneDeep(omit(input, ["stdout", "stderr", "logger"])),
stdout,
stderr,
logger: logger.scope(logger.scopeName, pluginName),
});
if (outputValidator && !outputValidator(result)) {
throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName});
throw getError(`E${type.toUpperCase()}OUTPUT`, { result, pluginName });
}
logger.success(`Completed step "${type}" of plugin "${pluginName}"`);
@@ -50,12 +50,12 @@ export default async (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;
}
};
Reflect.defineProperty(validator, 'pluginName', {value: pluginName, writable: false, enumerable: true});
Reflect.defineProperty(validator, "pluginName", { value: pluginName, writable: false, enumerable: true });
if (!isFunction(pluginOpt)) {
if (pluginsPath[name]) {
@@ -66,4 +66,4 @@ export default async (context, type, pluginOpt, pluginsPath) => {
}
return validator;
}
};
+33 -32
View File
@@ -1,7 +1,7 @@
import {identity} from 'lodash-es';
import pReduce from 'p-reduce';
import AggregateError from 'aggregate-error';
import {extractErrors} from '../utils.js';
import { identity } from "lodash-es";
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,34 +25,35 @@ import {extractErrors} from '../utils.js';
*
* @return {Pipeline} A Function that execute the `steps` sequentially
*/
export default (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async (input) => {
const results = [];
const errors = [];
await pReduce(
steps,
async (lastInput, step) => {
let result;
try {
// Call the step with the input computed at the end of the previous iteration and save intermediary result
result = await transform(await step(lastInput), step, lastInput);
results.push(result);
} catch (error) {
if (settleAll) {
errors.push(...extractErrors(error));
result = error;
} else {
throw error;
export default (steps, { settleAll = false, getNextInput = identity, transform = identity } = {}) =>
async (input) => {
const results = [];
const errors = [];
await pReduce(
steps,
async (lastInput, step) => {
let result;
try {
// Call the step with the input computed at the end of the previous iteration and save intermediary result
result = await transform(await step(lastInput), step, lastInput);
results.push(result);
} catch (error) {
if (settleAll) {
errors.push(...extractErrors(error));
result = error;
} else {
throw error;
}
}
}
// Prepare input for the next step, passing the input of the last iteration (or initial parameter for the first iteration) and the result of the current one
return getNextInput(lastInput, result);
},
input
);
if (errors.length > 0) {
throw new AggregateError(errors);
}
// Prepare input for the next step, passing the input of the last iteration (or initial parameter for the first iteration) and the result of the current one
return getNextInput(lastInput, result);
},
input
);
if (errors.length > 0) {
throw new AggregateError(errors);
}
return results;
}
return results;
};
+19 -10
View File
@@ -1,7 +1,7 @@
import {dirname} from 'node:path';
import {fileURLToPath} from 'node:url';
import {castArray, isArray, isFunction, isNil, isPlainObject, isString} from 'lodash-es';
import resolveFrom from 'resolve-from';
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { castArray, isArray, isFunction, isNil, isPlainObject, isString } from "lodash-es";
import resolveFrom from "resolve-from";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -38,7 +38,7 @@ export function validatePlugin(conf) {
);
}
export function validateStep({required}, conf) {
export function validateStep({ required }, conf) {
conf = castArray(conf).filter(Boolean);
if (required) {
return conf.length >= 1 && validateSteps(conf);
@@ -47,14 +47,23 @@ export function validateStep({required}, conf) {
return conf.length === 0 || validateSteps(conf);
}
export async function loadPlugin({cwd}, name, pluginsPath) {
export async function loadPlugin({ cwd }, name, pluginsPath) {
const basePath = pluginsPath[name]
? dirname(resolveFrom.silent(__dirname, pluginsPath[name]) || resolveFrom(cwd, pluginsPath[name]))
: __dirname;
// See https://github.com/mysticatea/eslint-plugin-node/issues/250
// eslint-disable-next-line node/no-unsupported-features/es-syntax
return isFunction(name) ? name : (await import(resolveFrom.silent(basePath, name) || resolveFrom(cwd, name))).default;
if (isFunction(name)) {
return name;
}
const file = resolveFrom.silent(basePath, name) || resolveFrom(cwd, name);
const { default: cjsExport, ...esmNamedExports } = await import(`file://${file}`);
if (cjsExport) {
return cjsExport;
}
return esmNamedExports;
}
export function parseConfig(plugin) {
@@ -63,7 +72,7 @@ export function parseConfig(plugin) {
if (isArray(plugin)) {
[path, config] = plugin;
} else if (isPlainObject(plugin) && !isNil(plugin.path)) {
({path, ...config} = plugin);
({ path, ...config } = plugin);
} else {
path = plugin;
}
+6202 -5438
View File
File diff suppressed because it is too large Load Diff
+29 -29
View File
@@ -26,54 +26,53 @@
"Matt Travi <npm@travi.org> (https://matt.travi.org/)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/error": "^3.0.0",
"@semantic-release/github": "^8.0.0",
"@semantic-release/npm": "^9.0.0",
"@semantic-release/release-notes-generator": "^10.0.0",
"@semantic-release/commit-analyzer": "^10.0.0",
"@semantic-release/error": "^4.0.0",
"@semantic-release/github": "^9.0.0",
"@semantic-release/npm": "^10.0.2",
"@semantic-release/release-notes-generator": "^11.0.0",
"aggregate-error": "^4.0.1",
"cosmiconfig": "^7.0.0",
"cosmiconfig": "^8.0.0",
"debug": "^4.0.0",
"env-ci": "^8.0.0",
"execa": "^6.1.0",
"env-ci": "^9.0.0",
"execa": "^7.0.0",
"figures": "^5.0.0",
"find-versions": "^5.1.0",
"get-stream": "^6.0.0",
"git-log-parser": "^1.2.0",
"hook-std": "^3.0.0",
"hosted-git-info": "^5.1.0",
"hosted-git-info": "^6.0.0",
"lodash-es": "^4.17.21",
"marked": "^4.1.0",
"marked": "^5.0.0",
"marked-terminal": "^5.1.1",
"micromatch": "^4.0.2",
"p-each-series": "^3.0.0",
"p-reduce": "^3.0.0",
"read-pkg-up": "^9.1.0",
"read-pkg-up": "^10.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.3.2",
"semver-diff": "^3.1.1",
"semver-diff": "^4.0.0",
"signale": "^1.2.1",
"yargs": "^17.5.1"
},
"devDependencies": {
"ava": "4.3.3",
"c8": "7.12.0",
"ava": "5.3.1",
"c8": "8.0.0",
"clear-module": "4.1.2",
"codecov": "3.8.3",
"delay": "5.0.0",
"dockerode": "3.3.4",
"file-url": "^4.0.0",
"fs-extra": "^10.1.0",
"got": "^12.5.0",
"dockerode": "3.3.5",
"file-url": "4.0.0",
"fs-extra": "11.1.1",
"got": "13.0.0",
"js-yaml": "4.1.0",
"mockserver-client": "5.14.0",
"nock": "13.2.9",
"p-retry": "^5.1.1",
"prettier": "^2.7.1",
"sinon": "14.0.0",
"mockserver-client": "5.15.0",
"nock": "13.3.1",
"p-retry": "5.1.2",
"prettier": "2.8.8",
"sinon": "15.2.0",
"stream-buffers": "3.0.2",
"tempy": "^3.0.0",
"testdouble": "3.16.6"
"tempy": "3.0.0",
"testdouble": "3.18.0"
},
"engines": {
"node": ">=18"
@@ -117,7 +116,8 @@
"trailingComma": "es5"
},
"publishConfig": {
"access": "public"
"access": "public",
"provenance": true
},
"repository": {
"type": "git",
@@ -125,8 +125,8 @@
},
"scripts": {
"codecov": "codecov -f coverage/coverage-final.json",
"lint": "prettier --check \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/*.js\"",
"lint:fix": "prettier --write \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/*.js\"",
"lint": "prettier --check \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/**/*.js\"",
"lint:fix": "prettier --write \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/**/*.js\"",
"pretest": "npm run lint",
"semantic-release": "./bin/semantic-release.js",
"test": "c8 ava --verbose",
+154 -154
View File
@@ -1,277 +1,277 @@
import test from 'ava';
import {union} from 'lodash-es';
import semver from 'semver';
import * as td from 'testdouble';
import test from "ava";
import { union } from "lodash-es";
import semver from "semver";
import * as td from "testdouble";
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
const getBranch = (branches, branch) => branches.find(({ name }) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({ version });
const merge = (branches, source, target, tag) => {
getBranch(branches, target).tags = union(
getBranch(branches, source).tags.filter(({version}) => !tag || semver.cmp(version, '<=', tag)),
getBranch(branches, source).tags.filter(({ version }) => !tag || semver.cmp(version, "<=", tag)),
getBranch(branches, target).tags
);
};
const remoteBranches = [];
const repositoryUrl = 'repositoryUrl';
const repositoryUrl = "repositoryUrl";
let expand, getTags, getBranches;
test.beforeEach(async (t) => {
getTags = (await td.replaceEsm('../../lib/branches/get-tags.js')).default;
expand = (await td.replaceEsm('../../lib/branches/expand.js')).default;
getBranches = (await import('../../lib/branches/index.js')).default;
})
getTags = (await td.replaceEsm("../../lib/branches/get-tags.js")).default;
expand = (await td.replaceEsm("../../lib/branches/expand.js")).default;
getBranches = (await import("../../lib/branches/index.js")).default;
});
test.afterEach.always((t) => {
td.reset();
});
test.serial('Enforce ranges with branching release workflow', async (t) => {
test.serial("Enforce ranges with branching release workflow", async (t) => {
const branches = [
{name: '1.x', tags: []},
{name: '1.0.x', tags: []},
{name: 'master', tags: []},
{name: 'next', tags: []},
{name: 'next-major', tags: []},
{name: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: true, tags: []},
{ name: "1.x", tags: [] },
{ name: "1.0.x", tags: [] },
{ name: "master", tags: [] },
{ name: "next", tags: [] },
{ name: "next-major", tags: [] },
{ name: "beta", prerelease: true, tags: [] },
{ name: "alpha", prerelease: true, tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
let result = (await getBranches(repositoryUrl, 'master', context)).map(({name, range}) => ({name, range,}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
let result = (await getBranches(repositoryUrl, "master", context)).map(({ name, range }) => ({ name, range }));
t.is(getBranch(result, "1.0.x").range, ">=1.0.0 <1.0.0", "Cannot release on 1.0.x before a releasing on master");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.0", "Cannot release on 1.x before a releasing on master");
t.is(getBranch(result, "master").range, ">=1.0.0");
t.is(getBranch(result, "next").range, ">=1.0.0");
t.is(getBranch(result, "next-major").range, ">=1.0.0");
release(branches, 'master', '1.0.0');
result = (await getBranches('repositoryUrl', 'master', context)).map(({name, range}) => ({name, range}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
t.is(getBranch(result, 'master').range, '>=1.0.0');
t.is(getBranch(result, 'next').range, '>=1.0.0');
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
release(branches, "master", "1.0.0");
result = (await getBranches("repositoryUrl", "master", context)).map(({ name, range }) => ({ name, range }));
t.is(getBranch(result, "1.0.x").range, ">=1.0.0 <1.0.0", "Cannot release on 1.0.x before a releasing on master");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.0", "Cannot release on 1.x before a releasing on master");
t.is(getBranch(result, "master").range, ">=1.0.0");
t.is(getBranch(result, "next").range, ">=1.0.0");
t.is(getBranch(result, "next-major").range, ">=1.0.0");
release(branches, 'master', '1.0.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "master", "1.0.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.0.1", "Can release only > than 1.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.0.1", "Can release only > than 1.0.1 on next-major");
merge(branches, 'master', 'next');
merge(branches, 'master', 'next-major');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "next");
merge(branches, "master", "next-major");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.0.1", "Can release only > than 1.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.0.1", "Can release only > than 1.0.1 on next-major");
release(branches, 'next', '1.1.0');
release(branches, 'next', '1.1.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "next", "1.1.0");
release(branches, "next", "1.1.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1', 'Can release only > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=1.1.1', 'Can release > than 1.1.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1", "Can release only > than 1.1.1 on next");
t.is(getBranch(result, "next-major").range, ">=1.1.1", "Can release > than 1.1.1 on next-major");
release(branches, 'next-major', '2.0.0');
release(branches, 'next-major', '2.0.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "next-major", "2.0.0");
release(branches, "next-major", "2.0.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1 <2.0.0", "Can release only patch or minor, > than 1.1.0 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
merge(branches, 'next-major', 'beta');
release(branches, 'beta', '3.0.0-beta.1');
merge(branches, 'beta', 'alpha');
release(branches, 'alpha', '4.0.0-alpha.1');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next-major", "beta");
release(branches, "beta", "3.0.0-beta.1");
merge(branches, "beta", "alpha");
release(branches, "alpha", "4.0.0-alpha.1");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
merge(branches, 'master', '1.0.x');
merge(branches, 'master', '1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.0.x");
merge(branches, "master", "1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
t.is(getBranch(result, "master").range, ">=1.0.1 <1.1.0", "Can release only patch, > than 1.0.1 on master");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.1',
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.1",
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.1', 'Cannot release on 1.x before >= 1.2.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.1", "Cannot release on 1.x before >= 1.2.0 is released on master");
release(branches, 'master', '1.0.2');
release(branches, 'master', '1.0.3');
release(branches, 'master', '1.0.4');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
release(branches, "master", "1.0.2");
release(branches, "master", "1.0.3");
release(branches, "master", "1.0.4");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
t.is(getBranch(result, "master").range, ">=1.0.4 <1.1.0", "Can release only patch, > than 1.0.4 on master");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.2',
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.2",
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 1.2.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.2", "Cannot release on 1.x before >= 1.2.0 is released on master");
merge(branches, 'next', 'master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next", "master");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "next").range, ">=1.1.1 <2.0.0", "Can release only patch or minor, > than 1.1.1 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release any version, > than 2.0.1 on next-major");
t.is(
getBranch(result, '1.0.x').range,
'>=1.0.1 <1.0.2',
'Cannot release on 1.0.x before 1.0.x version from master are merged'
getBranch(result, "1.0.x").range,
">=1.0.1 <1.0.2",
"Cannot release on 1.0.x before 1.0.x version from master are merged"
);
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.2", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'master', '1.0.x', '1.0.4');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.0.x", "1.0.4");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.1.0', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.1.0", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'master', '1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "master", "1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <1.1.1', 'Cannot release on 1.x before >= 2.0.0 is released on master');
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.1.1 <1.1.1", "Cannot release on 1.x before >= 2.0.0 is released on master");
merge(branches, 'next-major', 'next');
merge(branches, 'next', 'master');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "next-major", "next");
merge(branches, "next", "master");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=2.0.1', 'Can release only > than 2.0.1 on master');
t.is(getBranch(result, 'next').range, '>=2.0.1', 'Can release only > than 2.0.1 on next');
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release only > than 2.0.1 on next-major');
t.is(getBranch(result, '1.x').range, '>=1.1.1 <2.0.0', 'Can release on 1.x only within range');
t.is(getBranch(result, "master").range, ">=2.0.1", "Can release only > than 2.0.1 on master");
t.is(getBranch(result, "next").range, ">=2.0.1", "Can release only > than 2.0.1 on next");
t.is(getBranch(result, "next-major").range, ">=2.0.1", "Can release only > than 2.0.1 on next-major");
t.is(getBranch(result, "1.x").range, ">=1.1.1 <2.0.0", "Can release on 1.x only within range");
merge(branches, 'beta', 'master');
release(branches, 'master', '3.0.0');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
merge(branches, "beta", "master");
release(branches, "master", "3.0.0");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, 'master').range, '>=3.0.0', 'Can release only > than 3.0.0 on master');
t.is(getBranch(result, 'next').range, '>=3.0.0', 'Can release only > than 3.0.0 on next');
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only > than 3.0.0 on next-major');
t.is(getBranch(result, "master").range, ">=3.0.0", "Can release only > than 3.0.0 on master");
t.is(getBranch(result, "next").range, ">=3.0.0", "Can release only > than 3.0.0 on next");
t.is(getBranch(result, "next-major").range, ">=3.0.0", "Can release only > than 3.0.0 on next-major");
branches.push({name: '1.1.x', tags: []});
merge(branches, '1.x', '1.1.x');
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
branches.push({ name: "1.1.x", tags: [] });
merge(branches, "1.x", "1.1.x");
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
name,
range,
}));
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
t.is(getBranch(result, '1.1.x').range, '>=1.1.1 <1.2.0', 'Can release on 1.1.x only within range');
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
t.is(getBranch(result, "1.0.x").range, ">=1.0.4 <1.1.0", "Can release on 1.0.x only within range");
t.is(getBranch(result, "1.1.x").range, ">=1.1.1 <1.2.0", "Can release on 1.1.x only within range");
t.is(getBranch(result, "1.x").range, ">=1.2.0 <2.0.0", "Can release on 1.x only within range");
});
test.serial('Throw SemanticReleaseError for invalid configurations', async (t) => {
test.serial("Throw SemanticReleaseError for invalid configurations", async (t) => {
const branches = [
{name: '123', range: '123', tags: []},
{name: '1.x', tags: []},
{name: 'maintenance-1', range: '1.x', tags: []},
{name: '1.x.x', tags: []},
{name: 'beta', prerelease: '', tags: []},
{name: 'alpha', prerelease: 'alpha', tags: []},
{name: 'preview', prerelease: 'alpha', tags: []},
{ name: "123", range: "123", tags: [] },
{ name: "1.x", tags: [] },
{ name: "maintenance-1", range: "1.x", tags: [] },
{ name: "1.x.x", tags: [] },
{ name: "beta", prerelease: "", tags: [] },
{ name: "alpha", prerelease: "alpha", tags: [] },
{ name: "preview", prerelease: "alpha", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const error = await t.throwsAsync(getBranches(repositoryUrl, 'master', context));
const error = await t.throwsAsync(getBranches(repositoryUrl, "master", context));
const errors = [...error.errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EMAINTENANCEBRANCH");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EMAINTENANCEBRANCHES');
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EMAINTENANCEBRANCHES");
t.truthy(errors[1].message);
t.truthy(errors[1].details);
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPRERELEASEBRANCH');
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPRERELEASEBRANCH");
t.truthy(errors[2].message);
t.truthy(errors[2].details);
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'EPRERELEASEBRANCHES');
t.is(errors[3].name, "SemanticReleaseError");
t.is(errors[3].code, "EPRERELEASEBRANCHES");
t.truthy(errors[3].message);
t.truthy(errors[3].details);
t.is(errors[4].name, 'SemanticReleaseError');
t.is(errors[4].code, 'ERELEASEBRANCHES');
t.is(errors[4].name, "SemanticReleaseError");
t.is(errors[4].code, "ERELEASEBRANCHES");
t.truthy(errors[4].message);
t.truthy(errors[4].details);
});
test.serial('Throw a SemanticReleaseError if there is duplicate branches', async (t) => {
test.serial("Throw a SemanticReleaseError if there is duplicate branches", async (t) => {
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
{ name: "master", tags: [] },
{ name: "master", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EDUPLICATEBRANCHES");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
});
test.serial('Throw a SemanticReleaseError for each invalid branch name', async (t) => {
test.serial("Throw a SemanticReleaseError for each invalid branch name", async (t) => {
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
{ name: "~master", tags: [] },
{ name: "^master", tags: [] },
];
const context = {options: {branches}};
const context = { options: { branches } };
const remoteBranches = [];
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
td.when(getTags(context, remoteBranches)).thenResolve(branches);
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EINVALIDBRANCHNAME");
t.truthy(errors[0].message);
t.truthy(errors[0].details);
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EINVALIDBRANCHNAME');
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EINVALIDBRANCHNAME");
t.truthy(errors[1].message);
t.truthy(errors[1].details);
});
+44 -44
View File
@@ -1,54 +1,54 @@
import test from 'ava';
import expand from '../../lib/branches/expand.js';
import {gitCheckout, gitCommits, gitPush, gitRepo} from '../helpers/git-utils.js';
import test from "ava";
import expand from "../../lib/branches/expand.js";
import { gitCheckout, gitCommits, gitPush, gitRepo } from "../helpers/git-utils.js";
test('Expand branches defined with globs', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('1.0.x', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, '1.0.x', {cwd});
await gitCheckout('1.x.x', true, {cwd});
await gitCommits(['Third'], {cwd});
await gitPush(repositoryUrl, '1.x.x', {cwd});
await gitCheckout('2.x', true, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitPush(repositoryUrl, '2.x', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Fifth'], {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
await gitCheckout('pre/foo', true, {cwd});
await gitCommits(['Sixth'], {cwd});
await gitPush(repositoryUrl, 'pre/foo', {cwd});
await gitCheckout('pre/bar', true, {cwd});
await gitCommits(['Seventh'], {cwd});
await gitPush(repositoryUrl, 'pre/bar', {cwd});
await gitCheckout('beta', true, {cwd});
await gitCommits(['Eighth'], {cwd});
await gitPush(repositoryUrl, 'beta', {cwd});
test("Expand branches defined with globs", async (t) => {
const { cwd, repositoryUrl } = await gitRepo(true);
await gitCommits(["First"], { cwd });
await gitPush(repositoryUrl, "master", { cwd });
await gitCheckout("1.0.x", true, { cwd });
await gitCommits(["Second"], { cwd });
await gitPush(repositoryUrl, "1.0.x", { cwd });
await gitCheckout("1.x.x", true, { cwd });
await gitCommits(["Third"], { cwd });
await gitPush(repositoryUrl, "1.x.x", { cwd });
await gitCheckout("2.x", true, { cwd });
await gitCommits(["Fourth"], { cwd });
await gitPush(repositoryUrl, "2.x", { cwd });
await gitCheckout("next", true, { cwd });
await gitCommits(["Fifth"], { cwd });
await gitPush(repositoryUrl, "next", { cwd });
await gitCheckout("pre/foo", true, { cwd });
await gitCommits(["Sixth"], { cwd });
await gitPush(repositoryUrl, "pre/foo", { cwd });
await gitCheckout("pre/bar", true, { cwd });
await gitCommits(["Seventh"], { cwd });
await gitPush(repositoryUrl, "pre/bar", { cwd });
await gitCheckout("beta", true, { cwd });
await gitCommits(["Eighth"], { cwd });
await gitPush(repositoryUrl, "beta", { cwd });
const branches = [
// Should match all maintenance type branches
{name: '+([0-9])?(.{+([0-9]),x}).x'},
{name: 'master', channel: 'latest'},
{name: 'next'},
{name: 'pre/{foo,bar}', channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true},
{ name: "+([0-9])?(.{+([0-9]),x}).x" },
{ name: "master", channel: "latest" },
{ name: "next" },
{ name: "pre/{foo,bar}", channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true },
// Should be ignored as there is no matching branches in the repo
{name: 'missing'},
{ name: "missing" },
// Should be ignored as the matching branch in the repo is already matched by `/^pre\\/(\\w+)$/gi`
{name: '*/foo', channel: 'foo', prerelease: 'foo'},
{name: 'beta', channel: `channel-\${name}`, prerelease: true},
{ name: "*/foo", channel: "foo", prerelease: "foo" },
{ name: "beta", channel: `channel-\${name}`, prerelease: true },
];
t.deepEqual(await expand(repositoryUrl, {cwd}, branches), [
{name: '1.0.x'},
{name: '1.x.x'},
{name: '2.x'},
{name: 'master', channel: 'latest'},
{name: 'next'},
{name: 'pre/bar', channel: 'bar', prerelease: true},
{name: 'pre/foo', channel: 'foo', prerelease: true},
{name: 'beta', channel: 'channel-beta', prerelease: true},
t.deepEqual(await expand(repositoryUrl, { cwd }, branches), [
{ name: "1.0.x" },
{ name: "1.x.x" },
{ name: "2.x" },
{ name: "master", channel: "latest" },
{ name: "next" },
{ name: "pre/bar", channel: "bar", prerelease: true },
{ name: "pre/foo", channel: "foo", prerelease: true },
{ name: "beta", channel: "channel-beta", prerelease: true },
]);
});
+100 -97
View File
@@ -1,153 +1,156 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags.js';
import {gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion} from '../helpers/git-utils.js';
import test from "ava";
import getTags from "../../lib/branches/get-tags.js";
import { gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion } from "../helpers/git-utils.js";
test('Get the valid tags', async (t) => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
commits.push(...(await gitCommits(['Second'], {cwd})));
await gitTagVersion('v1.0.0', undefined, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitTagVersion('v3.0', undefined, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitTagVersion('v3.0.0-beta.1', undefined, {cwd});
test("Get the valid tags", async (t) => {
const { cwd } = await gitRepo();
const commits = await gitCommits(["First"], { cwd });
await gitTagVersion("foo", undefined, { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
commits.push(...(await gitCommits(["Second"], { cwd })));
await gitTagVersion("v1.0.0", undefined, { cwd });
commits.push(...(await gitCommits(["Third"], { cwd })));
await gitTagVersion("v3.0", undefined, { cwd });
commits.push(...(await gitCommits(["Fourth"], { cwd })));
await gitTagVersion("v3.0.0-beta.1", undefined, { cwd });
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}]);
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [{ name: "master" }]);
t.deepEqual(result, [
{
name: 'master',
name: "master",
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [null]},
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null] },
{ gitTag: "v2.0.0", version: "2.0.0", channels: [null] },
{ gitTag: "v3.0.0-beta.1", version: "3.0.0-beta.1", channels: [null] },
],
},
]);
});
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});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitCheckout('master', false, {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
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 });
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.0.0", { cwd });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v1.1.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.1.0", { cwd });
await gitCheckout("1.x", true, { cwd });
await gitCheckout("master", false, { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
await gitCheckout("next", true, { cwd });
await gitCommits(["Fourth"], { cwd });
await gitTagVersion("v3.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: ["next"] }), "v3.0.0", { cwd });
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
{name: '1.x'},
{name: 'master'},
{name: 'next'},
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [
{ name: "1.x" },
{ name: "master" },
{ name: "next" },
]);
t.deepEqual(result, [
{
name: '1.x',
name: "1.x",
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, '1.x']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '1.x']},
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null, "1.x"] },
{ gitTag: "v1.1.0", version: "1.1.0", channels: [null, "1.x"] },
],
},
{
name: 'master',
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [null, 'next']}],
name: "master",
tags: [...result[0].tags, { gitTag: "v2.0.0", version: "2.0.0", channels: [null, "next"] }],
},
{
name: 'next',
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
name: "next",
tags: [...result[1].tags, { gitTag: "v3.0.0", version: "3.0.0", channels: ["next"] }],
},
]);
});
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});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v2.0.x', undefined, {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v3.0', undefined, {cwd});
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 });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v2.0.x", undefined, { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v3.0", undefined, { cwd });
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [{ name: "master" }]);
t.deepEqual(result, [{name: 'master', tags: []}]);
t.deepEqual(result, [{ name: "master", tags: [] }]);
});
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {cwd});
await gitCheckout('master', false, {cwd});
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 });
await gitCommits(["Second"], { cwd });
await gitTagVersion("v1.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v1.0.0", { cwd });
await gitCommits(["Third"], { cwd });
await gitTagVersion("v2.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
await gitCommits(["Fourth"], { cwd });
await gitTagVersion("v3.0.0", undefined, { cwd });
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v3.0.0", { cwd });
await gitCheckout("master", false, { cwd });
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [
{ name: "master" },
{ name: "next" },
]);
t.deepEqual(result, [
{name: 'master', tags: []},
{name: 'next', tags: []},
{ name: "master", tags: [] },
{ name: "next", tags: [] },
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
const { cwd } = await gitRepo();
await gitCommits(["First"], { cwd });
await gitTagVersion('1.0.0', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [null]}]},
await gitTagVersion("1.0.0", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "1.0.0", version: "1.0.0", channels: [null] }] },
]);
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-\${version}-bar`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [null]}]},
await gitTagVersion("foo-1.0.0-bar", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-\${version}-bar` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "foo-1.0.0-bar", version: "1.0.0", channels: [null] }] },
]);
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
await gitTagVersion("foo-v1.0.0-bar", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-v\${version}-bar` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "foo-v1.0.0-bar", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('(.+)/1.0.0/(a-z)', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
await gitTagVersion("(.+)/1.0.0/(a-z)", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `(.+)/\${version}/(a-z)` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "(.+)/1.0.0/(a-z)", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('2.0.0-1.0.0-bar.1', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
await gitTagVersion("2.0.0-1.0.0-bar.1", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `2.0.0-\${version}-bar.1` } }, [{ name: "master" }]), [
{
name: 'master',
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channels: [null]}],
name: "master",
tags: [{ gitTag: "2.0.0-1.0.0-bar.1", version: "1.0.0", channels: [null] }],
},
]);
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}-bar.2`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
await gitTagVersion("3.0.0-bar.2", undefined, { cwd });
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}-bar.2` } }, [{ name: "master" }]), [
{ name: "master", tags: [{ gitTag: "3.0.0-bar.2", version: "3.0.0", channels: [null] }] },
]);
});
+193 -191
View File
@@ -1,17 +1,17 @@
import test from 'ava';
import * as normalize from '../../lib/branches/normalize.js';
import test from "ava";
import * as 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: []},
{name: '1.2.x', tags: []},
{ name: "1.x", channel: "1.x", tags: [] },
{ name: "1.1.x", tags: [] },
{ name: "1.2.x", tags: [] },
];
const release = [{name: 'master', tags: []}];
const release = [{ name: "master", tags: [] }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
type,
name,
range,
@@ -21,51 +21,51 @@ test('Maintenance branches - initial state', (t) => {
})),
[
{
type: 'maintenance',
name: '1.1.x',
range: '>=1.1.0 <1.0.0',
type: "maintenance",
name: "1.1.x",
range: ">=1.1.0 <1.0.0",
accept: [],
channel: '1.1.x',
mergeRange: '>=1.1.0 <1.2.0',
channel: "1.1.x",
mergeRange: ">=1.1.0 <1.2.0",
},
{
type: 'maintenance',
name: '1.2.x',
range: '>=1.2.0 <1.0.0',
type: "maintenance",
name: "1.2.x",
range: ">=1.2.0 <1.0.0",
accept: [],
channel: '1.2.x',
mergeRange: '>=1.2.0 <1.3.0',
channel: "1.2.x",
mergeRange: ">=1.2.0 <1.3.0",
},
{
type: 'maintenance',
name: '1.x',
range: '>=1.3.0 <1.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.3.0 <1.0.0",
accept: [],
channel: '1.x',
mergeRange: '>=1.3.0 <2.0.0',
channel: "1.x",
mergeRange: ">=1.3.0 <2.0.0",
},
]
);
});
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'])},
{name: '1.2.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1'])},
{name: '2.x.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.5.0'])},
{ 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"]) },
{ name: "1.2.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1"]) },
{ name: "2.x.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.5.0"]) },
];
const release = [
{
name: 'master',
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', '1.6.0', '2.0.0']),
name: "master",
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", "1.6.0", "2.0.0"]),
},
];
t.deepEqual(
normalize
.maintenance({maintenance, release})
.map(({type, name, range, accept, channel, mergeRange: maintenanceRange}) => ({
.maintenance({ maintenance, release })
.map(({ type, name, range, accept, channel, mergeRange: maintenanceRange }) => ({
type,
name,
range,
@@ -75,50 +75,50 @@ test('Maintenance branches - cap range to first release present on default branc
})),
[
{
type: 'maintenance',
name: 'name',
range: '>=1.1.1 <1.2.0',
accept: ['patch'],
channel: 'name',
mergeRange: '>=1.1.0 <1.2.0',
type: "maintenance",
name: "name",
range: ">=1.1.1 <1.2.0",
accept: ["patch"],
channel: "name",
mergeRange: ">=1.1.0 <1.2.0",
},
{
type: 'maintenance',
name: '1.2.x',
range: '>=1.2.1 <1.3.0',
accept: ['patch'],
channel: '1.2.x',
mergeRange: '>=1.2.0 <1.3.0',
type: "maintenance",
name: "1.2.x",
range: ">=1.2.1 <1.3.0",
accept: ["patch"],
channel: "1.2.x",
mergeRange: ">=1.2.0 <1.3.0",
},
{
type: 'maintenance',
name: '1.x',
range: '>=1.5.0 <1.6.0',
accept: ['patch'],
channel: '1.x',
mergeRange: '>=1.3.0 <2.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.5.0 <1.6.0",
accept: ["patch"],
channel: "1.x",
mergeRange: ">=1.3.0 <2.0.0",
},
{
type: 'maintenance',
name: '2.x.x',
range: '>=2.0.0 <1.6.0',
type: "maintenance",
name: "2.x.x",
range: ">=2.0.0 <1.6.0",
accept: [],
channel: '2.x.x',
mergeRange: '>=2.0.0 <3.0.0',
channel: "2.x.x",
mergeRange: ">=2.0.0 <3.0.0",
},
]
);
});
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'])},
{ 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"]) },
];
const release = [{name: 'master', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])}];
const release = [{ name: "master", tags: toTags(["1.0.0", "1.2.0", "1.3.0", "2.0.0"]) }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
type,
name,
range,
@@ -128,270 +128,272 @@ test('Maintenance branches - cap range to default branch last release if all rel
})),
[
{
type: 'maintenance',
name: '1.x',
range: '>=1.3.0 <2.0.0',
accept: ['patch', 'minor'],
channel: '1.x',
mergeRange: '>=1.0.0 <2.0.0',
type: "maintenance",
name: "1.x",
range: ">=1.3.0 <2.0.0",
accept: ["patch", "minor"],
channel: "1.x",
mergeRange: ">=1.0.0 <2.0.0",
},
{
type: 'maintenance',
name: '2.x.x',
range: '>=2.0.0 <2.0.0',
type: "maintenance",
name: "2.x.x",
range: ">=2.0.0 <2.0.0",
accept: [],
channel: '2.x.x',
mergeRange: '>=2.0.0 <3.0.0',
channel: "2.x.x",
mergeRange: ">=2.0.0 <3.0.0",
},
]
);
});
test('Release branches - initial state', (t) => {
test("Release branches - initial state", (t) => {
const release = [
{name: 'master', tags: []},
{name: 'next', channel: 'next', tags: []},
{name: 'next-major', tags: []},
{ name: "master", tags: [] },
{ name: "next", channel: "next", tags: [] },
{ name: "next-major", tags: [] },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=1.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=1.0.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
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'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
{ 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"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0", "2.0.0", "2.0.1", "2.1.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
{ type: "release", name: "master", range: ">=1.0.2 <1.1.0", accept: ["patch"], channel: undefined, main: true },
{
type: 'release',
name: 'next',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: 'next',
type: "release",
name: "next",
range: ">=1.2.0 <2.0.0",
accept: ["patch", "minor"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=2.1.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
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'])},
{ 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"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
type: "release",
name: "master",
range: ">=1.2.0 <2.0.0",
accept: ["patch", "minor"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=2.1.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
]
);
});
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'])}];
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(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
[{type: 'release', name: 'master', range: '>=1.2.0', accept: ['patch', 'minor', 'major'], channel: undefined}]
normalize
.release({ release })
.map(({ type, name, range, accept, channel }) => ({ type, name, range, accept, channel })),
[{ type: "release", name: "master", range: ">=1.2.0", accept: ["patch", "minor", "major"], channel: undefined }]
);
});
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'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0', '2.2.0'])},
{ 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"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0", "2.2.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
{ type: "release", name: "master", range: ">=2.0.0 <2.1.0", accept: ["patch"], channel: undefined, main: true },
{ type: "release", name: "next", range: ">=2.1.0 <2.2.0", accept: ["patch"], channel: "next", main: false },
{
type: 'release',
name: 'next-major',
range: '>=2.2.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=2.2.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
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'])},
{ 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"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=2.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=2.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
]
);
});
test('Release branches - limit releases on 2nd and 3rd 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'])},
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
{ 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"]) },
{ name: "next-major", tags: toTags(["1.0.0", "1.1.0", "2.0.0"]) },
];
t.deepEqual(
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
.release({ release })
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
[
{
type: 'release',
name: 'master',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
type: "release",
name: "master",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
type: "release",
name: "next",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: "next",
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
type: "release",
name: "next-major",
range: ">=3.0.0",
accept: ["patch", "minor", "major"],
channel: "next-major",
main: false,
},
]
);
});
test('Prerelease branches', (t) => {
test("Prerelease branches", (t) => {
const prerelease = [
{name: 'beta', channel: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: 'preview', tags: []},
{ name: "beta", channel: "beta", prerelease: true, tags: [] },
{ name: "alpha", prerelease: "preview", tags: [] },
];
t.deepEqual(
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
normalize.prerelease({ prerelease }).map(({ type, name, channel }) => ({ type, name, channel })),
[
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
{ type: "prerelease", name: "beta", channel: "beta" },
{ type: "prerelease", name: "alpha", channel: "alpha" },
]
);
});
test('Allow to set channel to "false" to prevent default', (t) => {
const maintenance = [{name: '1.x', channel: false, tags: []}];
const maintenance = [{ name: "1.x", channel: false, tags: [] }];
const release = [
{name: 'master', channel: false, tags: []},
{name: 'next', channel: false, tags: []},
{ name: "master", channel: false, tags: [] },
{ name: "next", channel: false, tags: [] },
];
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
const prerelease = [{ name: "beta", channel: false, prerelease: true, tags: [] }];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
[{name: '1.x', channel: false}]
normalize.maintenance({ maintenance, release }).map(({ name, channel }) => ({ name, channel })),
[{ name: "1.x", channel: false }]
);
t.deepEqual(
normalize.release({release}).map(({name, channel}) => ({name, channel})),
normalize.release({ release }).map(({ name, channel }) => ({ name, channel })),
[
{name: 'master', channel: false},
{name: 'next', channel: false},
{ name: "master", channel: false },
{ name: "next", channel: false },
]
);
t.deepEqual(
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
[{name: 'beta', channel: false}]
normalize.prerelease({ prerelease }).map(({ name, channel }) => ({ name, channel })),
[{ name: "beta", channel: false }]
);
});
+56 -54
View File
@@ -1,92 +1,94 @@
import test from 'ava';
import {maintenance, prerelease, release} from '../../lib/definitions/branches.js';
import test from "ava";
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) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(maintenance.filter({name: '1.x.x'}));
t.true(maintenance.filter({name: '1.0.x'}));
t.true(maintenance.filter({name: '1.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
t.true(maintenance.filter({name: 'some-name', range: ''}));
t.true(maintenance.filter({name: 'some-name', range: true}));
t.true(maintenance.filter({ name: "1.x.x" }));
t.true(maintenance.filter({ name: "1.0.x" }));
t.true(maintenance.filter({ name: "1.x" }));
t.true(maintenance.filter({ name: "some-name", range: "1.x.x" }));
t.true(maintenance.filter({ name: "some-name", range: "1.1.x" }));
t.true(maintenance.filter({ name: "some-name", range: "" }));
t.true(maintenance.filter({ name: "some-name", range: true }));
t.false(maintenance.filter({name: 'some-name', range: null}));
t.false(maintenance.filter({name: 'some-name', range: false}));
t.false(maintenance.filter({name: 'some-name'}));
t.false(maintenance.filter({name: '1.0.0'}));
t.false(maintenance.filter({name: 'x.x.x'}));
t.false(maintenance.filter({ name: "some-name", range: null }));
t.false(maintenance.filter({ name: "some-name", range: false }));
t.false(maintenance.filter({ name: "some-name" }));
t.false(maintenance.filter({ name: "1.0.0" }));
t.false(maintenance.filter({ name: "x.x.x" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
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'}));
t.true(maintenance.branchValidator({ name: "some-name", range: "1.x.x" }));
t.true(maintenance.branchValidator({ name: "some-name", range: "1.1.x" }));
t.false(maintenance.branchValidator({name: 'some-name', range: '^1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
t.false(maintenance.branchValidator({name: 'some-name', range: true}));
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
t.false(maintenance.branchValidator({ name: "some-name", range: "^1.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: ">=1.0.0 <2.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: "1.0.0" }));
t.false(maintenance.branchValidator({ name: "some-name", range: "wrong-range" }));
t.false(maintenance.branchValidator({ name: "some-name", range: true }));
t.false(maintenance.branchValidator({ name: "some-name", range: "" }));
});
test('The "maintenance" branches must have unique ranges', (t) => {
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
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'}]));
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) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
t.true(prerelease.filter({ name: "some-name", prerelease: true }));
t.true(prerelease.filter({ name: "some-name", prerelease: "beta" }));
t.true(prerelease.filter({ name: "some-name", prerelease: "" }));
t.false(prerelease.filter({name: 'some-name', prerelease: null}));
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name'}));
t.false(prerelease.filter({ name: "some-name", prerelease: null }));
t.false(prerelease.filter({ name: "some-name", prerelease: false }));
t.false(prerelease.filter({ name: "some-name" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
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'}));
t.true(prerelease.branchValidator({ name: "beta", prerelease: true }));
t.true(prerelease.branchValidator({ name: "some-name", prerelease: "beta" }));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: ''}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: null}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: false}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '000'}));
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '#beta'}));
t.false(prerelease.branchValidator({name: '000', prerelease: true}));
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "" }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: null }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: false }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "000" }));
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "#beta" }));
t.false(prerelease.branchValidator({ name: "000", prerelease: true }));
t.false(prerelease.branchValidator({ name: "#beta", prerelease: true }));
});
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
t.true(prerelease.branchesValidator([{ prerelease: "beta" }, { prerelease: "alpha" }]));
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: '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) => {
/* eslint-disable unicorn/no-fn-reference-in-iterator */
t.true(release.filter({name: 'some-name'}));
t.true(release.filter({ name: "some-name" }));
t.false(release.filter({name: '1.x.x'}));
t.false(release.filter({name: '1.0.x'}));
t.false(release.filter({name: 'some-name', range: '1.x.x'}));
t.false(release.filter({name: 'some-name', range: '1.1.x'}));
t.false(release.filter({name: 'some-name', prerelease: true}));
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
t.false(release.filter({ name: "1.x.x" }));
t.false(release.filter({ name: "1.0.x" }));
t.false(release.filter({ name: "some-name", range: "1.x.x" }));
t.false(release.filter({ name: "some-name", range: "1.1.x" }));
t.false(release.filter({ name: "some-name", prerelease: true }));
t.false(release.filter({ name: "some-name", prerelease: "beta" }));
/* eslint-enable unicorn/no-fn-reference-in-iterator */
});
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'}]));
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" }]));
t.false(release.branchesValidator([]));
t.false(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}, {name: 'branch4'}]));
t.false(
release.branchesValidator([{ name: "branch1" }, { name: "branch2" }, { name: "branch3" }, { name: "branch4" }])
);
});
+24 -24
View File
@@ -1,15 +1,15 @@
import test from 'ava';
import plugins from '../../lib/definitions/plugins.js';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants.js';
import test from "ava";
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) => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
t.false(plugins.analyzeCommits.outputValidator("invalid"));
t.false(plugins.analyzeCommits.outputValidator(1));
t.false(plugins.analyzeCommits.outputValidator({}));
t.true(plugins.analyzeCommits.outputValidator());
t.true(plugins.analyzeCommits.outputValidator(null));
t.true(plugins.analyzeCommits.outputValidator('major'));
t.true(plugins.analyzeCommits.outputValidator("major"));
});
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
@@ -18,57 +18,57 @@ test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
t.true(plugins.generateNotes.outputValidator());
t.true(plugins.generateNotes.outputValidator(null));
t.true(plugins.generateNotes.outputValidator(''));
t.true(plugins.generateNotes.outputValidator('string'));
t.true(plugins.generateNotes.outputValidator(""));
t.true(plugins.generateNotes.outputValidator("string"));
});
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'));
t.false(plugins.publish.outputValidator("string"));
t.true(plugins.publish.outputValidator({}));
t.true(plugins.publish.outputValidator());
t.true(plugins.publish.outputValidator(null));
t.true(plugins.publish.outputValidator(''));
t.true(plugins.publish.outputValidator(""));
t.true(plugins.publish.outputValidator(false));
});
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
t.false(plugins.addChannel.outputValidator(1));
t.false(plugins.addChannel.outputValidator('string'));
t.false(plugins.addChannel.outputValidator("string"));
t.true(plugins.addChannel.outputValidator({}));
t.true(plugins.addChannel.outputValidator());
t.true(plugins.addChannel.outputValidator(null));
t.true(plugins.addChannel.outputValidator(''));
t.true(plugins.addChannel.outputValidator(""));
});
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');
t.is(plugins.generateNotes.postprocess([undefined, 'note'], {env}), 'note');
t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
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");
t.is(plugins.generateNotes.postprocess([undefined, "note"], { env }), "note");
t.is(plugins.generateNotes.postprocess(["note 1", "", "note 2"], { env }), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(
plugins.generateNotes.postprocess(['note 1', undefined, 'note 2'], {env}),
plugins.generateNotes.postprocess(["note 1", undefined, "note 2"], { env }),
`note 1${RELEASE_NOTES_SEPARATOR}note 2`
);
t.is(
plugins.generateNotes.postprocess(
[`Note 1: Exposing token ${env.MY_TOKEN}`, `Note 2: Exposing token ${SECRET_REPLACEMENT}`],
{env}
{ env }
),
`Note 1: Exposing token ${SECRET_REPLACEMENT}${RELEASE_NOTES_SEPARATOR}Note 2: Exposing token ${SECRET_REPLACEMENT}`
);
});
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');
t.is(plugins.analyzeCommits.postprocess([null, 'patch']), 'patch');
t.is(plugins.analyzeCommits.postprocess(['wrong_type', 'minor']), 'minor');
t.is(plugins.analyzeCommits.postprocess(["major", "minor"]), "major");
t.is(plugins.analyzeCommits.postprocess(["", "minor"]), "minor");
t.is(plugins.analyzeCommits.postprocess([undefined, "patch"]), "patch");
t.is(plugins.analyzeCommits.postprocess([null, "patch"]), "patch");
t.is(plugins.analyzeCommits.postprocess(["wrong_type", "minor"]), "minor");
t.is(plugins.analyzeCommits.postprocess([]), undefined);
t.is(plugins.analyzeCommits.postprocess(['wrong_type']), undefined);
t.is(plugins.analyzeCommits.postprocess(["wrong_type"]), undefined);
});
+1 -1
View File
@@ -1 +1 @@
export default () => {}
export default () => {};
+3 -3
View File
@@ -1,4 +1,4 @@
import SemanticReleaseError from '@semantic-release/error';
import SemanticReleaseError from "@semantic-release/error";
class InheritedError extends SemanticReleaseError {
constructor(message, code) {
@@ -10,5 +10,5 @@ class InheritedError extends SemanticReleaseError {
}
export default () => {
throw new InheritedError('Inherited error', 'EINHERITED');
}
throw new InheritedError("Inherited error", "EINHERITED");
};
+3 -3
View File
@@ -1,5 +1,5 @@
export default () => {
const error = new Error('a');
error.errorProperty = 'errorProperty';
const error = new Error("a");
error.errorProperty = "errorProperty";
throw error;
}
};
+3 -3
View File
@@ -1,5 +1,5 @@
import AggregateError from 'aggregate-error';
import AggregateError from "aggregate-error";
export default () => {
throw new AggregateError([new Error('a'), new Error('b')]);
}
throw new AggregateError([new Error("a"), new Error("b")]);
};
+3
View File
@@ -0,0 +1,3 @@
export async function verifyConditions(pluginConfig, context) {
context.logger.log("verifyConditions called");
}
+1 -1
View File
@@ -1 +1 @@
export default (pluginConfig, context) => context
export default (pluginConfig, context) => context;
+2 -2
View File
@@ -1,6 +1,6 @@
export default (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}`);
throw new Error(`Throw error: Exposing ${env.MY_TOKEN}`);
}
};
+1 -1
View File
@@ -1 +1 @@
export default (pluginConfig, context) => ({pluginConfig, context})
export default (pluginConfig, context) => ({ pluginConfig, context });
+37
View File
@@ -43,6 +43,43 @@ test("Get the highest prerelease valid tag, ignoring other tags from other prere
});
});
test("Get the correct prerelease tag, when other prereleases share the same git HEAD", (t) => {
const testConfig = {
branch: {
name: "alpha",
prerelease: "alpha",
channel: "alpha",
tags: [
{ version: "1.0.0-beta.1", gitTag: "v1.0.0-beta.1", gitHead: "v1.0.0-beta.1", channels: ["beta"] },
{ version: "1.0.0-beta.2", gitTag: "v1.0.0-beta.2", gitHead: "v1.0.0-alpha.1", channels: ["alpha", "beta"] },
{ version: "1.0.0-alpha.1", gitTag: "v1.0.0-alpha.1", gitHead: "v1.0.0-alpha.1", channels: ["alpha", "beta"] },
],
type: "prerelease",
},
options: { tagFormat: `v\${version}`, debug: true },
};
const firstResult = getLastRelease(testConfig);
t.deepEqual(firstResult, {
version: "1.0.0-alpha.1",
gitTag: "v1.0.0-alpha.1",
name: "v1.0.0-alpha.1",
gitHead: "v1.0.0-alpha.1",
channels: ["alpha", "beta"],
});
testConfig.branch.prerelease = true;
const secondResult = getLastRelease(testConfig);
t.deepEqual(secondResult, {
version: "1.0.0-alpha.1",
gitTag: "v1.0.0-alpha.1",
name: "v1.0.0-alpha.1",
gitHead: "v1.0.0-alpha.1",
channels: ["alpha", "beta"],
});
});
test("Return empty object if no valid tag is found", (t) => {
const result = getLastRelease({
branch: {
+21
View File
@@ -275,3 +275,24 @@ test("Increase version for release on prerelease branch when there is no regular
"1.0.0-beta.2"
);
});
test("Increase patch when previous version shares HEAD with other releases", (t) => {
t.is(
getNextVersion({
branch: {
name: "alpha",
type: "prerelease",
prerelease: "alpha",
tags: [
{ gitTag: "v1.0.0-beta.1", version: "1.0.0-beta.1", channels: ["beta"] },
{ gitTag: "v1.0.0-beta.2", version: "1.0.0-beta.2", channels: ["alpha", "beta"] },
{ gitTag: "v1.0.0-alpha.1", version: "1.0.0-alpha.1", channels: ["alpha", "beta"] },
],
},
nextRelease: { type: "patch", channel: "alpha" },
lastRelease: { version: "v1.0.0-alpha.1", channels: ["alpha", "beta"] },
logger: t.context.logger,
}),
"1.0.0-alpha.2"
);
});
+59 -52
View File
@@ -1,10 +1,10 @@
import {temporaryDirectory} from 'tempy';
import {execa} from 'execa';
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.js';
import { temporaryDirectory } from "tempy";
import { execa } from "execa";
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.js";
/**
* Commit message information.
@@ -25,14 +25,14 @@ import {GIT_NOTE_REF} from '../../lib/definitions/constants.js';
*/
export async function initGit(withRemote) {
const cwd = temporaryDirectory();
const args = withRemote ? ['--bare', '--initial-branch=master'] : ['--initial-branch=master'];
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});
await execa("git", ["init", ...args], { cwd }).catch(() => {
const args = withRemote ? ["--bare"] : [];
return execa("git", ["init", ...args], { cwd });
});
const repositoryUrl = fileUrl(cwd);
return {cwd, repositoryUrl};
return { cwd, repositoryUrl };
}
/**
@@ -45,18 +45,18 @@ export async function initGit(withRemote) {
* @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, repositoryUrl} = await initGit(withRemote);
export async function gitRepo(withRemote, branch = "master") {
let { cwd, repositoryUrl } = await initGit(withRemote);
if (withRemote) {
await initBareRepo(repositoryUrl, branch);
cwd = await gitShallowClone(repositoryUrl, branch);
} else {
await gitCheckout(branch, true, {cwd});
await gitCheckout(branch, true, { cwd });
}
await execa('git', ['config', 'commit.gpgsign', false], {cwd});
await execa("git", ["config", "commit.gpgsign", false], { cwd });
return {cwd, repositoryUrl};
return { cwd, repositoryUrl };
}
/**
@@ -70,12 +70,12 @@ 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') {
export async function initBareRepo(repositoryUrl, branch = "master") {
const cwd = temporaryDirectory();
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
await gitCheckout(branch, true, {cwd});
await gitCommits(['Initial commit'], {cwd});
await execa('git', ['push', repositoryUrl, branch], {cwd});
await execa("git", ["clone", "--no-hardlinks", repositoryUrl, cwd], { cwd });
await gitCheckout(branch, true, { cwd });
await gitCommits(["Initial commit"], { cwd });
await execa("git", ["push", repositoryUrl, branch], { cwd });
}
/**
@@ -90,7 +90,9 @@ export async function gitCommits(messages, execaOptions) {
await pEachSeries(
messages,
async (message) =>
(await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOptions)).stdout
(
await execa("git", ["commit", "-m", message, "--allow-empty", "--no-gpg-sign"], execaOptions)
).stdout
);
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
}
@@ -104,12 +106,17 @@ export async function gitCommits(messages, execaOptions) {
* @return {Array<Object>} The list of parsed commits.
*/
export async function gitGetCommits(from, execaOptions) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
Object.assign(gitLogParser.fields, {
hash: "H",
message: "B",
gitTags: "d",
committerDate: { key: "ci", type: Date },
});
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}HEAD`},
{...execaOptions, env: {...process.env, ...execaOptions.env}}
{ _: `${from ? from + ".." : ""}HEAD` },
{ ...execaOptions, env: { ...process.env, ...execaOptions.env } }
)
)
).map((commit) => {
@@ -127,7 +134,7 @@ export async function gitGetCommits(from, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitCheckout(branch, create, execaOptions) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOptions);
await execa("git", create ? ["checkout", "-b", branch] : ["checkout", branch], execaOptions);
}
/**
@@ -137,7 +144,7 @@ export async function gitCheckout(branch, create, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitFetch(repositoryUrl, execaOptions) {
await execa('git', ['fetch', repositoryUrl], execaOptions);
await execa("git", ["fetch", repositoryUrl], execaOptions);
}
/**
@@ -148,7 +155,7 @@ export async function gitFetch(repositoryUrl, execaOptions) {
* @return {String} The sha of the head commit in the current git repository.
*/
export async function gitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
return (await execa("git", ["rev-parse", "HEAD"], execaOptions)).stdout;
}
/**
@@ -159,7 +166,7 @@ export async function gitHead(execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitTagVersion(tagName, sha, execaOptions) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOptions);
await execa("git", sha ? ["tag", "-f", tagName, sha] : ["tag", tagName], execaOptions);
}
/**
@@ -171,10 +178,10 @@ export async function gitTagVersion(tagName, sha, execaOptions) {
* @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) {
export async function gitShallowClone(repositoryUrl, branch = "master", depth = 1) {
const cwd = temporaryDirectory();
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
await execa("git", ["clone", "--no-hardlinks", "--no-tags", "-b", branch, "--depth", depth, repositoryUrl, cwd], {
cwd,
});
return cwd;
@@ -190,21 +197,21 @@ export async function gitShallowClone(repositoryUrl, branch = 'master', depth =
export async function gitDetachedHead(repositoryUrl, head) {
const cwd = temporaryDirectory();
await execa('git', ['init'], {cwd});
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
await execa('git', ['fetch', repositoryUrl], {cwd});
await execa('git', ['checkout', head], {cwd});
await execa("git", ["init"], { cwd });
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
await execa("git", ["fetch", repositoryUrl], { cwd });
await execa("git", ["checkout", head], { cwd });
return cwd;
}
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
const cwd = temporaryDirectory();
await execa('git', ['init'], {cwd});
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
await execa('git', ['fetch', '--force', repositoryUrl, `${branch}:remotes/origin/${branch}`], {cwd});
await execa('git', ['reset', '--hard', head], {cwd});
await execa('git', ['checkout', '-q', '-B', branch], {cwd});
await execa("git", ["init"], { cwd });
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
await execa("git", ["fetch", "--force", repositoryUrl, `${branch}:remotes/origin/${branch}`], { cwd });
await execa("git", ["reset", "--hard", head], { cwd });
await execa("git", ["checkout", "-q", "-B", branch], { cwd });
return cwd;
}
@@ -216,7 +223,7 @@ export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddConfig(name, value, execaOptions) {
await execa('git', ['config', '--add', name, value], execaOptions);
await execa("git", ["config", "--add", name, value], execaOptions);
}
/**
@@ -228,7 +235,7 @@ export async function gitAddConfig(name, value, execaOptions) {
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export async function gitTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
return (await execa("git", ["rev-list", "-1", tagName], execaOptions)).stdout;
}
/**
@@ -241,8 +248,8 @@ export async function gitTagHead(tagName, execaOptions) {
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOptions)).stdout
.split('\n')
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];
}
@@ -256,7 +263,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export async function gitCommitTag(gitHead, execaOptions) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOptions)).stdout;
return (await execa("git", ["describe", "--tags", "--exact-match", gitHead], execaOptions)).stdout;
}
/**
@@ -269,7 +276,7 @@ export async function gitCommitTag(gitHead, execaOptions) {
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl, branch, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOptions);
await execa("git", ["push", "--tags", repositoryUrl, `HEAD:${branch}`], execaOptions);
}
/**
@@ -279,7 +286,7 @@ export async function gitPush(repositoryUrl, branch, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function merge(ref, execaOptions) {
await execa('git', ['merge', '--no-ff', ref], execaOptions);
await execa("git", ["merge", "--no-ff", ref], execaOptions);
}
/**
@@ -289,7 +296,7 @@ export async function merge(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function mergeFf(ref, execaOptions) {
await execa('git', ['merge', '--ff', ref], execaOptions);
await execa("git", ["merge", "--ff", ref], execaOptions);
}
/**
@@ -299,7 +306,7 @@ export async function mergeFf(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function rebase(ref, execaOptions) {
await execa('git', ['rebase', ref], execaOptions);
await execa("git", ["rebase", ref], execaOptions);
}
/**
@@ -310,7 +317,7 @@ export async function rebase(ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOptions);
await execa("git", ["notes", "--ref", GIT_NOTE_REF, "add", "-m", note, ref], execaOptions);
}
/**
@@ -320,5 +327,5 @@ export async function gitAddNote(note, ref, execaOptions) {
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitGetNote(ref, execaOptions) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout;
return (await execa("git", ["notes", "--ref", GIT_NOTE_REF, "show", ref], execaOptions)).stdout;
}
+28 -17
View File
@@ -1,34 +1,45 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import pRetry from 'p-retry';
import {gitShallowClone, initBareRepo} from './git-utils.js';
import Docker from "dockerode";
import pRetry from "p-retry";
import { gitShallowClone, initBareRepo } from "./git-utils.js";
const IMAGE = 'semanticrelease/docker-gitbox:latest';
const IMAGE = "semanticrelease/docker-gitbox:latest";
const SERVER_PORT = 80;
const HOST_PORT = 2080;
const SERVER_HOST = 'localhost';
const GIT_USERNAME = 'integration';
const GIT_PASSWORD = 'suchsecure';
const SERVER_HOST = "localhost";
const GIT_USERNAME = "integration";
const GIT_PASSWORD = "suchsecure";
const docker = new Docker();
let container;
export const gitCredential = `${GIT_USERNAME}:${GIT_PASSWORD}`;
/**
* Download the `gitbox` Docker image, create a new container and start it.
* Download the `gitbox` Docker image
*/
export function pull() {
return docker.pull(IMAGE).then((stream) => {
return new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
});
});
}
/**
* create a new container and start it.
*/
export async function start() {
await getStream(await docker.pull(IMAGE));
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${SERVER_PORT}/tcp`]: [{HostPort: `${HOST_PORT}`}]},
HostConfig: {
PortBindings: { [`${SERVER_PORT}/tcp`]: [{ HostPort: `${HOST_PORT}` }] },
},
ExposedPorts: { [`${SERVER_PORT}/tcp`]: {} },
});
await container.start();
const exec = await container.exec({
Cmd: ['ng-auth', '-u', GIT_USERNAME, '-p', GIT_PASSWORD],
Cmd: ["ng-auth", "-u", GIT_USERNAME, "-p", GIT_PASSWORD],
AttachStdout: true,
AttachStderr: true,
});
@@ -51,9 +62,9 @@ export async function stop() {
* @param {String} [description=`Repository ${name}`] The repository description.
* @return {Object} The `repositoryUrl` (URL without auth) and `authUrl` (URL with auth).
*/
export async function createRepo(name, branch = 'master', description = `Repository ${name}`) {
export async function createRepo(name, branch = "master", description = `Repository ${name}`) {
const exec = await container.exec({
Cmd: ['repo-admin', '-n', name, '-d', description],
Cmd: ["repo-admin", "-n", name, "-d", description],
AttachStdout: true,
AttachStderr: true,
});
@@ -63,8 +74,8 @@ export async function createRepo(name, branch = 'master', description = `Reposit
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: 5, minTimeout: 500, factor: 2});
await pRetry(() => initBareRepo(authUrl, branch), { retries: 5, minTimeout: 500, factor: 2 });
const cwd = await gitShallowClone(authUrl);
return {cwd, repositoryUrl, authUrl};
return { cwd, repositoryUrl, authUrl };
}
+29 -18
View File
@@ -1,31 +1,42 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import pRetry from 'p-retry';
import {mockServerClient} from 'mockserver-client';
import Docker from "dockerode";
import got from "got";
import pRetry from "p-retry";
import { mockServerClient } from "mockserver-client";
const IMAGE = 'mockserver/mockserver:latest';
const IMAGE = "mockserver/mockserver:latest";
const MOCK_SERVER_PORT = 1080;
const MOCK_SERVER_HOST = 'localhost';
const MOCK_SERVER_HOST = "localhost";
const docker = new Docker();
let container;
/**
* Download the `mockserver` Docker image, create a new container and start it.
* Download the `mockserver` Docker image,
*/
export function pull() {
return docker.pull(IMAGE).then((stream) => {
return new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
});
});
}
/**
* create a new container and start it.
*/
export async function start() {
await getStream(await docker.pull(IMAGE));
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]},
HostConfig: {
PortBindings: { [`${MOCK_SERVER_PORT}/tcp`]: [{ HostPort: `${MOCK_SERVER_PORT}` }] },
},
ExposedPorts: { [`${MOCK_SERVER_PORT}/tcp`]: {} },
});
await container.start();
try {
// Wait for the mock server to be ready
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, {cache: false}), {
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, { cache: false }), {
retries: 7,
minTimeout: 1000,
factor: 2,
@@ -67,17 +78,17 @@ export const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`;
*/
export async function mock(
path,
{body: requestBody, headers: requestHeaders},
{method = 'POST', statusCode = 200, body: responseBody}
{ body: requestBody, headers: requestHeaders },
{ method = "POST", statusCode = 200, body: responseBody }
) {
await client.mockAnyResponse({
httpRequest: {path, method},
httpRequest: { path, method },
httpResponse: {
statusCode,
headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}],
headers: [{ name: "Content-Type", values: ["application/json; charset=utf-8"] }],
body: JSON.stringify(responseBody),
},
times: {remainingTimes: 1, unlimited: false},
times: { remainingTimes: 1, unlimited: false },
});
return {
@@ -85,7 +96,7 @@ export async function mock(
path,
headers: requestHeaders,
body: requestBody
? {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'}
? { type: "JSON", json: JSON.stringify(requestBody), matchType: "ONLY_MATCHING_FIELDS" }
: undefined,
};
}
+36 -25
View File
@@ -1,40 +1,51 @@
import path, {dirname} from 'node:path';
import {fileURLToPath} from 'node:url';
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import delay from 'delay';
import pRetry from 'p-retry';
import path, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { setTimeout } from "node:timers/promises";
import Docker from "dockerode";
import got from "got";
import pRetry from "p-retry";
const IMAGE = 'verdaccio/verdaccio:5';
const IMAGE = "verdaccio/verdaccio:5";
const REGISTRY_PORT = 4873;
const REGISTRY_HOST = 'localhost';
const NPM_USERNAME = 'integration';
const NPM_PASSWORD = 'suchsecure';
const NPM_EMAIL = 'integration@test.com';
const REGISTRY_HOST = "localhost";
const NPM_USERNAME = "integration";
const NPM_PASSWORD = "suchsecure";
const NPM_EMAIL = "integration@test.com";
const docker = new Docker();
const __dirname = dirname(fileURLToPath(import.meta.url));
let container, npmToken;
/**
* Download the `npm-registry-docker` Docker image, create a new container and start it.
* Download the `npm-registry-docker` Docker image
*/
export function pull() {
return docker.pull(IMAGE).then((stream) => {
return new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
});
});
}
/**
* create a new container and start it.
*/
export async function start() {
await getStream(await docker.pull(IMAGE));
container = await docker.createContainer({
Tty: true,
Image: IMAGE,
PortBindings: {[`${REGISTRY_PORT}/tcp`]: [{HostPort: `${REGISTRY_PORT}`}]},
Binds: [`${path.join(__dirname, 'config.yaml')}:/verdaccio/conf/config.yaml`],
HostConfig: {
PortBindings: { [`${REGISTRY_PORT}/tcp`]: [{ HostPort: `${REGISTRY_PORT}` }] },
Binds: [`${path.join(__dirname, "config.yaml")}:/verdaccio/conf/config.yaml`],
},
ExposedPorts: { [`${REGISTRY_PORT}/tcp`]: {} },
});
await container.start();
await delay(4000);
await setTimeout(4000);
try {
// Wait for the registry to be ready
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, {cache: false}), {
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, { cache: false }), {
retries: 7,
minTimeout: 1000,
factor: 2,
@@ -45,24 +56,24 @@ export async function start() {
// Create user
await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/user/org.couchdb.user:${NPM_USERNAME}`, {
method: 'PUT',
method: "PUT",
json: {
_id: `org.couchdb.user:${NPM_USERNAME}`,
name: NPM_USERNAME,
roles: [],
type: 'user',
type: "user",
password: NPM_PASSWORD,
email: NPM_EMAIL,
},
});
// Create token for user
({token: npmToken} = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
({ token: npmToken } = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
username: NPM_USERNAME,
password: NPM_PASSWORD,
method: 'POST',
headers: {'content-type': 'application/json'},
json: {password: NPM_PASSWORD, readonly: false, cidr_whitelist: []}
method: "POST",
headers: { "content-type": "application/json" },
json: { password: NPM_PASSWORD, readonly: false, cidr_whitelist: [] },
}).json());
}
+2 -2
View File
@@ -1,5 +1,5 @@
import {execa} from 'execa';
import { execa } from "execa";
export async function npmView(packageName, env) {
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
return JSON.parse((await execa("npm", ["view", packageName, "--json"], { env })).stdout);
}
+18 -5
View File
@@ -39,7 +39,11 @@ test.beforeEach((t) => {
};
});
test("Plugins are called with expected values", async (t) => {
test.afterEach.always((t) => {
td.reset();
});
test.serial("Plugins are called with expected values", async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const { cwd, repositoryUrl } = await gitRepo(true);
// Add commits to the master branch
@@ -1849,10 +1853,19 @@ test.serial("Throw an Error if plugin returns an unexpected value", async (t) =>
await td.replaceEsm("../lib/get-logger.js", null, () => t.context.logger);
await td.replaceEsm("env-ci", null, () => ({ isCi: true, branch: "master", isPr: false }));
const semanticRelease = (await import("../index.js")).default;
const error = await t.throwsAsync(
semanticRelease(options, { cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer() }),
{ instanceOf: SemanticReleaseError }
);
let error;
try {
await semanticRelease(options, {
cwd,
env: {},
stdout: new WritableStreamBuffer(),
stderr: new WritableStreamBuffer(),
});
} catch (e) {
error = e;
}
t.is(error.code, "EANALYZECOMMITSOUTPUT");
t.regex(error.details, /string/);
});
+31 -4
View File
@@ -1,11 +1,11 @@
import path from "path";
import path from "node:path";
import { setTimeout } from "node:timers/promises";
import test from "ava";
import * as td from "testdouble";
import { escapeRegExp } from "lodash-es";
import fsExtra from "fs-extra";
import { execa } from "execa";
import { WritableStreamBuffer } from "stream-buffers";
import delay from "delay";
import getAuthUrl from "../lib/get-git-auth-url.js";
import { SECRET_REPLACEMENT } from "../lib/definitions/constants.js";
@@ -44,8 +44,10 @@ const cli = path.resolve("./bin/semantic-release.js");
const pluginError = path.resolve("./test/fixtures/plugin-error");
const pluginInheritedError = path.resolve("./test/fixtures/plugin-error-inherited");
const pluginLogEnv = path.resolve("./test/fixtures/plugin-log-env");
const pluginEsmNamedExports = path.resolve("./test/fixtures/plugin-esm-named-exports");
test.before(async () => {
await Promise.all([gitbox.pull(), npmRegistry.pull(), mockServer.pull()]);
await Promise.all([gitbox.start(), npmRegistry.start(), mockServer.start()]);
env = {
@@ -294,7 +296,7 @@ test("Release patch, minor and major versions", async (t) => {
t.is(exitCode, 0);
// Wait for 3s as the change of dist-tag takes time to be reflected in the registry
await delay(3000);
await setTimeout(3000);
// Retrieve the published package from the registry and check version and gitHead
({
"dist-tags": { latest: releasedVersion },
@@ -597,7 +599,7 @@ test("Log unexpected errors from plugins and exit with 1", async (t) => {
// Verify the type and message are logged
t.regex(stderr, /Error: a/);
// Verify the the stacktrace is logged
t.regex(stderr, new RegExp(pluginError));
t.regex(stderr, new RegExp(process.platform === "win32" ? pluginError.replace(/\\/g, "\\\\") : pluginError));
// Verify the Error properties are logged
t.regex(stderr, /errorProperty: 'errorProperty'/);
t.is(exitCode, 1);
@@ -686,6 +688,7 @@ test("Use the valid git credentials when multiple are provided", async (t) => {
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
GIT_ASKPASS: "echo",
GIT_TERMINAL_PROMPT: 0,
GIT_CONFIG_PARAMETERS: "'credential.helper='",
},
branch: { name: "master" },
options: { repositoryUrl: "http://toto@localhost:2080/git/test-auth.git" },
@@ -706,6 +709,7 @@ test("Use the repository URL as is if none of the given git credentials are vali
GITLAB_TOKEN: "trash",
GIT_ASKPASS: "echo",
GIT_TERMINAL_PROMPT: 0,
GIT_CONFIG_PARAMETERS: "'credential.helper='",
},
branch: { name: "master" },
options: { repositoryUrl: dummyUrl },
@@ -713,3 +717,26 @@ test("Use the repository URL as is if none of the given git credentials are vali
dummyUrl
);
});
test("ESM Plugin with named exports", async (t) => {
const packageName = "plugin-exports";
// Create a git repository, set the current working directory at the root of the repo
t.log("Create git repository");
const { cwd, repositoryUrl } = await gitbox.createRepo(packageName);
await writeJson(path.resolve(cwd, "package.json"), {
name: packageName,
version: "0.0.0-dev",
repository: { url: repositoryUrl },
release: { plugins: [pluginEsmNamedExports] },
});
t.log("$ semantic-release");
const { stdout, stderr } = await execa(cli, [], {
env: { ...env, MY_TOKEN: "secret token" },
cwd,
reject: false,
extendEnv: false,
});
t.regex(stdout, new RegExp(`verifyConditions called`));
});
+117 -102
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {noop} from 'lodash-es';
import {stub} from 'sinon';
import normalize from '../../lib/plugins/normalize.js';
import test from "ava";
import { noop } from "lodash-es";
import { stub } from "sinon";
import normalize from "../../lib/plugins/normalize.js";
const cwd = process.cwd();
@@ -10,7 +10,7 @@ test.beforeEach((t) => {
t.context.log = stub();
t.context.error = stub();
t.context.success = stub();
t.context.stderr = {write: stub()};
t.context.stderr = { write: stub() };
t.context.logger = {
log: t.context.log,
error: t.context.error,
@@ -19,98 +19,108 @@ test.beforeEach((t) => {
};
});
test('Normalize and load plugin from string', async (t) => {
test("Normalize and load plugin from string", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
'./test/fixtures/plugin-noop.cjs',
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./test/fixtures/plugin-noop.cjs",
{}
);
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop.cjs"']);
});
test('Normalize and load plugin from object', async (t) => {
test("Normalize and load plugin from object", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'publish',
{path: './test/fixtures/plugin-noop.cjs'},
{ cwd, options: {}, logger: t.context.logger },
"publish",
{ path: "./test/fixtures/plugin-noop.cjs" },
{}
);
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop.cjs"']);
});
test('Normalize and load plugin from a base file path', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop.cjs', {
'./plugin-noop.cjs': './test/fixtures',
});
test("Normalize and load plugin from a base file path", async (t) => {
const plugin = await normalize(
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./plugin-noop.cjs",
{
"./plugin-noop.cjs": "./test/fixtures",
}
);
t.is(plugin.pluginName, './plugin-noop.cjs');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "./plugin-noop.cjs");
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], [
'Loaded plugin "verifyConditions" from "./plugin-noop.cjs" in shareable config "./test/fixtures"',
]);
});
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
'./plugin-error': './test/fixtures',
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "verifyConditions", "./plugin-error", {
"./plugin-error": "./test/fixtures",
});
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.pluginName, './plugin-error');
t.is(error.pluginName, "./plugin-error");
});
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
'./plugin-errors': './test/fixtures',
});
const plugin = await normalize(
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./plugin-errors",
{
"./plugin-errors": "./test/fixtures",
}
);
const errors = [...(await t.throwsAsync(plugin({options: {}}))).errors];
const errors = [...(await t.throwsAsync(plugin({ options: {} }))).errors];
for (const error of errors) {
t.is(error.pluginName, './plugin-errors');
t.is(error.pluginName, "./plugin-errors");
}
});
test('Normalize and load plugin from function', async (t) => {
test("Normalize and load plugin from function", async (t) => {
const pluginFunction = () => {};
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
t.is(plugin.pluginName, '[Function: pluginFunction]');
t.is(typeof plugin, 'function');
t.is(plugin.pluginName, "[Function: pluginFunction]");
t.is(typeof plugin, "function");
});
test('Normalize and load plugin that retuns multiple functions', async (t) => {
test("Normalize and load plugin that retuns multiple functions", async (t) => {
const plugin = await normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
'./test/fixtures/multi-plugin.cjs',
{ cwd, options: {}, logger: t.context.logger },
"verifyConditions",
"./test/fixtures/multi-plugin.cjs",
{}
);
t.is(typeof plugin, 'function');
t.is(typeof plugin, "function");
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin.cjs"']);
});
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
const analyzeCommits = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'analyzeCommits',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"analyzeCommits",
analyzeCommits,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EANALYZECOMMITSOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EANALYZECOMMITSOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -119,16 +129,16 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
const generateNotes = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'generateNotes',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"generateNotes",
generateNotes,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EGENERATENOTESOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EGENERATENOTESOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -137,16 +147,16 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
const publish = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'publish',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"publish",
publish,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EPUBLISHOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EPUBLISHOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -155,16 +165,16 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
const addChannel = stub().resolves(2);
const plugin = await normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
'addChannel',
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
"addChannel",
addChannel,
{}
);
const error = await t.throwsAsync(plugin({options: {}}));
const error = await t.throwsAsync(plugin({ options: {} }));
t.is(error.code, 'EADDCHANNELOUTPUT');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EADDCHANNELOUTPUT");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
t.regex(error.details, /2/);
@@ -172,60 +182,60 @@ test('Wrap "addChannel" plugin in a function that validate the output of the plu
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = {path: pluginFunction, conf: 'confValue'};
const options = {global: 'globalValue'};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}, param: 'param'});
const pluginConf = { path: pluginFunction, conf: "confValue" };
const options = { global: "globalValue" };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {}, param: "param" });
t.true(
pluginFunction.calledWithMatch(
{conf: 'confValue', global: 'globalValue'},
{param: 'param', logger: t.context.logger}
{ conf: "confValue", global: "globalValue" },
{ param: "param", logger: t.context.logger }
)
);
});
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = [pluginFunction, {conf: 'confValue'}];
const options = {global: 'globalValue'};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}, param: 'param'});
const pluginConf = [pluginFunction, { conf: "confValue" }];
const options = { global: "globalValue" };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {}, param: "param" });
t.true(
pluginFunction.calledWithMatch(
{conf: 'confValue', global: 'globalValue'},
{param: 'param', logger: t.context.logger}
{ conf: "confValue", global: "globalValue" },
{ param: "param", logger: t.context.logger }
)
);
});
test('Prevent plugins to modify "pluginConfig"', async (t) => {
const pluginFunction = stub().callsFake((pluginConfig) => {
pluginConfig.conf.subConf = 'otherConf';
pluginConfig.conf.subConf = "otherConf";
});
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
const options = {globalConf: {globalSubConf: 'originalGlobalConf'}};
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
await plugin({options: {}});
const pluginConf = { path: pluginFunction, conf: { subConf: "originalConf" } };
const options = { globalConf: { globalSubConf: "originalGlobalConf" } };
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
await plugin({ options: {} });
t.is(pluginConf.conf.subConf, 'originalConf');
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
t.is(pluginConf.conf.subConf, "originalConf");
t.is(options.globalConf.globalSubConf, "originalGlobalConf");
});
test('Prevent plugins to modify its input', async (t) => {
test("Prevent plugins to modify its input", async (t) => {
const pluginFunction = stub().callsFake((pluginConfig, options) => {
options.param.subParam = 'otherParam';
options.param.subParam = "otherParam";
});
const input = {param: {subParam: 'originalSubParam'}, options: {}};
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
const input = { param: { subParam: "originalSubParam" }, options: {} };
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
await plugin(input);
t.is(input.param.subParam, 'originalSubParam');
t.is(input.param.subParam, "originalSubParam");
});
test('Return noop if the plugin is not defined', async (t) => {
const plugin = await normalize({cwd, options: {}, logger: t.context.logger});
test("Return noop if the plugin is not defined", async (t) => {
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger });
t.is(plugin, noop);
});
@@ -233,12 +243,12 @@ test('Return noop if the plugin is not defined', 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 = await normalize(
{cwd, options: {}, logger: t.context.logger},
'',
'./test/fixtures/plugin-result-config',
{ cwd, options: {}, logger: t.context.logger },
"",
"./test/fixtures/plugin-result-config",
{}
);
const pluginResult = await plugin({options: {}});
const pluginResult = await plugin({ options: {} });
t.deepEqual(pluginResult.pluginConfig, {});
});
@@ -246,33 +256,38 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
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 = await normalize(
{cwd, options: {}, logger: t.context.logger},
'',
{path: './test/fixtures/plugin-result-config'},
{ cwd, options: {}, logger: t.context.logger },
"",
{ path: "./test/fixtures/plugin-result-config" },
{}
);
const pluginResult = await plugin({options: {}});
const pluginResult = await plugin({ options: {} });
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Throws an error if the plugin return an object without the expected plugin function', async (t) => {
test("Throws an error if the plugin return an object without the expected plugin function", async (t) => {
const error = await t.throwsAsync(() =>
normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', './test/fixtures/multi-plugin.cjs', {})
normalize(
{ cwd, options: {}, logger: t.context.logger },
"nonExistentPlugin",
"./test/fixtures/multi-plugin.cjs",
{}
)
);
t.is(error.code, 'EPLUGIN');
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, "EPLUGIN");
t.is(error.name, "SemanticReleaseError");
t.truthy(error.message);
t.truthy(error.details);
});
test('Throws an error if the plugin is not found', async (t) => {
test("Throws an error if the plugin is not found", async (t) => {
await t.throwsAsync(
() => normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', 'non-existing-path', {}),
() => normalize({ cwd, options: {}, logger: t.context.logger }, "nonExistentPlugin", "non-existing-path", {}),
{
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
code: "MODULE_NOT_FOUND",
instanceOf: Error,
}
);
+30 -30
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import {stub} from 'sinon';
import AggregateError from 'aggregate-error';
import pipeline from '../../lib/plugins/pipeline.js';
import test from "ava";
import { stub } from "sinon";
import AggregateError from "aggregate-error";
import pipeline from "../../lib/plugins/pipeline.js";
test('Execute each function in series passing the same input', async (t) => {
test("Execute each function in series passing the same input", async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
@@ -25,7 +25,7 @@ test('Execute each function in series passing a transformed input from "getNextI
const step4 = stub().resolves(4);
const getNextInput = (lastResult, result) => lastResult + result;
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(0);
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(0);
t.deepEqual(result, [1, 2, 3, 4]);
t.true(step1.calledWith(0));
@@ -44,7 +44,7 @@ test('Execute each function in series passing the "lastResult" and "result" to "
const step4 = stub().resolves(4);
const getNextInput = stub().returnsArg(0);
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(5);
t.deepEqual(result, [1, 2, 3, 4]);
t.deepEqual(getNextInput.args, [
@@ -63,7 +63,7 @@ test('Execute each function in series calling "transform" to modify the results'
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
const result = await pipeline([step1, step2, step3, step4], { getNextInput, transform })(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [
@@ -82,7 +82,7 @@ test('Execute each function in series calling "transform" to modify the results
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
const result = await pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput, transform })(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [
@@ -93,24 +93,24 @@ test('Execute each function in series calling "transform" to modify the results
]);
});
test('Stop execution and throw error if a step rejects', async (t) => {
test("Stop execution and throw error if a step rejects", async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().rejects(new Error('test error'));
const step2 = stub().rejects(new Error("test error"));
const step3 = stub().resolves(3);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
instanceOf: Error,
message: 'test error',
message: "test error",
});
t.is(error.message, 'test error');
t.is(error.message, "test error");
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.notCalled);
});
test('Throw all errors from the first step throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
test("Throw all errors from the first step throwing an AggregateError", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const step1 = stub().resolves(1);
const step2 = stub().rejects(new AggregateError([error1, error2]));
@@ -124,14 +124,14 @@ test('Throw all errors from the first step throwing an AggregateError', async (t
t.true(step3.notCalled);
});
test('Execute all even if a Promise rejects', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
test("Execute all even if a Promise rejects", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const step1 = stub().resolves(1);
const step2 = stub().rejects(error1);
const step3 = stub().rejects(error2);
const error = await t.throwsAsync(pipeline([step1, step2, step3], {settleAll: true})(0));
const error = await t.throwsAsync(pipeline([step1, step2, step3], { settleAll: true })(0));
t.deepEqual([...error.errors], [error1, error2]);
t.true(step1.calledWith(0));
@@ -139,31 +139,31 @@ test('Execute all even if a Promise rejects', async (t) => {
t.true(step3.calledWith(0));
});
test('Throw all errors from all steps throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
const error4 = new Error('test error 4');
test("Throw all errors from all steps throwing an AggregateError", async (t) => {
const error1 = new Error("test error 1");
const error2 = new Error("test error 2");
const error3 = new Error("test error 3");
const error4 = new Error("test error 4");
const step1 = stub().rejects(new AggregateError([error1, error2]));
const step2 = stub().rejects(new AggregateError([error3, error4]));
const error = await t.throwsAsync(pipeline([step1, step2], {settleAll: true})(0));
const error = await t.throwsAsync(pipeline([step1, step2], { settleAll: true })(0));
t.deepEqual([...error.errors], [error1, error2, error3, error4]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
});
test('Execute each function in series passing a transformed input even if a step rejects', async (t) => {
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
test("Execute each function in series passing a transformed input even if a step rejects", async (t) => {
const error2 = new Error("test error 2");
const error3 = new Error("test error 3");
const step1 = stub().resolves(1);
const step2 = stub().rejects(error2);
const step3 = stub().rejects(error3);
const step4 = stub().resolves(4);
const getNextInput = (previousResult, result) => previousResult + result;
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0));
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput })(0));
t.deepEqual([...error.errors], [error2, error3]);
t.true(step1.calledWith(0));
+163 -157
View File
@@ -1,9 +1,9 @@
import path from 'path';
import test from 'ava';
import {copy, outputFile} from 'fs-extra';
import {stub} from 'sinon';
import {temporaryDirectory} from 'tempy';
import getPlugins from '../../lib/plugins/index.js';
import path from "path";
import test from "ava";
import { copy, outputFile } from "fs-extra";
import { stub } from "sinon";
import { temporaryDirectory } from "tempy";
import getPlugins from "../../lib/plugins/index.js";
// Save the current working directory
const cwd = process.cwd();
@@ -12,32 +12,32 @@ test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.success = stub();
t.context.logger = {log: t.context.log, success: t.context.success, scope: () => t.context.logger};
t.context.logger = { log: t.context.log, success: t.context.success, scope: () => t.context.logger };
});
test('Export default plugins', async (t) => {
const plugins = await getPlugins({cwd, options: {}, logger: t.context.logger}, {});
test("Export default plugins", async (t) => {
const plugins = await getPlugins({ cwd, options: {}, logger: t.context.logger }, {});
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on steps config', async (t) => {
test("Export plugins based on steps config", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['./test/fixtures/plugin-noop.cjs', {path: './test/fixtures/plugin-noop.cjs'}],
generateNotes: './test/fixtures/plugin-noop.cjs',
analyzeCommits: {path: './test/fixtures/plugin-noop.cjs'},
verifyConditions: ["./test/fixtures/plugin-noop.cjs", { path: "./test/fixtures/plugin-noop.cjs" }],
generateNotes: "./test/fixtures/plugin-noop.cjs",
analyzeCommits: { path: "./test/fixtures/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
@@ -45,161 +45,161 @@ test('Export plugins based on steps config', async (t) => {
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (array)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugin2 = {verifyConditions: stub(), verifyRelease: stub()};
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugin2 = { verifyConditions: stub(), verifyRelease: stub() };
const plugins = await getPlugins(
{cwd, logger: t.context.logger, options: {plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {}}},
{ cwd, logger: t.context.logger, options: { plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {} } },
{}
);
await plugins.verifyConditions({options: {}});
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
t.true(plugin2.verifyConditions.calledOnce);
await plugins.publish({options: {}});
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
await plugins.verifyRelease({options: {}});
await plugins.verifyRelease({ options: {} });
t.true(plugin2.verifyRelease.notCalled);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (single definition)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {});
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: plugin1 } }, {});
await plugins.verifyConditions({options: {}});
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
await plugins.publish({options: {}});
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Merge global options, "plugins" options and step options', async (t) => {
const plugin1 = [{verifyConditions: stub(), publish: stub()}, {pluginOpt1: 'plugin1'}];
const plugin2 = [{verifyConditions: stub()}, {pluginOpt2: 'plugin2'}];
const plugin3 = [stub(), {pluginOpt3: 'plugin3'}];
const plugin1 = [{ verifyConditions: stub(), publish: stub() }, { pluginOpt1: "plugin1" }];
const plugin2 = [{ verifyConditions: stub() }, { pluginOpt2: "plugin2" }];
const plugin3 = [stub(), { pluginOpt3: "plugin3" }];
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {globalOpt: 'global', plugins: [plugin1, plugin2], verifyRelease: [plugin3]},
options: { globalOpt: "global", plugins: [plugin1, plugin2], verifyRelease: [plugin3] },
},
{}
);
await plugins.verifyConditions({options: {}});
t.deepEqual(plugin1[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
t.deepEqual(plugin2[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt2: 'plugin2'});
await plugins.verifyConditions({ options: {} });
t.deepEqual(plugin1[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
t.deepEqual(plugin2[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt2: "plugin2" });
await plugins.publish({options: {}});
t.deepEqual(plugin1[0].publish.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
await plugins.publish({ options: {} });
t.deepEqual(plugin1[0].publish.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
await plugins.verifyRelease({options: {}});
t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'});
await plugins.verifyRelease({ options: {} });
t.deepEqual(plugin3[0].args[0][0], { globalOpt: "global", pluginOpt3: "plugin3" });
});
test('Unknown steps of plugins configured in "plugins" are ignored', async (t) => {
const plugin1 = {verifyConditions: () => {}, unknown: () => {}};
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: [plugin1]}}, {});
const plugin1 = { verifyConditions: () => {}, unknown: () => {} };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: [plugin1] } }, {});
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(plugins.unknown, undefined);
});
test('Export plugins loaded from the dependency of a shareable config module', async (t) => {
test("Export plugins loaded from the dependency of a shareable config module", async (t) => {
const cwd = temporaryDirectory();
await copy(
'./test/fixtures/plugin-noop.cjs',
path.resolve(cwd, 'node_modules/shareable-config/node_modules/custom-plugin/index.js')
"./test/fixtures/plugin-noop.cjs",
path.resolve(cwd, "node_modules/shareable-config/node_modules/custom-plugin/index.js")
);
await outputFile(path.resolve(cwd, 'node_modules/shareable-config/index.js'), '');
await outputFile(path.resolve(cwd, "node_modules/shareable-config/index.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['custom-plugin', {path: 'custom-plugin'}],
generateNotes: 'custom-plugin',
analyzeCommits: {path: 'custom-plugin'},
verifyConditions: ["custom-plugin", { path: "custom-plugin" }],
generateNotes: "custom-plugin",
analyzeCommits: { path: "custom-plugin" },
verifyRelease: () => {},
},
},
{'custom-plugin': 'shareable-config'}
{ "custom-plugin": "shareable-config" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins loaded from the dependency of a shareable config file', async (t) => {
test("Export plugins loaded from the dependency of a shareable config file", async (t) => {
const cwd = temporaryDirectory();
await copy('./test/fixtures/plugin-noop.cjs', path.resolve(cwd, 'plugin/plugin-noop.cjs'));
await outputFile(path.resolve(cwd, 'shareable-config.js'), '');
await copy("./test/fixtures/plugin-noop.cjs", path.resolve(cwd, "plugin/plugin-noop.cjs"));
await outputFile(path.resolve(cwd, "shareable-config.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ['./plugin/plugin-noop.cjs', {path: './plugin/plugin-noop.cjs'}],
generateNotes: './plugin/plugin-noop.cjs',
analyzeCommits: {path: './plugin/plugin-noop.cjs'},
verifyConditions: ["./plugin/plugin-noop.cjs", { path: "./plugin/plugin-noop.cjs" }],
generateNotes: "./plugin/plugin-noop.cjs",
analyzeCommits: { path: "./plugin/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
{'./plugin/plugin-noop': './shareable-config.js'}
{ "./plugin/plugin-noop": "./shareable-config.js" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Use default when only options are passed for a single plugin', async (t) => {
test("Use default when only options are passed for a single plugin", async (t) => {
const analyzeCommits = {};
const generateNotes = {};
const publish = {};
@@ -211,7 +211,7 @@ test('Use default when only options are passed for a single plugin', async (t) =
cwd,
logger: t.context.logger,
options: {
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
analyzeCommits,
generateNotes,
publish,
@@ -223,96 +223,102 @@ test('Use default when only options are passed for a single plugin', async (t) =
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
// Verify only the plugins defined as an object with no `path` are set to the default value
t.falsy(success.path);
t.falsy(fail.path);
});
test('Merge global options with plugin options', async (t) => {
test("Merge global options with plugin options", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
globalOpt: 'global',
otherOpt: 'globally-defined',
verifyRelease: {path: './test/fixtures/plugin-result-config', localOpt: 'local', otherOpt: 'locally-defined'},
globalOpt: "global",
otherOpt: "globally-defined",
verifyRelease: { path: "./test/fixtures/plugin-result-config", localOpt: "local", otherOpt: "locally-defined" },
},
},
{}
);
const [result] = await plugins.verifyRelease({options: {}});
const [result] = await plugins.verifyRelease({ options: {} });
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
t.deepEqual(result.pluginConfig, { localOpt: "local", globalOpt: "global", otherOpt: "locally-defined" });
});
test('Throw an error for each invalid plugin configuration', async (t) => {
test("Throw an error for each invalid plugin configuration", async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
verifyConditions: 1,
analyzeCommits: [],
verifyRelease: [{}],
generateNotes: [{path: null}],
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
verifyConditions: 1,
analyzeCommits: [],
verifyRelease: [{}],
generateNotes: [{ path: null }],
},
},
},
{}
{}
)
)
)).errors,
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINCONF');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPLUGINCONF');
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'EPLUGINCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINCONF");
t.is(errors[3].name, "SemanticReleaseError");
t.is(errors[3].code, "EPLUGINCONF");
});
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {plugins: ['./test/fixtures/multi-plugin.cjs', './test/fixtures/plugin-noop.cjs', () => {}]},
},
{}
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: { plugins: ["./test/fixtures/multi-plugin.cjs", "./test/fixtures/plugin-noop.cjs", () => {}] },
},
{}
)
)
)).errors,
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINSCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINSCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
});
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', async (t) => {
const errors = [
...(await t.throwsAsync(() =>
getPlugins({cwd, logger: t.context.logger, options: {plugins: [1, {path: 1}, [() => {}, {}, {}]]}}, {})
)).errors,
...(
await t.throwsAsync(() =>
getPlugins({ cwd, logger: t.context.logger, options: { plugins: [1, { path: 1 }, [() => {}, {}, {}]] } }, {})
)
).errors,
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINSCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINSCONF');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPLUGINSCONF');
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINSCONF");
});
+128 -118
View File
@@ -1,214 +1,224 @@
import test from 'ava';
import {loadPlugin, parseConfig, validatePlugin, validateStep} from '../../lib/plugins/utils.js';
import test from "ava";
import { loadPlugin, parseConfig, validatePlugin, validateStep } from "../../lib/plugins/utils.js";
test('validatePlugin', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
test("validatePlugin", (t) => {
const path = "plugin-module";
const options = { option1: "value1", option2: "value2" };
t.true(validatePlugin(path), 'String definition');
t.true(validatePlugin({publish: () => {}}), 'Object definition');
t.true(validatePlugin([path]), 'Array definition');
t.true(validatePlugin([path, options]), 'Array definition with options');
t.true(validatePlugin([{publish: () => {}}, options]), 'Array definition with options and path as object');
t.true(validatePlugin({path}), 'Object with path definition');
t.true(validatePlugin({path, ...options}), 'Object with path definition with options');
t.true(validatePlugin(path), "String definition");
t.true(validatePlugin({ publish: () => {} }), "Object definition");
t.true(validatePlugin([path]), "Array definition");
t.true(validatePlugin([path, options]), "Array definition with options");
t.true(validatePlugin([{ publish: () => {} }, options]), "Array definition with options and path as object");
t.true(validatePlugin({ path }), "Object with path definition");
t.true(validatePlugin({ path, ...options }), "Object with path definition with options");
t.true(
validatePlugin({path: {publish: () => {}}, ...options}),
'Object with path definition with options and path as object'
validatePlugin({ path: { publish: () => {} }, ...options }),
"Object with path definition with options and path as object"
);
t.false(validatePlugin(1), 'String definition, wrong path');
t.false(validatePlugin([]), 'Array definition, missing path');
t.false(validatePlugin([path, options, {}]), 'Array definition, additional parameter');
t.false(validatePlugin([1]), 'Array definition, wrong path');
t.false(validatePlugin([path, 1]), 'Array definition, wrong options');
t.false(validatePlugin({path: 1}), 'Object definition, wrong path');
t.false(validatePlugin(1), "String definition, wrong path");
t.false(validatePlugin([]), "Array definition, missing path");
t.false(validatePlugin([path, options, {}]), "Array definition, additional parameter");
t.false(validatePlugin([1]), "Array definition, wrong path");
t.false(validatePlugin([path, 1]), "Array definition, wrong options");
t.false(validatePlugin({ path: 1 }), "Object definition, wrong path");
});
test('validateStep: optional plugin configuration', (t) => {
const type = {multiple: true, required: false};
test("validateStep: optional plugin configuration", (t) => {
const type = { multiple: true, required: false };
// Empty config
t.true(validateStep(type));
t.true(validateStep(type, []));
// Single value definition
t.true(validateStep(type, 'plugin-path.js'));
t.true(validateStep(type, "plugin-path.js"));
t.true(validateStep(type, () => {}));
t.true(validateStep(type, ['plugin-path.js']));
t.true(validateStep(type, ["plugin-path.js"]));
t.true(validateStep(type, [() => {}]));
t.false(validateStep(type, {}));
t.false(validateStep(type, [{}]));
// Array type definition
t.true(validateStep(type, [['plugin-path.js']]));
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
t.false(validateStep(type, [['plugin-path.js', 1]]));
t.true(validateStep(type, [["plugin-path.js"]]));
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
t.false(validateStep(type, [["plugin-path.js", 1]]));
// Object type definition
t.true(validateStep(type, {path: 'plugin-path.js'}));
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
t.true(validateStep(type, {path: () => {}, options: 'value'}));
t.false(validateStep(type, {path: null}));
t.true(validateStep(type, { path: "plugin-path.js" }));
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
t.true(validateStep(type, { path: () => {}, options: "value" }));
t.false(validateStep(type, { path: null }));
// Considered as an Array of 2 definitions and not as one Array definition in case of a muliple plugin type
t.false(validateStep(type, [() => {}, {options: 'value'}]));
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
t.false(validateStep(type, [() => {}, { options: "value" }]));
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
// Multiple definitions
t.true(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', 1],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", 1],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
{},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: null},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: null },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
});
test('validateStep: required plugin configuration', (t) => {
const type = {required: true};
test("validateStep: required plugin configuration", (t) => {
const type = { required: true };
// Empty config
t.false(validateStep(type));
t.false(validateStep(type, []));
// Single value definition
t.true(validateStep(type, 'plugin-path.js'));
t.true(validateStep(type, "plugin-path.js"));
t.true(validateStep(type, () => {}));
t.true(validateStep(type, ['plugin-path.js']));
t.true(validateStep(type, ["plugin-path.js"]));
t.true(validateStep(type, [() => {}]));
t.false(validateStep(type, {}));
t.false(validateStep(type, [{}]));
// Array type definition
t.true(validateStep(type, [['plugin-path.js']]));
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
t.false(validateStep(type, [['plugin-path.js', 1]]));
t.true(validateStep(type, [["plugin-path.js"]]));
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
t.false(validateStep(type, [["plugin-path.js", 1]]));
// Object type definition
t.true(validateStep(type, {path: 'plugin-path.js'}));
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
t.true(validateStep(type, {path: () => {}, options: 'value'}));
t.false(validateStep(type, {path: null}));
t.true(validateStep(type, { path: "plugin-path.js" }));
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
t.true(validateStep(type, { path: () => {}, options: "value" }));
t.false(validateStep(type, { path: null }));
// Considered as an Array of 2 definitions and not as one Array definition in the case of a muliple plugin type
t.false(validateStep(type, [() => {}, {options: 'value'}]));
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
t.false(validateStep(type, [() => {}, { options: "value" }]));
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
// Multiple definitions
t.true(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', 1],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", 1],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
{},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: 'plugin-path.js'},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: "plugin-path.js" },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
t.false(
validateStep(type, [
'plugin-path.js',
"plugin-path.js",
() => {},
['plugin-path.js'],
['plugin-path.js', {options: 'value'}],
[() => {}, {options: 'value'}],
{path: null},
{path: 'plugin-path.js', options: 'value'},
{path: () => {}, options: 'value'},
["plugin-path.js"],
["plugin-path.js", { options: "value" }],
[() => {}, { options: "value" }],
{ path: null },
{ path: "plugin-path.js", options: "value" },
{ path: () => {}, options: "value" },
])
);
});
test('loadPlugin', async (t) => {
test("loadPlugin", async (t) => {
const cwd = process.cwd();
const func = () => {};
t.is((await import('../fixtures/plugin-noop.cjs')).default, await loadPlugin({cwd: './test/fixtures'}, './plugin-noop.cjs', {}), 'From cwd');
t.is(
(await import('../fixtures/plugin-noop.cjs')).default,
await loadPlugin({cwd}, './plugin-noop.cjs', {'./plugin-noop.cjs': './test/fixtures'}),
'From a shareable config context'
(await import("../fixtures/plugin-noop.cjs")).default,
await loadPlugin({ cwd: "./test/fixtures" }, "./plugin-noop.cjs", {}),
"From cwd"
);
t.is(func, await loadPlugin({cwd}, func, {}), 'Defined as a function');
t.is(
(await import("../fixtures/plugin-noop.cjs")).default,
await loadPlugin({ cwd }, "./plugin-noop.cjs", { "./plugin-noop.cjs": "./test/fixtures" }),
"From a shareable config context"
);
const { ...namedExports } = await import("../fixtures/plugin-esm-named-exports.js");
const plugin = await loadPlugin({ cwd }, "./plugin-esm-named-exports.js", {
"./plugin-esm-named-exports.js": "./test/fixtures",
});
t.deepEqual(namedExports, plugin, "ESM with named exports");
t.is(func, await loadPlugin({ cwd }, func, {}), "Defined as a function");
});
test('parseConfig', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
test("parseConfig", (t) => {
const path = "plugin-module";
const options = { option1: "value1", option2: "value2" };
t.deepEqual(parseConfig(path), [path, {}], 'String definition');
t.deepEqual(parseConfig({path}), [path, {}], 'Object definition');
t.deepEqual(parseConfig({path, ...options}), [path, options], 'Object definition with options');
t.deepEqual(parseConfig([path]), [path, {}], 'Array definition');
t.deepEqual(parseConfig([path, options]), [path, options], 'Array definition with options');
t.deepEqual(parseConfig(path), [path, {}], "String definition");
t.deepEqual(parseConfig({ path }), [path, {}], "Object definition");
t.deepEqual(parseConfig({ path, ...options }), [path, options], "Object definition with options");
t.deepEqual(parseConfig([path]), [path, {}], "Array definition");
t.deepEqual(parseConfig([path, options]), [path, options], "Array definition with options");
});