Compare commits

...

99 Commits
v0.7 ... v0.15

Author SHA1 Message Date
Webber
44bde7feb9 Base number of commits off of the branch on origin 2020-05-02 16:37:24 +02:00
Webber
5328bda08e Base number of commits off of the branch 2020-05-02 16:37:24 +02:00
Webber
34e4b86924 Fix case where no tags does not trigger false 2020-05-01 20:32:41 +02:00
Webber
e9514b4e37 Stop hammering patches 🔨 2020-05-01 17:55:27 +02:00
Webber
d4d91e75c3 Configure codecov threshold 2020-05-01 17:55:27 +02:00
Webber
2085eff264 Add missing test for ref getters 2020-05-01 17:55:27 +02:00
Webber
98a1b078fc Update styles to latest unicorn 🦄 and prettier 🦋 2020-05-01 17:55:27 +02:00
Webber
afef854ea0 update packages to latest 2020-05-01 17:55:27 +02:00
Webber
a0a5de2a83 Cover all versioning paths 2020-05-01 16:15:56 +02:00
Webber
c146049b33 Mock all output methods from core 2020-05-01 16:15:56 +02:00
Webber
e46399169f Add tests for System model 2020-05-01 16:15:56 +02:00
Webber
b41026b36e Introduce smart fetching, based on type of local repo. 2020-05-01 16:15:56 +02:00
Webber
cd1d215dfa Remove unshallow fetch 2020-05-01 16:15:56 +02:00
Webber
d01e844eea Fix versioning for push event. 2020-05-01 16:15:56 +02:00
Webber
40564afbaf Fix failed test 2020-04-28 02:02:34 +02:00
Webber
b6f8040f4a Add tests for the versioning model 2020-04-28 02:02:34 +02:00
Webber
22c7d0e516 Improve versioning readme 📜 2020-04-26 21:45:10 +02:00
Webber
f85e50e499 Fix bug in hasAnyVersionTags 🐜 2020-04-26 21:45:10 +02:00
Webber
d75d7890d0 Implement versioning strategies in js 🧉 2020-04-26 21:45:10 +02:00
Webber
2e81e61af3 Add additional tests 🧪 2020-04-26 21:45:10 +02:00
Webber
ac76e9d562 Update readme with versioning examples 🧉 2020-04-26 21:45:10 +02:00
Webber
39a160b789 Allow versioning and version parameters. 2020-04-26 21:45:10 +02:00
Webber
e8a2eaad72 Cleanup default-build-script 2020-04-26 21:45:10 +02:00
Webber
6f1d03d8a8 Refactor Builder 2020-04-26 21:45:10 +02:00
Webber
5ee9c59113 Create VersionApplicator 2020-04-26 21:45:10 +02:00
Webber
03510d4a55 Abstract out the stdout reporter 2020-04-26 21:45:10 +02:00
Webber
ad65b0ece4 Abstract out the argument parser 2020-04-26 21:45:10 +02:00
Webber
a513e5b640 Add semantic versioning logic 2020-04-26 21:45:10 +02:00
Webber
328b0d8ac0 Add system extension for Process 2020-04-26 21:45:10 +02:00
Webber
e53dcf13e3 Add .editorconfig for the default-build-script project 2020-04-26 21:45:10 +02:00
dependabot-preview[bot]
3338b392a0 [Security] Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-13 22:32:17 +00:00
dependabot-preview[bot]
7e115b5cc3 Bump eslint-plugin-unicorn from 17.0.1 to 17.1.0
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 17.0.1 to 17.1.0.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v17.0.1...v17.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-10 18:22:23 +00:00
dependabot-preview[bot]
758108295c Bump eslint-plugin-react from 7.18.3 to 7.19.0
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.18.3 to 7.19.0.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.18.3...v7.19.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-10 09:00:11 +00:00
Webber
8684472273 install deps during build phase 2020-03-09 23:47:22 +01:00
Webber
38b6a42f5e fix dist file 2020-03-09 23:47:22 +01:00
litefeel
fbeaf77867 Apply merge request suggestions 2020-03-09 23:23:53 +01:00
litefeel
6f3a2bd992 Fixed compatibility 2020-03-09 23:23:53 +01:00
litefeel
e1eda1e876 Change UNITY_LICENSE_PATH to UNITY_LICENSE_FILE 2020-03-09 23:23:53 +01:00
litefeel
0088ca3094 Add UNITY_LICENSE_PATH 2020-03-09 23:23:53 +01:00
dependabot-preview[bot]
465f15a945 Bump eslint-plugin-unicorn from 16.1.1 to 17.0.1
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 16.1.1 to 17.0.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v16.1.1...v17.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 21:53:00 +00:00
dependabot-preview[bot]
ffe381e28a Bump @babel/core from 7.8.6 to 7.8.7
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.6 to 7.8.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.6...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 15:36:08 +00:00
dependabot-preview[bot]
085359d24f Bump @babel/preset-env from 7.8.6 to 7.8.7
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.8.6 to 7.8.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.6...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 15:26:03 +00:00
dependabot-preview[bot]
b47dcdab84 Bump @babel/core from 7.8.4 to 7.8.6
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.4 to 7.8.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.4...v7.8.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:18:56 +00:00
dependabot-preview[bot]
0c23c7d0a3 Bump @babel/preset-env from 7.8.4 to 7.8.6
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.8.4 to 7.8.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.4...v7.8.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:09:38 +00:00
dependabot-preview[bot]
f3099b6226 Bump lint-staged from 10.0.7 to 10.0.8
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.0.7 to 10.0.8.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v10.0.7...v10.0.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:00:06 +00:00
dependabot-preview[bot]
a969d3322f Bump babel-eslint from 10.0.3 to 10.1.0
Bumps [babel-eslint](https://github.com/babel/babel-eslint) from 10.0.3 to 10.1.0.
- [Release notes](https://github.com/babel/babel-eslint/releases)
- [Commits](https://github.com/babel/babel-eslint/compare/v10.0.3...v10.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-27 23:02:57 +00:00
dependabot-preview[bot]
8f63acf12b Bump @actions/github from 2.1.0 to 2.1.1
Bumps [@actions/github](https://github.com/actions/toolkit/tree/HEAD/packages/github) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/master/packages/github/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/github)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-21 08:40:38 +00:00
Webber
1813ee2895 add dist build 2020-02-15 18:02:01 +01:00
dependabot-preview[bot]
6e377601bf Bump @zeit/ncc from 0.21.0 to 0.21.1
Bumps [@zeit/ncc](https://github.com/zeit/ncc) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/zeit/ncc/releases)
- [Commits](https://github.com/zeit/ncc/compare/0.21.0...0.21.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-15 18:02:01 +01:00
dependabot-preview[bot]
f7321735d5 Bump husky from 4.2.2 to 4.2.3
Bumps [husky](https://github.com/typicode/husky) from 4.2.2 to 4.2.3.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v4.2.2...v4.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-13 08:13:42 +00:00
dependabot-preview[bot]
19429bf324 Bump eslint-plugin-unicorn from 16.0.0 to 16.1.1
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 16.0.0 to 16.1.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v16.0.0...v16.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 11:37:51 +01:00
dependabot-preview[bot]
04eaffcd1f Bump husky from 4.2.1 to 4.2.2
Bumps [husky](https://github.com/typicode/husky) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v4.2.1...v4.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 09:52:47 +01:00
Webber
2c1ef01beb Update readme to reflect v0.11 2020-02-11 20:59:27 +01:00
Webber
dfccf539d1 Add custom parameters to test workflow 2020-02-11 20:59:27 +01:00
Webber
c1a7c5f70b Separate custom parameters 2020-02-11 20:59:27 +01:00
dependabot-preview[bot]
7e4851cc3a Bump @zeit/ncc from 0.20.5 to 0.21.0
Bumps [@zeit/ncc](https://github.com/zeit/ncc) from 0.20.5 to 0.21.0.
- [Release notes](https://github.com/zeit/ncc/releases)
- [Commits](https://github.com/zeit/ncc/compare/0.20.5...0.21.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 13:55:58 +01:00
dependabot-preview[bot]
bbb5661b43 Bump husky from 4.0.0-beta.5 to 4.2.1
Bumps [husky](https://github.com/typicode/husky) from 4.0.0-beta.5 to 4.2.1.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v4.0.0-beta.5...v4.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 13:04:47 +01:00
dependabot-preview[bot]
678fb210be Bump eslint-plugin-flowtype from 4.5.2 to 4.6.0
Bumps [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype) from 4.5.2 to 4.6.0.
- [Release notes](https://github.com/gajus/eslint-plugin-flowtype/releases)
- [Commits](https://github.com/gajus/eslint-plugin-flowtype/compare/v4.5.2...v4.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 11:34:58 +01:00
dependabot-preview[bot]
90af9768a7 Bump eslint from 6.7.2 to 6.8.0
Bumps [eslint](https://github.com/eslint/eslint) from 6.7.2 to 6.8.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v6.7.2...v6.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 10:23:53 +01:00
dependabot-preview[bot]
6ab433b631 Bump @babel/core from 7.7.5 to 7.8.4
Bumps [@babel/core](https://github.com/babel/babel) from 7.7.5 to 7.8.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.5...v7.8.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 17:55:59 +01:00
dependabot-preview[bot]
d5c4c09fc4 Bump lint-staged from 9.5.0 to 10.0.7
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.5.0 to 10.0.7.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.5.0...v10.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 17:38:42 +01:00
dependabot-preview[bot]
3e69d63ec4 Bump eslint-config-prettier from 6.7.0 to 6.10.0
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.7.0 to 6.10.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/commits/v6.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 17:13:29 +01:00
dependabot-preview[bot]
c47d5a4d2e Bump eslint-plugin-react from 7.17.0 to 7.18.3
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.17.0 to 7.18.3.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.17.0...v7.18.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 16:49:56 +01:00
dependabot-preview[bot]
9dd75201b0 Bump eslint-plugin-import from 2.19.1 to 2.20.1
Bumps [eslint-plugin-import](https://github.com/benmosher/eslint-plugin-import) from 2.19.1 to 2.20.1.
- [Release notes](https://github.com/benmosher/eslint-plugin-import/releases)
- [Changelog](https://github.com/benmosher/eslint-plugin-import/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benmosher/eslint-plugin-import/compare/v2.19.1...v2.20.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 14:16:53 +01:00
Webber
308a9e173d fix: upgrade @actions/github from 2.0.0 to 2.0.1 2020-02-09 16:04:51 +01:00
snyk-bot
f5a61eed4a fix: upgrade @actions/exec from 1.0.2 to 1.0.3
Snyk has created this PR to upgrade @actions/exec from 1.0.2 to 1.0.3.

See this package in NPM:
https://www.npmjs.com/package/@actions/exec

See this project in Snyk:
https://app.snyk.io/org/webbertakken/project/e69ca31d-f4f2-4223-ae93-0245ffac3626?utm_source=github&utm_medium=upgrade-pr
2020-02-09 10:28:35 +01:00
Webber
051513bf4a Sort badges by color ugliness 🦄 2020-02-09 01:53:32 +01:00
Webber
d9382df690 put badges on 1 line 2020-02-09 01:53:32 +01:00
Webber
0df7fa7855 Add badges for lgtm, codecov, snyk 2020-02-09 01:24:03 +01:00
Webber
d1c1d276e0 Add token as env, so PRs will trigger codecov 2020-02-09 01:24:03 +01:00
Webber
ad1f47dd3a Set token in runner env 2020-02-09 01:24:03 +01:00
Webber
a0d197e1fa Integrate codecov 2020-02-09 01:24:03 +01:00
Webber
cc244fa58b Add updated index.js 2020-02-08 19:25:58 +01:00
dependabot-preview[bot]
c70fdac383 Bump @actions/core from 1.2.0 to 1.2.2
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.2.0 to 1.2.2.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/master/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-08 19:25:58 +01:00
dependabot-preview[bot]
81487e2140 Bump @babel/preset-env from 7.7.7 to 7.8.4
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.7.7 to 7.8.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.7...v7.8.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-08 19:09:38 +01:00
dependabot-preview[bot]
ef06de9255 Bump jest from 24.9.0 to 25.1.0
Bumps [jest](https://github.com/facebook/jest) from 24.9.0 to 25.1.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v24.9.0...v25.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-08 18:50:59 +01:00
dependabot-preview[bot]
64499d66cc Bump eslint-plugin-unicorn from 14.0.1 to 16.0.0
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 14.0.1 to 16.0.0.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v14.0.1...v16.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-08 17:34:17 +01:00
dependabot-preview[bot]
59ab903a78 Bump @babel/cli from 7.7.5 to 7.8.4
Bumps [@babel/cli](https://github.com/babel/babel) from 7.7.5 to 7.8.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.5...v7.8.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-08 15:12:50 +01:00
Webber Takken
2fa5b33111 Update README.md 2020-02-01 21:24:33 +01:00
Webber
0399609b07 Give more info about what is different 2020-02-01 21:02:56 +01:00
Webber
1c91a3bf31 Fix custom parameters 2020-02-01 21:02:56 +01:00
Webber
ae7f659e9f Remove unneeded trailing slash 2020-02-01 20:43:49 +01:00
Webber
b781b891ec Align diff test with rename 2020-02-01 20:43:49 +01:00
Webber
f3a984165e Rename builder folder to action folder (unity actions convention) 2020-02-01 20:43:49 +01:00
Webber
d8896dc4f5 Update references 2020-01-27 23:15:26 +01:00
Webber
4051832dc0 Add some more basic tests 🤷‍♂️ 2020-01-27 23:15:26 +01:00
Webber
fe2311ef4b Hint enabling cache if not already enabled. 2020-01-27 23:15:26 +01:00
Webber
37d5ce498f Add more complete steps to create workflow 2020-01-27 23:15:26 +01:00
Webber
6bff9d7c68 Update readme for 0.9 (custom parameters) 2020-01-27 20:42:52 +01:00
Webber
7d51d12262 Allow custom parameters 2020-01-27 20:42:52 +01:00
Webber
b382ae9023 Remove duplicate restore key 2020-01-26 01:34:34 +01:00
Webber
4c8f96d75c Add caching step in workflow 2020-01-26 01:34:34 +01:00
Webber
afddcfa5fd Move static tests to main workflow 2020-01-26 01:34:34 +01:00
Webber
ca2bcea3ad Minor cleanup 2020-01-26 01:34:34 +01:00
Webber
d5552eaa01 Further simplify activation and complete feedback to user 2020-01-21 21:59:49 +01:00
Webber
32081adc59 Remove debugging, switch -ne to -eq 2020-01-21 21:59:49 +01:00
Webber
ad034dd2a5 Simplify and fail faster for activation process 2020-01-21 21:59:49 +01:00
Webber
cad4a8a0e5 Simplify build-method implementation 2020-01-21 21:59:49 +01:00
Webber
1d1f81c0bb Refactor models to allow for build parameters...
Build parameters have to be parsed because they can no longer be implicitly passed, as they need to be interpreted for detecting extensions.
2020-01-21 00:28:05 +01:00
100 changed files with 4455 additions and 2753 deletions

View File

@@ -2,4 +2,4 @@
*
# Files required for the action
!builder/
!action/

View File

@@ -14,5 +14,8 @@ trim_trailing_whitespace = true
max_line_length = off
trim_trailing_whitespace = false
[*.{yml,yaml}]
max_line_length = off
[COMMIT_EDITMSG]
max_line_length = 0
max_line_length = off

View File

@@ -1,2 +1,2 @@
**/node_modules/**
**/builder/**
**/action/**

View File

@@ -6,8 +6,24 @@ on:
env:
UNITY_LICENSE: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n <Binding Key=\"2\" Value=\"d39b8e2f4d364b2e98b06afa0c6e08c5\"/>\n </MachineBindings>\n <MachineID Value=\"Xxo1ZKbdPu/IATrc0mPBYANJFF0=\"/>\n <SerialHash Value=\"1efd68fa935192b6090ac03c77d289a9f588c55a\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUg2WFMtUE00NS1SM0M4LUUyWlotWkdWOA==\"/>\n <SerialMasked Value=\"F4-H6XS-PM45-R3C8-E2ZZ-XXXX\"/>\n <StartDate Value=\"2018-05-02T00:00:00\"/>\n <UpdateDate Value=\"2019-11-25T18:23:38\"/>\n <InitialActivationDate Value=\"2018-05-02T14:21:28\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2019.2.11f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>JHdOBFmBNq2H8BrGFzir/StLoYo=</DigestValue></Reference></SignedInfo><SignatureValue>aENLHd37a51RtP2/g7YU0Pexf5mx0/ENXYGtrPzqwZ8NQt2AsSdxGnl0CUB45/GuNXfJVDt2HWot\ncNYZB2OylVBn1WHQbKZlPmm8gEAMz0MYbr4Isb5i5buryBrZlmbEOjnRI+pEg1CBwlgMo6xdtjjE\n/d7cC293QIUO91kdzRXftYou1dNaUyuPL9ZH65vdB2pDXGRNxgUVD+GnnqZA7b5L2HXqNQclcWAK\n5Yd1BeF3VzR1iLw9G/SmH5oOhnpXSmqbL4qk7LVP2/mgXpFk5kP4X8VC3z47obNhBIGq40dwWyEe\nUYk5/nRAOkZawDT+tcu96e06gPC9Cxk5PdbRbA==</SignatureValue></Signature></root>"
CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de'
jobs:
tests:
name: Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: yarn
- run: yarn lint
- run: yarn test --coverage
- run: bash <(curl -s https://codecov.io/bash)
- run: yarn build || { echo "build command should always succeed" ; exit 61; }
- run: yarn build --quiet && git diff --quiet action || { echo "action should be auto generated" ; git diff action ; exit 62; }
buildForAllPlatforms:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
runs-on: ubuntu-latest
@@ -34,12 +50,22 @@ jobs:
# - Switch # Build a Nintendo Switch player.
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-
Library-
- uses: ./
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
- uses: actions/upload-artifact@v1
with:
name: Build

View File

@@ -1,19 +0,0 @@
name: Test Action
on:
pull_request: {}
push: { branches: [master] }
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: yarn
- run: yarn lint
- run: yarn test
- run: yarn build || { echo "build command should always succeed" ; exit 61; }
- run: yarn build --quiet && git diff --quiet builder || { echo "builder should be auto generated" ; exit 62; }

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.idea
node_modules
coverage/

View File

@@ -1,2 +1,2 @@
**/node_modules/**
**/dist/**
**/action/**

202
README.md
View File

@@ -1,6 +1,9 @@
# Unity - Builder
[![Actions status](https://github.com/webbertakken/unity-builder/workflows/Actions%20%F0%9F%98%8E/badge.svg?event=push&branch=master)](https://github.com/webbertakken/unity-builder/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22Actions+%F0%9F%98%8E%22)
[![snyk - known vulnerabilities](https://snyk.io/test/github/webbertakken/unity-builder/badge.svg)](https://snyk.io/test/github/webbertakken/unity-builder)
[![lgtm - code quality](https://img.shields.io/lgtm/grade/javascript/g/webbertakken/unity-builder.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/webbertakken/unity-builder/context:javascript)
[![codecov - test coverage](https://codecov.io/gh/webbertakken/unity-builder/branch/master/graph/badge.svg)](https://codecov.io/gh/webbertakken/unity-builder)
---
@@ -31,10 +34,24 @@ collection repository for workflow documentation and reference implementation.
## Usage
#### Setup builder
By default the enabled scenes from the project's settings will be built.
Create or edit the file called `.github/workflows/main.yml` and add a job to it.
##### Personal License
Personal licenses require a one-time manual activation step (per unity version).
Make sure you
[acquire and activate](https://github.com/marketplace/actions/unity-request-activation-file)
your license file and add it as a secret.
Then, define the build step as follows:
```yaml
- uses: webbertakken/unity-builder@v0.5
- uses: webbertakken/unity-builder@v0.11
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with:
@@ -43,6 +60,75 @@ Create or edit the file called `.github/workflows/main.yml` and add a job to it.
targetPlatform: WebGL
```
##### Professional license
Professional licenses do not need any manual steps.
Instead, three variables will need to be set.
- `UNITY_EMAIL` (should contain the email address for your Unity account)
- `UNITY_PASSWORD` (the password that you use to login to Unity)
- `UNITY_SERIAL` (the serial provided by Unity)
Define the build step as follows:
```yaml
- uses: webbertakken/unity-builder@v0.11
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: path/to/your/project
unityVersion: 2020.X.XXXX
targetPlatform: WebGL
```
That is all you need to build your project.
#### Storing the build
To be able to access your built files,
they need to be uploaded as artifacts.
To do this it is recommended to use Github Actions official
[upload artifact action](https://github.com/marketplace/actions/upload-artifact)
after any build action.
By default, Builder outputs it's builds to a folder named `build`.
Example:
```yaml
- uses: actions/upload-artifact@v1
with:
name: Build
path: build
```
Builds can now be downloaded as Artifacts in the Actions tab.
#### Caching
In order to make builds run faster, you can cache Library files from previous
builds. To do so simply add Github Actions official
[cache action](https://github.com/marketplace/actions/cache) before any unity steps.
Example:
```yaml
- uses: actions/cache@v1.1.0
with:
path: path/to/your/project/Library
key: Library-MyProjectName-TargetPlatform
restore-keys: |
Library-MyProjectName-
Library-
```
This simple addition could speed up your build by more than 50%.
## Complete example
A complete workflow that builds every available platform could look like this:
```yaml
@@ -81,8 +167,17 @@ jobs:
- tvOS # Build to Apple's tvOS platform.
- Switch # Build a Nintendo Switch player.
steps:
- uses: actions/checkout@v1
- uses: webbertakken/unity-builder@v0.5
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v1.1.0
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-
Library-
- uses: webbertakken/unity-builder@v0.11
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
@@ -93,10 +188,7 @@ jobs:
path: build
```
> **Notes:**
>
> - Don't forget to replace _&lt;test-project&gt;_ with your project name.
> - By default the enabled scenes from the project's settings will be built.
> **Note:** _Environment variables are set for all jobs in the workflow like this._
## Configuration options
@@ -127,10 +219,10 @@ _**required:** `true`_
#### buildName
Name of the build.
Name of the build. Also the folder in which the build will be stored within `buildsPath`.
_**required:** `false`_
_**default:** `testBuild`_
_**default:** `<build_target>`_
#### buildsPath
@@ -153,7 +245,7 @@ There are two conditions for a custom buildCommand:
_**example:**_
```yaml
- uses: webbertakken/unity-builder@master
- uses: webbertakken/unity-builder@<version>
with:
buildMethod: EditorNamespace.BuilderClassName.StaticBulidMethod
```
@@ -161,6 +253,96 @@ _**example:**_
_**required:** `false`_
_**default:** Built-in script that will run a build out of the box._
#### versioning
The versioning strategy to use.
Strategies only work when no custom buildMethod is specified.
_**required:** `false`_
_**default:** `Auto`_
#### _These are the available strategies:_
##### None
No version will be set by Builder.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: None
```
Note that the version set in the project will be used instead.
##### Semantic (default)
Builder automatically generates a version based on [semantic versioning](https://semver.org/) out of the box.
The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
The latest tag dictates `<major>.<minor>` and the number of commits since that tag is used in `<patch>`.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: Semantic
```
This strategy works well for the following reasons:
- All builds have their unique version
- No version related commits are created
- No knowledge of git or versioning is required
- Developer keeps control over `major` and `minor` versions using tags.
- Zero configuration; It works out of the box
##### Tag
Uses the tag that points at `HEAD` as the version.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: Tag
```
This strategy works well when using a pipeline that specifically runs for tags.
The tag must be a version tag.
##### Custom
Allows specifying a custom version in the `version` field.
```yaml
- uses: webbertakken/unity-builder@<version>
with:
versioning: Custom
version: <some_version>
```
If there is a use case missing from Builder, feel free to create a feature request.
#### customParameters
Custom parameters to configure the build.
Parameters must start with a hyphen (`-`) and may be followed by a value (without hyphen).
Parameters without a value will be considered booleans (with a value of true).
_**example:**_
```yaml
- uses: webbertakken/unity-builder@<version>
with:
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
```
_**required:** `false`_
_**default:** ""_
## More actions
Visit

View File

@@ -32,4 +32,4 @@ branding:
color: 'gray-dark'
runs:
using: 'node12'
main: 'builder/index.js'
main: 'action/index.js'

View File

@@ -0,0 +1,4 @@
root = true
[*.cs]
resharper_check_namespace_highlighting = do_not_show

View File

@@ -57,6 +57,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Assets\Editor\Builder.cs" />
<Compile Include="Assets\Editor\Input\ArgumentsParser.cs" />
<Compile Include="Assets\Editor\Reporting\StdOutReporter.cs" />
<Compile Include="Assets\Editor\System\ProcessExtensions.cs" />
<Compile Include="Assets\Editor\Versioning\VersionApplicator.cs" />
<Compile Include="Assets\Editor\Versioning\Git.cs" />
<Compile Include="Assets\Editor\Versioning\VersionGenerator.cs" />
<Compile Include="Assets\Editor\Versioning\GitException.cs" />
<Reference Include="UnityEditor.TestRunner">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEditor.TestRunner.dll</HintPath>
</Reference>
@@ -688,6 +695,9 @@
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/Boo.Lang.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include=".editorconfig" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityBuilderAction.Input;
using UnityBuilderAction.Reporting;
using UnityBuilderAction.Versioning;
using UnityEditor;
using UnityEditor.Build.Reporting;
namespace UnityBuilderAction
{
static class Builder
{
public static void BuildProject()
{
// Gather values from args
var options = ArgumentsParser.GetValidatedOptions();
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
// Define BuildPlayer Options
var buildOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
};
// Set version for this build
VersionApplicator.SetVersion(options["version"]);
// Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
// Summary
BuildSummary summary = buildReport.summary;
StdOutReporter.ReportSummary(summary);
// Result
BuildResult result = summary.result;
StdOutReporter.ExitWithResult(result);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b6e5ef18d769419d887b56665969442b
timeCreated: 1587503329

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using UnityEditor;
namespace UnityBuilderAction.Input
{
public class ArgumentsParser
{
static string EOL = Environment.NewLine;
public static Dictionary<string, string> GetValidatedOptions()
{
ParseCommandLineArguments(out var validatedOptions);
if (!validatedOptions.TryGetValue("projectPath", out var projectPath)) {
Console.WriteLine("Missing argument -projectPath");
EditorApplication.Exit(110);
}
if (!validatedOptions.TryGetValue("buildTarget", out var buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
EditorApplication.Exit(120);
}
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out var customBuildPath)) {
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
const string defaultCustomBuildName = "TestBuild";
if (!validatedOptions.TryGetValue("customBuildName", out var customBuildName)) {
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
} else if (customBuildName == "") {
Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
return validatedOptions;
}
static void ParseCommandLineArguments(out Dictionary<string, string> providedArguments)
{
providedArguments = new Dictionary<string, string>();
string[] args = Environment.GetCommandLineArgs();
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Parsing settings #{EOL}" +
$"###########################{EOL}" +
$"{EOL}"
);
// Extract flags with optional values
for (int current = 0, next = 1; current < args.Length; current++, next++) {
// Parse flag
bool isFlag = args[current].StartsWith("-");
if (!isFlag) continue;
string flag = args[current].TrimStart('-');
// Parse optional value
bool flagHasValue = next < args.Length && !args[next].StartsWith("-");
string value = flagHasValue ? args[next].TrimStart('-') : "";
// Assign
Console.WriteLine($"Found flag \"{flag}\" with value \"{value}\".");
providedArguments.Add(flag, value);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46d2ec4a86604575be2b2d02b0df7b74
timeCreated: 1587503354

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 166f919334c44e7a80ae916667974e7d
timeCreated: 1587503566

View File

@@ -0,0 +1,50 @@
using System;
using UnityEditor;
using UnityEditor.Build.Reporting;
namespace UnityBuilderAction.Reporting
{
public class StdOutReporter
{
static string EOL = Environment.NewLine;
public static void ReportSummary(BuildSummary summary)
{
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Build results #{EOL}" +
$"###########################{EOL}" +
$"{EOL}" +
$"Duration: {summary.totalTime.ToString()}{EOL}" +
$"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
$"Errors: {summary.totalErrors.ToString()}{EOL}" +
$"Size: {summary.totalSize.ToString()} bytes{EOL}" +
$"{EOL}"
);
}
public static void ExitWithResult(BuildResult result)
{
if (result == BuildResult.Succeeded) {
Console.WriteLine("Build succeeded!");
EditorApplication.Exit(0);
}
if (result == BuildResult.Failed) {
Console.WriteLine("Build failed!");
EditorApplication.Exit(101);
}
if (result == BuildResult.Cancelled) {
Console.WriteLine("Build cancelled!");
EditorApplication.Exit(102);
}
if (result == BuildResult.Unknown) {
Console.WriteLine("Build result is unknown!");
EditorApplication.Exit(103);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e59b491a4124442ea7277f76761cdc8a
timeCreated: 1587503545

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5da3bd7e18c43d79243410166c8dc9a
timeCreated: 1587493708

View File

@@ -0,0 +1,42 @@
using System.Diagnostics;
using System.Text;
public static class ProcessExtensions
{
// Execute an application or binary with given arguments
//
// See: https://stackoverflow.com/questions/4291912/process-start-how-to-get-the-output
public static int Run(this Process process, string application,
string arguments, string workingDirectory, out string output,
out string errors)
{
// Configure how to run the application
process.StartInfo = new ProcessStartInfo {
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = application,
Arguments = arguments,
WorkingDirectory = workingDirectory
};
// Read the output
var outputBuilder = new StringBuilder();
var errorsBuilder = new StringBuilder();
process.OutputDataReceived += (_, args) => outputBuilder.AppendLine(args.Data);
process.ErrorDataReceived += (_, args) => errorsBuilder.AppendLine(args.Data);
// Run the application and wait for it to complete
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
// Format the output
output = outputBuilder.ToString().TrimEnd();
errors = errorsBuilder.ToString().TrimEnd();
return process.ExitCode;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 29c1880a390c4af7be821b7877602815
timeCreated: 1587494270

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1c3bddf6d8984cde9208e3f0fe584879
timeCreated: 1587490700

View File

@@ -0,0 +1,116 @@
using System;
using UnityEngine;
namespace UnityBuilderAction.Versioning
{
public static class Git
{
const string application = @"git";
/// <summary>
/// Generate a version based on the latest tag and the amount of commits.
/// Format: 0.1.2 (where 2 is the amount of commits).
///
/// If no tag is present in the repository then v0.0 is assumed.
/// This would result in 0.0.# where # is the amount of commits.
/// </summary>
public static string GenerateSemanticCommitVersion()
{
string version;
if (HasAnyVersionTags()) {
version = GetSemanticCommitVersion();
Console.WriteLine("Repository has a valid version tag.");
} else {
version = $"0.0.{GetTotalNumberOfCommits()}";
Console.WriteLine("Repository does not have tags to base the version on.");
}
Console.WriteLine($"Version is {version}");
return version;
}
/// <summary>
/// Get the version of the current tag.
///
/// The tag must point at HEAD for this method to work.
///
/// Output Format:
/// #.* (where # is the major version and * can be any number of any type of character)
/// </summary>
public static string GetTagVersion()
{
string version = Run(@"tag --points-at HEAD | grep v[0-9]*");
version = version.Substring(1);
return version;
}
/// <summary>
/// Get the total number of commits.
/// </summary>
static int GetTotalNumberOfCommits()
{
string numberOfCommitsAsString = Run(@"git rev-list --count HEAD");
return int.Parse(numberOfCommitsAsString);
}
/// <summary>
/// Whether or not the repository has any version tags yet.
/// </summary>
static bool HasAnyVersionTags()
{
return "0" != Run(@"tag --list --merged HEAD | grep v[0-9]* | wc -l");
}
/// <summary>
/// Retrieves the build version from git based on the most recent matching tag and
/// commit history. This returns the version as: {major.minor.build} where 'build'
/// represents the nth commit after the tagged commit.
/// Note: The initial 'v' and the commit hash are removed.
/// </summary>
static string GetSemanticCommitVersion()
{
// v0.1-2-g12345678 (where 2 is the amount of commits, g stands for git)
string version = GetVersionString();
// 0.1-2
version = version.Substring(1, version.LastIndexOf('-') - 1);
// 0.1.2
version = version.Replace('-', '.');
return version;
}
/// <summary>
/// Get version string.
///
/// Format: `v0.1-2-g12345678` (where 2 is the amount of commits since the last tag)
///
/// See: https://softwareengineering.stackexchange.com/questions/141973/how-do-you-achieve-a-numeric-versioning-scheme-with-git
/// </summary>
static string GetVersionString()
{
return Run(@"describe --tags --long --match ""v[0-9]*""");
// Todo - implement split function based on this more complete query
// return Run(@"describe --long --tags --dirty --always");
}
/// <summary>
/// Runs git binary with any given arguments and returns the output.
/// </summary>
static string Run(string arguments)
{
using (var process = new System.Diagnostics.Process()) {
string workingDirectory = Application.dataPath;
int exitCode = process.Run(application, arguments, workingDirectory, out string output, out string errors);
if (exitCode != 0) { throw new GitException(exitCode, errors); }
return output;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cdec7fa0f5bb44958fdf74d4658a4601
timeCreated: 1587495075

View File

@@ -0,0 +1,14 @@
using System;
namespace UnityBuilderAction.Versioning
{
public class GitException : InvalidOperationException
{
public readonly int code;
public GitException(int code, string errors) : base(errors)
{
this.code = code;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4d375e209fd14fc5bc2f3dc3c78ac574
timeCreated: 1587490750

View File

@@ -0,0 +1,23 @@
using System;
using JetBrains.Annotations;
using UnityEditor;
namespace UnityBuilderAction.Versioning
{
public class VersionApplicator
{
public static void SetVersion(string version)
{
if (version == "none") {
return;
}
Apply(version);
}
static void Apply(string version)
{
PlayerSettings.bundleVersion = version;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 30483367ddc84699a0da377ccb93769a
timeCreated: 1587504315

View File

@@ -0,0 +1,10 @@
namespace UnityBuilderAction.Versioning
{
public static class VersionGenerator
{
public static string Generate()
{
return Git.GenerateSemanticCommitVersion();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9892e03ae8314b7eacd793c8002de007
timeCreated: 1587490842

View File

@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Untracked/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Versioning/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

1
action/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +1,29 @@
#!/usr/bin/env bash
if [[ -n "$UNITY_LICENSE" ]]; then
if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
#
# PERSONAL LICENSE MODE
#
# This will activate Unity, using a license file
#
# Note that this is the ONLY WAY for PERSONAL LICENSES in 2019.
# Note that this is the ONLY WAY for PERSONAL LICENSES in 2020.
# * See for more details: https://gitlab.com/gableroux/unity3d-gitlab-ci-example/issues/5#note_72815478
#
# The license file can be acquired using `webbertakken/request-manual-activation-file` action.
LICENSE_MODE="personal"
echo "Requesting activation (personal license)"
# Set the license file path
FILE_PATH=UnityLicenseFile.ulf
# Copy license file from Github variables
echo "$UNITY_LICENSE" | tr -d '\r' > $FILE_PATH
if [[ -n "$UNITY_LICENSE" ]]; then
# Copy license file from Github variables
echo "$UNITY_LICENSE" | tr -d '\r' > $FILE_PATH
elif [[ -n "$UNITY_LICENSE_FILE" ]]; then
# Copy license file from file system
cat "$UNITY_LICENSE_FILE" | tr -d '\r' > $FILE_PATH
fi
#
# Activate license
#
# This is expected to always exit with code 1 (both success and failure).
#
echo "Requesting activation"
ACTIVATION_OUTPUT=$(xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
@@ -31,17 +31,23 @@ if [[ -n "$UNITY_LICENSE" ]]; then
-logFile /dev/stdout \
-quit \
-manualLicenseFile $FILE_PATH)
# Convert to exit code 0 by echoing the current exit code.
echo $?
# Exit code is now 0
# TODO - remove debugging
echo $ACTIVATION_OUTPUT
echo $ACTIVATION_OUTPUT | grep 'config is NOT valid, switching to default'
echo $ACTIVATION_OUTPUT | grep 'config is NOT valid, switching to default' | wc -l
# Store the exit code from the verify command
UNITY_EXIT_CODE=$?
# TODO - Derive exit code by grepping success statement
UNITY_EXIT_CODE=$(echo $ACTIVATION_OUTPUT | grep 'config is NOT valid, switching to default' | wc -l)
# The exit code for personal activation is always 1;
# Determine whether activation was successful.
#
# Successful output should include the following:
#
# "LICENSE SYSTEM [2020120 18:51:20] Next license update check is after 2019-11-25T18:23:38"
#
ACTIVATION_SUCCESSFUL=$(echo $ACTIVATION_OUTPUT | grep 'Next license update check is after' | wc -l)
# Set exit code to 0 if activation was successful
if [[ $ACTIVATION_SUCCESSFUL -eq 1 ]]; then
UNITY_EXIT_CODE=0
fi;
# Remove license file
rm -f $FILE_PATH
@@ -54,8 +60,9 @@ elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
#
# Note: This is the preferred way for PROFESSIONAL LICENSES.
#
LICENSE_MODE="professional"
echo "Requesting activation (professional license)"
# Activate license
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
/opt/Unity/Editor/Unity \
-batchmode \
@@ -71,16 +78,18 @@ elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
else
#
# LICENSE ACTIVATION FAILED
# NO LICENSE ACTIVATION STRATEGY MATCHED
#
# This will exit since both personal and professional activation modes failed
# This will exit since no activation strategies could be matched.
#
echo "No personal or professional licenses provided!"
echo "Please ensure you have setup one of these licensing methods:"
echo " - Personal: Set the UNITY_LICENSE environment variable."
echo " - Professional: Set the UNITY_EMAIL, UNITY_PASSWORD and UNITY_SERIAL environment variables."
echo "See https://github.com/webbertakken/unity-builder#usage for details."
echo "License activation strategy could not be determined."
echo ""
echo "Visit https://github.com/webbertakken/unity-builder#usage for more"
echo "details on how to set up one of the possible activation strategies."
# Immediately exit as no UNITY_EXIT_CODE can be derrived.
exit 1;
fi
#
@@ -88,10 +97,10 @@ fi
#
if [ $UNITY_EXIT_CODE -eq 0 ]; then
# Activation was a success
echo "Activation ($LICENSE_MODE) complete."
echo "Activation complete."
else
# Activation failed so exit with the code from the license verification step
echo "Unclassified error occured while trying to activate ($LICENSE_MODE) license."
echo "Unclassified error occured while trying to activate license."
echo "Exit code was: $UNITY_EXIT_CODE"
exit $UNITY_EXIT_CODE
fi

View File

@@ -4,53 +4,28 @@
# Set project path
#
UNITY_PROJECT_PATH=$GITHUB_WORKSPACE/$PROJECT_PATH
UNITY_PROJECT_PATH="$GITHUB_WORKSPACE/$PROJECT_PATH"
echo "Using project path \"$UNITY_PROJECT_PATH\"."
#
# Set the name for the build
# Display the name for the build, doubles as the output name
#
if [ -z "$BUILD_NAME" ]; then
BUILD_NAME="build-$(date '+%F-%H%M')"
fi
echo "Using build name \"$BUILD_NAME\"."
#
# Set the builds target platform;
#
# Web: WebGL
# Desktop: StandaloneOSX, StandaloneWindows, StandaloneWindows64, StandaloneLinux64
# Console: PS4, XboxOne, Switch
# Mobile: Android, iOS
# Other: tvOS, Lumin, BJM, WSAPlayer
#
# Default to WebGL (no particular reason)
# Display the build's target platform;
#
if [ -z "$BUILD_TARGET" ]; then
BUILD_TARGET=WebGL
fi
echo "Using build target \"$BUILD_TARGET\"."
#
# Set builds path
# Display build path and file
#
if [ -z "$BUILDS_PATH" ]; then
BUILDS_PATH=build
fi
BUILDS_FULL_PATH=$GITHUB_WORKSPACE/$BUILDS_PATH
# TODO - Cleanup
BUILD_FOLDER=$BUILD_TARGET-$UNITY_VERSION
CURRENT_BUILD_PATH=$BUILDS_PATH/$BUILD_FOLDER
CURRENT_BUILD_FULL_PATH=$BUILDS_FULL_PATH/$BUILD_FOLDER
# TODO - Determine the file or folder based on BUILD_TARGET
CUSTOM_BUILD_PATH=$BUILDS_FULL_PATH/$BUILD_FOLDER/$BUILD_TARGET
echo "Using build path \"$CURRENT_BUILD_PATH\"."
echo "Using build path \"$BUILD_PATH\" to save file \"$BUILD_FILE\"."
BUILD_PATH_FULL="$GITHUB_WORKSPACE/$BUILD_PATH"
CUSTOM_BUILD_PATH="$BUILD_PATH_FULL/$BUILD_FILE"
#
# Set the build method, must reference one of:
@@ -71,25 +46,26 @@ if [ -z "$BUILD_METHOD" ]; then
#
echo "Using built-in build method."
# Create Editor directory if it does not exist
mkdir -p $UNITY_PROJECT_PATH/Assets/Editor/
mkdir -p "$UNITY_PROJECT_PATH/Assets/Editor/"
# Copy the build script of Unity Builder action
cp -r /UnityBuilderAction/Assets/Editor $UNITY_PROJECT_PATH/Assets/Editor/
cp -r "/UnityBuilderAction/Assets/Editor" "$UNITY_PROJECT_PATH/Assets/Editor/"
# Set the Build method to that of UnityBuilder Action
BUILD_METHOD="UnityBuilderAction.Builder.BuildProject"
# Verify recursive paths
ls -Ralph $UNITY_PROJECT_PATH/Assets/Editor/
ls -Ralph "$UNITY_PROJECT_PATH/Assets/Editor/"
#
else
# User has provided their own build method.
# Assume they also bring their own script.
#
echo "User set build method to $BUILD_METHOD."
echo "Using build method \"$BUILD_METHOD\"."
#
fi
# Set build method to execute as flag + argument
EXECUTE_BUILD_METHOD="-executeMethod $BUILD_METHOD"
#
# Display custom parameters
#
echo "Using custom parameters $CUSTOM_PARAMETERS."
# The build specification below may require Unity 2019.2.11f1 or later (not tested below).
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
@@ -98,25 +74,15 @@ EXECUTE_BUILD_METHOD="-executeMethod $BUILD_METHOD"
# Build info
#
echo ""
echo "###########################"
echo "# All builds dir #"
echo "###########################"
echo ""
echo "Creating \"$BUILDS_FULL_PATH\" if it does not exist."
mkdir -p $BUILDS_FULL_PATH
ls -alh $BUILDS_FULL_PATH
echo ""
echo "###########################"
echo "# Current build dir #"
echo "###########################"
echo ""
echo "Creating \"$CURRENT_BUILD_FULL_PATH\" if it does not exist."exist."
mkdir -p $CURRENT_BUILD_FULL_PATH
ls -alh $CURRENT_BUILD_FULL_PATH
echo "Creating \"$BUILD_PATH_FULL\" if it does not exist."
mkdir -p "$BUILD_PATH_FULL"
ls -alh "$BUILD_PATH_FULL"
echo ""
echo "###########################"
@@ -142,7 +108,10 @@ xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
-buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \
$EXECUTE_BUILD_METHOD
-executeMethod "$BUILD_METHOD" \
-versioning "$VERSIONING" \
-version "$VERSION" \
$CUSTOM_PARAMETERS
# Catch exit code
BUILD_EXIT_CODE=$?
@@ -164,4 +133,4 @@ echo "# Build directory #"
echo "###########################"
echo ""
ls -alh $CURRENT_BUILD_FULL_PATH
ls -alh "$BUILD_PATH_FULL"

View File

@@ -1,147 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace UnityBuilderAction
{
static class Builder
{
private static string EOL = Environment.NewLine;
private static void ParseCommandLineArguments(out Dictionary<string, string> providedArguments)
{
providedArguments = new Dictionary<string, string>();
string[] args = Environment.GetCommandLineArgs();
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Parsing settings #{EOL}" +
$"###########################{EOL}" +
$"{EOL}"
);
// Extract flags with optional values
for (int current = 0, next = 1; current < args.Length; current++, next++) {
// Parse flag
bool isFlag = args[current].StartsWith("-");
if (!isFlag) continue;
string flag = args[current].TrimStart('-');
// Parse optional value
bool flagHasValue = next < args.Length && !args[next].StartsWith("-");
string value = flagHasValue ? args[next].TrimStart('-') : "";
// Assign
Console.WriteLine($"Found flag \"{flag}\" with value \"{value}\".");
providedArguments.Add(flag, value);
}
}
private static Dictionary<string, string> GetValidatedOptions()
{
ParseCommandLineArguments(out var validatedOptions);
if (!validatedOptions.TryGetValue("projectPath", out var projectPath)) {
Console.WriteLine("Missing argument -projectPath");
EditorApplication.Exit(110);
}
if (!validatedOptions.TryGetValue("buildTarget", out var buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
EditorApplication.Exit(120);
}
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out var customBuildPath)) {
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
string defaultCustomBuildName = "TestBuild";
if (!validatedOptions.TryGetValue("customBuildName", out var customBuildName)) {
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
else if (customBuildName == "") {
Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
}
return validatedOptions;
}
public static void BuildProject()
{
// Gather values from args
var options = GetValidatedOptions();
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
// Define BuildPlayer Options
var buildOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
};
// Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildOptions);
// Summary
BuildSummary summary = buildReport.summary;
ReportSummary(summary);
// Result
BuildResult result = summary.result;
ExitWithResult(result);
}
private static void ReportSummary(BuildSummary summary)
{
Console.WriteLine(
$"{EOL}" +
$"###########################{EOL}" +
$"# Build results #{EOL}" +
$"###########################{EOL}" +
$"{EOL}" +
$"Duration: {summary.totalTime.ToString()}{EOL}" +
$"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
$"Errors: {summary.totalErrors.ToString()}{EOL}" +
$"Size: {summary.totalSize.ToString()} bytes{EOL}" +
$"{EOL}"
);
}
private static void ExitWithResult(BuildResult result)
{
if (result == BuildResult.Succeeded) {
Console.WriteLine("Build succeeded!");
EditorApplication.Exit(0);
}
if (result == BuildResult.Failed) {
Console.WriteLine("Build failed!");
EditorApplication.Exit(101);
}
if (result == BuildResult.Cancelled) {
Console.WriteLine("Build cancelled!");
EditorApplication.Exit(102);
}
if (result == BuildResult.Unknown) {
Console.WriteLine("Build result is unknown!");
EditorApplication.Exit(103);
}
}
}
}

File diff suppressed because one or more lines are too long

39
codecov.yml Normal file
View File

@@ -0,0 +1,39 @@
codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: '70...100'
status:
patch:
default:
# basic
target: auto
threshold: 100% # don't require patch to have coverage per se
base: auto
project:
default:
# basic
target: auto
threshold: 5%
base: auto
flags:
- unit
paths:
- 'src'
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: 'reach,diff,flags,tree'
behavior: default
require_changes: no

View File

@@ -5,4 +5,5 @@ module.exports = {
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: { '^.+\\.(js|jsx)?$': 'babel-jest' },
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
setupFilesAfterEnv: ['./src/jest.setup.js'],
};

View File

@@ -2,44 +2,45 @@
"name": "unity-builder",
"version": "0.5.0",
"description": "Build Unity projects for different platforms.",
"main": "builder/index.js",
"main": "action/index.js",
"repository": "git@github.com:webbertakken/unity-builder.git",
"author": "Webber <webber@takken.io>",
"license": "MIT",
"scripts": {
"build": "ncc build src --out builder --minify",
"prebuild": "yarn",
"build": "ncc build src --out action --minify",
"lint": "prettier --check \"src/**/*.js\" && eslint src",
"test": "jest"
},
"dependencies": {
"@actions/core": "^1.2.0",
"@actions/exec": "1.0.2",
"@actions/github": "^2.0.0"
"@actions/core": "^1.2.4",
"@actions/exec": "1.0.4",
"@actions/github": "^2.1.1"
},
"devDependencies": {
"@babel/cli": "7.7.5",
"@babel/core": "7.7.5",
"@babel/preset-env": "7.7.7",
"@zeit/ncc": "0.20.5",
"babel-eslint": "10.0.3",
"eslint": "6.7.2",
"eslint-config-airbnb": "18.0.1",
"eslint-config-prettier": "6.7.0",
"eslint-plugin-flowtype": "4.5.2",
"eslint-plugin-import": "2.19.1",
"@babel/cli": "7.8.4",
"@babel/core": "7.9.6",
"@babel/preset-env": "7.9.6",
"@zeit/ncc": "0.22.1",
"babel-eslint": "10.1.0",
"eslint": "6.8.0",
"eslint-config-airbnb": "18.1.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-flowtype": "4.7.0",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.17.0",
"eslint-plugin-unicorn": "14.0.1",
"husky": "4.0.0-beta.5",
"jest": "24.9.0",
"lint-staged": "9.5.0",
"eslint-plugin-prettier": "3.1.3",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-unicorn": "19.0.1",
"husky": "4.2.5",
"jest": "25.5.3",
"lint-staged": "10.2.2",
"lodash-es": "4.17.15",
"prettier": "1.19.1"
"prettier": "2.0.5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && yarn build && git add builder/index.js"
"pre-commit": "lint-staged && yarn build && git add action/index.js"
}
},
"lint-staged": {

View File

@@ -1,22 +1,22 @@
import Action from './model/action';
import Docker from './model/docker';
import ImageTag from './model/image-tag';
import Input from './model/input';
import { Action, BuildParameters, Cache, Docker, Input, ImageTag } from './model';
const core = require('@actions/core');
async function action() {
Action.checkCompatibility();
Cache.verify();
const { dockerfile, workspace, builderFolder } = Action;
const { version, platform, projectPath, buildName, buildsPath, method } = Input.getFromUser();
const { dockerfile, workspace, actionFolder } = Action;
const buildParameters = BuildParameters.create(await Input.getFromUser());
const baseImage = new ImageTag(buildParameters);
const baseImage = new ImageTag({ version, platform });
const builtImage = await Docker.build({ path: builderFolder, dockerfile, baseImage });
// Build docker image
const builtImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
await Docker.run(builtImage, { workspace, platform, projectPath, buildName, buildsPath, method });
// Run docker image
await Docker.run(builtImage, { workspace, ...buildParameters });
}
action().catch(error => {
action().catch((error) => {
core.setFailed(error.message);
});

49
src/jest.setup.js Normal file
View File

@@ -0,0 +1,49 @@
expect.extend({
toBeOfType(received, expectedType) {
const type = typeof received;
const pass = type === expectedType;
const message = () => `
Expected value to be of type ${this.utils.printExpected(expectedType)},
but received ${this.utils.printReceived(type)}`;
return {
message,
pass,
};
},
toBeEitherAFunctionOrAnObject(received) {
const type = typeof received;
const pass = ['object', 'function'].includes(type);
const message = () => `Expected a ${this.utils.printExpected('function')}
or an ${this.utils.printExpected('object')},
but received ${type}`;
return {
message,
pass,
};
},
toBeParsableToANumber(received) {
let pass = false;
let errorMessage = '';
try {
Number.parseInt(received, 10);
pass = true;
} catch (error) {
errorMessage = error;
}
const message = () => `Expected ${this.utils.printExpected(received)} to be parsable as a number
, but received error: ${this.utils.printReceived(errorMessage)}.`;
return {
message,
pass,
};
},
});

View File

@@ -0,0 +1,17 @@
// Import this named export into your test file:
import Platform from '../platform';
export const mockGetFromUser = jest.fn().mockResolvedValue({
version: '',
targetPlatform: Platform.types.Test,
projectPath: '.',
buildName: Platform.types.Test,
buildsPath: 'build',
buildMethod: undefined,
buildVersion: '1.3.37',
customParameters: '',
});
export default {
getFromUser: mockGetFromUser,
};

View File

@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Versioning determineVersion throws for invalid strategy 0 1`] = `"Versioning strategy should be one of None, Semantic, Tag, Custom."`;
exports[`Versioning determineVersion throws for invalid strategy somethingRandom 1`] = `"Versioning strategy should be one of None, Semantic, Tag, Custom."`;
exports[`Versioning determineVersion throws for invalid strategy undefined 1`] = `"Versioning strategy should be one of None, Semantic, Tag, Custom."`;

View File

@@ -1,6 +1,6 @@
import path from 'path';
export default class Action {
class Action {
static get supportedPlatforms() {
return ['linux'];
}
@@ -25,12 +25,12 @@ export default class Action {
return path.dirname(path.dirname(__filename));
}
static get builderFolder() {
return `${Action.rootFolder}/builder`;
static get actionFolder() {
return `${Action.rootFolder}/action`;
}
static get dockerfile() {
return `${Action.builderFolder}/Dockerfile`;
return `${Action.actionFolder}/Dockerfile`;
}
static get workspace() {
@@ -44,3 +44,5 @@ export default class Action {
}
}
}
export default Action;

View File

@@ -20,11 +20,11 @@ describe('Action', () => {
expect(fs.existsSync(rootFolder)).toStrictEqual(true);
});
it('returns the builder folder', () => {
const { builderFolder } = Action;
it('returns the action folder', () => {
const { actionFolder } = Action;
expect(path.basename(builderFolder)).toStrictEqual('builder');
expect(fs.existsSync(builderFolder)).toStrictEqual(true);
expect(path.basename(actionFolder)).toStrictEqual('action');
expect(fs.existsSync(actionFolder)).toStrictEqual(true);
});
it('returns the docker file', () => {

View File

@@ -0,0 +1,42 @@
import Platform from './platform';
class BuildParameters {
static create(parameters) {
const {
version,
targetPlatform,
projectPath,
buildName,
buildsPath,
buildMethod,
buildVersion,
customParameters,
} = parameters;
return {
version,
platform: targetPlatform,
projectPath,
buildName,
buildPath: `${buildsPath}/${targetPlatform}`,
buildFile: this.parseBuildFile(buildName, targetPlatform),
buildMethod,
buildVersion,
customParameters,
};
}
static parseBuildFile(filename, platform) {
if (Platform.isWindows(platform)) {
return `${filename}.exe`;
}
if (Platform.isAndroid(platform)) {
return `${filename}.apk`;
}
return filename;
}
}
export default BuildParameters;

View File

@@ -0,0 +1,83 @@
import BuildParameters from './build-parameters';
import Platform from './platform';
describe('BuildParameters', () => {
describe('create', () => {
const someParameters = {
version: 'someVersion',
targetPlatform: 'somePlatform',
projectPath: 'path/to/project',
buildName: 'someBuildName',
buildsPath: 'someBuildsPath',
buildMethod: 'Namespace.Class.Method',
customParameters: '-someParam someValue',
};
it('does not throw', () => {
expect(() => BuildParameters.create(someParameters)).not.toThrow();
});
it('returns the version', () => {
expect(BuildParameters.create(someParameters).version).toStrictEqual(someParameters.version);
});
it('returns the platform', () => {
expect(BuildParameters.create(someParameters).platform).toStrictEqual(
someParameters.targetPlatform,
);
});
it('returns the project path', () => {
expect(BuildParameters.create(someParameters).projectPath).toStrictEqual(
someParameters.projectPath,
);
});
it('returns the build name', () => {
expect(BuildParameters.create(someParameters).buildName).toStrictEqual(
someParameters.buildName,
);
});
it('returns the build path', () => {
expect(BuildParameters.create(someParameters).buildPath).toStrictEqual(
`${someParameters.buildsPath}/${someParameters.targetPlatform}`,
);
});
describe('build file', () => {
it('returns the build file', () => {
expect(BuildParameters.create(someParameters).buildFile).toStrictEqual(
someParameters.buildName,
);
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s',
(targetPlatform) => {
expect(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile,
).toStrictEqual(`${someParameters.buildName}.exe`);
},
);
test.each([Platform.types.Android])('appends apk for %s', (targetPlatform) => {
expect(
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile,
).toStrictEqual(`${someParameters.buildName}.apk`);
});
});
it('returns the build method', () => {
expect(BuildParameters.create(someParameters).buildMethod).toStrictEqual(
someParameters.buildMethod,
);
});
it('returns the custom parameters', () => {
expect(BuildParameters.create(someParameters).customParameters).toStrictEqual(
someParameters.customParameters,
);
});
});
});

25
src/model/cache.js Normal file
View File

@@ -0,0 +1,25 @@
import fs from 'fs';
import Action from './action';
import Project from './project';
class Cache {
static verify() {
if (!fs.existsSync(Project.libraryFolder)) {
this.notifyAboutCachingPossibility();
}
}
static notifyAboutCachingPossibility() {
if (Action.isRunningLocally) {
return;
}
// eslint-disable-next-line no-console
console.log(`
Library folder does not exist.
Consider setting up caching to speed up your workflow
If this is not your first build.`);
}
}
export default Cache;

11
src/model/cache.test.js Normal file
View File

@@ -0,0 +1,11 @@
import Cache from './cache';
jest.mock('./input');
describe('Cache', () => {
describe('Verification', () => {
it('does not throw', () => {
expect(() => Cache.verify()).not.toThrow();
});
});
});

View File

@@ -1,7 +1,7 @@
import { exec } from '@actions/exec';
import ImageTag from './image-tag';
export default class Docker {
class Docker {
static async build(buildParameters, silent = false) {
const { path, dockerfile, baseImage } = buildParameters;
const { version, platform } = baseImage;
@@ -12,28 +12,42 @@ export default class Docker {
--build-arg IMAGE=${baseImage} \
--tag ${tag}`;
await exec(command, null, { silent });
await exec(command, undefined, { silent });
return tag;
}
static async run(image, parameters, silent = false) {
const { workspace, platform, projectPath, buildName, buildsPath, method } = parameters;
const { version } = image;
const {
version,
workspace,
platform,
projectPath,
buildName,
buildPath,
buildFile,
buildMethod,
buildVersion,
customParameters,
} = parameters;
const command = `docker run \
--workdir /github/workspace \
--rm \
--env UNITY_LICENSE \
--env UNITY_LICENSE_FILE \
--env UNITY_EMAIL \
--env UNITY_PASSWORD \
--env UNITY_SERIAL \
--env UNITY_VERSION=${version} \
--env PROJECT_PATH=${projectPath} \
--env BUILD_TARGET=${platform} \
--env BUILD_NAME=${buildName} \
--env BUILDS_PATH=${buildsPath} \
--env BUILD_METHOD=${method} \
--env UNITY_VERSION="${version}" \
--env PROJECT_PATH="${projectPath}" \
--env BUILD_TARGET="${platform}" \
--env BUILD_NAME="${buildName}" \
--env BUILD_PATH="${buildPath}" \
--env BUILD_FILE="${buildFile}" \
--env BUILD_METHOD="${buildMethod}" \
--env VERSION="${buildVersion}" \
--env CUSTOM_PARAMETERS="${customParameters}" \
--env HOME=/github/home \
--env GITHUB_REF \
--env GITHUB_SHA \
@@ -56,6 +70,8 @@ export default class Docker {
--volume "${workspace}":"/github/workspace" \
${image}`;
await exec(command, null, { silent });
await exec(command, undefined, { silent });
}
}
export default Docker;

View File

@@ -4,7 +4,7 @@ import ImageTag from './image-tag';
describe('Docker', () => {
it('builds', async () => {
const path = Action.builderFolder;
const path = Action.actionFolder;
const dockerfile = `${path}/Dockerfile`;
const baseImage = new ImageTag({
repository: '',

View File

@@ -0,0 +1,8 @@
class CommandExecutionError extends Error {
constructor(message) {
super(message);
this.name = 'CommandExecutionError';
}
}
export default CommandExecutionError;

View File

@@ -0,0 +1,14 @@
import CommandExecutionError from './command-execution-error';
describe('CommandExecutionError', () => {
it('instantiates', () => {
expect(() => new CommandExecutionError()).not.toThrow();
});
test.each([1, 'one', { name: '!' }])('Displays title %s', (message) => {
const error = new CommandExecutionError(message);
expect(error.name).toStrictEqual('CommandExecutionError');
expect(error.message).toStrictEqual(message.toString());
});
});

View File

@@ -0,0 +1,8 @@
class NotImplementedException extends Error {
constructor(message) {
super(message);
this.name = 'NotImplementedException';
}
}
export default NotImplementedException;

View File

@@ -0,0 +1,14 @@
import NotImplementedException from './not-implemented-exception';
describe('NotImplementedException', () => {
it('instantiates', () => {
expect(() => new NotImplementedException()).not.toThrow();
});
test.each([1, 'one', { name: '!' }])('Displays title %s', (message) => {
const error = new NotImplementedException(message);
expect(error.name).toStrictEqual('NotImplementedException');
expect(error.message).toStrictEqual(message.toString());
});
});

View File

@@ -0,0 +1,8 @@
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
export default ValidationError;

View File

@@ -0,0 +1,14 @@
import ValidationError from './validation-error';
describe('ValidationError', () => {
it('instantiates', () => {
expect(() => new ValidationError()).not.toThrow();
});
test.each([1, 'one', { name: '!' }])('Displays title %s', (message) => {
const error = new ValidationError(message);
expect(error.name).toStrictEqual('ValidationError');
expect(error.message).toStrictEqual(message.toString());
});
});

View File

@@ -1,6 +1,7 @@
import { has, get, trimEnd, trimStart } from 'lodash-es';
import Platform from './platform';
export default class ImageTag {
class ImageTag {
constructor(imageProperties) {
const {
repository = 'gableroux',
@@ -13,14 +14,14 @@ export default class ImageTag {
throw new Error(`Invalid version "${version}".`);
}
if (!has(ImageTag.targetPlatformToBuilderPlatformMap, platform)) {
if (!has(ImageTag.targetPlatformToImageSuffixMap, platform)) {
throw new Error(`Platform "${platform}" is currently not supported.`);
}
const builderPlatform = get(
ImageTag.targetPlatformToBuilderPlatformMap,
ImageTag.targetPlatformToImageSuffixMap,
platform,
ImageTag.builderPlatforms.generic,
ImageTag.imageSuffixes.generic,
);
Object.assign(this, { repository, name, version, platform, builderPlatform });
@@ -30,7 +31,7 @@ export default class ImageTag {
return /^20\d{2}\.\d\.\w{3,4}|3$/;
}
static get builderPlatforms() {
static get imageSuffixes() {
return {
generic: '',
webgl: 'webgl',
@@ -42,31 +43,31 @@ export default class ImageTag {
};
}
static get targetPlatformToBuilderPlatformMap() {
const { generic, webgl, mac, windows, android, ios, facebook } = ImageTag.builderPlatforms;
static get targetPlatformToImageSuffixMap() {
const { generic, webgl, mac, windows, android, ios, facebook } = ImageTag.imageSuffixes;
// @see: https://docs.unity3d.com/ScriptReference/BuildTarget.html
return {
StandaloneOSX: mac,
StandaloneWindows: windows,
StandaloneWindows64: windows,
StandaloneLinux64: windows,
iOS: ios,
Android: android,
WebGL: webgl,
WSAPlayer: windows,
PS4: windows,
XboxOne: windows,
tvOS: windows,
Switch: windows,
[Platform.types.StandaloneOSX]: mac,
[Platform.types.StandaloneWindows]: windows,
[Platform.types.StandaloneWindows64]: windows,
[Platform.types.StandaloneLinux64]: windows,
[Platform.types.iOS]: ios,
[Platform.types.Android]: android,
[Platform.types.WebGL]: webgl,
[Platform.types.WSAPlayer]: windows,
[Platform.types.PS4]: windows,
[Platform.types.XboxOne]: windows,
[Platform.types.tvOS]: windows,
[Platform.types.Switch]: windows,
// Unsupported
Lumin: windows,
BJM: windows,
Stadia: windows,
Facebook: facebook,
NoTarget: generic,
[Platform.types.Lumin]: windows,
[Platform.types.BJM]: windows,
[Platform.types.Stadia]: windows,
[Platform.types.Facebook]: facebook,
[Platform.types.NoTarget]: generic,
// Test specific
Test: generic,
[Platform.types.Test]: generic,
};
}
@@ -84,3 +85,5 @@ export default class ImageTag {
return `${image}:${tag}`;
}
}
export default ImageTag;

View File

@@ -31,16 +31,16 @@ describe('UnityImageVersion', () => {
expect(image.builderPlatform).toStrictEqual(some.builderPlatform);
});
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', (version) => {
expect(() => new ImageTag({ version, platform: some.platform })).not.toThrow();
});
test.each(['some version', '', 1, null])('throws for incorrect versions %p', version => {
test.each(['some version', '', 1])('throws for incorrect versions %p', (version) => {
const { platform } = some;
expect(() => new ImageTag({ version, platform })).toThrow();
});
test.each([undefined, 'nonExisting'])('throws for unsupported target %p', platform => {
test.each([undefined, 'nonExisting'])('throws for unsupported target %p', (platform) => {
expect(() => new ImageTag({ platform })).toThrow();
});
});

23
src/model/index.js Normal file
View File

@@ -0,0 +1,23 @@
import Action from './action';
import BuildParameters from './build-parameters';
import Cache from './cache';
import Docker from './docker';
import Input from './input';
import ImageTag from './image-tag';
import Platform from './platform';
import Project from './project';
import Unity from './unity';
import Versioning from './versioning';
export {
Action,
BuildParameters,
Cache,
Docker,
Input,
ImageTag,
Platform,
Project,
Unity,
Versioning,
};

17
src/model/index.test.js Normal file
View File

@@ -0,0 +1,17 @@
import * as Index from '.';
describe('Index', () => {
test.each([
'Action',
'BuildParameters',
'Cache',
'Docker',
'ImageTag',
'Input',
'Platform',
'Project',
'Unity',
])('exports %s', (exportedModule) => {
expect(Index[exportedModule]).toBeEitherAFunctionOrAnObject();
});
});

View File

@@ -1,22 +1,39 @@
import Platform from './platform';
import Versioning from './versioning';
const core = require('@actions/core');
export default class Input {
static getFromUser() {
class Input {
static async getFromUser() {
// Input variables specified in workflows using "with" prop.
const version = core.getInput('unityVersion');
const platform = core.getInput('targetPlatform');
const projectPath = core.getInput('projectPath');
const buildName = core.getInput('buildName');
const buildsPath = core.getInput('buildsPath');
const buildMethod = core.getInput('buildMethod');
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
const rawProjectPath = core.getInput('projectPath') || '.';
const buildName = core.getInput('buildName') || targetPlatform;
const buildsPath = core.getInput('buildsPath') || 'build';
const buildMethod = core.getInput('buildMethod'); // processed in docker file
const versioningStrategy = core.getInput('versioning') || 'Semantic';
const specifiedVersion = core.getInput('version') || '';
const customParameters = core.getInput('customParameters') || '';
// Sanitise input
const projectPath = rawProjectPath.replace(/\/$/, '');
// Parse input
const buildVersion = await Versioning.determineVersion(versioningStrategy, specifiedVersion);
// Return validated input
return {
version,
platform,
targetPlatform,
projectPath,
buildName,
buildsPath,
method: buildMethod,
buildMethod,
buildVersion,
customParameters,
};
}
}
export default Input;

View File

@@ -1,9 +1,27 @@
import Input from './input';
import Versioning from './versioning';
const determineVersion = jest
.spyOn(Versioning, 'determineVersion')
.mockImplementation(() => '1.3.37');
afterEach(() => {
jest.clearAllMocks();
});
describe('Input', () => {
describe('getFromUser', () => {
it('does not throw', () => {
expect(() => Input.getFromUser()).not.toThrow();
it('does not throw', async () => {
await expect(Input.getFromUser()).resolves.not.toBeNull();
});
it('returns an object', async () => {
await expect(typeof (await Input.getFromUser())).toStrictEqual('object');
});
it('calls version generator once', async () => {
await Input.getFromUser();
expect(determineVersion).toHaveBeenCalledTimes(1);
});
});
});

51
src/model/platform.js Normal file
View File

@@ -0,0 +1,51 @@
class Platform {
static get default() {
return Platform.types.StandaloneWindows64;
}
static get types() {
return {
StandaloneOSX: 'StandaloneOSX',
StandaloneWindows: 'StandaloneWindows',
StandaloneWindows64: 'StandaloneWindows64',
StandaloneLinux64: 'StandaloneLinux64',
iOS: 'iOS',
Android: 'Android',
WebGL: 'WebGL',
WSAPlayer: 'WSAPlayer',
PS4: 'PS4',
XboxOne: 'XboxOne',
tvOS: 'tvOS',
Switch: 'Switch',
// Unsupported
Lumin: 'Lumin',
BJM: 'BJM',
Stadia: 'Stadia',
Facebook: 'Facebook',
NoTarget: 'NoTarget',
// Test specific
Test: 'Test',
};
}
static isWindows(platform) {
switch (platform) {
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
return true;
default:
return false;
}
}
static isAndroid(platform) {
switch (platform) {
case Platform.types.Android:
return true;
default:
return false;
}
}
}
export default Platform;

View File

@@ -0,0 +1,37 @@
import Platform from './platform';
describe('Platform', () => {
describe('default', () => {
it('does not throw', () => {
expect(() => Platform.default).not.toThrow();
});
it('returns a string', () => {
expect(typeof Platform.default).toStrictEqual('string');
});
it('returns a platform', () => {
expect(Object.values(Platform.types)).toContain(Platform.default);
});
});
describe('isWindows', () => {
it('returns true for windows', () => {
expect(Platform.isWindows(Platform.types.StandaloneWindows64)).toStrictEqual(true);
});
it('returns false for MacOS', () => {
expect(Platform.isWindows(Platform.types.StandaloneOSX)).toStrictEqual(false);
});
});
describe('isAndroid', () => {
it('returns true for Android', () => {
expect(Platform.isAndroid(Platform.types.Android)).toStrictEqual(true);
});
it('returns false for Windows', () => {
expect(Platform.isAndroid(Platform.types.StandaloneWindows64)).toStrictEqual(false);
});
});
});

24
src/model/project.js Normal file
View File

@@ -0,0 +1,24 @@
import * as core from '@actions/core';
import Unity from './unity';
import Action from './action';
class Project {
static get relativePath() {
// Todo - properly use Input for this.
const projectPath = core.getInput('projectPath') || '.';
return `${projectPath}`;
}
static get absolutePath() {
const { workspace } = Action;
return `${workspace}/${this.relativePath}`;
}
static get libraryFolder() {
return `${this.relativePath}/${Unity.libraryFolder}`;
}
}
export default Project;

35
src/model/project.test.js Normal file
View File

@@ -0,0 +1,35 @@
import Project from './project';
jest.mock('./input');
describe('Platform', () => {
describe('relativePath', () => {
it('does not throw', () => {
expect(() => Project.relativePath).not.toThrow();
});
it('returns a string', () => {
expect(typeof Project.relativePath).toStrictEqual('string');
});
});
describe('absolutePath', () => {
it('does not throw', () => {
expect(() => Project.absolutePath).not.toThrow();
});
it('returns a string', () => {
expect(typeof Project.absolutePath).toStrictEqual('string');
});
});
describe('libraryFolder', () => {
it('does not throw', () => {
expect(() => Project.libraryFolder).not.toThrow();
});
it('returns a string', () => {
expect(typeof Project.libraryFolder).toStrictEqual('string');
});
});
});

44
src/model/system.js Normal file
View File

@@ -0,0 +1,44 @@
import * as core from '@actions/core';
import { exec } from '@actions/exec';
class System {
static async run(command, arguments_, options) {
let result = '';
let error = '';
let debug = '';
const listeners = {
stdout: (dataBuffer) => {
result += dataBuffer.toString();
},
stderr: (dataBuffer) => {
error += dataBuffer.toString();
},
debug: (dataString) => {
debug += dataString.toString();
},
};
const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options });
if (debug !== '') {
core.debug(debug);
}
if (result !== '') {
core.info(result);
}
if (exitCode !== 0) {
throw new Error(error);
}
if (error !== '') {
core.warning(error);
}
return result;
}
}
export default System;

56
src/model/system.test.js Normal file
View File

@@ -0,0 +1,56 @@
import * as core from '@actions/core';
import System from './system';
jest.spyOn(core, 'debug').mockImplementation(() => {});
const info = jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(core, 'warning').mockImplementation(() => {});
jest.spyOn(core, 'error').mockImplementation(() => {});
afterEach(() => {
jest.clearAllMocks();
});
describe('System', () => {
describe('run', () => {
it('runs a command successfully', async () => {
await expect(System.run('true')).resolves.not.toBeNull();
});
it('outputs results', async () => {
await expect(System.run('echo test')).resolves.toStrictEqual('test\n');
});
it('throws on when error code is not 0', async () => {
await expect(System.run('false')).rejects.toThrowError();
});
it('throws when no arguments are given', async () => {
await expect(System.run()).rejects.toThrowError();
});
it('outputs info', async () => {
await expect(System.run('echo test')).resolves.not.toBeNull();
expect(info).toHaveBeenLastCalledWith('test\n');
});
it('outputs info only once', async () => {
await expect(System.run('echo 1')).resolves.not.toBeNull();
expect(info).toHaveBeenCalledTimes(1);
expect(info).toHaveBeenLastCalledWith('1\n');
info.mockClear();
await expect(System.run('echo 2')).resolves.not.toBeNull();
await expect(System.run('echo 3')).resolves.not.toBeNull();
expect(info).toHaveBeenCalledTimes(2);
expect(info).toHaveBeenLastCalledWith('3\n');
});
it('allows pipes using buffer', async () => {
await expect(
System.run('sh', undefined, {
input: Buffer.from('git tag --list --merged HEAD | grep v[0-9]* | wc -l'),
}),
).resolves.toBeParsableToANumber();
});
});
});

7
src/model/unity.js Normal file
View File

@@ -0,0 +1,7 @@
class Unity {
static get libraryFolder() {
return 'Library';
}
}
export default Unity;

13
src/model/unity.test.js Normal file
View File

@@ -0,0 +1,13 @@
import Unity from './unity';
describe('Unity', () => {
describe('libraryFolder', () => {
it('does not throw', () => {
expect(() => Unity.libraryFolder).not.toThrow();
});
it('returns a string', () => {
expect(typeof Unity.libraryFolder).toStrictEqual('string');
});
});
});

210
src/model/versioning.js Normal file
View File

@@ -0,0 +1,210 @@
import * as core from '@actions/core';
import NotImplementedException from './error/not-implemented-exception';
import ValidationError from './error/validation-error';
import System from './system';
export default class Versioning {
static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
}
/**
* Get the branch name of the (related) branch
*/
static get branch() {
// Todo - use optional chaining (https://github.com/zeit/ncc/issues/534)
return this.headRef || (this.ref && this.ref.slice(11));
}
/**
* For pull requests we can reliably use GITHUB_HEAD_REF
*/
static get headRef() {
return process.env.GITHUB_HEAD_REF;
}
/**
* For branches GITHUB_REF will have format `refs/heads/feature-branch-1`
*/
static get ref() {
return process.env.GITHUB_REF;
}
/**
* Regex to parse version description into separate fields
*/
static get descriptionRegex() {
return /^v([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static async determineVersion(strategy, inputVersion) {
// Validate input
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
throw new ValidationError(
`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`,
);
}
let version;
switch (strategy) {
case this.strategies.None:
version = 'none';
break;
case this.strategies.Custom:
version = inputVersion;
break;
case this.strategies.Semantic:
version = await this.generateSemanticVersion();
break;
case this.strategies.Tag:
version = await this.generateTagVersion();
break;
default:
throw new NotImplementedException(`Strategy ${strategy} is not implemented.`);
}
return version;
}
/**
* Automatically generates a version based on SemVer out of the box.
*
* The version works as follows: `<major>.<minor>.<patch>` for example `0.1.2`.
*
* The latest tag dictates `<major>.<minor>`
* The number of commits since that tag dictates`<patch>`.
*
* @See: https://semver.org/
*/
static async generateSemanticVersion() {
await this.fetch();
if (await this.isDirty()) {
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
}
if (!(await this.hasAnyVersionTags())) {
const version = `0.0.${await this.getTotalNumberOfCommits()}`;
core.info(`Generated version ${version} (no version tags found).`);
return version;
}
const { tag, commits, hash } = await this.parseSemanticVersion();
core.info(`Found semantic version ${tag}.${commits} for ${this.branch}@${hash}`);
return `${tag}.${commits}`;
}
/**
* Generate the proper version for unity based on an existing tag.
*/
static async generateTagVersion() {
let tag = await this.getTag();
if (tag.charAt(0) === 'v') {
tag = tag.slice(1);
}
return tag;
}
/**
* Parses the versionDescription into their named parts.
*/
static async parseSemanticVersion() {
const description = await this.getVersionDescription();
try {
const [match, tag, commits, hash] = this.descriptionRegex.exec(description);
return {
match,
tag,
commits,
hash,
};
} catch (error) {
throw new Error(`Failed to parse git describe output: "${description}".`);
}
}
/**
* Retrieves refs from the configured remote.
*
* Fetch unshallow for incomplete repository, but fall back to normal fetch.
*
* Note: `--all` should not be used, and would break fetching for push event.
*/
static async fetch() {
try {
await System.run('git', ['fetch', '--unshallow']);
} catch (error) {
core.warning(error);
await System.run('git', ['fetch']);
}
}
/**
* Retrieves information about the branch.
*
* Format: `v0.12-24-gd2198ab`
*
* In this format v0.12 is the latest tag, 24 are the number of commits since, and gd2198ab
* identifies the current commit.
*/
static async getVersionDescription() {
return System.run('git', [
'describe',
'--long',
'--tags',
'--always',
'--debug',
`origin/${this.branch}`,
]);
}
/**
* Returns whether there are uncommitted changes that are not ignored.
*/
static async isDirty() {
const output = await System.run('git', ['status', '--porcelain']);
return output !== '';
}
/**
* Get the tag if there is one pointing at HEAD
*/
static async getTag() {
return System.run('git', ['tag', '--points-at', 'HEAD']);
}
/**
* Whether or not the repository has any version tags yet.
*/
static async hasAnyVersionTags() {
const numberOfCommitsAsString = await System.run('sh', undefined, {
input: Buffer.from('git tag --list --merged HEAD | grep v[0-9]* | wc -l'),
silent: false,
});
const numberOfCommits = Number.parseInt(numberOfCommitsAsString, 10);
return numberOfCommits !== 0;
}
/**
* Get the total number of commits on head.
*
* Note: HEAD should not be used, as it may be detached, resulting in an additional count.
*/
static async getTotalNumberOfCommits() {
const numberOfCommitsAsString = await System.run('git', [
'rev-list',
'--count',
`origin/${this.branch}`,
]);
return Number.parseInt(numberOfCommitsAsString, 10);
}
}

View File

@@ -0,0 +1,307 @@
import * as core from '@actions/core';
import NotImplementedException from './error/not-implemented-exception';
import System from './system';
import Versioning from './versioning';
afterEach(() => {
jest.restoreAllMocks();
});
describe('Versioning', () => {
describe('strategies', () => {
it('returns an object', () => {
expect(typeof Versioning.strategies).toStrictEqual('object');
});
it('has items', () => {
expect(Object.values(Versioning.strategies).length).toBeGreaterThan(2);
});
it('has an opt out option', () => {
expect(Versioning.strategies).toHaveProperty('None');
});
it('has the semantic option', () => {
expect(Versioning.strategies).toHaveProperty('Semantic');
});
it('has a strategy for tags', () => {
expect(Versioning.strategies).toHaveProperty('Tag');
});
it('has an option that allows custom input', () => {
expect(Versioning.strategies).toHaveProperty('Custom');
});
});
describe('branch', () => {
it('returns headRef when set', () => {
const headReference = jest
.spyOn(Versioning, 'headRef', 'get')
.mockReturnValue('feature-branch-1');
expect(Versioning.branch).toStrictEqual('feature-branch-1');
expect(headReference).toHaveBeenCalledTimes(1);
});
it('returns part of Ref when set', () => {
jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue(undefined);
const reference = jest
.spyOn(Versioning, 'ref', 'get')
.mockReturnValue('refs/heads/feature-branch-2');
expect(Versioning.branch).toStrictEqual('feature-branch-2');
expect(reference).toHaveBeenCalledTimes(2);
});
it('prefers headRef over ref when set', () => {
const headReference = jest
.spyOn(Versioning, 'headRef', 'get')
.mockReturnValue('feature-branch-1');
const reference = jest
.spyOn(Versioning, 'ref', 'get')
.mockReturnValue('refs/heads/feature-2');
expect(Versioning.branch).toStrictEqual('feature-branch-1');
expect(headReference).toHaveBeenCalledTimes(1);
expect(reference).toHaveBeenCalledTimes(0);
});
it('returns undefined when headRef and ref are not set', () => {
const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue(undefined);
const reference = jest.spyOn(Versioning, 'ref', 'get').mockReturnValue(undefined);
expect(Versioning.branch).not.toBeDefined();
expect(headReference).toHaveBeenCalledTimes(1);
expect(reference).toHaveBeenCalledTimes(1);
});
});
describe('headRef', () => {
it('does not throw', () => {
expect(() => Versioning.headRef).not.toThrow();
});
});
describe('ref', () => {
it('does not throw', () => {
expect(() => Versioning.ref).not.toThrow();
});
});
describe('descriptionRegex', () => {
it('is a valid regex', () => {
expect(Versioning.descriptionRegex).toBeInstanceOf(RegExp);
});
test.each(['v1.1-1-g12345678', 'v0.1-2-g12345678', 'v0.0-500-gA9B6C3D0-dirty'])(
'is happy with valid %s',
(description) => {
expect(Versioning.descriptionRegex.test(description)).toBeTruthy();
},
);
test.each([undefined, 'v0', 'v0.1', 'v0.1.2', 'v0.1-2', 'v0.1-2-g'])(
'does not like %s',
(description) => {
expect(Versioning.descriptionRegex.test(description)).toBeFalsy();
// Also never expect without the v to work for any of these cases.
expect(Versioning.descriptionRegex.test(description?.substr(1))).toBeFalsy();
},
);
});
describe('determineVersion', () => {
test.each([undefined, 0, 'somethingRandom'])(
'throws for invalid strategy %s',
async (strategy) => {
await expect(Versioning.determineVersion(strategy)).rejects.toThrowErrorMatchingSnapshot();
},
);
describe('opt out strategy', () => {
it("returns 'none'", async () => {
await expect(Versioning.determineVersion('None', 'v1.0')).resolves.toMatchInlineSnapshot(
`"none"`,
);
});
});
describe('custom strategy', () => {
test.each([undefined, 0, 'v0.1', '1', 'CamelCase', 'dashed-version'])(
'returns the inputVersion for %s',
async (inputVersion) => {
await expect(Versioning.determineVersion('Custom', inputVersion)).resolves.toStrictEqual(
inputVersion,
);
},
);
});
describe('semantic strategy', () => {
it('refers to generateSemanticVersion', async () => {
const generateSemanticVersion = jest
.spyOn(Versioning, 'generateSemanticVersion')
.mockResolvedValue('1.3.37');
await expect(Versioning.determineVersion('Semantic')).resolves.toStrictEqual('1.3.37');
expect(generateSemanticVersion).toHaveBeenCalledTimes(1);
});
});
describe('tag strategy', () => {
it('refers to generateTagVersion', async () => {
const generateTagVersion = jest
.spyOn(Versioning, 'generateTagVersion')
.mockResolvedValue('0.1');
await expect(Versioning.determineVersion('Tag')).resolves.toStrictEqual('0.1');
expect(generateTagVersion).toHaveBeenCalledTimes(1);
});
});
describe('not implemented strategy', () => {
it('throws a not implemented exception', async () => {
const strategy = 'Test';
jest.spyOn(Versioning, 'strategies', 'get').mockReturnValue({ [strategy]: strategy });
await expect(Versioning.determineVersion(strategy)).rejects.toThrowError(
NotImplementedException,
);
});
});
});
describe('generateTagVersion', () => {
it('removes the v', async () => {
jest.spyOn(Versioning, 'getTag').mockResolvedValue('v1.3.37');
await expect(Versioning.generateTagVersion()).resolves.toStrictEqual('1.3.37');
});
});
describe('parseSemanticVersion', () => {
it('returns the named parts', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('v0.1-2-g12345678');
await expect(Versioning.parseSemanticVersion()).resolves.toMatchObject({
tag: '0.1',
commits: '2',
hash: '12345678',
});
});
it('throws when no match could be made', async () => {
jest.spyOn(Versioning, 'getVersionDescription').mockResolvedValue('no-match-can-be-made');
await expect(Versioning.parseSemanticVersion()).rejects.toThrowErrorMatchingInlineSnapshot(
`"Failed to parse git describe output: \\"no-match-can-be-made\\"."`,
);
});
});
describe('getVersionDescription', () => {
it('returns the commands output', async () => {
const runOutput = 'someValue';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.getVersionDescription()).resolves.toStrictEqual(runOutput);
});
});
describe('fetch', () => {
it('awaits the command', async () => {
jest.spyOn(core, 'warning').mockImplementation(() => {});
jest.spyOn(System, 'run').mockResolvedValue(undefined);
await expect(Versioning.fetch()).resolves.not.toThrow();
});
it('falls back to the second strategy when the first fails', async () => {
jest.spyOn(core, 'warning').mockImplementation(() => {});
const gitFetch = jest
.spyOn(System, 'run')
.mockResolvedValue(undefined)
.mockRejectedValueOnce(undefined);
await expect(Versioning.fetch()).resolves.not.toThrow();
expect(gitFetch).toHaveBeenCalledTimes(2);
});
});
describe('generateSemanticVersion', () => {
it('returns a proper version from description', async () => {
jest.spyOn(System, 'run').mockResolvedValue(undefined);
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
jest.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(true);
jest.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(2);
jest.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
match: '0.1-2-g1b345678',
tag: '0.1',
commits: '2',
hash: '1b345678',
});
await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual('0.1.2');
});
it('throws when dirty', async () => {
jest.spyOn(System, 'run').mockResolvedValue(undefined);
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(true);
await expect(Versioning.generateSemanticVersion()).rejects.toThrowError();
});
it('falls back to commits only, when no tags are present', async () => {
const commits = Math.round(Math.random() * 10);
jest.spyOn(System, 'run').mockResolvedValue(undefined);
jest.spyOn(core, 'info').mockImplementation(() => {});
jest.spyOn(Versioning, 'isDirty').mockResolvedValue(false);
jest.spyOn(Versioning, 'hasAnyVersionTags').mockResolvedValue(false);
jest.spyOn(Versioning, 'getTotalNumberOfCommits').mockResolvedValue(commits);
await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual(`0.0.${commits}`);
});
});
describe('isDirty', () => {
it('returns true when there are files listed', async () => {
const runOutput = 'file.ext\nfile2.ext';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isDirty()).resolves.toStrictEqual(true);
});
it('returns false when there is no output', async () => {
const runOutput = '';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.isDirty()).resolves.toStrictEqual(false);
});
});
describe('getTag', () => {
it('returns the commands output', async () => {
const runOutput = 'v1.0';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.getTag()).resolves.toStrictEqual(runOutput);
});
});
describe('hasAnyVersionTags', () => {
it('returns false when the command returns 0', async () => {
const runOutput = '0';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(false);
});
it('returns true when the command returns >= 0', async () => {
const runOutput = '9';
jest.spyOn(System, 'run').mockResolvedValue(runOutput);
await expect(Versioning.hasAnyVersionTags()).resolves.toStrictEqual(true);
});
});
describe('getTotalNumberOfCommits', () => {
it('returns a number from the command', async () => {
jest.spyOn(System, 'run').mockResolvedValue('9');
await expect(Versioning.getTotalNumberOfCommits()).resolves.toStrictEqual(9);
});
});
});

4822
yarn.lock

File diff suppressed because it is too large Load Diff