From ef8b86ed5c21137e87ef00b140f3cfce3643660b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20C=2E=20Viesca=20Ruiz?= Date: Wed, 12 Aug 2020 11:52:41 +0200 Subject: [PATCH] test: add tests for the release output --- __mocks__/env-ci.ts | 16 ++ __mocks__/signale.ts | 15 ++ cSpell.json | 7 +- jest.config.js | 2 +- package-lock.json | 236 +++++++++++++++++++++----- package.json | 3 + src/index.spec.ts | 278 +++++++++++++++++++++++++++++++ src/index.ts | 50 +++--- src/utilities/git.spec.ts | 201 ++++++++++++++++++++++ src/utilities/git.ts | 121 ++++++++++++++ src/utilities/inputProcessors.ts | 2 +- 11 files changed, 868 insertions(+), 63 deletions(-) create mode 100644 __mocks__/env-ci.ts create mode 100644 __mocks__/signale.ts create mode 100644 src/index.spec.ts create mode 100644 src/utilities/git.spec.ts create mode 100644 src/utilities/git.ts diff --git a/__mocks__/env-ci.ts b/__mocks__/env-ci.ts new file mode 100644 index 00000000..cb009d6c --- /dev/null +++ b/__mocks__/env-ci.ts @@ -0,0 +1,16 @@ +/* eslint-disable immutable/no-mutation */ +/* eslint-disable unicorn/filename-case */ +/* eslint-disable unicorn/prevent-abbreviations */ +import * as envCi from 'env-ci'; + +module.exports = ({ + cwd, + env, +}: { + cwd: string; + env: string; +}): { [key: string]: unknown } => { + const { isCi, isPr, ...other } = envCi({ cwd, env }); + + return { isCi: false, isPr: false, ...other }; +}; diff --git a/__mocks__/signale.ts b/__mocks__/signale.ts new file mode 100644 index 00000000..7aa31b11 --- /dev/null +++ b/__mocks__/signale.ts @@ -0,0 +1,15 @@ +/* eslint-disable class-methods-use-this */ + +import * as signale from 'signale'; + +class Signale extends signale.Signale { + /* eslint-disable @typescript-eslint/naming-convention */ + public _log(): void {} + + public _logger(): void {} + + public _write(): void {} + /* eslint-enable @typescript-eslint/naming-convention */ +} + +export { Signale }; diff --git a/cSpell.json b/cSpell.json index e240b8ae..4b577bfd 100644 --- a/cSpell.json +++ b/cSpell.json @@ -28,12 +28,17 @@ "codeowners", "commitlint", "editorconfig", + "execa", "gitignore", + "gpgsign", + "hardlinks", "premajor", "preminor", "prepatch", "prettierignore", "prettierrc", - "ridedott" + "ridedott", + "signale", + "tempy" ] } diff --git a/jest.config.js b/jest.config.js index 54d4c8cf..699a95e2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,6 +12,6 @@ module.exports = { }, preset: 'ts-jest', resetMocks: true, - roots: ['/src'], + roots: ['/src', '/__mocks__'], testEnvironment: 'node', }; diff --git a/package-lock.json b/package-lock.json index 077c9d9b..b0d30d54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -531,7 +531,8 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.3.4.tgz", "integrity": "sha512-f4HigYjeIBn9f7OuNv5zh2y5vWaAhNFrfeul8CRJDy82l3Y+09lxOTGxfF3uMXKrZq4LmuK6qvvRCZ8mUrVvzQ==", - "dev": true + "dev": true, + "optional": true }, "@commitlint/format": { "version": "9.1.1", @@ -629,6 +630,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.3.5.tgz", "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", "dev": true, + "optional": true, "requires": { "@commitlint/execute-rule": "^8.3.4", "@commitlint/resolve-extends": "^8.3.5", @@ -644,6 +646,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "dev": true, + "optional": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -656,6 +659,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, + "optional": true, "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -665,7 +669,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "dev": true, + "optional": true } } } @@ -703,6 +708,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.3.5.tgz", "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", "dev": true, + "optional": true, "requires": { "import-fresh": "^3.0.0", "lodash": "4.17.15", @@ -2313,6 +2319,24 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "tempy": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.5.0.tgz", + "integrity": "sha512-VEY96x7gbIRfsxqsafy2l5yVxxp3PhwAGoWMyC2D2Zt5DmEv+2tGiPOrquNRpf21hhGnKLVEsuqleqiZmKG/qw==", + "requires": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.12.0", + "unique-string": "^2.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + } + } + }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", @@ -3120,6 +3144,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, + "optional": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -3129,7 +3154,8 @@ "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "dev": true, + "optional": true } } }, @@ -3320,6 +3346,7 @@ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, + "optional": true, "requires": { "callsites": "^2.0.0" }, @@ -3328,7 +3355,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3337,6 +3365,7 @@ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, + "optional": true, "requires": { "caller-callsite": "^2.0.0" } @@ -3935,7 +3964,8 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5252,25 +5282,61 @@ "dev": true }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" }, "dependencies": { - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { - "pump": "^3.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" } } } @@ -5607,6 +5673,12 @@ "flat-cache": "^2.0.1" } }, + "file-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/file-url/-/file-url-3.0.0.tgz", + "integrity": "sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA==", + "dev": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6654,7 +6726,8 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true + "dev": true, + "optional": true }, "is-extendable": { "version": "0.1.1", @@ -6731,9 +6804,10 @@ "dev": true }, "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true }, "is-symbol": { "version": "1.0.3", @@ -13201,11 +13275,20 @@ } }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + } } }, "nwsapi": { @@ -14231,6 +14314,21 @@ } } }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -14254,6 +14352,15 @@ } } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -14274,6 +14381,12 @@ } } }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -14310,6 +14423,15 @@ "remove-trailing-separator": "^1.0.1" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -15116,25 +15238,22 @@ "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==" }, "tempy": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.5.0.tgz", - "integrity": "sha512-VEY96x7gbIRfsxqsafy2l5yVxxp3PhwAGoWMyC2D2Zt5DmEv+2tGiPOrquNRpf21hhGnKLVEsuqleqiZmKG/qw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, "requires": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", - "type-fest": "^0.12.0", + "type-fest": "^0.16.0", "unique-string": "^2.0.0" }, "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, "type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true } } }, @@ -15667,6 +15786,43 @@ "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==", "requires": { "execa": "^1.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + } } }, "word-wrap": { diff --git a/package.json b/package.json index 3451a3f0..e7db2c72 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,14 @@ "commitizen": "^4.1.2", "cspell": "^4.0.63", "eslint": "^7.6.0", + "execa": "^4.0.3", + "file-url": "^3.0.0", "husky": "^4.2.5", "jest": "^25.5.4", "lint-staged": "^10.2.11", "npm-run-all": "^4.1.5", "prettier": "^2.0.5", + "tempy": "^0.6.0", "ts-jest": "^25.5.1", "typescript": "^3.9.7" }, diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 00000000..3c2b94c4 --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,278 @@ +import * as actionsCore from '@actions/core'; +import * as actionsExec from '@actions/exec'; + +import { release } from '.'; +import { gitCommits, gitPush, gitRepo } from './utilities/git'; +import { InputParameters } from './utilities/inputProcessors'; + +const execSpy = jest.spyOn(actionsExec, 'exec'); +const getInputSpy = jest.spyOn(actionsCore, 'getInput'); + +const optionsOverride = { + addChannel: jest.fn().mockResolvedValue(true), + prepare: jest.fn().mockResolvedValue(true), + publish: jest.fn().mockResolvedValue(true), + success: jest.fn().mockResolvedValue(true), + verifyConditions: jest.fn().mockResolvedValue(true), + verifyRelease: jest.fn().mockResolvedValue(true), +}; + +describe('release', (): void => { + beforeEach((): void => { + execSpy.mockImplementation(); + getInputSpy.mockImplementation((name: string): string => { + if (name === InputParameters.CommitAssets) { + return './src'; + } + + if (name === InputParameters.DryRun) { + return 'true'; + } + + if (name === InputParameters.NodeModule) { + return 'true'; + } + + if (name === InputParameters.ReleaseAssets) { + return './src'; + } + + if (name === InputParameters.ReleaseBranches) { + return JSON.stringify([ + '+([0-9])?(.{+([0-9]),x}).x', + 'master', + 'next', + 'next-major', + { name: 'beta', prerelease: 'beta' }, + { name: 'alpha', prerelease: 'alpha' }, + ]); + } + + if (name === InputParameters.ReleaseRules) { + return ''; + } + + return ''; + }); + }); + + it('"feat" makes a minor release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['feat: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('minor'); + expect(result.nextRelease.version).toStrictEqual('1.1.0'); + } + }); + + it('"feat" with a BREAKING CHANGE makes a major release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits( + ['feat: add a major change\n\nBREAKING CHANGE: break something'], + { cwd }, + ); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('major'); + expect(result.nextRelease.version).toStrictEqual('2.0.0'); + } + }); + + it('"fix" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['fix: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"perf" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['perf: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"build" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['build: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"ci" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['ci: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"docs" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['docs: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"improvement" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['improvement: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('"refactor" makes a patch release using the default release rules', async (): Promise< + void + > => { + expect.assertions(2); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['refactor: add a minor change'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + if (result !== false) { + expect(result.nextRelease.type).toStrictEqual('patch'); + expect(result.nextRelease.version).toStrictEqual('1.0.1'); + } + }); + + it('makes no release when subject includes skip release', async (): Promise< + void + > => { + expect.assertions(1); + + // Generate git + const { cwd } = await gitRepo(); + await gitCommits(['feat: [skip release]'], { cwd }); + await gitPush('origin', 'master', { cwd }); + + const configurationOverride = { + cwd, + }; + + const result = await release(optionsOverride, configurationOverride); + + expect(result).toStrictEqual(false); + }); +}); diff --git a/src/index.ts b/src/index.ts index 12c26c4b..acdc1a79 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,10 @@ const writerOptions = { transform, }; -export const release = async (): Promise => { +export const release = async ( + overrideOptions?: Options, + overrideConfig?: Config, +): Promise => { await installDependencies(); const semanticRelease = ((await import( @@ -39,25 +42,32 @@ export const release = async (): Promise => { const branches = processInputReleaseBranches(); const releaseRules = processInputReleaseRules(); - const result: Result = await semanticRelease({ - /* eslint-disable unicorn/prevent-abbreviations */ - ...(branches === undefined ? {} : { branches }), - dryRun: processInputDryRun(), - parserOpts: parseOptions, - plugins: generatePlugins({ - commitAssets: processInputCommitAssets(), - isNodeModule: processInputNodeModule(), - releaseAssets: processInputReleaseAssets(), - }), - preset: 'angular', - releaseRules, - writerOpts: writerOptions, - /* eslint-enable unicorn/prevent-abbreviations */ - }); + /* istanbul ignore next */ + const result: Result = await semanticRelease( + { + /* eslint-disable unicorn/prevent-abbreviations */ + ...(branches === undefined ? {} : { branches }), + dryRun: processInputDryRun(), + parserOpts: parseOptions, + plugins: generatePlugins({ + commitAssets: processInputCommitAssets(), + isNodeModule: processInputNodeModule(), + releaseAssets: processInputReleaseAssets(), + }), + preset: 'angular', + releaseRules, + writerOpts: writerOptions, + ...(overrideOptions === undefined ? {} : overrideOptions), + /* eslint-enable unicorn/prevent-abbreviations */ + }, + { ...(overrideConfig === undefined ? {} : overrideConfig) }, + ); - reportResults(result); + return result; }; -release().catch((error: Error): void => { - setFailed(JSON.stringify(error)); -}); +release() + .then(reportResults) + .catch((error: Error): void => { + setFailed(JSON.stringify(error)); + }); diff --git a/src/utilities/git.spec.ts b/src/utilities/git.spec.ts new file mode 100644 index 00000000..9fec0556 --- /dev/null +++ b/src/utilities/git.spec.ts @@ -0,0 +1,201 @@ +import * as execa from 'execa'; + +import { + gitCommits, + gitPush, + gitRepo, + gitShallowClone, + gitTagVersion, + initGitRemote, +} from './git'; + +describe('git utility', (): void => { + describe('initGitRemote', (): void => { + it('initializes local `remote` git repository in a temporary directory', async (): Promise< + void + > => { + expect.assertions(1); + + const { cwd } = await initGitRemote(); + + const isBareRepository = ( + await execa('git', ['rev-parse', '--is-bare-repository'], { + cwd, + }) + ).stdout; + + expect(isBareRepository).toStrictEqual('true'); + }); + }); + + describe('gitShallowClone', (): void => { + it('creates a shallow clone of a `remote` git repository', async (): Promise< + void + > => { + expect.assertions(1); + + const { remoteRepositoryUrl } = await initGitRemote(); + const cloneWorkingDirectory = await gitShallowClone(remoteRepositoryUrl); + + const isGitRepository = ( + await execa('git', ['rev-parse', '--git-dir'], { + cwd: cloneWorkingDirectory, + }) + ).stdout; + + expect(isGitRepository).toStrictEqual('.git'); + }); + }); + + describe('gitCommits', (): void => { + it('creates commits on the git repository present in the `cwd` option', async (): Promise< + void + > => { + expect.assertions(1); + + const { cwd } = await gitRepo(); + + await gitCommits(['feat: initial commit'], { + cwd, + }); + + const commitMessage = ( + await execa('git', ['log', '-1', '--pretty=%s'], { + cwd, + }) + ).stdout; + + expect(commitMessage).toStrictEqual('feat: initial commit'); + }); + + it('creates commits in order on the git repository present in the `cwd` option', async (): Promise< + void + > => { + expect.assertions(1); + + const { cwd } = await gitRepo(); + + await gitCommits( + ['feat: initial commit', 'feat: second commit', 'feat: third commit'], + { cwd }, + ); + + const commitMessages = ( + await execa('git', ['log', '-3', '--pretty=%s'], { + cwd, + }) + ).stdout; + + expect(commitMessages).toMatchInlineSnapshot(` + "feat: third commit + feat: second commit + feat: initial commit" + `); + }); + }); + + describe('gitTagVersion', (): void => { + it('creates a tag on the head commit of the repository present in the `cwd` option', async (): Promise< + void + > => { + expect.assertions(1); + + const { cwd } = await gitRepo(); + + await gitCommits(['feat: second commit'], { + cwd, + }); + await gitTagVersion('v1.1.0', { cwd }); + + const tagName = ( + await execa('git', ['describe', '--tags'], { + cwd, + }) + ).stdout; + + expect(tagName).toContain('v1.1.0'); + }); + }); + + describe('gitRepo', (): void => { + it('creates a temporary git remote repository and a git clone repository', async (): Promise< + void + > => { + expect.assertions(5); + + const { cwd } = await gitRepo(); + + const configurationUserName = ( + await execa('git', ['config', '--get', 'user.name'], { + cwd, + }) + ).stdout; + + const configurationUserEmail = ( + await execa('git', ['config', '--get', 'user.email'], { + cwd, + }) + ).stdout; + + const configurationGpgSign = ( + await execa('git', ['config', '--get', 'commit.gpgsign'], { + cwd, + }) + ).stdout; + + const commitMessage = ( + await execa('git', ['log', '-1', '--pretty=%s'], { + cwd, + }) + ).stdout; + + const tagName = ( + await execa('git', ['describe', '--tags'], { + cwd, + }) + ).stdout; + + expect(commitMessage).toStrictEqual('feat: initial commit'); + expect(configurationUserName).toStrictEqual('test@ridedott.com'); + expect(configurationUserEmail).toStrictEqual('test@ridedott.com'); + expect(configurationGpgSign).toStrictEqual('false'); + expect(tagName).toContain('v1.0.0'); + }); + }); + + describe('gitPush', (): void => { + it('pushes to the remote repository from the git repository present in the `cwd` option', async (): Promise< + void + > => { + expect.assertions(1); + + const { + cwd: remoteWorkingDirectory, + remoteRepositoryUrl, + } = await initGitRemote(); + const cloneWorkingDirectory = await gitShallowClone(remoteRepositoryUrl); + + await execa('git', ['config', 'user.email', 'test@ridedott.com'], { + cwd: cloneWorkingDirectory, + }); + await execa('git', ['config', 'user.name', 'test@ridedott.com'], { + cwd: cloneWorkingDirectory, + }); + await execa('git', ['config', 'commit.gpgsign', 'false'], { + cwd: cloneWorkingDirectory, + }); + await gitCommits(['feat: initial commit'], { + cwd: cloneWorkingDirectory, + }); + await gitPush('origin', 'master', { cwd: cloneWorkingDirectory }); + + const commitMessage = ( + await execa('git', ['log', '-1', '--pretty=%s'], { + cwd: remoteWorkingDirectory, + }) + ).stdout; + + expect(commitMessage).toStrictEqual('feat: initial commit'); + }); + }); +}); diff --git a/src/utilities/git.ts b/src/utilities/git.ts new file mode 100644 index 00000000..2543ae7a --- /dev/null +++ b/src/utilities/git.ts @@ -0,0 +1,121 @@ +/* eslint-disable no-await-in-loop */ +import * as execa from 'execa'; +import * as fileUrl from 'file-url'; +import { directory } from 'tempy'; + +/** + * Initialize local `remote` git repository in a temporary directory. + */ +export const initGitRemote = async (): Promise<{ + cwd: string; + remoteRepositoryUrl: string; +}> => { + const cwd = directory(); + + await execa('git', ['init', '--bare'], { cwd }); + + return { cwd, remoteRepositoryUrl: fileUrl(cwd) }; +}; + +/** + * Create a shallow clone of a git repository. The shallow will contain a + * limited number of commit and no tags. + */ +export const gitShallowClone = async ( + repositoryUrl: string, + depth: number = 1, +): Promise => { + const cloneWorkingDirectory = directory(); + + await execa( + 'git', + [ + 'clone', + '--no-hardlinks', + '--no-tags', + '--depth', + depth.toString(), + repositoryUrl, + cloneWorkingDirectory, + ], + { + cwd: cloneWorkingDirectory, + }, + ); + + return cloneWorkingDirectory; +}; + +/** + * Create commits on the git repository present in the `cwd` option. + */ +export const gitCommits = async ( + messages: string[], + execaOptions: execa.Options, +): Promise => { + for (const message of messages) { + await execa( + 'git', + ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], + execaOptions, + ); + } +}; + +/** + * Create a tag on the head commit on the git repository present in the `cwd` + * option. + */ +export const gitTagVersion = async ( + tagName: string, + execaOptions: execa.Options, +): Promise => { + await execa('git', ['tag', tagName], execaOptions); +}; + +/** + * Creates a temporary git remote repository and a git clone repository. + */ +export const gitRepo = async (): Promise<{ + cwd: string; + repositoryUrl: string; +}> => { + const { remoteRepositoryUrl } = await initGitRemote(); + + const cloneWorkingDirectory = await gitShallowClone(remoteRepositoryUrl); + + await execa('git', ['config', 'user.email', 'test@ridedott.com'], { + cwd: cloneWorkingDirectory, + }); + await execa('git', ['config', 'user.name', 'test@ridedott.com'], { + cwd: cloneWorkingDirectory, + }); + await execa('git', ['config', 'commit.gpgsign', 'false'], { + cwd: cloneWorkingDirectory, + }); + await execa('npm', ['init', '-y'], { cwd: cloneWorkingDirectory }); + await execa('git', ['add', '--all'], { cwd: cloneWorkingDirectory }); + await gitCommits(['feat: initial commit'], { cwd: cloneWorkingDirectory }); + await gitTagVersion('v1.0.0', { cwd: cloneWorkingDirectory }); + await execa('git', ['push', remoteRepositoryUrl], { + cwd: cloneWorkingDirectory, + }); + + return { cwd: cloneWorkingDirectory, repositoryUrl: remoteRepositoryUrl }; +}; + +/** + * Push to the remote repository from the git repository present in the `cwd` + * option. + */ +export const gitPush = async ( + repositoryUrl: string, + branch: string, + execaOptions: execa.Options, +): Promise => { + await execa( + 'git', + ['push', '--tags', repositoryUrl, `HEAD:${branch}`], + execaOptions, + ); +}; diff --git a/src/utilities/inputProcessors.ts b/src/utilities/inputProcessors.ts index 9ea8773d..b67eaf65 100644 --- a/src/utilities/inputProcessors.ts +++ b/src/utilities/inputProcessors.ts @@ -2,7 +2,7 @@ import { getInput } from '@actions/core'; import * as joi from '@hapi/joi'; import { BranchSpec } from 'semantic-release'; -enum InputParameters { +export enum InputParameters { CommitAssets = 'commit-assets', DryRun = 'dry-run', NodeModule = 'node-module',