mirror of
https://github.com/PaulHatch/semantic-version.git
synced 2025-12-27 13:08:17 +00:00
Compare commits
160 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1025b26f2 | ||
|
|
6b919cc3ae | ||
|
|
7b71828c01 | ||
|
|
bdf7908364 | ||
|
|
305899e7da | ||
|
|
c4f3793c16 | ||
|
|
7adc5c502c | ||
|
|
c423ebb784 | ||
|
|
a8f8f59fd7 | ||
|
|
8c0b779a80 | ||
|
|
4df56d00ce | ||
|
|
e528d273e7 | ||
|
|
23baf5d553 | ||
|
|
8d3552d384 | ||
|
|
f53462a96e | ||
|
|
fce0e75dfd | ||
|
|
ec20cad99a | ||
|
|
cfbfddabdd | ||
|
|
61963e734d | ||
|
|
d3c0da227f | ||
|
|
0995adf892 | ||
|
|
270924986e | ||
|
|
5f6f89c4e0 | ||
|
|
4fafb1f5a0 | ||
|
|
ba1fbef849 | ||
|
|
d93d2fb887 | ||
|
|
4f07cfb9e0 | ||
|
|
976ff820fc | ||
|
|
ce15f9a933 | ||
|
|
5a995f7e27 | ||
|
|
cc7cc19f01 | ||
|
|
d439666925 | ||
|
|
59b55a49a0 | ||
|
|
d3ce4be042 | ||
|
|
9a5d07b45d | ||
|
|
f9d3daa396 | ||
|
|
6a1b048e03 | ||
|
|
37aa192825 | ||
|
|
e27fda7711 | ||
|
|
9e89a29a4a | ||
|
|
e34c211152 | ||
|
|
1fb98ec223 | ||
|
|
ddf8faf6a4 | ||
|
|
61243c9221 | ||
|
|
8b3b8f89c6 | ||
|
|
346a6f2b12 | ||
|
|
a951df0155 | ||
|
|
6e136d69ea | ||
|
|
ea50fff3e4 | ||
|
|
d160cb59d5 | ||
|
|
999339635f | ||
|
|
0b58042494 | ||
|
|
2fa33887a3 | ||
|
|
5447a3c144 | ||
|
|
124679ea79 | ||
|
|
87f3035988 | ||
|
|
c7f5a74370 | ||
|
|
5003ad4622 | ||
|
|
4142d3bfe6 | ||
|
|
74fca7fef9 | ||
|
|
5a79aff975 | ||
|
|
02763ed6d3 | ||
|
|
ba6e71e658 | ||
|
|
92bc0a8ba4 | ||
|
|
d2341fffbc | ||
|
|
239974c98c | ||
|
|
8f634060c4 | ||
|
|
fc2c04be5c | ||
|
|
e1e99bd214 | ||
|
|
b505a7cf06 | ||
|
|
472c83c1ce | ||
|
|
8a595f42df | ||
|
|
ae4e812946 | ||
|
|
3a712b126c | ||
|
|
cd16d71443 | ||
|
|
0e3d4652d7 | ||
|
|
c5231eede4 | ||
|
|
dbd6d82818 | ||
|
|
2ee0b297bd | ||
|
|
0c47e4e106 | ||
|
|
d508520389 | ||
|
|
31f4e3fdf0 | ||
|
|
3d2ea28b25 | ||
|
|
dc8f0c5755 | ||
|
|
2c7f4b8ddd | ||
|
|
00b872db01 | ||
|
|
401ff16900 | ||
|
|
17c60feff5 | ||
|
|
cdc37631c1 | ||
|
|
2c0a2e32a8 | ||
|
|
dfbf3dfcfb | ||
|
|
27c5676700 | ||
|
|
8e99275875 | ||
|
|
e244f82296 | ||
|
|
57b16efd19 | ||
|
|
ed818a75eb | ||
|
|
d061f3fb0e | ||
|
|
1afa8eb537 | ||
|
|
01ccd0e687 | ||
|
|
ac2e147b35 | ||
|
|
2909e6bd51 | ||
|
|
63597febc6 | ||
|
|
6e48779949 | ||
|
|
13e0565bfd | ||
|
|
12cce3bff2 | ||
|
|
c81b8f3498 | ||
|
|
fccf69567f | ||
|
|
9475102a4a | ||
|
|
9e192e115f | ||
|
|
fada5c116a | ||
|
|
0de6177951 | ||
|
|
439a68181d | ||
|
|
0c009e37e1 | ||
|
|
1412b429b4 | ||
|
|
80758ecbf6 | ||
|
|
959e71ff1c | ||
|
|
32d6426aef | ||
|
|
bd43de5619 | ||
|
|
316dd04102 | ||
|
|
8430b15d4c | ||
|
|
b75f063d41 | ||
|
|
06e32738a3 | ||
|
|
c47459a3b9 | ||
|
|
1f05445eed | ||
|
|
ebcb352d0b | ||
|
|
b38e4671e9 | ||
|
|
65140edf77 | ||
|
|
5c67e25f43 | ||
|
|
4160681a29 | ||
|
|
866aab0885 | ||
|
|
8ce630d670 | ||
|
|
ae095094f7 | ||
|
|
48bb665d5c | ||
|
|
0383dec643 | ||
|
|
bcb0692ebb | ||
|
|
800ea493f7 | ||
|
|
596215aec5 | ||
|
|
858da7b39c | ||
|
|
2dba8dd7da | ||
|
|
2311db86bb | ||
|
|
bfa36c6087 | ||
|
|
e7dede1b52 | ||
|
|
987b40ae05 | ||
|
|
a600f925a2 | ||
|
|
917165076c | ||
|
|
7f624e6710 | ||
|
|
02c6ec5e4f | ||
|
|
c7b61df97d | ||
|
|
8350354c9c | ||
|
|
ef6fe2de1c | ||
|
|
1ce73af6aa | ||
|
|
6df55e478b | ||
|
|
c27e70c121 | ||
|
|
4f2e9fedc3 | ||
|
|
2b9a4e8857 | ||
|
|
bedbb2c292 | ||
|
|
e4de6a96b5 | ||
|
|
709e271b17 | ||
|
|
71e429646c | ||
|
|
c50a3c86c6 |
87 changed files with 16328 additions and 5706 deletions
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
|
|
@ -1,26 +1,35 @@
|
||||||
name: "test-local"
|
name: "Build"
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- "releases/*"
|
- "feature/*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
name: "Test and Run"
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Test
|
- name: Test
|
||||||
run: git config --global user.name "Test User" && git config --global user.email "test@example.com" && npm test
|
run: npm test
|
||||||
- name: Package
|
- name: Package
|
||||||
run: npm run package
|
run: npm run package
|
||||||
- name: Run Action
|
- name: Run Action
|
||||||
uses: ./
|
uses: ./
|
||||||
id: run
|
id: run
|
||||||
with:
|
with:
|
||||||
main-branch: master
|
debug: true
|
||||||
release-branch: release
|
- name: Print Diagnostic Output
|
||||||
|
run: echo $DEBUG_OUTPUT
|
||||||
|
env:
|
||||||
|
DEBUG_OUTPUT: ${{ steps.run.outputs.debug_output }}
|
||||||
|
|
|
||||||
281
CHANGELOG.md
Normal file
281
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [5.4.0] - 2024-01-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated to Node.js v20 runtime
|
||||||
|
- Updated all dependencies to latest versions
|
||||||
|
|
||||||
|
## [5.3.0] - 2023-09-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Branch-based versioning mode** (`version_from_branch` input) - Major/minor versions can now be derived from branch names (e.g., `release/v1`, `release/1.2`). Only considers tags matching the branch version, useful for maintaining multiple release lines
|
||||||
|
- Enhanced diagnostics documentation in contributing guide
|
||||||
|
- Improved warning messages to clarify when no tags are found vs when tags exist but don't match criteria
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `GITHUB_REF_NAME` environment variable no longer causes failures during testing
|
||||||
|
- `bump_each_commit` now properly respects `enable_prerelease_mode` setting
|
||||||
|
- Non-version branches are properly ignored when using branch-based versioning
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated Jest configuration for better test isolation
|
||||||
|
- Rebuilt distribution files with latest changes
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
- `use_branches` input is deprecated and will be removed in v6.0.0 - use `version_from_branch` instead
|
||||||
|
|
||||||
|
## [5.2.1] - 2023-08-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Diagnostic mode output was not being properly included in the action's output, preventing debugging
|
||||||
|
|
||||||
|
## [5.2.0] - 2023-08-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Debug/diagnostic mode** (`debug` input) - Captures and outputs diagnostic information for troubleshooting version calculations. Useful when the source repository isn't available for direct inspection
|
||||||
|
|
||||||
|
## [5.1.0] - 2023-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Patch pattern filtering** (`bump_each_commit_patch_pattern` input) - When using `bump_each_commit`, patch version only increments if commit matches specified pattern. Supports JavaScript regex syntax with flags (e.g., `/fix\(.*\)/i`)
|
||||||
|
- **Pre-release mode** (`enable_prerelease_mode` input) - Prevents automatic major version bumps for 0.x.x versions. When enabled, "major" changes become "minor" and "minor" become "patch", preventing premature 1.0.0 releases
|
||||||
|
- `is_tagged` output - Boolean indicating if the current commit already has a version tag
|
||||||
|
- Previous version commit information outputs (`previous_commit`, `previous_version`) for better version tracking
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Corrected tag ordering when determining previous version (was using reverse order incorrectly)
|
||||||
|
- Increased test timeout for Windows environments to prevent CI failures
|
||||||
|
- Fixed test failures in environments with global GPG signing enabled
|
||||||
|
- Documentation typo: "version" output name was incorrectly documented
|
||||||
|
|
||||||
|
## [5.0.3] - 2023-01-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Pre-release tags on current commit were not being handled correctly when determining version increments
|
||||||
|
- Fixed incorrect parameter name mapping that was causing action failures
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies to latest versions
|
||||||
|
|
||||||
|
## [5.0.2] - 2022-12-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Build output mapping was incorrect, causing the action to fail when generating outputs
|
||||||
|
|
||||||
|
## [5.0.1] - 2022-12-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Tag prefix and namespace values are now properly escaped when constructing regex patterns, preventing regex errors with special characters
|
||||||
|
- Fixed unescaped dots in regex patterns that could cause incorrect matching
|
||||||
|
- Test suite now consistently uses 'master' as branch name to avoid CI failures
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Syntax highlighting for code examples in documentation
|
||||||
|
- Contributing.md guide for developers
|
||||||
|
- Test coverage for namespaces containing forward slashes
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Documentation updated to reflect correct input parameter names
|
||||||
|
- Version calculation no longer limits the number of tags retrieved, ensuring accurate version determination in repos with many tags
|
||||||
|
|
||||||
|
## [5.0.0] - 2022-12-20 - Major Rewrite
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Complete TypeScript rewrite** - Action rewritten from JavaScript to TypeScript with modular architecture
|
||||||
|
- **Author tracking** - New `authors` output lists all commit authors since last release, formatted as CSV (JSON option available via `user_format_type`)
|
||||||
|
- **Commit body searching** - `search_commit_body` input allows searching commit message bodies for version patterns, not just the subject line
|
||||||
|
- **Branch support** - Can now use branch names instead of tags for versioning with `use_branches` input
|
||||||
|
- **Improved outputs** - Additional metadata including `version_type`, commit hashes, and more detailed version information
|
||||||
|
- **Namespace support without tags** - Namespaces now work even when no existing tags match the namespace
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Pre-release tags (alpha, beta, rc) are now properly excluded from version calculations unless explicitly included
|
||||||
|
- Fixed issue where current commit's tag wasn't properly considered when calculating previous version
|
||||||
|
- Tag ordering now uses git's version sort instead of author date, providing more accurate version ordering
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Architecture completely redesigned with providers, resolvers, classifiers, and formatters for better extensibility
|
||||||
|
- Short tag support has been completely removed (was deprecated in v4)
|
||||||
|
- Updated to actions/core@1.10.0 and modernized all dependencies
|
||||||
|
- Node.js 16 compatibility
|
||||||
|
|
||||||
|
## [4.0.3] - 2021-10-29
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Version output now properly uses the user-supplied version format template combined with namespace
|
||||||
|
- Updated dependencies and improved test coverage
|
||||||
|
- Documentation clarifications for better user understanding
|
||||||
|
|
||||||
|
## [4.0.2] - 2021-04-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Tag prefixes can now contain forward slashes (e.g., `releases/v`), enabling more flexible tagging schemes
|
||||||
|
|
||||||
|
## [4.0.1] - 2021-02-25
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed regex pattern for matching full version tags when `short_tags` is disabled
|
||||||
|
|
||||||
|
## [4.0.0] - 2021-02-08
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Breaking**: Branch parameter now defaults to `HEAD` instead of requiring explicit branch name
|
||||||
|
- Branch names no longer include `origin/` prefix, simplifying branch-based versioning
|
||||||
|
- Reintroduced support for using `HEAD` as branch parameter (was removed in v3)
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
- `branch` input is now deprecated in favor of automatic HEAD detection
|
||||||
|
|
||||||
|
## [3.3.1] - 2021-01-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `version_tag` output now includes namespace value, making it easier to identify versioned releases in multi-project repositories
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved documentation clarity for namespace feature
|
||||||
|
- Enhanced readme formatting and examples
|
||||||
|
|
||||||
|
## [3.3.0] - 2021-01-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Regular expression support** for `major_pattern` and `minor_pattern` - Wrap patterns in `/` to use regex (e.g., `/breaking:\s/i`)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed logic that prevented version tags from being properly matched when calculating increments
|
||||||
|
|
||||||
|
## [3.2.1] - 2021-01-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Tagged commits now properly preserve their increment value instead of resetting to 0
|
||||||
|
- SVG diagrams now have proper background color for better visibility
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies
|
||||||
|
- Documentation improvements
|
||||||
|
|
||||||
|
## [3.2.0] - 2020-12-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **`bump_each_commit` mode** - Every commit creates a new patch version, useful for continuous deployment scenarios
|
||||||
|
- **`short_tags` toggle** - When set to `false`, only full semantic version tags (e.g., v1.2.3) are considered, ignoring short tags (e.g., v1)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved documentation with visual diagrams
|
||||||
|
- Removed deprecated parameters from documentation
|
||||||
|
- Enhanced readme clarity with better examples
|
||||||
|
|
||||||
|
## [3.1.2] - 2020-10-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Full Windows support** - Fixed line ending issues and command execution on Windows
|
||||||
|
- Action now properly exits when current commit already has a version tag
|
||||||
|
- Current commit's tag is now used as the version when applicable
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Complete Windows support in test suite with OS-specific temp directories
|
||||||
|
- Windows runner added to CI pipeline alongside Linux
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Commands now run silently to reduce log noise
|
||||||
|
- Improved error handling for command execution failures
|
||||||
|
- Added warning about actions/checkout@v2 shallow clone behavior that can affect version detection
|
||||||
|
|
||||||
|
## [3.1.1] - 2020-09-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Change detection now works correctly when no previous tags exist in the repository
|
||||||
|
|
||||||
|
## [3.1.0] - 2020-09-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **`version_tag` output** - Returns the complete version tag including prefix and namespace
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved logging for change detection to help with debugging
|
||||||
|
- Command execution failures are now logged as info rather than errors (they're handled gracefully)
|
||||||
|
- Updated package dependencies
|
||||||
|
|
||||||
|
## [3.0.0] - 2020-09-02 - Multi-Project Support
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Namespace support** (`namespace` input) - Enables multiple projects/components in same repo with isolated versioning
|
||||||
|
- **Improved mono-repo support** - Each namespace maintains its own version sequence
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Breaking**: `change_path` input now filters which paths trigger version changes rather than just detecting changes
|
||||||
|
- Removed verbose action output for cleaner logs
|
||||||
|
- Modernized codebase and dependencies for GitHub Actions runner compatibility
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Deprecated action inputs from v2
|
||||||
|
|
||||||
|
## [2.1.1] - 2020-02-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Release link generation now uses correct branch name format
|
||||||
|
|
||||||
|
## [2.1.0] - 2020-01-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Path-based change detection** (`change_path` input) - Specify paths to monitor for changes, useful for mono-repos where not all changes should trigger version bumps
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Release link now uses branch name from action input rather than GitHub environment variable
|
||||||
|
- Release link is now output to action logs for visibility
|
||||||
|
|
||||||
|
## [2.0.0] - 2019-12-24
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Breaking**: Now uses `git describe` for more reliable tag detection instead of `git log`
|
||||||
|
- Added warning when repository has no tags, helping users understand why versioning starts at 0.0.0
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-12-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Empty tag prefixes are now supported (useful for repos that use plain version numbers without 'v' prefix)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Documentation for `version_format` input parameter
|
||||||
|
|
||||||
|
## [1.0.0] - 2019-12-11 - Initial Release
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Automatic semantic versioning based on git commit history
|
||||||
|
- Version bumping through commit message markers: `(MAJOR)` and `(MINOR)`
|
||||||
|
- Customizable version output format via `version_format` input
|
||||||
|
- Support for both short (v1) and full (v1.0.0) version tags
|
||||||
|
- Increment counter for commits since last version tag
|
||||||
|
- No manual version maintenance required - fully automated from git history
|
||||||
|
|
||||||
|
[5.4.0]: https://github.com/PaulHatch/semantic-version/compare/v5.3.0...v5.4.0
|
||||||
|
[5.3.0]: https://github.com/PaulHatch/semantic-version/compare/v5.2.1...v5.3.0
|
||||||
|
[5.2.1]: https://github.com/PaulHatch/semantic-version/compare/v5.2.0...v5.2.1
|
||||||
|
[5.2.0]: https://github.com/PaulHatch/semantic-version/compare/v5.1.0...v5.2.0
|
||||||
|
[5.1.0]: https://github.com/PaulHatch/semantic-version/compare/v5.0.3...v5.1.0
|
||||||
|
[5.0.3]: https://github.com/PaulHatch/semantic-version/compare/v5.0.2...v5.0.3
|
||||||
|
[5.0.2]: https://github.com/PaulHatch/semantic-version/compare/v5.0.1...v5.0.2
|
||||||
|
[5.0.1]: https://github.com/PaulHatch/semantic-version/compare/v5.0.0...v5.0.1
|
||||||
|
[5.0.0]: https://github.com/PaulHatch/semantic-version/compare/v4.0.3...v5.0.0
|
||||||
|
[4.0.3]: https://github.com/PaulHatch/semantic-version/compare/v4.0.2...v4.0.3
|
||||||
|
[4.0.2]: https://github.com/PaulHatch/semantic-version/compare/v4.0.1...v4.0.2
|
||||||
|
[4.0.1]: https://github.com/PaulHatch/semantic-version/compare/v4...v4.0.1
|
||||||
|
[4.0.0]: https://github.com/PaulHatch/semantic-version/compare/v3.3.1...v4
|
||||||
|
[3.3.1]: https://github.com/PaulHatch/semantic-version/compare/v3.3...v3.3.1
|
||||||
|
[3.3.0]: https://github.com/PaulHatch/semantic-version/compare/v3.2.1...v3.3
|
||||||
|
[3.2.1]: https://github.com/PaulHatch/semantic-version/compare/v3.2...v3.2.1
|
||||||
|
[3.2.0]: https://github.com/PaulHatch/semantic-version/compare/v3.1.2...v3.2
|
||||||
|
[3.1.2]: https://github.com/PaulHatch/semantic-version/compare/v3.1.1...v3.1.2
|
||||||
|
[3.1.1]: https://github.com/PaulHatch/semantic-version/compare/v3.1...v3.1.1
|
||||||
|
[3.1.0]: https://github.com/PaulHatch/semantic-version/compare/v3...v3.1
|
||||||
|
[3.0.0]: https://github.com/PaulHatch/semantic-version/compare/v2.1.1...v3
|
||||||
|
[2.1.1]: https://github.com/PaulHatch/semantic-version/compare/v2.1...v2.1.1
|
||||||
|
[2.1.0]: https://github.com/PaulHatch/semantic-version/compare/v2...v2.1
|
||||||
|
[2.0.0]: https://github.com/PaulHatch/semantic-version/compare/v1.0.1...v2
|
||||||
|
[1.0.1]: https://github.com/PaulHatch/semantic-version/compare/v1...v1.0.1
|
||||||
|
[1.0.0]: https://github.com/PaulHatch/semantic-version/releases/tag/v1
|
||||||
82
action.yml
82
action.yml
|
|
@ -5,26 +5,71 @@ branding:
|
||||||
color: "blue"
|
color: "blue"
|
||||||
inputs:
|
inputs:
|
||||||
branch:
|
branch:
|
||||||
description: "The branch name"
|
description: "Set to specify a specific branch, default is the current HEAD"
|
||||||
required: true
|
required: true
|
||||||
default: "master"
|
default: "HEAD"
|
||||||
tag_prefix:
|
tag_prefix:
|
||||||
description: "The prefix to use to identify tags"
|
description: "The prefix to use to identify tags"
|
||||||
required: false
|
required: false
|
||||||
default: "v"
|
default: "v"
|
||||||
|
use_branches:
|
||||||
|
description: "(Deprecated) Use branches instead of tags"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
version_from_branch:
|
||||||
|
description: If true, the branch will be used to select the maximum version
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
major_pattern:
|
major_pattern:
|
||||||
description: "a string which, if present in a git commit, indicates that a change represents a major (breaking) change"
|
description: "A string which, if present in a git commit, indicates that a change represents a major (breaking) change. Wrap with '/' to match using a regular expression."
|
||||||
required: true
|
required: true
|
||||||
default: "(MAJOR)"
|
default: "/!:|BREAKING CHANGE:/"
|
||||||
|
major_regexp_flags:
|
||||||
|
description: "A string which indicates the flags used by the `major_pattern` regular expression. Supported flags: idgs"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
minor_pattern:
|
minor_pattern:
|
||||||
description: "a string which, if present in a git commit, indicates that a change represents a minor (feature) change"
|
description: "A string which, if present in a git commit, indicates that a change represents a minor (feature) change. Wrap with '/' to match using a regular expression."
|
||||||
required: true
|
required: true
|
||||||
default: "(MINOR)"
|
default: "/feat:/"
|
||||||
format:
|
minor_regexp_flags:
|
||||||
|
description: "A string which indicates the flags used by the `minor_pattern` regular expression. Supported flags: idgs"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
version_format:
|
||||||
description: "Pattern to use when formatting output version"
|
description: "Pattern to use when formatting output version"
|
||||||
required: true
|
required: true
|
||||||
default: "${major}.${minor}.${patch}"
|
default: "${major}.${minor}.${patch}"
|
||||||
|
change_path:
|
||||||
|
description: "Path to check for changes. If any changes are detected in the path the 'changed' output will true. Enter multiple paths separated by spaces."
|
||||||
|
required: false
|
||||||
|
namespace:
|
||||||
|
description: "Use to create a named sub-version. This value will be appended to tags created for this version."
|
||||||
|
required: false
|
||||||
|
bump_each_commit:
|
||||||
|
description: "If true, every commit will be treated as a bump to the version."
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
|
search_commit_body:
|
||||||
|
description: "If true, the body of commits will also be searched for major/minor patterns to determine the version type."
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
|
user_format_type:
|
||||||
|
description: "The output method used to generate list of users, 'csv' or 'json'. Default is 'csv'."
|
||||||
|
required: true
|
||||||
|
default: "csv"
|
||||||
|
enable_prerelease_mode:
|
||||||
|
description: "Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged."
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
|
bump_each_commit_patch_pattern:
|
||||||
|
description: "If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched."
|
||||||
|
required: true
|
||||||
|
default: ""
|
||||||
|
debug:
|
||||||
|
description: "If enabled, diagnostic information will be added to the action output"
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
outputs:
|
outputs:
|
||||||
major:
|
major:
|
||||||
description: "Current major number"
|
description: "Current major number"
|
||||||
|
|
@ -34,8 +79,27 @@ outputs:
|
||||||
description: "Current patch number"
|
description: "Current patch number"
|
||||||
increment:
|
increment:
|
||||||
description: "An additional value indicating the number of commits for the current version"
|
description: "An additional value indicating the number of commits for the current version"
|
||||||
|
version_type:
|
||||||
|
description: "Indicates the type of change this version represents vs the previous, e.g. 'major', 'minor', 'patch', or 'none'"
|
||||||
version:
|
version:
|
||||||
description: "The version result, in the format {major}.{minor}.{patch}"
|
description: "The version result, in the format {major}.{minor}.{patch}"
|
||||||
|
version_tag:
|
||||||
|
description: "The version tag"
|
||||||
|
changed:
|
||||||
|
description: "Indicates whether there was a change since the last version if change_path was specified. If no change_path was specified this value will always be true since the entire repo is considered."
|
||||||
|
is_tagged:
|
||||||
|
description: "Indicates that the commit had a tag that matched the 'versionTag' format"
|
||||||
|
authors:
|
||||||
|
description: "List of users contributing commits to this version"
|
||||||
|
previous_commit:
|
||||||
|
description: "Hash of the previous commit"
|
||||||
|
previous_version:
|
||||||
|
description: "Indicates the previous version"
|
||||||
|
current_commit:
|
||||||
|
description: "The current commit hash"
|
||||||
|
debug_output:
|
||||||
|
description: "Diagnostic information, if debug is enabled"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "node12"
|
using: "node20"
|
||||||
main: "dist/index.js"
|
main: "dist/index.js"
|
||||||
105
contributing.md
Normal file
105
contributing.md
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Fixes and enhancements are welcome, but if you are planning to do a lot of work, it is a good idea to raise an issue first to discuss it. Generally enhancements should follow the goals of the project described in the main readme:
|
||||||
|
|
||||||
|
- Allow the version to be injected into the build
|
||||||
|
- Derive the version only from the git repository itself
|
||||||
|
- Do not require the version to be maintained by hand
|
||||||
|
- Resolve the version deterministically for a given commit
|
||||||
|
- Provide an easy mechanism for incrementing major and minor versions by developers
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
If you have found or believe you have found a bug please open a ticket. If you are having trouble using the action and need help please use the discussions page.
|
||||||
|
|
||||||
|
Since nearly all questions are related to a specific repository it can be difficult to diagnose issues from a description alone. There are a few ways to provide additional information that can help diagnose the problem.
|
||||||
|
|
||||||
|
### Creating Diagnostic Information
|
||||||
|
|
||||||
|
There is a debug option which produces diagnostic information. This information can be used to troubleshoot and even to rerun the action without access to the original repository, for example with a debugger attached. To enable this option set the `debug` input to `true` and then use the `debug_output` output to access the information. The following configuration will print the debug output to the console:
|
||||||
|
|
||||||
|
```
|
||||||
|
- name: Version
|
||||||
|
uses: paulhatch/semantic-version@v5.4.0
|
||||||
|
id: version
|
||||||
|
with:
|
||||||
|
tag_prefix: ""
|
||||||
|
version_format: "${major}.${minor}.${patch}.${increment}"
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
- name: Print Diagnostic Output
|
||||||
|
run: echo "$DEBUG_OUTPUT"
|
||||||
|
env:
|
||||||
|
DEBUG_OUTPUT: ${{ steps.version.outputs.debug_output }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please review the information before posting it to avoid disclosing any sensitive information. In particular the output may contain names and email addresses of the committers, as well as commit messages for recent commits.
|
||||||
|
|
||||||
|
### Providing a Test Case
|
||||||
|
|
||||||
|
If you are planning to open a ticket or post to discussions, it is extremely helpful if you can provide a test case that demonstrates the problem. This project includes a test helper than makes it very easy to create new tests with just a few lines of code.
|
||||||
|
|
||||||
|
To get started simply:
|
||||||
|
|
||||||
|
- Ensure you have the latest version of NodeJS installed (https://nodejs.org)
|
||||||
|
- Clone the repository, `git clone https://github.com/PaulHatch/semantic-version.git` or `git@github.com:PaulHatch/semantic-version.git`
|
||||||
|
- Run `npm install` in the root of the repository to install the dependencies
|
||||||
|
- Run `npm run test` to run the tests
|
||||||
|
|
||||||
|
The src/main.test.ts file contains integration tests that validate all the features of this action, to add a new test case simply add a new function to the bottom of this file.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
test('Name of test goes here', async () => {
|
||||||
|
// This method creates a test repository in your temp directory, the repo
|
||||||
|
// object returned provides methods to interact with the repository
|
||||||
|
const repo = createTestRepo({
|
||||||
|
// Specify any config options you want to set, the options available can be found
|
||||||
|
// in the src/ActionConfig.ts file
|
||||||
|
tagPrefix: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// the make commit method creates a commit with the specified message, an empty file will be
|
||||||
|
// automatically created for the commit
|
||||||
|
repo.makeCommit('Initial Commit');
|
||||||
|
|
||||||
|
// an optional second parameter can be used to specify the path of the file to commit,
|
||||||
|
// which will be created if it does not exist already
|
||||||
|
repo.makeCommit('Initial Commit', 'subdir');
|
||||||
|
|
||||||
|
// the exec method runs an arbitrary command in the repository
|
||||||
|
repo.exec('git tag 0.0.1')
|
||||||
|
|
||||||
|
// the runAction method runs the action and returns the result
|
||||||
|
const result = await repo.runAction();
|
||||||
|
|
||||||
|
// finally, the use whatever assertion you want to validate the result
|
||||||
|
expect(result.formattedVersion).toBe('0.0.1+0');
|
||||||
|
}, 15000);
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses the [Jest](https://jestjs.io/) testing framework. Really it is generally not necessary to be very familiar with Jest, just copy the pattern of the existing tests and you should be fine.
|
||||||
|
|
||||||
|
Once you have a failing test case that demostrates the problem, you can just past it into the ticket, there is no need to create a repository unless you want to.
|
||||||
|
|
||||||
|
|
||||||
|
### Providing an Example Repository
|
||||||
|
|
||||||
|
If you are not able to provide a test case, it is still very helpful to provide an example repository that demonstrates the problem. Many times people are experiencing problems with a configuration of a private repository. This is quite difficult to debug without access to the repository, if you are able to provide a public repository that demonstrates the problem it will make it much easier to debug and can eliminate a great deal of back and forth.
|
||||||
|
|
||||||
|
## Forking the Project
|
||||||
|
|
||||||
|
If want to do something that does not fit with the project goals, particually if you want to include information from an outside system, it is probably best to fork the project and create your own action. One of the goals of this project starting in 5.0.0 is to be easy to fork and customize, and to that end the action has been broken into individual providers than can be replaced. All providers have been implemented using async calls specifically to support calls to external systems.
|
||||||
|
|
||||||
|
The steps of this action are:
|
||||||
|
|
||||||
|
- Get the current commit
|
||||||
|
- Get the last release
|
||||||
|
- Get commits between the last release and the current commit
|
||||||
|
- Classify the release
|
||||||
|
|
||||||
|
Additionally a few formatter provide modular behavior to these step:
|
||||||
|
- A tag formmater is used to parse and format the version number
|
||||||
|
- A version formatter is used to format output version string
|
||||||
|
- A user formatter is used to format the user information in the output (JSON and CSV are provided in the default implementation)
|
||||||
|
|
||||||
|
Each one includes at least one default, but can be replaced by a custom provider by implementing the appropriate interface and updating the `ConfigurationProvider` to return your action instead. This should allow you to continue to merge in changes from the main project as needed with minimal conflict.
|
||||||
5353
dist/index.js
vendored
5353
dist/index.js
vendored
File diff suppressed because it is too large
Load diff
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
97
dist/licenses.txt
vendored
Normal file
97
dist/licenses.txt
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
@actions/core
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2019 GitHub
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@actions/exec
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2019 GitHub
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
@actions/http-client
|
||||||
|
MIT
|
||||||
|
Actions Http Client for Node.js
|
||||||
|
|
||||||
|
Copyright (c) GitHub, Inc.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
@actions/io
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2019 GitHub
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
tunnel
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2012 Koichi Kobayashi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
uuid
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
1
dist/sourcemap-register.js
vendored
Normal file
1
dist/sourcemap-register.js
vendored
Normal file
File diff suppressed because one or more lines are too long
106
guide.md
Normal file
106
guide.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Configuration Guide
|
||||||
|
|
||||||
|
## Choosing a Release Strategy
|
||||||
|
|
||||||
|
This section is designed to help you choose a release strategy for your project and help you configure GitHub Workflow to use that strategy. It is organized starting from the most simple with each strategy supporting more complex needs, allowing you to start at the top and continue until you find the simplest strategy that meets your needs.
|
||||||
|
|
||||||
|
Note that in the examples given `latest` is used, but you will likely want to pin your version to a specific version.
|
||||||
|
|
||||||
|
### Increment Every Release
|
||||||
|
|
||||||
|
If your project has no gating requirements and you want to release every time a commit is pushed to the default branch, you can use the _Increment Every Release_ strategy. This may be appropriate for documentation projects, very small projects, or in cases where "shipping" a broken version isn't a big deal. The key limitation of this strategy is that once you push a commit, the version is going to increments no matter what. If you push a version and your build or automated tests fail, you'll have a version that is broken and you'll have to increment the version again to fix it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: paulhatch/semantic-version@latest
|
||||||
|
with:
|
||||||
|
bump_each_commit: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Increment from Commit Message
|
||||||
|
|
||||||
|
Very similar to the strategy above, using the _Increment from Commit Message_ means that you are making the decision to increment the version at the time you commit the code, however by using the `bump_each_commit_patch_pattern` parameter introduced in v5.1.0, you can prevent the version from incrementing for a commit unless it matches one of the patters (major, minor, or patch).
|
||||||
|
|
||||||
|
Compared to the _Increment Every Release_ strategy, this strategy allows you to make a decision about whether or not to increment the version for a particular commit, allowing you to add commits to a repo that do not increment the version. Again, you make the decision to increment the version at the time you commit the code, so this strategy may not be appropriate for some project types.
|
||||||
|
|
||||||
|
On the other hand, if you have a fast deployment strategy, such as "every commit goes to prod" and don't mind versions being created for failed builds, this may be the right choice.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: paulhatch/semantic-version@latest
|
||||||
|
with:
|
||||||
|
bump_each_commit: true
|
||||||
|
bump_each_commit_patch_pattern: "(PATCH)"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Tag Versioning
|
||||||
|
|
||||||
|
This strategy is the most common and is the best option for many projects. It allows you to make the decision to release a version after the build has run, which is essentially the primary motivation and main purpose for this action.
|
||||||
|
|
||||||
|
The only real limitation of this strategy is that it does not allow for multiple versions to receive ongoing updates, which may be necessary for certain types of projects which are distributed and receive ongoing maintenance of older versions. This is in contrast to projects that are developed only for a single deployment and are not distributed.
|
||||||
|
|
||||||
|
Tags should generally not be created automatically as part of the build, which can cause strange behavior unless you've taken care to prevent race conditions. Creating tags automatically also largely negates the purpose of this strategy.
|
||||||
|
|
||||||
|
_This is the default behavior, so no special options are required._
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: paulhatch/semantic-version@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Branch + Tag Versioning
|
||||||
|
|
||||||
|
So far all the options considered have assumed that a single, ever incrementing version is being released, and that once a new major or minor version is tagged no further updates are made for previous versions. This is appropriate for many projects such as web applications and most libraries, however if you need to support on-going update to multiple major or major+minor versions, using only the approaches above can lead to problems if you are merging updates into multiple branches, as any tags may be picked up and cause the version of an older branch to unexpectedly jump.
|
||||||
|
|
||||||
|
To accomplish this, we can enable the `version_from_branch`, which will cause the major and optionally the minor version to be taken from the current branch name rather than the tag, and to filter out tags that do not begin with the same version number(s). The `version_from_branch` input can be either a boolean or a regex string to be used to identify the version from the branch name. By default this will be `[0-9]+.[0-9]+$|[0-9]+$` e.g. match the final number or pair of numbers separated by a `.`. This default is probably appropriate for the majority of cases as it will match any prefix, for example branches named:
|
||||||
|
|
||||||
|
- `release/v1`
|
||||||
|
- `release/v1.2`
|
||||||
|
- `v1`
|
||||||
|
- `v1.2`
|
||||||
|
- `1`
|
||||||
|
- `1.2`
|
||||||
|
|
||||||
|
Note that when using this strategy you should always tag at the same time as the branch is created to ensure that the increment value is correct.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: paulhatch/semantic-version@latest
|
||||||
|
with:
|
||||||
|
version_from_branch: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternately, you can override the branch pattern.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: paulhatch/semantic-version@latest
|
||||||
|
with:
|
||||||
|
version_from_branch: "/v([0-9]+.[0-9]+$|[0-9]+)$/"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Namespace Services / "Monorepo" Support
|
||||||
|
|
||||||
|
If your project contains multiple services which you wish to version independently, you can use the `namespace` and `change_path` inputs to provide a version for a specific service which increments only when a file in the specified path is changed. (Or, if you are only build on push/pull requests you can just use the GitHub Action's [`paths`/`paths-ignore`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onpushpull_requestpull_request_targetpathspaths-ignore) feature to block the trigger itself and run the workflow only when files in a specific path are changed. In contrast this method will also work on other triggers like `workflow_dispatch`.)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- id: version
|
||||||
|
uses: paulhatch/semantic-version@latest
|
||||||
|
with:
|
||||||
|
change_path: "src/my-service"
|
||||||
|
namespace: my-service
|
||||||
|
- name: Cancel if Unchanged
|
||||||
|
if: ${{ ! fromJSON(steps.version.outputs.changed) }}
|
||||||
|
run: |
|
||||||
|
gh run cancel ${{ github.run_id }}
|
||||||
|
gh run watch ${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Configuration
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `tag_prefix` | The prefix to use for the tag. Defaults to `v`, generally you will use either `v` or an empty string. Note that the tag format is distinct from the version. Tags used for versioning must always follow the pattern `{tag_prefix}{major}.{minor}.{patch}` with and optional `-{namespace}` suffix. |
|
||||||
|
| `major_pattern` and `minor_pattern` | These strings are used to determine the type of version to create. If any commit message since matches the `major_pattern` the major version will be incremented, if it matches the `minor_pattern` the minor version will be incremented. If neither pattern matches, the patch version will be incremented. These can be specified either as strings or as regular expression by wrapping the expression in `/`. The defaults follow [Conventional Commits](https://www.conventionalcommits.org/): `/!:|BREAKING CHANGE:/` for major and `/feat:/` for minor. |
|
||||||
|
| `version_format` | A value such as `${major}.${minor}.${patch}-prerelease${increment}` that will be used to format the version value of the output, **formatting this value is the only effect of this input parameter!** It is not used for parsing or any other purpose. It is a convenient alternative to formatting the output in a subsequent step. |
|
||||||
|
| `user_format_type` | Indicates the format of the `authors` output. Can be `json` or `yaml`. |
|
||||||
|
| `enable_prerelease_mode` | If true, major changes to versions starting with 0 will result in a minor change, preventing ths initial version `1.0.0`` from being created automatically by someone checking in a commit with the major pattern. |
|
||||||
141
index.js
141
index.js
|
|
@ -1,141 +0,0 @@
|
||||||
const core = require('@actions/core');
|
|
||||||
const exec = require("@actions/exec");
|
|
||||||
const eol = require('os').EOL;
|
|
||||||
|
|
||||||
const cmd = async (command, ...args) => {
|
|
||||||
let output = '';
|
|
||||||
const options = {
|
|
||||||
silent: true
|
|
||||||
};
|
|
||||||
options.listeners = {
|
|
||||||
stdout: (data) => { output += data.toString(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
await exec.exec(command, args, options)
|
|
||||||
.catch(err => { core.error(`${command} ${args.join(' ')} failed: ${err}`); throw err; });
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setOutput = (major, minor, patch, increment) => {
|
|
||||||
const format = core.getInput('format', { required: true });
|
|
||||||
var version = format
|
|
||||||
.replace('${major}', major)
|
|
||||||
.replace('${minor}', minor)
|
|
||||||
.replace('${patch}', patch)
|
|
||||||
.replace('${increment}', increment);
|
|
||||||
|
|
||||||
core.info(`Version is ${major}.${minor}.${patch}+${increment}`);
|
|
||||||
core.info(`To create a release for this `)
|
|
||||||
core.setOutput("version", version);
|
|
||||||
core.setOutput("major", major.toString());
|
|
||||||
core.setOutput("minor", minor.toString());
|
|
||||||
core.setOutput("patch", patch.toString());
|
|
||||||
core.setOutput("increment", increment.toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
try {
|
|
||||||
const remote = await cmd('git', 'remote');
|
|
||||||
const remoteExists = remote !== '';
|
|
||||||
const remotePrefix = remoteExists ? 'origin/' : '';
|
|
||||||
|
|
||||||
const tagPrefix = core.getInput('tag_prefix') || '';
|
|
||||||
const branch = `${remotePrefix}${core.getInput('branch', { required: true })}`;
|
|
||||||
const majorPattern = core.getInput('major_pattern', { required: true });
|
|
||||||
const minorPattern = core.getInput('minor_pattern', { required: true });
|
|
||||||
|
|
||||||
const releasePattern = `${tagPrefix}*`;
|
|
||||||
let major = 0, minor = 0, patch = 0, increment = 0;
|
|
||||||
|
|
||||||
let lastCommitAll = (await cmd('git', 'rev-list', '-n1', '--all')).trim();
|
|
||||||
|
|
||||||
if (lastCommitAll === '') {
|
|
||||||
// empty repo
|
|
||||||
setOutput('0', '0', '0', '0');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//let commit = (await cmd('git', 'rev-parse', 'HEAD')).trim();
|
|
||||||
|
|
||||||
let tag = '';
|
|
||||||
try {
|
|
||||||
tag = (await cmd(
|
|
||||||
'git',
|
|
||||||
`describe`,
|
|
||||||
`--tags`,
|
|
||||||
`--abbrev=0`,
|
|
||||||
`--match=${releasePattern}`,
|
|
||||||
`${branch}~1`
|
|
||||||
)).trim();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
tag = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let root;
|
|
||||||
if (tag === '') {
|
|
||||||
if (remoteExists) {
|
|
||||||
core.warning('No tags are present for this repository. If this is unexpected, check to ensure that tags have been pulled from the remote.');
|
|
||||||
}
|
|
||||||
// no release tags yet, use the initial commit as the root
|
|
||||||
root = '';
|
|
||||||
} else {
|
|
||||||
// parse the version tag
|
|
||||||
let tagParts = tag.split('/');
|
|
||||||
let versionValues = tagParts[tagParts.length - 1]
|
|
||||||
.substr(tagPrefix.length)
|
|
||||||
.split('.');
|
|
||||||
|
|
||||||
major = parseInt(versionValues[0]);
|
|
||||||
minor = versionValues.length > 1 ? parseInt(versionValues[1]) : 0;
|
|
||||||
patch = versionValues.length > 2 ? parseInt(versionValues[2]) : 0;
|
|
||||||
|
|
||||||
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
|
|
||||||
throw `Invalid tag ${tag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
root = await cmd('git', `merge-base`, tag, branch);
|
|
||||||
}
|
|
||||||
root = root.trim();
|
|
||||||
|
|
||||||
const log = await cmd(
|
|
||||||
'git',
|
|
||||||
'log',
|
|
||||||
'--pretty="%s"',
|
|
||||||
'--author-date-order',
|
|
||||||
root === '' ? branch : `${root}..${branch}`);
|
|
||||||
|
|
||||||
let history = log
|
|
||||||
.trim()
|
|
||||||
.split(eol)
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
// Discover the change time from the history log by finding the oldest log
|
|
||||||
// that could set the version.
|
|
||||||
|
|
||||||
const majorIndex = history.findIndex(x => x.includes(majorPattern));
|
|
||||||
const minorIndex = history.findIndex(x => x.includes(minorPattern));
|
|
||||||
|
|
||||||
if (majorIndex !== -1) {
|
|
||||||
increment = history.length - (majorIndex + 1);
|
|
||||||
patch = 0;
|
|
||||||
minor = 0;
|
|
||||||
major++;
|
|
||||||
} else if (minorIndex !== -1) {
|
|
||||||
increment = history.length - (minorIndex + 1);
|
|
||||||
patch = 0;
|
|
||||||
minor++;
|
|
||||||
} else {
|
|
||||||
increment = history.length - 1;
|
|
||||||
patch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOutput(major, minor, patch, increment);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
core.error(error);
|
|
||||||
core.setFailed(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
287
index.test.js
287
index.test.js
|
|
@ -1,287 +0,0 @@
|
||||||
const cp = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
const process = require('process');
|
|
||||||
|
|
||||||
// Action input variables
|
|
||||||
process.env['INPUT_BRANCH'] = "master";
|
|
||||||
process.env['INPUT_TAG_PREFIX'] = "v";
|
|
||||||
process.env['INPUT_MAJOR_PATTERN'] = "(MAJOR)";
|
|
||||||
process.env['INPUT_MINOR_PATTERN'] = "(MINOR)";
|
|
||||||
process.env['INPUT_FORMAT'] = "${major}.${minor}.${patch}";
|
|
||||||
|
|
||||||
// Creates a randomly named git repository and returns a function to execute commands in it
|
|
||||||
const createTestRepo = (inputs) => {
|
|
||||||
const repoDirectory = `/tmp/test${Math.random().toString(36).substring(2, 15)}`;
|
|
||||||
cp.execSync(`mkdir ${repoDirectory} && git init ${repoDirectory}`);
|
|
||||||
|
|
||||||
let env = {};
|
|
||||||
if (inputs !== undefined) {
|
|
||||||
for (let key in inputs) {
|
|
||||||
env[`INPUT_${key.toUpperCase()}`] = inputs[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const run = (command) => execute(repoDirectory, command, env);
|
|
||||||
|
|
||||||
// Configure up git user
|
|
||||||
run(`git config user.name "Test User"`);
|
|
||||||
run(`git config user.email "test@example.com"`);
|
|
||||||
|
|
||||||
let i = 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
clean: () => execute('/tmp', `rm -rf ${repoDirectory}`),
|
|
||||||
makeCommit: (msg) => {
|
|
||||||
run(`touch test${i++}`);
|
|
||||||
run(`git add --all`);
|
|
||||||
run(`git commit -m '${msg}'`);
|
|
||||||
},
|
|
||||||
runAction: () => run(`node ${path.join(__dirname, 'index.js')}`),
|
|
||||||
exec: run
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Executes a set of commands in the specified directory
|
|
||||||
const execute = (workingDirectory, command, env) => {
|
|
||||||
try {
|
|
||||||
return String(cp.execSync(command, { env: { ...process.env, ...env }, cwd: workingDirectory }));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(String(e.stdout));
|
|
||||||
console.error(String(e.stderr));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test('Empty repository version is correct', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
var result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.0+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Repository with commits shows increment', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit(`Second Commit`); // 0.0.1+1
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.1+1');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Tagging does not break version', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit(`Second Commit`); // 0.0.1+1
|
|
||||||
repo.exec('git tag v0.0.1')
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.1+1');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Minor update bumps minor version and resets increment', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit (MINOR)'); // 0.1.0+0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.1.0+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Major update bumps major version and resets increment', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit (MAJOR)'); // 1.0.0+0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 1.0.0+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Multiple major commits are idempotent', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit (MAJOR)'); // 1.0.0+0
|
|
||||||
repo.makeCommit('Third Commit (MAJOR)'); // 1.0.0+1
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 1.0.0+1');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Minor commits after a major commit are ignored', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit (MAJOR)'); // 1.0.0+0
|
|
||||||
repo.makeCommit('Third Commit (MINOR)'); // 1.0.0+1
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 1.0.0+1');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Tags start new version', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit'); // 0.0.1+1
|
|
||||||
repo.exec('git tag v0.0.1');
|
|
||||||
repo.makeCommit('Third Commit'); // 0.0.2+0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.2+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Version pulled from last release branch', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.exec('git tag v0.0.1');
|
|
||||||
repo.makeCommit('Second Commit'); // 0.0.2+0
|
|
||||||
repo.exec('git tag v5.6.7');
|
|
||||||
repo.makeCommit('Third Commit'); // 5.6.7+0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 5.6.8+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Removed for now
|
|
||||||
test('Tags on branches are used', () => {
|
|
||||||
|
|
||||||
// This test checks that tags are counted correctly even if they are not on
|
|
||||||
// the main branch:
|
|
||||||
// master o--o--o--o <- expecting v0.0.2
|
|
||||||
// \
|
|
||||||
// release o--o <- taged v0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit'); // 0.0.1+1
|
|
||||||
repo.makeCommit('Third Commit'); // 0.1.1+2
|
|
||||||
repo.exec('git checkout -b release/0.0.1')
|
|
||||||
repo.makeCommit('Fourth Commit'); // 0.1.1+3
|
|
||||||
repo.exec('git tag v0.0.1');
|
|
||||||
repo.exec('git checkout master');
|
|
||||||
repo.makeCommit('Fifth Commit'); // 0.0.2.0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.2+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
test('Merged tags do not affect version', () => {
|
|
||||||
|
|
||||||
// This test checks that merges don't override tags
|
|
||||||
|
|
||||||
// Tagged v0.0.2
|
|
||||||
// v
|
|
||||||
// master o--o--o---o---o <- expecting v0.0.3+1
|
|
||||||
// \ /
|
|
||||||
// release o---o <- taged v0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit'); // 0.0.1+1
|
|
||||||
repo.makeCommit('Third Commit'); // 0.1.1+2
|
|
||||||
repo.exec('git checkout -b release/0.0.1')
|
|
||||||
repo.makeCommit('Fourth Commit'); // 0.1.1+3
|
|
||||||
repo.exec('git tag v0.0.1');
|
|
||||||
repo.exec('git checkout master');
|
|
||||||
repo.makeCommit('Fifth Commit'); // 0.0.2.0
|
|
||||||
repo.exec('git tag v0.0.2');
|
|
||||||
repo.exec('git merge release/0.0.1');
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.3+1');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Version tags do not require all three version numbers', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit (MAJOR)'); // 1.0.0+0
|
|
||||||
repo.exec('git tag v1');
|
|
||||||
repo.makeCommit(`Second Commit`); // 1.0.1+0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 1.0.1+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Format input is respected', () => {
|
|
||||||
const repo = createTestRepo({ format: 'M${major}m${minor}p${patch}i${increment}' }); // M0m0p0i0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // M1m2p3i0
|
|
||||||
repo.exec('git tag v1.2.3');
|
|
||||||
repo.makeCommit(`Second Commit`); // M1m2p4i0
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('M1m2p4i0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Version prefixes are not required/can be empty', () => {
|
|
||||||
const repo = createTestRepo({ tag_prefix: '' }); // 0.0.0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1
|
|
||||||
repo.exec('git tag 0.0.1');
|
|
||||||
repo.makeCommit(`Second Commit`); // 0.0.2
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 0.0.2');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Tag order comes from commit order, not tag create order', () => {
|
|
||||||
const repo = createTestRepo(); // 0.0.0+0
|
|
||||||
|
|
||||||
repo.makeCommit('Initial Commit'); // 0.0.1+0
|
|
||||||
repo.makeCommit('Second Commit'); // 0.0.1+1
|
|
||||||
repo.makeCommit('Third Commit'); // 0.0.1+2
|
|
||||||
repo.exec('git tag v2.0.0');
|
|
||||||
repo.exec('sleep 2');
|
|
||||||
repo.exec('git tag v1.0.0 HEAD~1');
|
|
||||||
repo.makeCommit('Fourth Commit'); // 0.0.1+2
|
|
||||||
|
|
||||||
const result = repo.runAction();
|
|
||||||
|
|
||||||
|
|
||||||
expect(result).toMatch('Version is 2.0.1+0');
|
|
||||||
|
|
||||||
repo.clean();
|
|
||||||
});
|
|
||||||
12
jest.config.js
Normal file
12
jest.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = {
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': 'ts-jest'
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
modulePaths: [
|
||||||
|
"<rootDir>/src/"
|
||||||
|
],
|
||||||
|
}
|
||||||
45
lib/ActionConfig.js
Normal file
45
lib/ActionConfig.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.ActionConfig = void 0;
|
||||||
|
/** Represents the input configuration for the semantic-version action */
|
||||||
|
class ActionConfig {
|
||||||
|
constructor() {
|
||||||
|
/** Set to specify a specific branch, default is the current HEAD */
|
||||||
|
this.branch = "HEAD";
|
||||||
|
/** The prefix to use to identify tags */
|
||||||
|
this.tagPrefix = "v";
|
||||||
|
/** (Deprecated) Use branches instead of tags */
|
||||||
|
this.useBranches = false;
|
||||||
|
/** If true, the branch will be used to select the maximum version. */
|
||||||
|
this.versionFromBranch = false;
|
||||||
|
/** A string which, if present in a git commit, indicates that a change represents a major (breaking) change. Wrap with '/' to match using a regular expression. */
|
||||||
|
this.majorPattern = "/!:|BREAKING CHANGE:/";
|
||||||
|
/** A string which indicates the flags used by the `majorPattern` regular expression. */
|
||||||
|
this.majorFlags = "";
|
||||||
|
/** A string which, if present in a git commit, indicates that a change represents a minor (feature) change. Wrap with '/' to match using a regular expression. */
|
||||||
|
this.minorPattern = "/feat:/";
|
||||||
|
/** A string which indicates the flags used by the `minorPattern` regular expression. */
|
||||||
|
this.minorFlags = "";
|
||||||
|
/** Pattern to use when formatting output version */
|
||||||
|
this.versionFormat = '${major}.${minor}.${patch}';
|
||||||
|
/** Path to check for changes. If any changes are detected in the path the 'changed' output will true. Enter multiple paths separated by spaces. */
|
||||||
|
this.changePath = '';
|
||||||
|
/** Use to create a named sub-version. This value will be appended to tags created for this version. */
|
||||||
|
this.namespace = "";
|
||||||
|
/** If true, every commit will be treated as a bump to the version. */
|
||||||
|
this.bumpEachCommit = false;
|
||||||
|
/** If true, the body of commits will also be searched for major/minor patterns to determine the version type */
|
||||||
|
this.searchCommitBody = false;
|
||||||
|
/** The output method used to generate list of users, 'csv' or 'json'. Default is 'csv'. */
|
||||||
|
this.userFormatType = "csv";
|
||||||
|
/** Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the versionType output is unchanged. */
|
||||||
|
this.enablePrereleaseMode = false;
|
||||||
|
/** If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched. */
|
||||||
|
this.bumpEachCommitPatchPattern = "";
|
||||||
|
/** If enabled, diagnostic information will be added to the action output. */
|
||||||
|
this.debug = false;
|
||||||
|
/** Diagnostics to replay */
|
||||||
|
this.replay = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ActionConfig = ActionConfig;
|
||||||
64
lib/CommandRunner.js
Normal file
64
lib/CommandRunner.js
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.cmd = void 0;
|
||||||
|
// Using require instead of import to support integration testing
|
||||||
|
const exec = __importStar(require("@actions/exec"));
|
||||||
|
const DebugManager_1 = require("./DebugManager");
|
||||||
|
const cmd = (command, ...args) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
const debugManager = DebugManager_1.DebugManager.getInstance();
|
||||||
|
if (debugManager.isReplayMode()) {
|
||||||
|
return debugManager.replayCommand(command, args);
|
||||||
|
}
|
||||||
|
let output = '', errors = '';
|
||||||
|
const options = {
|
||||||
|
silent: true,
|
||||||
|
listeners: {
|
||||||
|
stdout: (data) => { output += data.toString(); },
|
||||||
|
stderr: (data) => { errors += data.toString(); },
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let caughtError = null;
|
||||||
|
try {
|
||||||
|
yield exec.exec(command, args, options);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
caughtError = err;
|
||||||
|
}
|
||||||
|
debugManager.recordCommand(command, args, output, errors, caughtError);
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
exports.cmd = cmd;
|
||||||
45
lib/ConfigurationProvider.js
Normal file
45
lib/ConfigurationProvider.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.ConfigurationProvider = void 0;
|
||||||
|
const CsvUserFormatter_1 = require("./formatting/CsvUserFormatter");
|
||||||
|
const BranchVersioningTagFormatter_1 = require("./formatting/BranchVersioningTagFormatter");
|
||||||
|
const DefaultTagFormatter_1 = require("./formatting/DefaultTagFormatter");
|
||||||
|
const DefaultVersionFormatter_1 = require("./formatting/DefaultVersionFormatter");
|
||||||
|
const JsonUserFormatter_1 = require("./formatting/JsonUserFormatter");
|
||||||
|
const DefaultCommitsProvider_1 = require("./providers/DefaultCommitsProvider");
|
||||||
|
const DefaultCurrentCommitResolver_1 = require("./providers/DefaultCurrentCommitResolver");
|
||||||
|
const DefaultVersionClassifier_1 = require("./providers/DefaultVersionClassifier");
|
||||||
|
const DefaultLastReleaseResolver_1 = require("./providers/DefaultLastReleaseResolver");
|
||||||
|
const BumpAlwaysVersionClassifier_1 = require("./providers/BumpAlwaysVersionClassifier");
|
||||||
|
const DebugManager_1 = require("./DebugManager");
|
||||||
|
class ConfigurationProvider {
|
||||||
|
constructor(config) {
|
||||||
|
this.config = config;
|
||||||
|
DebugManager_1.DebugManager.getInstance().initializeConfig(config);
|
||||||
|
}
|
||||||
|
GetCurrentCommitResolver() { return new DefaultCurrentCommitResolver_1.DefaultCurrentCommitResolver(this.config); }
|
||||||
|
GetLastReleaseResolver() { return new DefaultLastReleaseResolver_1.DefaultLastReleaseResolver(this.config); }
|
||||||
|
GetCommitsProvider() { return new DefaultCommitsProvider_1.DefaultCommitsProvider(this.config); }
|
||||||
|
GetVersionClassifier() {
|
||||||
|
if (this.config.bumpEachCommit) {
|
||||||
|
return new BumpAlwaysVersionClassifier_1.BumpAlwaysVersionClassifier(this.config);
|
||||||
|
}
|
||||||
|
return new DefaultVersionClassifier_1.DefaultVersionClassifier(this.config);
|
||||||
|
}
|
||||||
|
GetVersionFormatter() { return new DefaultVersionFormatter_1.DefaultVersionFormatter(this.config); }
|
||||||
|
GetTagFormatter(branchName) {
|
||||||
|
if (this.config.versionFromBranch) {
|
||||||
|
return new BranchVersioningTagFormatter_1.BranchVersioningTagFormatter(this.config, branchName);
|
||||||
|
}
|
||||||
|
return new DefaultTagFormatter_1.DefaultTagFormatter(this.config);
|
||||||
|
}
|
||||||
|
GetUserFormatter() {
|
||||||
|
switch (this.config.userFormatType) {
|
||||||
|
case 'json': return new JsonUserFormatter_1.JsonUserFormatter(this.config);
|
||||||
|
case 'csv': return new CsvUserFormatter_1.CsvUserFormatter(this.config);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown user format type: ${this.config.userFormatType}, supported types: json, csv`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ConfigurationProvider = ConfigurationProvider;
|
||||||
100
lib/DebugManager.js
Normal file
100
lib/DebugManager.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DebugManager = void 0;
|
||||||
|
/** Utility class for managing debug mode and diagnostic information */
|
||||||
|
class DebugManager {
|
||||||
|
constructor() {
|
||||||
|
this.debugEnabled = false;
|
||||||
|
this.replayMode = false;
|
||||||
|
this.diagnosticInfo = null;
|
||||||
|
}
|
||||||
|
/** Returns the singleton instance of the DebugManager */
|
||||||
|
static getInstance() {
|
||||||
|
if (!DebugManager.instance) {
|
||||||
|
DebugManager.instance = new DebugManager();
|
||||||
|
}
|
||||||
|
return DebugManager.instance;
|
||||||
|
}
|
||||||
|
/** Clears the singleton instance of the DebugManager (used for testing) */
|
||||||
|
static clearState() {
|
||||||
|
DebugManager.instance = new DebugManager();
|
||||||
|
}
|
||||||
|
/** Returns true if debug mode is enabled */
|
||||||
|
isDebugEnabled() {
|
||||||
|
return this.debugEnabled;
|
||||||
|
}
|
||||||
|
/** Returns true if replay mode is enabled */
|
||||||
|
isReplayMode() {
|
||||||
|
return this.replayMode;
|
||||||
|
}
|
||||||
|
initializeConfig(config) {
|
||||||
|
if (config.debug) {
|
||||||
|
this.setDebugEnabled(true);
|
||||||
|
}
|
||||||
|
else if (config.replay.length > 0) {
|
||||||
|
this.replayFromDiagnostics(config.replay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Enables or disables debug mode, also clears any existing diagnostics info */
|
||||||
|
setDebugEnabled(enableDebug = true) {
|
||||||
|
this.debugEnabled = enableDebug;
|
||||||
|
this.replayMode = false;
|
||||||
|
this.diagnosticInfo = new DiagnosticInfo();
|
||||||
|
}
|
||||||
|
;
|
||||||
|
/** Enables replay mode and loads the diagnostic information from the specified string */
|
||||||
|
replayFromDiagnostics(diagnostics) {
|
||||||
|
this.debugEnabled = false;
|
||||||
|
this.replayMode = true;
|
||||||
|
this.diagnosticInfo = JSON.parse(diagnostics);
|
||||||
|
}
|
||||||
|
/** Returns a JSON string containing the diagnostic information for this run */
|
||||||
|
getDebugOutput(emptyRepo = false) {
|
||||||
|
return this.isDebugEnabled() ? JSON.stringify(this.diagnosticInfo) : '';
|
||||||
|
}
|
||||||
|
/** Records a command and its output for diagnostic purposes */
|
||||||
|
recordCommand(command, args, output, stderr, error) {
|
||||||
|
var _a;
|
||||||
|
if (this.isDebugEnabled()) {
|
||||||
|
(_a = this.diagnosticInfo) === null || _a === void 0 ? void 0 : _a.recordCommand(command, args, output, stderr, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Replays the specified command and returns the output */
|
||||||
|
replayCommand(command, args) {
|
||||||
|
if (this.diagnosticInfo === null) {
|
||||||
|
throw new Error('No diagnostic information available for replay');
|
||||||
|
}
|
||||||
|
const commandResult = this.diagnosticInfo.commands.find(c => c.command === command && JSON.stringify(c.args) === JSON.stringify(args));
|
||||||
|
if (!commandResult) {
|
||||||
|
throw new Error(`No result found in diagnostic for command "${command}"`);
|
||||||
|
}
|
||||||
|
if (commandResult.error) {
|
||||||
|
throw commandResult.error;
|
||||||
|
}
|
||||||
|
if (commandResult.stderr) {
|
||||||
|
console.error(commandResult.stderr);
|
||||||
|
}
|
||||||
|
return commandResult.output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DebugManager = DebugManager;
|
||||||
|
/** Represents a CLI command result */
|
||||||
|
class CommandResult {
|
||||||
|
constructor(command, args, output, stderr, error) {
|
||||||
|
this.command = command;
|
||||||
|
this.args = args;
|
||||||
|
this.output = output;
|
||||||
|
this.stderr = stderr;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Represents the result of the commands executed for a run */
|
||||||
|
class DiagnosticInfo {
|
||||||
|
constructor() {
|
||||||
|
this.commands = [];
|
||||||
|
this.empty = false;
|
||||||
|
}
|
||||||
|
recordCommand(command, args, output, stderr, error) {
|
||||||
|
this.commands.push(new CommandResult(command, args, output, stderr, error));
|
||||||
|
}
|
||||||
|
}
|
||||||
40
lib/VersionResult.js
Normal file
40
lib/VersionResult.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.VersionResult = void 0;
|
||||||
|
/** Represents the total output for the action */
|
||||||
|
class VersionResult {
|
||||||
|
/**
|
||||||
|
* Creates a new result instance
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
* @param increment - The number of commits for this version (usually used to create version suffix)
|
||||||
|
* @param versionType - The type of version, e.g. major, minor, patch
|
||||||
|
* @param formattedVersion - The formatted semantic version
|
||||||
|
* @param versionTag - The string to be used as a Git tag
|
||||||
|
* @param changed - True if the version was changed, otherwise false
|
||||||
|
* @param isTagged - True if the commit had a tag that matched the `versionTag` format
|
||||||
|
* @param authors - Authors formatted according to the format mode (e.g. JSON, CSV, YAML, etc.)
|
||||||
|
* @param currentCommit - The current commit hash
|
||||||
|
* @param previousCommit - The previous commit hash
|
||||||
|
* @param previousVersion - The previous version
|
||||||
|
* @param debugOutput - Diagnostic information, if debug is enabled
|
||||||
|
*/
|
||||||
|
constructor(major, minor, patch, increment, versionType, formattedVersion, versionTag, changed, isTagged, authors, currentCommit, previousCommit, previousVersion, debugOutput) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
this.increment = increment;
|
||||||
|
this.versionType = versionType;
|
||||||
|
this.formattedVersion = formattedVersion;
|
||||||
|
this.versionTag = versionTag;
|
||||||
|
this.changed = changed;
|
||||||
|
this.isTagged = isTagged;
|
||||||
|
this.authors = authors;
|
||||||
|
this.currentCommit = currentCommit;
|
||||||
|
this.previousCommit = previousCommit;
|
||||||
|
this.previousVersion = previousVersion;
|
||||||
|
this.debugOutput = debugOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VersionResult = VersionResult;
|
||||||
55
lib/action.js
Normal file
55
lib/action.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.runAction = void 0;
|
||||||
|
const VersionResult_1 = require("./VersionResult");
|
||||||
|
const VersionType_1 = require("./providers/VersionType");
|
||||||
|
const UserInfo_1 = require("./providers/UserInfo");
|
||||||
|
const VersionInformation_1 = require("./providers/VersionInformation");
|
||||||
|
const DebugManager_1 = require("./DebugManager");
|
||||||
|
function runAction(configurationProvider) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const currentCommitResolver = configurationProvider.GetCurrentCommitResolver();
|
||||||
|
const lastReleaseResolver = configurationProvider.GetLastReleaseResolver();
|
||||||
|
const commitsProvider = configurationProvider.GetCommitsProvider();
|
||||||
|
const versionClassifier = configurationProvider.GetVersionClassifier();
|
||||||
|
const versionFormatter = configurationProvider.GetVersionFormatter();
|
||||||
|
const tagFormatter = configurationProvider.GetTagFormatter(yield currentCommitResolver.ResolveBranchNameAsync());
|
||||||
|
const userFormatter = configurationProvider.GetUserFormatter();
|
||||||
|
const debugManager = DebugManager_1.DebugManager.getInstance();
|
||||||
|
if (yield currentCommitResolver.IsEmptyRepoAsync()) {
|
||||||
|
const versionInfo = new VersionInformation_1.VersionInformation(0, 0, 0, 0, VersionType_1.VersionType.None, [], false, false);
|
||||||
|
return new VersionResult_1.VersionResult(versionInfo.major, versionInfo.minor, versionInfo.patch, versionInfo.increment, versionInfo.type, versionFormatter.Format(versionInfo), tagFormatter.Format(versionInfo), versionInfo.changed, versionInfo.isTagged, userFormatter.Format('author', []), '', '', tagFormatter.Parse(tagFormatter.Format(versionInfo)).join('.'), debugManager.getDebugOutput(true));
|
||||||
|
}
|
||||||
|
const currentCommit = yield currentCommitResolver.ResolveAsync();
|
||||||
|
const lastRelease = yield lastReleaseResolver.ResolveAsync(currentCommit, tagFormatter);
|
||||||
|
const commitSet = yield commitsProvider.GetCommitsAsync(lastRelease.hash, currentCommit);
|
||||||
|
const classification = yield versionClassifier.ClassifyAsync(lastRelease, commitSet);
|
||||||
|
const { isTagged } = lastRelease;
|
||||||
|
const { major, minor, patch, increment, type, changed } = classification;
|
||||||
|
// At this point all necessary data has been pulled from the database, create
|
||||||
|
// version information to be used by the formatters
|
||||||
|
let versionInfo = new VersionInformation_1.VersionInformation(major, minor, patch, increment, type, commitSet.commits, changed, isTagged);
|
||||||
|
// Group all the authors together, count the number of commits per author
|
||||||
|
const allAuthors = versionInfo.commits
|
||||||
|
.reduce((acc, commit) => {
|
||||||
|
const key = `${commit.author} <${commit.authorEmail}>`;
|
||||||
|
acc[key] = acc[key] || { n: commit.author, e: commit.authorEmail, c: 0 };
|
||||||
|
acc[key].c++;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const authors = Object.values(allAuthors)
|
||||||
|
.map((u) => new UserInfo_1.UserInfo(u.n, u.e, u.c))
|
||||||
|
.sort((a, b) => b.commits - a.commits);
|
||||||
|
return new VersionResult_1.VersionResult(versionInfo.major, versionInfo.minor, versionInfo.patch, versionInfo.increment, versionInfo.type, versionFormatter.Format(versionInfo), tagFormatter.Format(versionInfo), versionInfo.changed, versionInfo.isTagged, userFormatter.Format('author', authors), currentCommit, lastRelease.hash, `${lastRelease.major}.${lastRelease.minor}.${lastRelease.patch}`, debugManager.getDebugOutput());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.runAction = runAction;
|
||||||
87
lib/formatting/BranchVersioningTagFormatter.js
Normal file
87
lib/formatting/BranchVersioningTagFormatter.js
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.BranchVersioningTagFormatter = void 0;
|
||||||
|
const DefaultTagFormatter_1 = require("./DefaultTagFormatter");
|
||||||
|
/** Default tag formatter which allows a prefix to be specified */
|
||||||
|
class BranchVersioningTagFormatter extends DefaultTagFormatter_1.DefaultTagFormatter {
|
||||||
|
getRegex(pattern) {
|
||||||
|
if (/^\/.+\/[i]*$/.test(pattern)) {
|
||||||
|
const regexEnd = pattern.lastIndexOf('/');
|
||||||
|
const parsedFlags = pattern.slice(pattern.lastIndexOf('/') + 1);
|
||||||
|
return new RegExp(pattern.slice(1, regexEnd), parsedFlags);
|
||||||
|
}
|
||||||
|
return new RegExp(pattern);
|
||||||
|
}
|
||||||
|
constructor(config, branchName) {
|
||||||
|
super(config);
|
||||||
|
const pattern = config.versionFromBranch === true ?
|
||||||
|
new RegExp("[0-9]+.[0-9]+$|[0-9]+$") :
|
||||||
|
this.getRegex(config.versionFromBranch);
|
||||||
|
const result = pattern.exec(branchName);
|
||||||
|
if (result === null) {
|
||||||
|
this.major = NaN;
|
||||||
|
this.onVersionBranch = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let branchVersion;
|
||||||
|
switch (result === null || result === void 0 ? void 0 : result.length) {
|
||||||
|
case 1:
|
||||||
|
branchVersion = result[0];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
branchVersion = result[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unable to parse version from branch named '${branchName}' using pattern '${pattern}'`);
|
||||||
|
}
|
||||||
|
this.onVersionBranch = true;
|
||||||
|
const versionValues = branchVersion.split('.');
|
||||||
|
if (versionValues.length > 2) {
|
||||||
|
throw new Error(`The version string '${branchVersion}' parsed from branch '${branchName}' is invalid. It must be in the format 'major.minor' or 'major'`);
|
||||||
|
}
|
||||||
|
this.major = parseInt(versionValues[0]);
|
||||||
|
if (isNaN(this.major)) {
|
||||||
|
throw new Error(`The major version '${versionValues[0]}' parsed from branch '${branchName}' is invalid. It must be a number.`);
|
||||||
|
}
|
||||||
|
if (versionValues.length > 1) {
|
||||||
|
this.minor = parseInt(versionValues[1]);
|
||||||
|
if (isNaN(this.minor)) {
|
||||||
|
throw new Error(`The minor version '${versionValues[1]}' parsed from branch '${branchName}' is invalid. It must be a number.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GetPattern() {
|
||||||
|
let pattern = super.GetPattern();
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
if (this.minor === undefined) {
|
||||||
|
return pattern.replace('*[0-9].*[0-9].*[0-9]', `${this.major}.*[0-9].*[0-9]`);
|
||||||
|
}
|
||||||
|
return pattern.replace('*[0-9].*[0-9].*[0-9]', `${this.major}.${this.minor}.*[0-9]`);
|
||||||
|
}
|
||||||
|
IsValid(tag) {
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return super.IsValid(tag);
|
||||||
|
}
|
||||||
|
if (!super.IsValid(tag)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const parsed = super.Parse(tag);
|
||||||
|
if (parsed[0] !== this.major) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.minor !== undefined && parsed[1] !== this.minor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Parse(tag) {
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return super.Parse(tag);
|
||||||
|
}
|
||||||
|
const parsed = super.Parse(tag);
|
||||||
|
return [this.major, this.minor || parsed[1], parsed[2]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BranchVersioningTagFormatter = BranchVersioningTagFormatter;
|
||||||
12
lib/formatting/CsvUserFormatter.js
Normal file
12
lib/formatting/CsvUserFormatter.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CsvUserFormatter = void 0;
|
||||||
|
class CsvUserFormatter {
|
||||||
|
constructor(config) {
|
||||||
|
// placeholder for consistency with other formatters
|
||||||
|
}
|
||||||
|
Format(type, users) {
|
||||||
|
return users.map(user => `${user.name} <${user.email}>`).join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CsvUserFormatter = CsvUserFormatter;
|
||||||
59
lib/formatting/DefaultTagFormatter.js
Normal file
59
lib/formatting/DefaultTagFormatter.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultTagFormatter = void 0;
|
||||||
|
/** Default tag formatter which allows a prefix to be specified */
|
||||||
|
class DefaultTagFormatter {
|
||||||
|
constructor(config) {
|
||||||
|
this.namespace = config.namespace;
|
||||||
|
this.tagPrefix = config.tagPrefix;
|
||||||
|
this.namespaceSeperator = '-'; // maybe make configurable in the future
|
||||||
|
}
|
||||||
|
Format(versionInfo) {
|
||||||
|
const result = `${this.tagPrefix}${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`;
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return `${result}${this.namespaceSeperator}${this.namespace}`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
GetPattern() {
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return `${this.tagPrefix}*[0-9].*[0-9].*[0-9]${this.namespaceSeperator}${this.namespace}`;
|
||||||
|
}
|
||||||
|
return `${this.tagPrefix}*[0-9].*[0-9].*[0-9]`;
|
||||||
|
}
|
||||||
|
Parse(tag) {
|
||||||
|
if (tag === '') {
|
||||||
|
return [0, 0, 0];
|
||||||
|
}
|
||||||
|
let tagParts = tag
|
||||||
|
.replace(this.tagPrefix, '<--!PREFIX!-->')
|
||||||
|
.replace(this.namespace, '<--!NAMESPACE!-->')
|
||||||
|
.split('/');
|
||||||
|
const stripedTag = tagParts[tagParts.length - 1]
|
||||||
|
.replace('<--!PREFIX!-->', this.tagPrefix)
|
||||||
|
.replace('<--!NAMESPACE!-->', this.namespace);
|
||||||
|
let versionValues = stripedTag
|
||||||
|
.substring(this.tagPrefix.length)
|
||||||
|
.slice(0, this.namespace === '' ? 999 : -(this.namespace.length + 1))
|
||||||
|
.split('.');
|
||||||
|
let major = parseInt(versionValues[0]);
|
||||||
|
let minor = versionValues.length > 1 ? parseInt(versionValues[1]) : 0;
|
||||||
|
let patch = versionValues.length > 2 ? parseInt(versionValues[2]) : 0;
|
||||||
|
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
|
||||||
|
throw `Invalid tag ${tag} (${versionValues})`;
|
||||||
|
}
|
||||||
|
return [major, minor, patch];
|
||||||
|
}
|
||||||
|
;
|
||||||
|
IsValid(tag) {
|
||||||
|
const regexEscape = (literal) => literal.replace(/\W/g, '\\$&');
|
||||||
|
const tagPrefix = regexEscape(this.tagPrefix);
|
||||||
|
const namespaceSeperator = regexEscape(this.namespaceSeperator);
|
||||||
|
const namespace = regexEscape(this.namespace);
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return new RegExp(`^${tagPrefix}[0-9]+\.[0-9]+\.[0-9]+${namespaceSeperator}${namespace}$`).test(tag);
|
||||||
|
}
|
||||||
|
return new RegExp(`^${tagPrefix}[0-9]+\.[0-9]+\.[0-9]+$`).test(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultTagFormatter = DefaultTagFormatter;
|
||||||
16
lib/formatting/DefaultVersionFormatter.js
Normal file
16
lib/formatting/DefaultVersionFormatter.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultVersionFormatter = void 0;
|
||||||
|
class DefaultVersionFormatter {
|
||||||
|
constructor(config) {
|
||||||
|
this.formatString = config.versionFormat;
|
||||||
|
}
|
||||||
|
Format(versionInfo) {
|
||||||
|
return this.formatString
|
||||||
|
.replace('${major}', versionInfo.major.toString())
|
||||||
|
.replace('${minor}', versionInfo.minor.toString())
|
||||||
|
.replace('${patch}', versionInfo.patch.toString())
|
||||||
|
.replace('${increment}', versionInfo.increment.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultVersionFormatter = DefaultVersionFormatter;
|
||||||
13
lib/formatting/JsonUserFormatter.js
Normal file
13
lib/formatting/JsonUserFormatter.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.JsonUserFormatter = void 0;
|
||||||
|
class JsonUserFormatter {
|
||||||
|
constructor(config) {
|
||||||
|
// placeholder for consistency with other formatters
|
||||||
|
}
|
||||||
|
Format(type, users) {
|
||||||
|
let result = users.map(u => ({ name: u.name, email: u.email }));
|
||||||
|
return JSON.stringify(result).replace('\n', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.JsonUserFormatter = JsonUserFormatter;
|
||||||
2
lib/formatting/TagFormatter.js
Normal file
2
lib/formatting/TagFormatter.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
2
lib/formatting/UserFormatter.js
Normal file
2
lib/formatting/UserFormatter.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
2
lib/formatting/VersionFormatter.js
Normal file
2
lib/formatting/VersionFormatter.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
16
lib/formatting/YamlUserFormatter.js
Normal file
16
lib/formatting/YamlUserFormatter.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.YamlUserFormatter = void 0;
|
||||||
|
class YamlUserFormatter {
|
||||||
|
constructor(config) {
|
||||||
|
this.lineBreak = config.userFormatLineBreak || '\n';
|
||||||
|
this.includeType = config.includeType || false;
|
||||||
|
}
|
||||||
|
Format(type, users) {
|
||||||
|
const result = users.flatMap(u => [`- name: "${u.name}"`, ` email: "${u.email}"`]).join(this.lineBreak);
|
||||||
|
return this.includeType ?
|
||||||
|
`${type}:${this.lineBreak}${result}` :
|
||||||
|
result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.YamlUserFormatter = YamlUserFormatter;
|
||||||
11
lib/formatting/index.js
Normal file
11
lib/formatting/index.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.JsonUserFormatter = exports.DefaultVersionFormatter = exports.DefaultTagFormatter = exports.CsvUserFormatter = void 0;
|
||||||
|
var CsvUserFormatter_1 = require("./CsvUserFormatter");
|
||||||
|
Object.defineProperty(exports, "CsvUserFormatter", { enumerable: true, get: function () { return CsvUserFormatter_1.CsvUserFormatter; } });
|
||||||
|
var DefaultTagFormatter_1 = require("./DefaultTagFormatter");
|
||||||
|
Object.defineProperty(exports, "DefaultTagFormatter", { enumerable: true, get: function () { return DefaultTagFormatter_1.DefaultTagFormatter; } });
|
||||||
|
var DefaultVersionFormatter_1 = require("./DefaultVersionFormatter");
|
||||||
|
Object.defineProperty(exports, "DefaultVersionFormatter", { enumerable: true, get: function () { return DefaultVersionFormatter_1.DefaultVersionFormatter; } });
|
||||||
|
var JsonUserFormatter_1 = require("./JsonUserFormatter");
|
||||||
|
Object.defineProperty(exports, "JsonUserFormatter", { enumerable: true, get: function () { return JsonUserFormatter_1.JsonUserFormatter; } });
|
||||||
121
lib/main.js
Normal file
121
lib/main.js
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.run = void 0;
|
||||||
|
const action_1 = require("./action");
|
||||||
|
const ConfigurationProvider_1 = require("./ConfigurationProvider");
|
||||||
|
const core = __importStar(require("@actions/core"));
|
||||||
|
const VersionType_1 = require("./providers/VersionType");
|
||||||
|
function setOutput(versionResult) {
|
||||||
|
const { major, minor, patch, increment, versionType, formattedVersion, versionTag, changed, isTagged, authors, currentCommit, previousCommit, previousVersion, debugOutput } = versionResult;
|
||||||
|
const repository = process.env.GITHUB_REPOSITORY;
|
||||||
|
if (!changed) {
|
||||||
|
core.info('No changes detected for this commit');
|
||||||
|
}
|
||||||
|
core.info(`Version is ${formattedVersion}`);
|
||||||
|
if (repository !== undefined) {
|
||||||
|
core.info(`To create a release for this version, go to https://github.com/${repository}/releases/new?tag=${versionTag}&target=${currentCommit.split('/').slice(-1)[0]}`);
|
||||||
|
}
|
||||||
|
core.setOutput("version", formattedVersion);
|
||||||
|
core.setOutput("major", major.toString());
|
||||||
|
core.setOutput("minor", minor.toString());
|
||||||
|
core.setOutput("patch", patch.toString());
|
||||||
|
core.setOutput("increment", increment.toString());
|
||||||
|
core.setOutput("version_type", VersionType_1.VersionType[versionType].toLowerCase());
|
||||||
|
core.setOutput("changed", changed.toString());
|
||||||
|
core.setOutput("is_tagged", isTagged.toString());
|
||||||
|
core.setOutput("version_tag", versionTag);
|
||||||
|
core.setOutput("authors", authors);
|
||||||
|
core.setOutput("previous_commit", previousCommit);
|
||||||
|
core.setOutput("previous_version", previousVersion);
|
||||||
|
core.setOutput("current_commit", currentCommit);
|
||||||
|
core.setOutput("debug_output", debugOutput);
|
||||||
|
}
|
||||||
|
function run() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
function toBool(value) {
|
||||||
|
if (!value || value.toLowerCase() === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (value.toLowerCase() === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function toStringOrBool(value) {
|
||||||
|
if (!value || value === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const config = {
|
||||||
|
branch: core.getInput('branch'),
|
||||||
|
tagPrefix: core.getInput('tag_prefix'),
|
||||||
|
useBranches: toBool(core.getInput('use_branches')),
|
||||||
|
versionFromBranch: toStringOrBool(core.getInput('version_from_branch')),
|
||||||
|
majorPattern: core.getInput('major_pattern'),
|
||||||
|
minorPattern: core.getInput('minor_pattern'),
|
||||||
|
majorFlags: core.getInput('major_regexp_flags'),
|
||||||
|
minorFlags: core.getInput('minor_regexp_flags'),
|
||||||
|
versionFormat: core.getInput('version_format'),
|
||||||
|
changePath: core.getInput('change_path'),
|
||||||
|
namespace: core.getInput('namespace'),
|
||||||
|
bumpEachCommit: toBool(core.getInput('bump_each_commit')),
|
||||||
|
searchCommitBody: toBool(core.getInput('search_commit_body')),
|
||||||
|
userFormatType: core.getInput('user_format_type'),
|
||||||
|
enablePrereleaseMode: toBool(core.getInput('enable_prerelease_mode')),
|
||||||
|
bumpEachCommitPatchPattern: core.getInput('bump_each_commit_patch_pattern'),
|
||||||
|
debug: toBool(core.getInput('debug')),
|
||||||
|
replay: ''
|
||||||
|
};
|
||||||
|
if (config.useBranches) {
|
||||||
|
core.warning(`The 'use_branches' input option is deprecated, please see the documentation for more information on how to use branches`);
|
||||||
|
}
|
||||||
|
if (config.versionFormat === '' && core.getInput('format') !== '') {
|
||||||
|
core.warning(`The 'format' input is deprecated, use 'versionFormat' instead`);
|
||||||
|
config.versionFormat = core.getInput('format');
|
||||||
|
}
|
||||||
|
if (core.getInput('short_tags') !== '') {
|
||||||
|
core.warning(`The 'short_tags' input option is no longer supported`);
|
||||||
|
}
|
||||||
|
const configurationProvider = new ConfigurationProvider_1.ConfigurationProvider(config);
|
||||||
|
const result = yield (0, action_1.runAction)(configurationProvider);
|
||||||
|
setOutput(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.run = run;
|
||||||
|
run();
|
||||||
92
lib/providers/BumpAlwaysVersionClassifier.js
Normal file
92
lib/providers/BumpAlwaysVersionClassifier.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.BumpAlwaysVersionClassifier = void 0;
|
||||||
|
const DefaultVersionClassifier_1 = require("./DefaultVersionClassifier");
|
||||||
|
const VersionClassification_1 = require("./VersionClassification");
|
||||||
|
const VersionType_1 = require("./VersionType");
|
||||||
|
class BumpAlwaysVersionClassifier extends DefaultVersionClassifier_1.DefaultVersionClassifier {
|
||||||
|
constructor(config) {
|
||||||
|
super(config);
|
||||||
|
this.enablePrereleaseMode = config.enablePrereleaseMode;
|
||||||
|
this.patchPattern = !config.bumpEachCommitPatchPattern ?
|
||||||
|
_ => true :
|
||||||
|
this.parsePattern(config.bumpEachCommitPatchPattern, "", config.searchCommitBody);
|
||||||
|
}
|
||||||
|
ClassifyAsync(lastRelease, commitSet) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (lastRelease.currentPatch !== null) {
|
||||||
|
return new VersionClassification_1.VersionClassification(VersionType_1.VersionType.None, 0, false, lastRelease.currentMajor, lastRelease.currentMinor, lastRelease.currentPatch);
|
||||||
|
}
|
||||||
|
let { major, minor, patch } = lastRelease;
|
||||||
|
let type = VersionType_1.VersionType.None;
|
||||||
|
let increment = 0;
|
||||||
|
if (commitSet.commits.length === 0) {
|
||||||
|
return new VersionClassification_1.VersionClassification(type, 0, false, major, minor, patch);
|
||||||
|
}
|
||||||
|
for (let commit of commitSet.commits.reverse()) {
|
||||||
|
if (this.majorPattern(commit)) {
|
||||||
|
type = VersionType_1.VersionType.Major;
|
||||||
|
}
|
||||||
|
else if (this.minorPattern(commit)) {
|
||||||
|
type = VersionType_1.VersionType.Minor;
|
||||||
|
}
|
||||||
|
else if (this.patchPattern(commit) ||
|
||||||
|
(major === 0 && minor === 0 && patch === 0 && commitSet.commits.length > 0)) {
|
||||||
|
type = VersionType_1.VersionType.Patch;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = VersionType_1.VersionType.None;
|
||||||
|
}
|
||||||
|
if (this.enablePrereleaseMode && major === 0) {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType_1.VersionType.Major:
|
||||||
|
case VersionType_1.VersionType.Minor:
|
||||||
|
minor += 1;
|
||||||
|
patch = 0;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
case VersionType_1.VersionType.Patch:
|
||||||
|
patch += 1;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
increment++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType_1.VersionType.Major:
|
||||||
|
major += 1;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
case VersionType_1.VersionType.Minor:
|
||||||
|
minor += 1;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case VersionType_1.VersionType.Patch:
|
||||||
|
patch += 1;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
increment++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new VersionClassification_1.VersionClassification(type, increment, true, major, minor, patch);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BumpAlwaysVersionClassifier = BumpAlwaysVersionClassifier;
|
||||||
32
lib/providers/CommitInfo.js
Normal file
32
lib/providers/CommitInfo.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CommitInfo = void 0;
|
||||||
|
/** Represents information about a commit */
|
||||||
|
class CommitInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new commit information instance
|
||||||
|
* @param hash - The hash of the commit
|
||||||
|
* @param subject - The subject of the commit message
|
||||||
|
* @param body - The body of the commit message
|
||||||
|
* @param author - The author's name
|
||||||
|
* @param authorEmail - The author's email
|
||||||
|
* @param authorDate - The date the commit was authored
|
||||||
|
* @param committer - The committer's name
|
||||||
|
* @param committerEmail - The committer's email
|
||||||
|
* @param committerDate - The date the commit was committed
|
||||||
|
* @param tags - List of any tags associated with this commit
|
||||||
|
*/
|
||||||
|
constructor(hash, subject, body, author, authorEmail, authorDate, committer, committerEmail, committerDate, tags) {
|
||||||
|
this.hash = hash;
|
||||||
|
this.subject = subject;
|
||||||
|
this.body = body;
|
||||||
|
this.author = author;
|
||||||
|
this.authorEmail = authorEmail;
|
||||||
|
this.authorDate = authorDate;
|
||||||
|
this.committer = committer;
|
||||||
|
this.committerEmail = committerEmail;
|
||||||
|
this.committerDate = committerDate;
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CommitInfo = CommitInfo;
|
||||||
11
lib/providers/CommitInfoSet.js
Normal file
11
lib/providers/CommitInfoSet.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CommitInfoSet = void 0;
|
||||||
|
/** Represents information about a set of commits */
|
||||||
|
class CommitInfoSet {
|
||||||
|
constructor(changed, commits) {
|
||||||
|
this.changed = changed;
|
||||||
|
this.commits = commits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CommitInfoSet = CommitInfoSet;
|
||||||
2
lib/providers/CommitsProvider.js
Normal file
2
lib/providers/CommitsProvider.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
2
lib/providers/CurrentCommitResolver.js
Normal file
2
lib/providers/CurrentCommitResolver.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
79
lib/providers/DefaultCommitsProvider.js
Normal file
79
lib/providers/DefaultCommitsProvider.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultCommitsProvider = void 0;
|
||||||
|
const CommandRunner_1 = require("../CommandRunner");
|
||||||
|
const CommitInfo_1 = require("./CommitInfo");
|
||||||
|
const CommitInfoSet_1 = require("./CommitInfoSet");
|
||||||
|
class DefaultCommitsProvider {
|
||||||
|
constructor(config) {
|
||||||
|
this.changePath = config.changePath;
|
||||||
|
}
|
||||||
|
GetCommitsAsync(startHash, endHash) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const logSplitter = `@@@START_RECORD`;
|
||||||
|
const formatPlaceholders = Object.entries({
|
||||||
|
hash: '%H',
|
||||||
|
subject: '%s',
|
||||||
|
body: '%b',
|
||||||
|
author: '%an',
|
||||||
|
authorEmail: '%ae',
|
||||||
|
authorDate: '%aI',
|
||||||
|
committer: '%cn',
|
||||||
|
committerEmail: '%ce',
|
||||||
|
committerDate: '%cI',
|
||||||
|
tags: '%d'
|
||||||
|
});
|
||||||
|
const pretty = logSplitter + '%n' + formatPlaceholders
|
||||||
|
.map(x => `@@@${x[0]}%n${x[1]}`)
|
||||||
|
.join('%n');
|
||||||
|
var logCommand = `git log --pretty="${pretty}" --author-date-order ${(startHash === '' ? endHash : `${startHash}..${endHash}`)}`;
|
||||||
|
if (this.changePath !== '') {
|
||||||
|
logCommand += ` -- ${this.changePath}`;
|
||||||
|
}
|
||||||
|
const log = yield (0, CommandRunner_1.cmd)(logCommand);
|
||||||
|
const entries = log
|
||||||
|
.split(logSplitter)
|
||||||
|
.slice(1);
|
||||||
|
const commits = entries.map(entry => {
|
||||||
|
const fields = entry
|
||||||
|
.split(`@@@`)
|
||||||
|
.slice(1)
|
||||||
|
.reduce((acc, value) => {
|
||||||
|
const firstLine = value.indexOf('\n');
|
||||||
|
const key = value.substring(0, firstLine);
|
||||||
|
acc[key] = value.substring(firstLine + 1).trim();
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const tags = fields.tags
|
||||||
|
.split(',')
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter((v) => v.startsWith('tags: '))
|
||||||
|
.map((v) => v.substring(5).trim());
|
||||||
|
return new CommitInfo_1.CommitInfo(fields.hash, fields.subject, fields.body, fields.author, fields.authorEmail, new Date(fields.authorDate), fields.committer, fields.committerEmail, new Date(fields.committerDate), tags);
|
||||||
|
});
|
||||||
|
// check for changes
|
||||||
|
let changed = true;
|
||||||
|
if (this.changePath !== '') {
|
||||||
|
if (startHash === '') {
|
||||||
|
const changedFiles = yield (0, CommandRunner_1.cmd)(`git log --name-only --oneline ${endHash} -- ${this.changePath}`);
|
||||||
|
changed = changedFiles.length > 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const changedFiles = yield (0, CommandRunner_1.cmd)(`git diff --name-only ${startHash}..${endHash} -- ${this.changePath}`);
|
||||||
|
changed = changedFiles.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CommitInfoSet_1.CommitInfoSet(changed, commits);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultCommitsProvider = DefaultCommitsProvider;
|
||||||
39
lib/providers/DefaultCurrentCommitResolver.js
Normal file
39
lib/providers/DefaultCurrentCommitResolver.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultCurrentCommitResolver = void 0;
|
||||||
|
const CommandRunner_1 = require("../CommandRunner");
|
||||||
|
class DefaultCurrentCommitResolver {
|
||||||
|
constructor(config) {
|
||||||
|
this.branch = config.branch;
|
||||||
|
}
|
||||||
|
ResolveAsync() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.branch === 'HEAD') {
|
||||||
|
return (yield (0, CommandRunner_1.cmd)('git', 'rev-parse', 'HEAD')).trim();
|
||||||
|
}
|
||||||
|
return this.branch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
IsEmptyRepoAsync() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let lastCommitAll = (yield (0, CommandRunner_1.cmd)('git', 'rev-list', '-n1', '--all')).trim();
|
||||||
|
return lastCommitAll === '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ResolveBranchNameAsync() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const branchName = this.branch == 'HEAD' ? yield (0, CommandRunner_1.cmd)('git', 'rev-parse', '--abbrev-ref', 'HEAD') : this.branch;
|
||||||
|
return branchName.trim();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultCurrentCommitResolver = DefaultCurrentCommitResolver;
|
||||||
99
lib/providers/DefaultLastReleaseResolver.js
Normal file
99
lib/providers/DefaultLastReleaseResolver.js
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultLastReleaseResolver = void 0;
|
||||||
|
const CommandRunner_1 = require("../CommandRunner");
|
||||||
|
const ReleaseInformation_1 = require("./ReleaseInformation");
|
||||||
|
const core = __importStar(require("@actions/core"));
|
||||||
|
class DefaultLastReleaseResolver {
|
||||||
|
constructor(config) {
|
||||||
|
this.changePath = config.changePath;
|
||||||
|
this.useBranches = config.useBranches;
|
||||||
|
}
|
||||||
|
ResolveAsync(current, tagFormatter) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const releasePattern = tagFormatter.GetPattern();
|
||||||
|
let currentTag = (yield (0, CommandRunner_1.cmd)(`git tag --points-at ${current} ${releasePattern}`)).trim();
|
||||||
|
currentTag = tagFormatter.IsValid(currentTag) ? currentTag : '';
|
||||||
|
const isTagged = currentTag !== '';
|
||||||
|
const [currentMajor, currentMinor, currentPatch] = !!currentTag ? tagFormatter.Parse(currentTag) : [null, null, null];
|
||||||
|
let tagsCount = 0;
|
||||||
|
let tag = '';
|
||||||
|
try {
|
||||||
|
const refPrefixPattern = this.useBranches ? 'refs/heads/' : 'refs/tags/';
|
||||||
|
if (!!currentTag) {
|
||||||
|
// If we already have the current branch tagged, we are checking for the previous one
|
||||||
|
// so that we will have an accurate increment (assuming the new tag is the expected one)
|
||||||
|
const command = `git for-each-ref --sort=-v:*refname --format=%(refname:short) --merged=${current} ${refPrefixPattern}${releasePattern}`;
|
||||||
|
const tags = (yield (0, CommandRunner_1.cmd)(command)).split('\n');
|
||||||
|
tagsCount = tags.length;
|
||||||
|
tag = tags
|
||||||
|
.find(t => tagFormatter.IsValid(t) && t !== currentTag) || '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const command = `git for-each-ref --sort=-v:*refname --format=%(refname:short) --merged=${current} ${refPrefixPattern}${releasePattern}`;
|
||||||
|
const tags = (yield (0, CommandRunner_1.cmd)(command)).split('\n');
|
||||||
|
tagsCount = tags.length;
|
||||||
|
tag = tags
|
||||||
|
.find(t => tagFormatter.IsValid(t)) || '';
|
||||||
|
}
|
||||||
|
tag = tag.trim();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
tag = '';
|
||||||
|
}
|
||||||
|
if (tag === '') {
|
||||||
|
if ((yield (0, CommandRunner_1.cmd)('git', 'remote')) !== '') {
|
||||||
|
// Since there is no remote, we assume that there are no other tags to pull. In
|
||||||
|
// practice this isn't likely to happen, but it keeps the test output from being
|
||||||
|
// polluted with a bunch of warnings.
|
||||||
|
if (tagsCount > 0) {
|
||||||
|
core.warning(`None of the ${tagsCount} tags(s) found were valid version tags for the present configuration. If this is unexpected, check to ensure that the configuration is correct and matches the tag format you are using.`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
core.warning('No tags are present for this repository. If this is unexpected, check to ensure that tags have been pulled from the remote.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [major, minor, patch] = tagFormatter.Parse('');
|
||||||
|
// no release tags yet, use the initial commit as the root
|
||||||
|
return new ReleaseInformation_1.ReleaseInformation(major, minor, patch, '', currentMajor, currentMinor, currentPatch, isTagged);
|
||||||
|
}
|
||||||
|
// parse the version tag
|
||||||
|
const [major, minor, patch] = tagFormatter.Parse(tag);
|
||||||
|
const root = yield (0, CommandRunner_1.cmd)('git', `merge-base`, tag, current);
|
||||||
|
return new ReleaseInformation_1.ReleaseInformation(major, minor, patch, root.trim(), currentMajor, currentMinor, currentPatch, isTagged);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultLastReleaseResolver = DefaultLastReleaseResolver;
|
||||||
106
lib/providers/DefaultVersionClassifier.js
Normal file
106
lib/providers/DefaultVersionClassifier.js
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DefaultVersionClassifier = void 0;
|
||||||
|
const VersionClassification_1 = require("./VersionClassification");
|
||||||
|
const VersionType_1 = require("./VersionType");
|
||||||
|
class DefaultVersionClassifier {
|
||||||
|
constructor(config) {
|
||||||
|
const searchBody = config.searchCommitBody;
|
||||||
|
this.majorPattern = this.parsePattern(config.majorPattern, config.majorFlags, searchBody);
|
||||||
|
this.minorPattern = this.parsePattern(config.minorPattern, config.minorFlags, searchBody);
|
||||||
|
this.enablePrereleaseMode = config.enablePrereleaseMode;
|
||||||
|
}
|
||||||
|
parsePattern(pattern, flags, searchBody) {
|
||||||
|
if (/^\/.+\/[i]*$/.test(pattern)) {
|
||||||
|
const regexEnd = pattern.lastIndexOf('/');
|
||||||
|
const parsedFlags = pattern.slice(pattern.lastIndexOf('/') + 1);
|
||||||
|
const regex = new RegExp(pattern.slice(1, regexEnd), parsedFlags || flags);
|
||||||
|
return searchBody ?
|
||||||
|
(commit) => regex.test(commit.subject) || regex.test(commit.body) :
|
||||||
|
(commit) => regex.test(commit.subject);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const matchString = pattern;
|
||||||
|
return searchBody ?
|
||||||
|
(commit) => commit.subject.includes(matchString) || commit.body.includes(matchString) :
|
||||||
|
(commit) => commit.subject.includes(matchString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getNextVersion(current, type) {
|
||||||
|
if (this.enablePrereleaseMode && current.major === 0) {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType_1.VersionType.Major:
|
||||||
|
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
||||||
|
case VersionType_1.VersionType.Minor:
|
||||||
|
case VersionType_1.VersionType.Patch:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch + 1 };
|
||||||
|
case VersionType_1.VersionType.None:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown change type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case VersionType_1.VersionType.Major:
|
||||||
|
return { major: current.major + 1, minor: 0, patch: 0 };
|
||||||
|
case VersionType_1.VersionType.Minor:
|
||||||
|
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
||||||
|
case VersionType_1.VersionType.Patch:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch + 1 };
|
||||||
|
case VersionType_1.VersionType.None:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown change type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveCommitType(commitsSet) {
|
||||||
|
if (commitsSet.commits.length === 0) {
|
||||||
|
return { type: VersionType_1.VersionType.None, increment: 0, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
const commits = commitsSet.commits.reverse();
|
||||||
|
let index = 1;
|
||||||
|
for (let commit of commits) {
|
||||||
|
if (this.majorPattern(commit)) {
|
||||||
|
return { type: VersionType_1.VersionType.Major, increment: commits.length - index, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
index = 1;
|
||||||
|
for (let commit of commits) {
|
||||||
|
if (this.minorPattern(commit)) {
|
||||||
|
return { type: VersionType_1.VersionType.Minor, increment: commits.length - index, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return { type: VersionType_1.VersionType.Patch, increment: commitsSet.commits.length - 1, changed: true };
|
||||||
|
}
|
||||||
|
ClassifyAsync(lastRelease, commitSet) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const { type, increment, changed } = this.resolveCommitType(commitSet);
|
||||||
|
const { major, minor, patch } = this.getNextVersion(lastRelease, type);
|
||||||
|
if (lastRelease.currentPatch !== null) {
|
||||||
|
// If the current commit is tagged, we must use that version. Here we check if the version we have resolved from the
|
||||||
|
// previous commits is the same as the current version. If it is, we will use the increment value, otherwise we reset
|
||||||
|
// to zero. For example:
|
||||||
|
// - commit 1 - v1.0.0+0
|
||||||
|
// - commit 2 - v1.0.0+1
|
||||||
|
// - commit 3 was tagged v2.0.0 - v2.0.0+0
|
||||||
|
// - commit 4 - v2.0.1+0
|
||||||
|
const versionsMatch = lastRelease.currentMajor === major && lastRelease.currentMinor === minor && lastRelease.currentPatch === patch;
|
||||||
|
const currentIncrement = versionsMatch ? increment : 0;
|
||||||
|
return new VersionClassification_1.VersionClassification(VersionType_1.VersionType.None, currentIncrement, false, lastRelease.currentMajor, lastRelease.currentMinor, lastRelease.currentPatch);
|
||||||
|
}
|
||||||
|
return new VersionClassification_1.VersionClassification(type, increment, changed, major, minor, patch);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.DefaultVersionClassifier = DefaultVersionClassifier;
|
||||||
2
lib/providers/LastReleaseResolver.js
Normal file
2
lib/providers/LastReleaseResolver.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
28
lib/providers/ReleaseInformation.js
Normal file
28
lib/providers/ReleaseInformation.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.ReleaseInformation = void 0;
|
||||||
|
// Finds the hash of the last commit
|
||||||
|
class ReleaseInformation {
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
* @param major - the major version number
|
||||||
|
* @param minor - the minor version number
|
||||||
|
* @param patch - the patch version number
|
||||||
|
* @param hash - the hash of commit of the last release
|
||||||
|
* @param currentMajor - the major version number from the current commit
|
||||||
|
* @param currentMinor - the minor version number from the current commit
|
||||||
|
* @param currentPatch - the patch version number from the current commit
|
||||||
|
* @param isTagged - whether the current commit is tagged with a version
|
||||||
|
*/
|
||||||
|
constructor(major, minor, patch, hash, currentMajor, currentMinor, currentPatch, isTagged) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
this.hash = hash;
|
||||||
|
this.currentMajor = currentMajor;
|
||||||
|
this.currentMinor = currentMinor;
|
||||||
|
this.currentPatch = currentPatch;
|
||||||
|
this.isTagged = isTagged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ReleaseInformation = ReleaseInformation;
|
||||||
83
lib/providers/TagLastReleaseResolver.js
Normal file
83
lib/providers/TagLastReleaseResolver.js
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.TagLastReleaseResolver = void 0;
|
||||||
|
const CommandRunner_1 = require("../CommandRunner");
|
||||||
|
const ReleaseInformation_1 = require("./ReleaseInformation");
|
||||||
|
const core = __importStar(require("@actions/core"));
|
||||||
|
class TagLastReleaseResolver {
|
||||||
|
constructor(config) {
|
||||||
|
this.changePath = config.changePath;
|
||||||
|
}
|
||||||
|
ResolveAsync(current, tagFormatter) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const releasePattern = tagFormatter.GetPattern();
|
||||||
|
let currentTag = (yield (0, CommandRunner_1.cmd)(`git tag --points-at ${current} ${releasePattern}`)).trim();
|
||||||
|
const [currentMajor, currentMinor, currentPatch] = !!currentTag ? tagFormatter.Parse(currentTag) : [null, null, null];
|
||||||
|
let tag = '';
|
||||||
|
try {
|
||||||
|
if (!!currentTag) {
|
||||||
|
// If we already have the current branch tagged, we are checking for the previous one
|
||||||
|
// so that we will have an accurate increment (assuming the new tag is the expected one)
|
||||||
|
const command = `git for-each-ref --count=2 --sort=-v:*refname --format=%(refname:short) --merged=${current} refs/tags/${releasePattern}`;
|
||||||
|
tag = yield (0, CommandRunner_1.cmd)(command);
|
||||||
|
tag = tag.split('\n').at(-1) || '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const command = `git for-each-ref --count=1 --sort=-v:*refname --format=%(refname:short) --merged=${current} refs/tags/${releasePattern}`;
|
||||||
|
tag = yield (0, CommandRunner_1.cmd)(command);
|
||||||
|
}
|
||||||
|
tag = tag.trim();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
tag = '';
|
||||||
|
}
|
||||||
|
if (tag === '') {
|
||||||
|
if ((yield (0, CommandRunner_1.cmd)('git', 'remote')) !== '') {
|
||||||
|
// Since there is no remote, we assume that there are no other tags to pull. In
|
||||||
|
// practice this isn't likely to happen, but it keeps the test output from being
|
||||||
|
// polluted with a bunch of warnings.
|
||||||
|
core.warning('No tags are present for this repository. If this is unexpected, check to ensure that tags have been pulled from the remote.');
|
||||||
|
}
|
||||||
|
// no release tags yet, use the initial commit as the root
|
||||||
|
return new ReleaseInformation_1.ReleaseInformation(0, 0, 0, '', currentMajor, currentMinor, currentPatch);
|
||||||
|
}
|
||||||
|
// parse the version tag
|
||||||
|
const [major, minor, patch] = tagFormatter.Parse(tag);
|
||||||
|
const root = yield (0, CommandRunner_1.cmd)('git', `merge-base`, tag, current);
|
||||||
|
return new ReleaseInformation_1.ReleaseInformation(major, minor, patch, root.trim(), currentMajor, currentMinor, currentPatch);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.TagLastReleaseResolver = TagLastReleaseResolver;
|
||||||
18
lib/providers/UserInfo.js
Normal file
18
lib/providers/UserInfo.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.UserInfo = void 0;
|
||||||
|
/** Represents information about a user (e.g. committer, author, tagger) */
|
||||||
|
class UserInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
* @param name - User's name
|
||||||
|
* @param email - User's email
|
||||||
|
* @param commits - Number of commits in the scope evaluated
|
||||||
|
*/
|
||||||
|
constructor(name, email, commits) {
|
||||||
|
this.name = name;
|
||||||
|
this.email = email;
|
||||||
|
this.commits = commits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.UserInfo = UserInfo;
|
||||||
24
lib/providers/VersionClassification.js
Normal file
24
lib/providers/VersionClassification.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.VersionClassification = void 0;
|
||||||
|
/** The result of a version classification */
|
||||||
|
class VersionClassification {
|
||||||
|
/**
|
||||||
|
* Creates a new version classification result instance
|
||||||
|
* @param type - The type of change the current range represents
|
||||||
|
* @param increment - The number of commits which have this version, usually zero-based
|
||||||
|
* @param changed - True if the version has changed, false otherwise
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
*/
|
||||||
|
constructor(type, increment, changed, major, minor, patch) {
|
||||||
|
this.type = type;
|
||||||
|
this.increment = increment;
|
||||||
|
this.changed = changed;
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VersionClassification = VersionClassification;
|
||||||
2
lib/providers/VersionClassifier.js
Normal file
2
lib/providers/VersionClassifier.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
31
lib/providers/VersionInformation.js
Normal file
31
lib/providers/VersionInformation.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.VersionInformation = void 0;
|
||||||
|
/**
|
||||||
|
* Represents the "resolved" information about a version change, serves
|
||||||
|
* as the input to formatters that produce the final output
|
||||||
|
*/
|
||||||
|
class VersionInformation {
|
||||||
|
/**
|
||||||
|
* Creates a new version information instance
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
* @param increment - The number of commits for this version
|
||||||
|
* @param type - The type of change the current range represents
|
||||||
|
* @param commits - The list of commits for this version
|
||||||
|
* @param changed - True if the version has changed, false otherwise
|
||||||
|
* @param isTagged - True if the current commit is a version-tagged commit
|
||||||
|
*/
|
||||||
|
constructor(major, minor, patch, increment, type, commits, changed, isTagged) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
this.increment = increment;
|
||||||
|
this.type = type;
|
||||||
|
this.commits = commits;
|
||||||
|
this.changed = changed;
|
||||||
|
this.isTagged = isTagged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.VersionInformation = VersionInformation;
|
||||||
15
lib/providers/VersionType.js
Normal file
15
lib/providers/VersionType.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.VersionType = void 0;
|
||||||
|
/** Indicates the type of change a particular version change represents */
|
||||||
|
var VersionType;
|
||||||
|
(function (VersionType) {
|
||||||
|
/** Indicates a major version change */
|
||||||
|
VersionType["Major"] = "Major";
|
||||||
|
/** Indicates a minor version change */
|
||||||
|
VersionType["Minor"] = "Minor";
|
||||||
|
/** Indicates a patch version change */
|
||||||
|
VersionType["Patch"] = "Patch";
|
||||||
|
/** Indicates no change--generally this means that the current commit is already tagged with a version */
|
||||||
|
VersionType["None"] = "None";
|
||||||
|
})(VersionType || (exports.VersionType = VersionType = {}));
|
||||||
23
lib/providers/index.js
Normal file
23
lib/providers/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.VersionType = exports.VersionInformation = exports.VersionClassification = exports.UserInfo = exports.TagLastReleaseResolver = exports.ReleaseInformation = exports.DefaultVersionClassifier = exports.DefaultCurrentCommitResolver = exports.DefaultCommitsProvider = exports.CommitInfo = void 0;
|
||||||
|
var CommitInfo_1 = require("./CommitInfo");
|
||||||
|
Object.defineProperty(exports, "CommitInfo", { enumerable: true, get: function () { return CommitInfo_1.CommitInfo; } });
|
||||||
|
var DefaultCommitsProvider_1 = require("./DefaultCommitsProvider");
|
||||||
|
Object.defineProperty(exports, "DefaultCommitsProvider", { enumerable: true, get: function () { return DefaultCommitsProvider_1.DefaultCommitsProvider; } });
|
||||||
|
var DefaultCurrentCommitResolver_1 = require("./DefaultCurrentCommitResolver");
|
||||||
|
Object.defineProperty(exports, "DefaultCurrentCommitResolver", { enumerable: true, get: function () { return DefaultCurrentCommitResolver_1.DefaultCurrentCommitResolver; } });
|
||||||
|
var DefaultVersionClassifier_1 = require("./DefaultVersionClassifier");
|
||||||
|
Object.defineProperty(exports, "DefaultVersionClassifier", { enumerable: true, get: function () { return DefaultVersionClassifier_1.DefaultVersionClassifier; } });
|
||||||
|
var ReleaseInformation_1 = require("./ReleaseInformation");
|
||||||
|
Object.defineProperty(exports, "ReleaseInformation", { enumerable: true, get: function () { return ReleaseInformation_1.ReleaseInformation; } });
|
||||||
|
var TagLastReleaseResolver_1 = require("./TagLastReleaseResolver");
|
||||||
|
Object.defineProperty(exports, "TagLastReleaseResolver", { enumerable: true, get: function () { return TagLastReleaseResolver_1.TagLastReleaseResolver; } });
|
||||||
|
var UserInfo_1 = require("./UserInfo");
|
||||||
|
Object.defineProperty(exports, "UserInfo", { enumerable: true, get: function () { return UserInfo_1.UserInfo; } });
|
||||||
|
var VersionClassification_1 = require("./VersionClassification");
|
||||||
|
Object.defineProperty(exports, "VersionClassification", { enumerable: true, get: function () { return VersionClassification_1.VersionClassification; } });
|
||||||
|
var VersionInformation_1 = require("./VersionInformation");
|
||||||
|
Object.defineProperty(exports, "VersionInformation", { enumerable: true, get: function () { return VersionInformation_1.VersionInformation; } });
|
||||||
|
var VersionType_1 = require("./VersionType");
|
||||||
|
Object.defineProperty(exports, "VersionType", { enumerable: true, get: function () { return VersionType_1.VersionType; } });
|
||||||
130
minor.drawio.svg
Normal file
130
minor.drawio.svg
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="421px" height="271px" viewBox="-0.5 -0.5 421 271" content="<mxfile host="9c90bc84-0eec-4321-8fe4-32d91483c5f7" modified="2020-12-28T22:19:05.395Z" agent="5.0 (Macintosh; Intel Mac OS X 11_0_0) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="_UF7RC4DdOApeqGn3r0c" version="13.10.0" type="embed"><diagram id="gI28yuYEcBMMNC8PbDnz" name="Page-1">5Zhdb9owFIZ/DdJ2U8Xxwsdly6CbNLZpXbVrlxjHq5MTOeZrv37HiUMIHh3SKB2Fi8h+/ZHj57wOTjp0mK5uNcuTCcRcdcIgXnXo+04Ydsk7vFphXQlhb1AJQsu4kkgj3Mlf3ImBU+cy5kWrowFQRuZtcQpZxqempTGtYdnuNgPVvmvOBPeEuylTvvpDxiap1H4UNPoHLkVS35kEruWBTR+Fhnnm7tcJ6bj8Vc0pq+dy/YuExbDckuioQ4cawFSldDXkyqKtsVXjxntaN3FrnplDBrg8LZia8zriMi6zrlnwGNG4KmiTgICMqVGj3pTr5XbGAGtNn08AOYoExZ/cmLXLM5sbQCkxqXKtftRuIQXM9dTF4SIzTAvuetFKshFuDXMrveWQcqPX2EFzxYxctJPLnEfEpl/DCQsO1Z+xhQdgUwrdavEsE2n4Xc7KhSxxv7QXz4q8svBMrizE/TQWXBu+enKldWvX+cvtP0pdfdm4uZaSLSPX2r+w6Z6PpahvqeilLEXPzFJhdDpLDc7HUpFvqf5LWSo6M0uR3uks1T8zNoPToSHEY/OdCWFXdm1lchVcEY8Wrsu0sRRGwyMfggKNSgaZRTmTSu1ITEmRYVXxmZ3BMpJ4FLt2cirjWO3LQntTH4F7GOz+c0Ye+M1pb5t8eAzy/rmipI3aTXApxMPIJ957LuD+vy4CR+SXBJz0Tgjcf+HYAL+YZ8rghLz/j6MTz+Jr+1reJAiVsbRhPw15+xxFjv6654Z+BYm33aSLkp390Y/aU1TnPTdqJxebMA5Lj39Em8gMjVzaq5CQYWmasEzwAksxdrJfQuwlKYtM4DXlRWE/aLzS7UPDvz+vSPRcG6jnZWgIaSqtXyaOuz0WdZUF9YCZ6wpTrrtSZC3c5zEzZRbfTD5+/vLtbd0DI5DNqNeZwuiADB7pUIXV5vNZtSWbT5R09Bs=</diagram></mxfile>" style="background-color: rgb(255, 255, 255);">
|
||||||
|
<defs/>
|
||||||
|
<g>
|
||||||
|
<path d="M 125 240 L 125 196.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 125 191.12 L 128.5 198.12 L 125 196.37 L 121.5 198.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="125" cy="255" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 125 160 L 125 116.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 125 111.12 L 128.5 118.12 L 125 116.37 L 121.5 118.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="125" cy="175" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 125 80 L 125 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 125 31.12 L 128.5 38.12 L 125 36.37 L 121.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="125" cy="95" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<ellipse cx="125" cy="15" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<rect x="150" y="245" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 255px; margin-left: 152px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="152" y="259" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="150" y="165" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 175px; margin-left: 152px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="152" y="179" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="150" y="85" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 95px; margin-left: 152px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.1.0+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="152" y="99" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.1.0+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="150" y="5" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 15px; margin-left: 152px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.1.0+1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="152" y="19" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.1.0+1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 260 95 L 220 95" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<rect x="270" y="85" width="150" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 148px; height: 1px; padding-top: 95px; margin-left: 272px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Minor version changes due to the tag message
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="272" y="99" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Minor version changes due...
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="0" y="85" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 95px; margin-left: 2px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Commit Message:
|
||||||
|
<br/>
|
||||||
|
<i>
|
||||||
|
Updates (MINOR)
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="2" y="99" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Commit Message:...
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<switch>
|
||||||
|
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||||
|
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||||
|
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||||
|
Viewer does not support full SVG 1.1
|
||||||
|
</text>
|
||||||
|
</a>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
10960
package-lock.json
generated
10960
package-lock.json
generated
File diff suppressed because it is too large
Load diff
33
package.json
33
package.json
|
|
@ -1,12 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "semantic-version",
|
"name": "semantic-version",
|
||||||
"version": "1.0.0",
|
"version": "0.0.0",
|
||||||
"description": "Semantic Version GitHub Action",
|
"description": "Semantic Version GitHub Action",
|
||||||
"main": "index.js",
|
"main": "lib/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint index.js",
|
"build": "tsc",
|
||||||
"package": "ncc build index.js -o dist",
|
"format": "prettier --write **/**.ts",
|
||||||
"test": "eslint index.js && jest"
|
"format-check": "prettier --check **/**.ts",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"package": "ncc build --source-map --license licenses.txt",
|
||||||
|
"test": "jest --runInBand --config ./jest.config.js",
|
||||||
|
"all": "npm run build && npm run format && npm run lint && npm run package && npm test"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -24,13 +28,20 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/paulhatch/semantic-version#readme",
|
"homepage": "https://github.com/paulhatch/semantic-version#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.1.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/exec": "^1.0.1"
|
"@actions/exec": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@zeit/ncc": "^0.20.5",
|
"@types/node": "^20.11.13",
|
||||||
"eslint": "^6.3.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
"eslint-plugin-jest": "^23.1.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"jest": "^24.9.0"
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-github": "^4.10.1",
|
||||||
|
"eslint-plugin-jest": "^27.6.3",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"prettier": "^3.2.4",
|
||||||
|
"ts-jest": "^29.1.2",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
182
readme.md
182
readme.md
|
|
@ -1,35 +1,189 @@
|
||||||
<p align="center">
|

|
||||||
<a href="https://github.com/paulhatch/semantic-version"><img alt="GitHub Actions status" src="https://github.com/paulhatch/semantic-version/workflows/test-local/badge.svg"></a>
|
|
||||||
</p>
|
See the [configuration guide](guide.md) for help getting started, selecting a versioning strategy and example configurations, or [contributing.md](contributing.md) for information on how to get help or contribute to this project.
|
||||||
|
|
||||||
# Git-Based Semantic Versioning
|
# Git-Based Semantic Versioning
|
||||||
|
|
||||||
This action produces a [semantic version](https://semver.org) for a repository
|
This action produces a [semantic version](https://semver.org) for a repository
|
||||||
using the repository's git history.
|
using the repository's git history without ever requiring a human to choose or
|
||||||
|
manually assign the version number.
|
||||||
|
|
||||||
This action is designed to facilitate assigning version numbers during a build
|
This action is designed to facilitate assigning version numbers during a build
|
||||||
automatically while publishing version that only increment by one value per
|
automatically while publishing version that only increment by one value per
|
||||||
release. To accomplish this, the next version number is calculated along with
|
release. To accomplish this, the next version number is calculated along with
|
||||||
a commit increment indicating the number of commits for this version. The
|
a commit increment indicating the number of commits for this version. The
|
||||||
commit messages are inspected to determine the type of version change the next
|
commit messages are inspected to determine the type of version change the next
|
||||||
version represents. Including the term `(MAJOR)` or `(MINOR)` in the commit
|
version represents. By default, this action follows [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
message alters the type of change the next version will represent.
|
patterns: commits with `feat:` trigger minor version bumps, and commits with a `!` suffix
|
||||||
|
(e.g., `feat!:`, `fix!:`) or containing `BREAKING CHANGE:` trigger major version bumps.
|
||||||
|
|
||||||
|
# Background
|
||||||
|
|
||||||
|
Automatic versioning during a build presents a chicken-and-egg problem--we
|
||||||
|
want the version to increase by a single value between each release, but we
|
||||||
|
usually do not know at build time whether a new build will be released or not.
|
||||||
|
Generally a build is tagged as part of a release step after passing testing and
|
||||||
|
other quality controls, so if we want to use the version number in the build
|
||||||
|
itself, especially for a build triggered by a commit, we cannot rely on having
|
||||||
|
a proper tag for the build. Most CI systems offer a "build number", but this
|
||||||
|
does not correspond to our semantic version and relies on the state of the CI
|
||||||
|
tool. It is with this in mind that this tool was developed with the following
|
||||||
|
goals:
|
||||||
|
|
||||||
|
- Allow the version to be injected into the build
|
||||||
|
- Derive the version only from the git repository itself
|
||||||
|
- Do not require the version to be maintained by hand
|
||||||
|
- Resolve the version deterministically for a given commit (see caveats below)
|
||||||
|
- Provide an easy mechanism for incrementing major and minor versions by developers
|
||||||
|
|
||||||
|
To solve this problem, this action calculates the next _implied_ version based on
|
||||||
|
the most recently tagged version and the commit messages. An additional value called
|
||||||
|
the "increment" tracks the count of commits since the last version change, allowing
|
||||||
|
a label to be created to mark pre-release versions. The version produced by this
|
||||||
|
action is always the implied version (unless `bump_each_commit` is set to `true`).
|
||||||
|
Subsequently tagging a commit that is chosen as the implied version is what bumps
|
||||||
|
the version for future commits.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_Unless the current commit is already tagged, the version produced by this action will be one value ahead of the last tag._
|
||||||
|
|
||||||
|
## Major and Minor Versions
|
||||||
|
|
||||||
|
The commit messages for the span of commits from the last tag are checked for the
|
||||||
|
presence of version bump patterns. By default, `feat:` triggers a minor version bump,
|
||||||
|
while `!:` (e.g., `feat!:`, `fix!:`) or `BREAKING CHANGE:` triggers a major version bump. If a pattern
|
||||||
|
is encountered that commit is treated as the start of a major or minor version
|
||||||
|
instead of the default patch level. As with normal commits the implied version
|
||||||
|
will only increment by one value since the last tag regardless of how many major
|
||||||
|
or minor commits are encountered. Major commits override minor commits, so a set
|
||||||
|
of commits containing both major and minor tags will result in a major version
|
||||||
|
increment.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Tags on Previous Commits
|
||||||
|
|
||||||
|
Adding a tag to an older commit changes the implicit version of commits since the
|
||||||
|
tagged commit. If a tag is assigned to an older commit, the commits that come after
|
||||||
|
it will be given the new version if the build were to be retriggered, for example:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
<!-- start usage -->
|
<!-- start usage -->
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: paulhatch/semantic-version@v1-beta
|
- uses: paulhatch/semantic-version@v5.4.0
|
||||||
with:
|
with:
|
||||||
# The branch to count commits on
|
|
||||||
branch: "master"
|
|
||||||
# The prefix to use to identify tags
|
# The prefix to use to identify tags
|
||||||
tag_prefix: "v"
|
tag_prefix: "v"
|
||||||
# A string which, if present in a git commit, indicates that a change represents a major (breaking) change
|
# A string which, if present in a git commit, indicates that a change represents a
|
||||||
major_pattern: "(MAJOR)"
|
# major (breaking) change, supports regular expressions wrapped with '/'
|
||||||
# Same as above except indicating a minor change
|
major_pattern: "/!:|BREAKING CHANGE:/"
|
||||||
minor_pattern: "(MINOR)"
|
# A string which indicates the flags used by the `major_pattern` regular expression. Supported flags: idgs
|
||||||
|
major_regexp_flags: ""
|
||||||
|
# Same as above except indicating a minor change, supports regular expressions wrapped with '/'
|
||||||
|
minor_pattern: "/feat:/"
|
||||||
|
# A string which indicates the flags used by the `minor_pattern` regular expression. Supported flags: idgs
|
||||||
|
minor_regexp_flags: ""
|
||||||
# A string to determine the format of the version output
|
# A string to determine the format of the version output
|
||||||
format: "${major}.${minor}.${patch}-prerelease.${increment}"
|
version_format: "${major}.${minor}.${patch}-prerelease${increment}"
|
||||||
|
# Optional path to check for changes. If any changes are detected in the path the
|
||||||
|
# 'changed' output will true. Enter multiple paths separated by spaces.
|
||||||
|
change_path: "src/my-service"
|
||||||
|
# Named version, will be used as suffix for name version tag
|
||||||
|
namespace: my-service
|
||||||
|
# If this is set to true, *every* commit will be treated as a new version.
|
||||||
|
bump_each_commit: false
|
||||||
|
# If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched.
|
||||||
|
bump_each_commit_patch_pattern: ""
|
||||||
|
# If true, the body of commits will also be searched for major/minor patterns to determine the version type.
|
||||||
|
search_commit_body: false
|
||||||
|
# The output method used to generate list of users, 'csv' or 'json'.
|
||||||
|
user_format_type: "csv"
|
||||||
|
# Prevents pre-v1.0.0 version from automatically incrementing the major version.
|
||||||
|
# If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged.
|
||||||
|
enable_prerelease_mode: true
|
||||||
|
# If enabled, diagnostic information will be added to the action output.
|
||||||
|
debug: false
|
||||||
|
# If true, the branch will be used to select the maximum version.
|
||||||
|
version_from_branch: false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
- *major*, *minor*, and *patch* provide the version numbers that have been determined for this commit
|
||||||
|
- *increment* is an additional value indicating the number of commits for the current version, starting at zero. This can be used as part of a pre-release label.
|
||||||
|
- *version_type* is the type of version change the new version represents, e.g. `major`, `minor`, `patch`, or `none`.
|
||||||
|
- *version* is a formatted version string created using the format input. This is a convenience value to provide a preformatted representation of the data generated by this action.
|
||||||
|
- *version_tag* is a string identifier that would be used to tag the current commit as the "released" version. Typically this would only be used to generate a Git tag name.
|
||||||
|
- *changed* indicates whether there was a change since the last version if change_path was specified. If no `change_path` was specified this value will always be true since the entire repo is considered. (It is possible to create a commit with no changes, but the Git cli rejects this by default and this case is not considered here)
|
||||||
|
- *is_tagged* indicates whether the current commit has a tag matching `tag_prefix`
|
||||||
|
- *authors* is a list of authors that have committed to this version, formatted as either csv or json.
|
||||||
|
- *current_commit* is the current commit hash.
|
||||||
|
- *previous_commit* is the previous commit hash.
|
||||||
|
- *previous_version* is the previous version.
|
||||||
|
- *debug_output* will show diagnostic information, if debug is enabled
|
||||||
|
|
||||||
|
There are two types of "version" string, one is the semantic version output that can be used to identify a build and can include prerelease data and metadata specific to the commit such as `v2.0.1-pre001+cf6e75` (you would produce this string yourself using the version information from this action plus whatever metadata you wanted to add), the other is the tag version string, which identifies a specific commit as being a specific version.
|
||||||
|
|
||||||
|
## Using Multiple Versions in the Same Repository
|
||||||
|
|
||||||
|
It is possible to create additional versions for multiple project co-existing
|
||||||
|
in one repository, for example you may have a Helm chart, database migration,
|
||||||
|
or simply be hosting multiple projects in the same repository and want them to
|
||||||
|
be versioned independently. There are a few settings that can be used to
|
||||||
|
accomplish this:
|
||||||
|
|
||||||
|
First, you can set the `change_path` input to specify a path that will be
|
||||||
|
inspected for changes. Commits which do no change any files in this path will
|
||||||
|
not increase the `increment` output. In addition, if there are no changes in
|
||||||
|
a given commit with this path specified, the `changed` value will be false.
|
||||||
|
|
||||||
|
Second, the input `namespace` can be set to create an additional named version.
|
||||||
|
If this value is set, it will be appended (separated by a hyphen) to the end of
|
||||||
|
tags for the version, and only tags with this value appended will be considered
|
||||||
|
when determining the version. The namespace will be pruned from the string
|
||||||
|
output as "version" within the action.
|
||||||
|
|
||||||
|
Finally, set different values for `major_pattern` and `minor_pattern` than the
|
||||||
|
other projects in order to be able to mark these commits independently.
|
||||||
|
|
||||||
|
To use secondary versions in a workflow, simply create additional steps in a
|
||||||
|
job referencing semantic version multiple times. For example, a project tagged
|
||||||
|
like `v1.2.3+0-db` could be configured like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Application Version
|
||||||
|
id: version
|
||||||
|
uses: paulhatch/semantic-version@v5.4.0
|
||||||
|
with:
|
||||||
|
change_path: "src/service"
|
||||||
|
- name: Database Version
|
||||||
|
id: db-version
|
||||||
|
uses: paulhatch/semantic-version@v5.4.0
|
||||||
|
with:
|
||||||
|
major_pattern: "(MAJOR-DB)"
|
||||||
|
minor_pattern: "(MINOR-DB)"
|
||||||
|
change_path: "src/migrations"
|
||||||
|
namespace: db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Note Regarding the Checkout action
|
||||||
|
|
||||||
|
Beginning in v2, `actions/checkout` [does not include tags/history by default](https://github.com/actions/checkout/issues/100).
|
||||||
|
This history is required to determine the version correctly. To include the history
|
||||||
|
and tags, specify the fetch-depth parameter in your checkout action declaration. Specify
|
||||||
|
zero to pull the full history and tags.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can set this number to a value high enough to pull all the commits
|
||||||
|
you'd expect to have in a release.
|
||||||
|
|
|
||||||
39
src/ActionConfig.ts
Normal file
39
src/ActionConfig.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/** Represents the input configuration for the semantic-version action */
|
||||||
|
export class ActionConfig {
|
||||||
|
/** Set to specify a specific branch, default is the current HEAD */
|
||||||
|
public branch: string = "HEAD";
|
||||||
|
/** The prefix to use to identify tags */
|
||||||
|
public tagPrefix: string = "v";
|
||||||
|
/** (Deprecated) Use branches instead of tags */
|
||||||
|
public useBranches: boolean = false;
|
||||||
|
/** If true, the branch will be used to select the maximum version. */
|
||||||
|
public versionFromBranch: string | boolean = false;
|
||||||
|
/** A string which, if present in a git commit, indicates that a change represents a major (breaking) change. Wrap with '/' to match using a regular expression. */
|
||||||
|
public majorPattern: string = "/!:|BREAKING CHANGE:/";
|
||||||
|
/** A string which indicates the flags used by the `majorPattern` regular expression. */
|
||||||
|
public majorFlags: string = "";
|
||||||
|
/** A string which, if present in a git commit, indicates that a change represents a minor (feature) change. Wrap with '/' to match using a regular expression. */
|
||||||
|
public minorPattern: string = "/feat:/";
|
||||||
|
/** A string which indicates the flags used by the `minorPattern` regular expression. */
|
||||||
|
public minorFlags: string = "";
|
||||||
|
/** Pattern to use when formatting output version */
|
||||||
|
public versionFormat: string = '${major}.${minor}.${patch}';
|
||||||
|
/** Path to check for changes. If any changes are detected in the path the 'changed' output will true. Enter multiple paths separated by spaces. */
|
||||||
|
public changePath: string = '';
|
||||||
|
/** Use to create a named sub-version. This value will be appended to tags created for this version. */
|
||||||
|
public namespace: string = "";
|
||||||
|
/** If true, every commit will be treated as a bump to the version. */
|
||||||
|
public bumpEachCommit: boolean = false;
|
||||||
|
/** If true, the body of commits will also be searched for major/minor patterns to determine the version type */
|
||||||
|
public searchCommitBody: boolean = false;
|
||||||
|
/** The output method used to generate list of users, 'csv' or 'json'. Default is 'csv'. */
|
||||||
|
public userFormatType: string = "csv";
|
||||||
|
/** Prevents pre-v1.0.0 version from automatically incrementing the major version. If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the versionType output is unchanged. */
|
||||||
|
public enablePrereleaseMode: boolean = false;
|
||||||
|
/** If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched. */
|
||||||
|
public bumpEachCommitPatchPattern: string = "";
|
||||||
|
/** If enabled, diagnostic information will be added to the action output. */
|
||||||
|
public debug: boolean = false;
|
||||||
|
/** Diagnostics to replay */
|
||||||
|
public replay: string = '';
|
||||||
|
}
|
||||||
34
src/CommandRunner.ts
Normal file
34
src/CommandRunner.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Using require instead of import to support integration testing
|
||||||
|
import * as exec from '@actions/exec';
|
||||||
|
import { DebugManager } from './DebugManager';
|
||||||
|
|
||||||
|
export const cmd = async (command: string, ...args: any): Promise<string> => {
|
||||||
|
|
||||||
|
const debugManager = DebugManager.getInstance();
|
||||||
|
|
||||||
|
if (debugManager.isReplayMode()) {
|
||||||
|
return debugManager.replayCommand(command, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '', errors = '';
|
||||||
|
const options = {
|
||||||
|
silent: true,
|
||||||
|
listeners: {
|
||||||
|
stdout: (data: any) => { output += data.toString(); },
|
||||||
|
stderr: (data: any) => { errors += data.toString(); },
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
silent: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let caughtError: any = null;
|
||||||
|
try {
|
||||||
|
await exec.exec(command, args, options);
|
||||||
|
} catch (err) {
|
||||||
|
caughtError = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugManager.recordCommand(command, args, output, errors, caughtError);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
60
src/ConfigurationProvider.ts
Normal file
60
src/ConfigurationProvider.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { CsvUserFormatter } from './formatting/CsvUserFormatter'
|
||||||
|
import { BranchVersioningTagFormatter } from './formatting/BranchVersioningTagFormatter'
|
||||||
|
import { DefaultTagFormatter } from './formatting/DefaultTagFormatter'
|
||||||
|
import { DefaultVersionFormatter } from './formatting/DefaultVersionFormatter'
|
||||||
|
import { JsonUserFormatter } from './formatting/JsonUserFormatter'
|
||||||
|
import { TagFormatter } from './formatting/TagFormatter'
|
||||||
|
import { UserFormatter } from './formatting/UserFormatter'
|
||||||
|
import { VersionFormatter } from './formatting/VersionFormatter'
|
||||||
|
import { CommitsProvider } from './providers/CommitsProvider'
|
||||||
|
import { CurrentCommitResolver } from './providers/CurrentCommitResolver'
|
||||||
|
import { DefaultCommitsProvider } from './providers/DefaultCommitsProvider'
|
||||||
|
import { DefaultCurrentCommitResolver } from './providers/DefaultCurrentCommitResolver'
|
||||||
|
import { DefaultVersionClassifier } from './providers/DefaultVersionClassifier'
|
||||||
|
import { LastReleaseResolver } from './providers/LastReleaseResolver'
|
||||||
|
import { DefaultLastReleaseResolver } from './providers/DefaultLastReleaseResolver'
|
||||||
|
import { VersionClassifier } from './providers/VersionClassifier'
|
||||||
|
import { BumpAlwaysVersionClassifier } from './providers/BumpAlwaysVersionClassifier'
|
||||||
|
import { ActionConfig } from './ActionConfig';
|
||||||
|
import { DebugManager } from './DebugManager';
|
||||||
|
|
||||||
|
export class ConfigurationProvider {
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.config = config;
|
||||||
|
DebugManager.getInstance().initializeConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private config: ActionConfig;
|
||||||
|
|
||||||
|
public GetCurrentCommitResolver(): CurrentCommitResolver { return new DefaultCurrentCommitResolver(this.config); }
|
||||||
|
|
||||||
|
public GetLastReleaseResolver(): LastReleaseResolver { return new DefaultLastReleaseResolver(this.config); }
|
||||||
|
|
||||||
|
public GetCommitsProvider(): CommitsProvider { return new DefaultCommitsProvider(this.config); }
|
||||||
|
|
||||||
|
public GetVersionClassifier(): VersionClassifier {
|
||||||
|
if (this.config.bumpEachCommit) {
|
||||||
|
return new BumpAlwaysVersionClassifier(this.config);
|
||||||
|
}
|
||||||
|
return new DefaultVersionClassifier(this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetVersionFormatter(): VersionFormatter { return new DefaultVersionFormatter(this.config); }
|
||||||
|
|
||||||
|
public GetTagFormatter(branchName: string): TagFormatter {
|
||||||
|
if (this.config.versionFromBranch) {
|
||||||
|
return new BranchVersioningTagFormatter(this.config, branchName);
|
||||||
|
}
|
||||||
|
return new DefaultTagFormatter(this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetUserFormatter(): UserFormatter {
|
||||||
|
switch (this.config.userFormatType) {
|
||||||
|
case 'json': return new JsonUserFormatter(this.config);
|
||||||
|
case 'csv': return new CsvUserFormatter(this.config);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown user format type: ${this.config.userFormatType}, supported types: json, csv`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/DebugManager.ts
Normal file
115
src/DebugManager.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { ActionConfig } from "./ActionConfig";
|
||||||
|
|
||||||
|
|
||||||
|
/** Utility class for managing debug mode and diagnostic information */
|
||||||
|
export class DebugManager {
|
||||||
|
private constructor() { }
|
||||||
|
|
||||||
|
private static instance: DebugManager;
|
||||||
|
/** Returns the singleton instance of the DebugManager */
|
||||||
|
public static getInstance(): DebugManager {
|
||||||
|
if (!DebugManager.instance) {
|
||||||
|
DebugManager.instance = new DebugManager();
|
||||||
|
}
|
||||||
|
return DebugManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the singleton instance of the DebugManager (used for testing) */
|
||||||
|
public static clearState() {
|
||||||
|
DebugManager.instance = new DebugManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private debugEnabled: boolean = false;
|
||||||
|
private replayMode: boolean = false;
|
||||||
|
private diagnosticInfo: DiagnosticInfo | null = null;
|
||||||
|
|
||||||
|
/** Returns true if debug mode is enabled */
|
||||||
|
public isDebugEnabled(): boolean {
|
||||||
|
return this.debugEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if replay mode is enabled */
|
||||||
|
public isReplayMode(): boolean {
|
||||||
|
return this.replayMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeConfig(config: ActionConfig) {
|
||||||
|
if (config.debug) {
|
||||||
|
this.setDebugEnabled(true);
|
||||||
|
} else if (config.replay.length > 0) {
|
||||||
|
this.replayFromDiagnostics(config.replay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Enables or disables debug mode, also clears any existing diagnostics info */
|
||||||
|
public setDebugEnabled(enableDebug: boolean = true): void {
|
||||||
|
this.debugEnabled = enableDebug;
|
||||||
|
this.replayMode = false;
|
||||||
|
this.diagnosticInfo = new DiagnosticInfo();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Enables replay mode and loads the diagnostic information from the specified string */
|
||||||
|
public replayFromDiagnostics(diagnostics: string): void {
|
||||||
|
this.debugEnabled = false
|
||||||
|
this.replayMode = true;
|
||||||
|
this.diagnosticInfo = JSON.parse(diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a JSON string containing the diagnostic information for this run */
|
||||||
|
public getDebugOutput(emptyRepo: boolean = false): string {
|
||||||
|
return this.isDebugEnabled() ? JSON.stringify(this.diagnosticInfo) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Records a command and its output for diagnostic purposes */
|
||||||
|
public recordCommand(command: string, args: any[], output: string, stderr: string, error: any): void {
|
||||||
|
if (this.isDebugEnabled()) {
|
||||||
|
this.diagnosticInfo?.recordCommand(command, args, output, stderr, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Replays the specified command and returns the output */
|
||||||
|
public replayCommand(command: string, args: any[]): string {
|
||||||
|
if (this.diagnosticInfo === null) {
|
||||||
|
throw new Error('No diagnostic information available for replay');
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandResult = this.diagnosticInfo.commands.find(c => c.command === command && JSON.stringify(c.args) === JSON.stringify(args));
|
||||||
|
if (!commandResult) {
|
||||||
|
throw new Error(`No result found in diagnostic for command "${command}"`);
|
||||||
|
}
|
||||||
|
if (commandResult.error) {
|
||||||
|
throw commandResult.error;
|
||||||
|
}
|
||||||
|
if (commandResult.stderr) {
|
||||||
|
console.error(commandResult.stderr);
|
||||||
|
}
|
||||||
|
return commandResult.output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a CLI command result */
|
||||||
|
class CommandResult {
|
||||||
|
public command: string;
|
||||||
|
public args: any[];
|
||||||
|
public output: string;
|
||||||
|
public stderr: string;
|
||||||
|
public error: any;
|
||||||
|
public constructor(command: string, args: any[], output: string, stderr: string, error: any) {
|
||||||
|
this.command = command;
|
||||||
|
this.args = args;
|
||||||
|
this.output = output;
|
||||||
|
this.stderr = stderr;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the result of the commands executed for a run */
|
||||||
|
class DiagnosticInfo {
|
||||||
|
public commands: CommandResult[] = [];
|
||||||
|
public empty: boolean = false;
|
||||||
|
public recordCommand(command: string, args: any[], output: string, stderr: string, error: any): void {
|
||||||
|
this.commands.push(new CommandResult(command, args, output, stderr, error));
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/VersionResult.ts
Normal file
38
src/VersionResult.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { UserInfo } from "./providers/UserInfo";
|
||||||
|
import { VersionType } from "./providers/VersionType";
|
||||||
|
|
||||||
|
/** Represents the total output for the action */
|
||||||
|
export class VersionResult {
|
||||||
|
/**
|
||||||
|
* Creates a new result instance
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
* @param increment - The number of commits for this version (usually used to create version suffix)
|
||||||
|
* @param versionType - The type of version, e.g. major, minor, patch
|
||||||
|
* @param formattedVersion - The formatted semantic version
|
||||||
|
* @param versionTag - The string to be used as a Git tag
|
||||||
|
* @param changed - True if the version was changed, otherwise false
|
||||||
|
* @param isTagged - True if the commit had a tag that matched the `versionTag` format
|
||||||
|
* @param authors - Authors formatted according to the format mode (e.g. JSON, CSV, YAML, etc.)
|
||||||
|
* @param currentCommit - The current commit hash
|
||||||
|
* @param previousCommit - The previous commit hash
|
||||||
|
* @param previousVersion - The previous version
|
||||||
|
* @param debugOutput - Diagnostic information, if debug is enabled
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public major: number,
|
||||||
|
public minor: number,
|
||||||
|
public patch: number,
|
||||||
|
public increment: number,
|
||||||
|
public versionType: VersionType,
|
||||||
|
public formattedVersion: string,
|
||||||
|
public versionTag: string,
|
||||||
|
public changed: boolean,
|
||||||
|
public isTagged: boolean,
|
||||||
|
public authors: string,
|
||||||
|
public currentCommit: string,
|
||||||
|
public previousCommit: string,
|
||||||
|
public previousVersion: string,
|
||||||
|
public debugOutput: string) { }
|
||||||
|
}
|
||||||
82
src/action.ts
Normal file
82
src/action.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ConfigurationProvider } from './ConfigurationProvider';
|
||||||
|
import { VersionResult } from './VersionResult';
|
||||||
|
import { VersionType } from './providers/VersionType';
|
||||||
|
import { UserInfo } from './providers/UserInfo';
|
||||||
|
import { VersionInformation } from './providers/VersionInformation';
|
||||||
|
import { DebugManager } from './DebugManager';
|
||||||
|
|
||||||
|
export async function runAction(configurationProvider: ConfigurationProvider): Promise<VersionResult> {
|
||||||
|
|
||||||
|
const currentCommitResolver = configurationProvider.GetCurrentCommitResolver();
|
||||||
|
const lastReleaseResolver = configurationProvider.GetLastReleaseResolver();
|
||||||
|
const commitsProvider = configurationProvider.GetCommitsProvider();
|
||||||
|
const versionClassifier = configurationProvider.GetVersionClassifier();
|
||||||
|
const versionFormatter = configurationProvider.GetVersionFormatter();
|
||||||
|
const tagFormatter = configurationProvider.GetTagFormatter(await currentCommitResolver.ResolveBranchNameAsync());
|
||||||
|
const userFormatter = configurationProvider.GetUserFormatter();
|
||||||
|
|
||||||
|
const debugManager = DebugManager.getInstance();
|
||||||
|
|
||||||
|
if (await currentCommitResolver.IsEmptyRepoAsync()) {
|
||||||
|
|
||||||
|
const versionInfo = new VersionInformation(0, 0, 0, 0, VersionType.None, [], false, false);
|
||||||
|
return new VersionResult(
|
||||||
|
versionInfo.major,
|
||||||
|
versionInfo.minor,
|
||||||
|
versionInfo.patch,
|
||||||
|
versionInfo.increment,
|
||||||
|
versionInfo.type,
|
||||||
|
versionFormatter.Format(versionInfo),
|
||||||
|
tagFormatter.Format(versionInfo),
|
||||||
|
versionInfo.changed,
|
||||||
|
versionInfo.isTagged,
|
||||||
|
userFormatter.Format('author', []),
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
tagFormatter.Parse(tagFormatter.Format(versionInfo)).join('.'),
|
||||||
|
debugManager.getDebugOutput(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCommit = await currentCommitResolver.ResolveAsync();
|
||||||
|
const lastRelease = await lastReleaseResolver.ResolveAsync(currentCommit, tagFormatter);
|
||||||
|
const commitSet = await commitsProvider.GetCommitsAsync(lastRelease.hash, currentCommit);
|
||||||
|
const classification = await versionClassifier.ClassifyAsync(lastRelease, commitSet);
|
||||||
|
|
||||||
|
const { isTagged } = lastRelease;
|
||||||
|
const { major, minor, patch, increment, type, changed } = classification;
|
||||||
|
|
||||||
|
// At this point all necessary data has been pulled from the database, create
|
||||||
|
// version information to be used by the formatters
|
||||||
|
let versionInfo = new VersionInformation(major, minor, patch, increment, type, commitSet.commits, changed, isTagged);
|
||||||
|
|
||||||
|
// Group all the authors together, count the number of commits per author
|
||||||
|
const allAuthors = versionInfo.commits
|
||||||
|
.reduce((acc: any, commit) => {
|
||||||
|
const key = `${commit.author} <${commit.authorEmail}>`;
|
||||||
|
acc[key] = acc[key] || { n: commit.author, e: commit.authorEmail, c: 0 };
|
||||||
|
acc[key].c++;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const authors = Object.values(allAuthors)
|
||||||
|
.map((u: any) => new UserInfo(u.n, u.e, u.c))
|
||||||
|
.sort((a: UserInfo, b: UserInfo) => b.commits - a.commits);
|
||||||
|
|
||||||
|
return new VersionResult(
|
||||||
|
versionInfo.major,
|
||||||
|
versionInfo.minor,
|
||||||
|
versionInfo.patch,
|
||||||
|
versionInfo.increment,
|
||||||
|
versionInfo.type,
|
||||||
|
versionFormatter.Format(versionInfo),
|
||||||
|
tagFormatter.Format(versionInfo),
|
||||||
|
versionInfo.changed,
|
||||||
|
versionInfo.isTagged,
|
||||||
|
userFormatter.Format('author', authors),
|
||||||
|
currentCommit,
|
||||||
|
lastRelease.hash,
|
||||||
|
`${lastRelease.major}.${lastRelease.minor}.${lastRelease.patch}`,
|
||||||
|
debugManager.getDebugOutput()
|
||||||
|
);
|
||||||
|
}
|
||||||
104
src/formatting/BranchVersioningTagFormatter.ts
Normal file
104
src/formatting/BranchVersioningTagFormatter.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
|
||||||
|
import { ActionConfig } from '../ActionConfig';
|
||||||
|
import { DefaultTagFormatter } from './DefaultTagFormatter';
|
||||||
|
|
||||||
|
/** Default tag formatter which allows a prefix to be specified */
|
||||||
|
export class BranchVersioningTagFormatter extends DefaultTagFormatter {
|
||||||
|
|
||||||
|
private onVersionBranch: boolean;
|
||||||
|
private major: number;
|
||||||
|
private minor?: number;
|
||||||
|
|
||||||
|
private getRegex(pattern: string) {
|
||||||
|
if (/^\/.+\/[i]*$/.test(pattern)) {
|
||||||
|
const regexEnd = pattern.lastIndexOf('/');
|
||||||
|
const parsedFlags = pattern.slice(pattern.lastIndexOf('/') + 1);
|
||||||
|
return new RegExp(pattern.slice(1, regexEnd), parsedFlags);
|
||||||
|
}
|
||||||
|
return new RegExp(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(config: ActionConfig, branchName: string) {
|
||||||
|
super(config);
|
||||||
|
const pattern = config.versionFromBranch === true ?
|
||||||
|
new RegExp("[0-9]+.[0-9]+$|[0-9]+$") :
|
||||||
|
this.getRegex(config.versionFromBranch as string);
|
||||||
|
const result = pattern.exec(branchName);
|
||||||
|
|
||||||
|
if (result === null) {
|
||||||
|
this.major = NaN;
|
||||||
|
this.onVersionBranch = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let branchVersion: string;
|
||||||
|
switch (result?.length) {
|
||||||
|
case 1:
|
||||||
|
branchVersion = result[0];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
branchVersion = result[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unable to parse version from branch named '${branchName}' using pattern '${pattern}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onVersionBranch = true;
|
||||||
|
|
||||||
|
const versionValues = branchVersion.split('.');
|
||||||
|
if (versionValues.length > 2) {
|
||||||
|
throw new Error(`The version string '${branchVersion}' parsed from branch '${branchName}' is invalid. It must be in the format 'major.minor' or 'major'`);
|
||||||
|
}
|
||||||
|
this.major = parseInt(versionValues[0]);
|
||||||
|
if (isNaN(this.major)) {
|
||||||
|
throw new Error(`The major version '${versionValues[0]}' parsed from branch '${branchName}' is invalid. It must be a number.`);
|
||||||
|
}
|
||||||
|
if (versionValues.length > 1) {
|
||||||
|
this.minor = parseInt(versionValues[1]);
|
||||||
|
if (isNaN(this.minor)) {
|
||||||
|
throw new Error(`The minor version '${versionValues[1]}' parsed from branch '${branchName}' is invalid. It must be a number.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override GetPattern(): string {
|
||||||
|
let pattern = super.GetPattern();
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.minor === undefined) {
|
||||||
|
return pattern.replace('*[0-9].*[0-9].*[0-9]', `${this.major}.*[0-9].*[0-9]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern.replace('*[0-9].*[0-9].*[0-9]', `${this.major}.${this.minor}.*[0-9]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override IsValid(tag: string): boolean {
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return super.IsValid(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!super.IsValid(tag)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = super.Parse(tag);
|
||||||
|
if (parsed[0] !== this.major) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.minor !== undefined && parsed[1] !== this.minor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override Parse(tag: string): [major: number, minor: number, patch: number] {
|
||||||
|
if (!this.onVersionBranch) {
|
||||||
|
return super.Parse(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = super.Parse(tag);
|
||||||
|
return [this.major, this.minor || parsed[1], parsed[2]];
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/formatting/CsvUserFormatter.ts
Normal file
14
src/formatting/CsvUserFormatter.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ActionConfig } from '../ActionConfig';
|
||||||
|
import { UserInfo } from '../providers/UserInfo';
|
||||||
|
import { UserFormatter } from './UserFormatter';
|
||||||
|
|
||||||
|
export class CsvUserFormatter implements UserFormatter {
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
// placeholder for consistency with other formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format(type: string, users: UserInfo[]): string {
|
||||||
|
return users.map(user => `${user.name} <${user.email}>`).join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/formatting/DefaultTagFormatter.ts
Normal file
80
src/formatting/DefaultTagFormatter.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { TagFormatter } from './TagFormatter';
|
||||||
|
import { VersionInformation } from "../providers/VersionInformation";
|
||||||
|
import { ActionConfig } from '../ActionConfig';
|
||||||
|
|
||||||
|
/** Default tag formatter which allows a prefix to be specified */
|
||||||
|
export class DefaultTagFormatter implements TagFormatter {
|
||||||
|
|
||||||
|
private tagPrefix: string;
|
||||||
|
private namespace: string;
|
||||||
|
private namespaceSeperator: string;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.namespace = config.namespace;
|
||||||
|
this.tagPrefix = config.tagPrefix;
|
||||||
|
this.namespaceSeperator = '-'; // maybe make configurable in the future
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format(versionInfo: VersionInformation): string {
|
||||||
|
const result = `${this.tagPrefix}${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`;
|
||||||
|
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return `${result}${this.namespaceSeperator}${this.namespace}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetPattern(): string {
|
||||||
|
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return `${this.tagPrefix}*[0-9].*[0-9].*[0-9]${this.namespaceSeperator}${this.namespace}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.tagPrefix}*[0-9].*[0-9].*[0-9]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parse(tag: string): [major: number, minor: number, patch: number] {
|
||||||
|
|
||||||
|
if(tag === '') {
|
||||||
|
return [0, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let tagParts = tag
|
||||||
|
.replace(this.tagPrefix, '<--!PREFIX!-->')
|
||||||
|
.replace(this.namespace, '<--!NAMESPACE!-->')
|
||||||
|
.split('/');
|
||||||
|
|
||||||
|
const stripedTag = tagParts[tagParts.length - 1]
|
||||||
|
.replace('<--!PREFIX!-->', this.tagPrefix)
|
||||||
|
.replace('<--!NAMESPACE!-->', this.namespace);
|
||||||
|
|
||||||
|
let versionValues = stripedTag
|
||||||
|
.substring(this.tagPrefix.length)
|
||||||
|
.slice(0, this.namespace === '' ? 999 : -(this.namespace.length + 1))
|
||||||
|
.split('.');
|
||||||
|
|
||||||
|
let major = parseInt(versionValues[0]);
|
||||||
|
let minor = versionValues.length > 1 ? parseInt(versionValues[1]) : 0;
|
||||||
|
let patch = versionValues.length > 2 ? parseInt(versionValues[2]) : 0;
|
||||||
|
|
||||||
|
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
|
||||||
|
throw `Invalid tag ${tag} (${versionValues})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [major, minor, patch];
|
||||||
|
};
|
||||||
|
|
||||||
|
public IsValid(tag: string): boolean {
|
||||||
|
const regexEscape = (literal: string) => literal.replace(/\W/g, '\\$&');
|
||||||
|
const tagPrefix = regexEscape(this.tagPrefix);
|
||||||
|
const namespaceSeperator = regexEscape(this.namespaceSeperator);
|
||||||
|
const namespace = regexEscape(this.namespace);
|
||||||
|
|
||||||
|
if (!!this.namespace) {
|
||||||
|
return new RegExp(`^${tagPrefix}[0-9]+\.[0-9]+\.[0-9]+${namespaceSeperator}${namespace}$`).test(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegExp(`^${tagPrefix}[0-9]+\.[0-9]+\.[0-9]+$`).test(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/formatting/DefaultVersionFormatter.ts
Normal file
20
src/formatting/DefaultVersionFormatter.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { VersionFormatter } from './VersionFormatter';
|
||||||
|
import { VersionInformation } from "../providers/VersionInformation";
|
||||||
|
import { ActionConfig } from '../ActionConfig';
|
||||||
|
|
||||||
|
export class DefaultVersionFormatter implements VersionFormatter {
|
||||||
|
|
||||||
|
private formatString: string;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.formatString = config.versionFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format(versionInfo: VersionInformation): string {
|
||||||
|
return this.formatString
|
||||||
|
.replace('${major}', versionInfo.major.toString())
|
||||||
|
.replace('${minor}', versionInfo.minor.toString())
|
||||||
|
.replace('${patch}', versionInfo.patch.toString())
|
||||||
|
.replace('${increment}', versionInfo.increment.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/formatting/JsonUserFormatter.ts
Normal file
13
src/formatting/JsonUserFormatter.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { ActionConfig } from '../ActionConfig';
|
||||||
|
import { UserInfo } from '../providers/UserInfo';
|
||||||
|
import { UserFormatter } from './UserFormatter';
|
||||||
|
|
||||||
|
export class JsonUserFormatter implements UserFormatter {
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
// placeholder for consistency with other formatters
|
||||||
|
}
|
||||||
|
public Format(type: string, users: UserInfo[]): string {
|
||||||
|
let result: any = users.map(u => ({ name: u.name, email: u.email }));
|
||||||
|
return JSON.stringify(result).replace('\n', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/formatting/TagFormatter.ts
Normal file
8
src/formatting/TagFormatter.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { VersionInformation } from "../providers/VersionInformation";
|
||||||
|
|
||||||
|
export interface TagFormatter {
|
||||||
|
Format(versionInfo: VersionInformation): string;
|
||||||
|
GetPattern(): string;
|
||||||
|
Parse(tag: string): [major: number, minor: number, patch: number];
|
||||||
|
IsValid(tag: string): boolean;
|
||||||
|
}
|
||||||
6
src/formatting/UserFormatter.ts
Normal file
6
src/formatting/UserFormatter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { UserInfo } from "../providers/UserInfo";
|
||||||
|
|
||||||
|
/** Formats a list of users as a string (e.g. JSON, YAML, CSV, etc) */
|
||||||
|
export interface UserFormatter {
|
||||||
|
Format(type: string, users: UserInfo[]): string;
|
||||||
|
}
|
||||||
6
src/formatting/VersionFormatter.ts
Normal file
6
src/formatting/VersionFormatter.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { VersionInformation } from "../providers/VersionInformation";
|
||||||
|
|
||||||
|
// Formatters
|
||||||
|
export interface VersionFormatter {
|
||||||
|
Format(versionInfo: VersionInformation): string;
|
||||||
|
}
|
||||||
1146
src/main.test.ts
Normal file
1146
src/main.test.ts
Normal file
File diff suppressed because it is too large
Load diff
97
src/main.ts
Normal file
97
src/main.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { runAction } from './action';
|
||||||
|
import { ActionConfig } from './ActionConfig';
|
||||||
|
import { ConfigurationProvider } from './ConfigurationProvider';
|
||||||
|
import { VersionResult } from './VersionResult';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import { VersionType } from './providers/VersionType';
|
||||||
|
|
||||||
|
function setOutput(versionResult: VersionResult) {
|
||||||
|
const { major, minor, patch, increment, versionType, formattedVersion, versionTag, changed, isTagged, authors, currentCommit, previousCommit, previousVersion, debugOutput } = versionResult;
|
||||||
|
|
||||||
|
const repository = process.env.GITHUB_REPOSITORY;
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
core.info('No changes detected for this commit');
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Version is ${formattedVersion}`);
|
||||||
|
if (repository !== undefined) {
|
||||||
|
core.info(`To create a release for this version, go to https://github.com/${repository}/releases/new?tag=${versionTag}&target=${currentCommit.split('/').slice(-1)[0]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.setOutput("version", formattedVersion);
|
||||||
|
core.setOutput("major", major.toString());
|
||||||
|
core.setOutput("minor", minor.toString());
|
||||||
|
core.setOutput("patch", patch.toString());
|
||||||
|
core.setOutput("increment", increment.toString());
|
||||||
|
core.setOutput("version_type", VersionType[versionType].toLowerCase());
|
||||||
|
core.setOutput("changed", changed.toString());
|
||||||
|
core.setOutput("is_tagged", isTagged.toString());
|
||||||
|
core.setOutput("version_tag", versionTag);
|
||||||
|
core.setOutput("authors", authors);
|
||||||
|
core.setOutput("previous_commit", previousCommit);
|
||||||
|
core.setOutput("previous_version", previousVersion);
|
||||||
|
core.setOutput("current_commit", currentCommit);
|
||||||
|
core.setOutput("debug_output", debugOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run() {
|
||||||
|
|
||||||
|
function toBool(value: string): boolean {
|
||||||
|
if (!value || value.toLowerCase() === 'false') {
|
||||||
|
return false;
|
||||||
|
} else if (value.toLowerCase() === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStringOrBool(value: string): string | boolean {
|
||||||
|
if (!value || value === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: ActionConfig = {
|
||||||
|
branch: core.getInput('branch'),
|
||||||
|
tagPrefix: core.getInput('tag_prefix'),
|
||||||
|
useBranches: toBool(core.getInput('use_branches')),
|
||||||
|
versionFromBranch: toStringOrBool(core.getInput('version_from_branch')),
|
||||||
|
majorPattern: core.getInput('major_pattern'),
|
||||||
|
minorPattern: core.getInput('minor_pattern'),
|
||||||
|
majorFlags: core.getInput('major_regexp_flags'),
|
||||||
|
minorFlags: core.getInput('minor_regexp_flags'),
|
||||||
|
versionFormat: core.getInput('version_format'),
|
||||||
|
changePath: core.getInput('change_path'),
|
||||||
|
namespace: core.getInput('namespace'),
|
||||||
|
bumpEachCommit: toBool(core.getInput('bump_each_commit')),
|
||||||
|
searchCommitBody: toBool(core.getInput('search_commit_body')),
|
||||||
|
userFormatType: core.getInput('user_format_type'),
|
||||||
|
enablePrereleaseMode: toBool(core.getInput('enable_prerelease_mode')),
|
||||||
|
bumpEachCommitPatchPattern: core.getInput('bump_each_commit_patch_pattern'),
|
||||||
|
debug: toBool(core.getInput('debug')),
|
||||||
|
replay: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.useBranches) {
|
||||||
|
core.warning(`The 'use_branches' input option is deprecated, please see the documentation for more information on how to use branches`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.versionFormat === '' && core.getInput('format') !== '') {
|
||||||
|
core.warning(`The 'format' input is deprecated, use 'versionFormat' instead`);
|
||||||
|
config.versionFormat = core.getInput('format');
|
||||||
|
}
|
||||||
|
if (core.getInput('short_tags') !== '') {
|
||||||
|
core.warning(`The 'short_tags' input option is no longer supported`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configurationProvider = new ConfigurationProvider(config);
|
||||||
|
const result = await runAction(configurationProvider);
|
||||||
|
setOutput(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
93
src/providers/BumpAlwaysVersionClassifier.ts
Normal file
93
src/providers/BumpAlwaysVersionClassifier.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
import { DefaultVersionClassifier } from "./DefaultVersionClassifier";
|
||||||
|
import { ReleaseInformation } from "./ReleaseInformation";
|
||||||
|
import { VersionClassification } from "./VersionClassification";
|
||||||
|
import { VersionType } from "./VersionType";
|
||||||
|
|
||||||
|
export class BumpAlwaysVersionClassifier extends DefaultVersionClassifier {
|
||||||
|
|
||||||
|
protected patchPattern: (commit: CommitInfo) => boolean;
|
||||||
|
protected enablePrereleaseMode: boolean;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
this.enablePrereleaseMode = config.enablePrereleaseMode;
|
||||||
|
this.patchPattern = !config.bumpEachCommitPatchPattern ?
|
||||||
|
_ => true :
|
||||||
|
this.parsePattern(config.bumpEachCommitPatchPattern, "", config.searchCommitBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ClassifyAsync(lastRelease: ReleaseInformation, commitSet: CommitInfoSet): Promise<VersionClassification> {
|
||||||
|
|
||||||
|
if (lastRelease.currentPatch !== null) {
|
||||||
|
return new VersionClassification(VersionType.None, 0, false, <number>lastRelease.currentMajor, <number>lastRelease.currentMinor, <number>lastRelease.currentPatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let { major, minor, patch } = lastRelease;
|
||||||
|
let type = VersionType.None;
|
||||||
|
let increment = 0;
|
||||||
|
|
||||||
|
if (commitSet.commits.length === 0) {
|
||||||
|
return new VersionClassification(type, 0, false, major, minor, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let commit of commitSet.commits.reverse()) {
|
||||||
|
|
||||||
|
if (this.majorPattern(commit)) {
|
||||||
|
type = VersionType.Major;
|
||||||
|
} else if (this.minorPattern(commit)) {
|
||||||
|
type = VersionType.Minor;
|
||||||
|
} else if (this.patchPattern(commit) ||
|
||||||
|
(major === 0 && minor === 0 && patch === 0 && commitSet.commits.length > 0)) {
|
||||||
|
type = VersionType.Patch;
|
||||||
|
} else {
|
||||||
|
type = VersionType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.enablePrereleaseMode && major === 0) {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType.Major:
|
||||||
|
case VersionType.Minor:
|
||||||
|
minor += 1;
|
||||||
|
patch = 0;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
case VersionType.Patch:
|
||||||
|
patch += 1;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
increment++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType.Major:
|
||||||
|
major += 1;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
case VersionType.Minor:
|
||||||
|
minor += 1;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case VersionType.Patch:
|
||||||
|
patch += 1;
|
||||||
|
increment = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
increment++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VersionClassification(type, increment, true, major, minor, patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/providers/CommitInfo.ts
Normal file
27
src/providers/CommitInfo.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/** Represents information about a commit */
|
||||||
|
export class CommitInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new commit information instance
|
||||||
|
* @param hash - The hash of the commit
|
||||||
|
* @param subject - The subject of the commit message
|
||||||
|
* @param body - The body of the commit message
|
||||||
|
* @param author - The author's name
|
||||||
|
* @param authorEmail - The author's email
|
||||||
|
* @param authorDate - The date the commit was authored
|
||||||
|
* @param committer - The committer's name
|
||||||
|
* @param committerEmail - The committer's email
|
||||||
|
* @param committerDate - The date the commit was committed
|
||||||
|
* @param tags - List of any tags associated with this commit
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public hash: string,
|
||||||
|
public subject: string,
|
||||||
|
public body: string,
|
||||||
|
public author: string,
|
||||||
|
public authorEmail: string,
|
||||||
|
public authorDate: Date,
|
||||||
|
public committer: string,
|
||||||
|
public committerEmail: string,
|
||||||
|
public committerDate: Date,
|
||||||
|
public tags: string[]) { }
|
||||||
|
}
|
||||||
9
src/providers/CommitInfoSet.ts
Normal file
9
src/providers/CommitInfoSet.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
|
||||||
|
/** Represents information about a set of commits */
|
||||||
|
export class CommitInfoSet {
|
||||||
|
constructor(
|
||||||
|
public changed: boolean,
|
||||||
|
public commits: CommitInfo[]
|
||||||
|
){}
|
||||||
|
}
|
||||||
14
src/providers/CommitsProvider.ts
Normal file
14
src/providers/CommitsProvider.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a provider to retrieve commit information for a range of commits
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CommitsProvider {
|
||||||
|
/**
|
||||||
|
* Gets the commit information for a range of commits
|
||||||
|
* @param startHash - The hash of commit of the last release, result should be exclusive
|
||||||
|
* @param endHash - The hash of the current commit, result should be inclusive
|
||||||
|
*/
|
||||||
|
GetCommitsAsync(startHash: string, endHash: string): Promise<CommitInfoSet>;
|
||||||
|
}
|
||||||
19
src/providers/CurrentCommitResolver.ts
Normal file
19
src/providers/CurrentCommitResolver.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/** Resolver to obtain information about the repository */
|
||||||
|
export interface CurrentCommitResolver {
|
||||||
|
/**
|
||||||
|
* Resolves the current commit.
|
||||||
|
* @returns The current commit.
|
||||||
|
*/
|
||||||
|
ResolveAsync(): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Returns true if the repository is empty
|
||||||
|
* @returns True if the repository is empty
|
||||||
|
*/
|
||||||
|
IsEmptyRepoAsync(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current branch
|
||||||
|
* @returns The current branch
|
||||||
|
*/
|
||||||
|
ResolveBranchNameAsync(): Promise<string>;
|
||||||
|
}
|
||||||
94
src/providers/DefaultCommitsProvider.ts
Normal file
94
src/providers/DefaultCommitsProvider.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import { cmd } from "../CommandRunner";
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
import { CommitsProvider } from "./CommitsProvider";
|
||||||
|
|
||||||
|
export class DefaultCommitsProvider implements CommitsProvider {
|
||||||
|
|
||||||
|
private changePath: string;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.changePath = config.changePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetCommitsAsync(startHash: string, endHash: string): Promise<CommitInfoSet> {
|
||||||
|
|
||||||
|
const logSplitter = `@@@START_RECORD`
|
||||||
|
const formatPlaceholders = Object.entries({
|
||||||
|
hash: '%H',
|
||||||
|
subject: '%s',
|
||||||
|
body: '%b',
|
||||||
|
author: '%an',
|
||||||
|
authorEmail: '%ae',
|
||||||
|
authorDate: '%aI',
|
||||||
|
committer: '%cn',
|
||||||
|
committerEmail: '%ce',
|
||||||
|
committerDate: '%cI',
|
||||||
|
tags: '%d'
|
||||||
|
});
|
||||||
|
|
||||||
|
const pretty = logSplitter + '%n' + formatPlaceholders
|
||||||
|
.map(x => `@@@${x[0]}%n${x[1]}`)
|
||||||
|
.join('%n');
|
||||||
|
|
||||||
|
var logCommand = `git log --pretty="${pretty}" --author-date-order ${(startHash === '' ? endHash : `${startHash}..${endHash}`)}`;
|
||||||
|
|
||||||
|
if (this.changePath !== '') {
|
||||||
|
logCommand += ` -- ${this.changePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = await cmd(logCommand);
|
||||||
|
|
||||||
|
const entries = log
|
||||||
|
.split(logSplitter)
|
||||||
|
.slice(1);
|
||||||
|
|
||||||
|
const commits = entries.map(entry => {
|
||||||
|
const fields: any = entry
|
||||||
|
.split(`@@@`)
|
||||||
|
.slice(1)
|
||||||
|
.reduce((acc: any, value: string) => {
|
||||||
|
const firstLine = value.indexOf('\n');
|
||||||
|
const key = value.substring(0, firstLine);
|
||||||
|
acc[key] = value.substring(firstLine + 1).trim();
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const tags = fields.tags
|
||||||
|
.split(',')
|
||||||
|
.map((v: string) => v.trim())
|
||||||
|
.filter((v: string) => v.startsWith('tags: '))
|
||||||
|
.map((v: string) => v.substring(5).trim());
|
||||||
|
|
||||||
|
return new CommitInfo(
|
||||||
|
fields.hash,
|
||||||
|
fields.subject,
|
||||||
|
fields.body,
|
||||||
|
fields.author,
|
||||||
|
fields.authorEmail,
|
||||||
|
new Date(fields.authorDate),
|
||||||
|
fields.committer,
|
||||||
|
fields.committerEmail,
|
||||||
|
new Date(fields.committerDate),
|
||||||
|
tags
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// check for changes
|
||||||
|
|
||||||
|
let changed = true;
|
||||||
|
if (this.changePath !== '') {
|
||||||
|
if (startHash === '') {
|
||||||
|
const changedFiles = await cmd(`git log --name-only --oneline ${endHash} -- ${this.changePath}`);
|
||||||
|
changed = changedFiles.length > 0;
|
||||||
|
} else {
|
||||||
|
const changedFiles = await cmd(`git diff --name-only ${startHash}..${endHash} -- ${this.changePath}`);
|
||||||
|
changed = changedFiles.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommitInfoSet(changed, commits);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/providers/DefaultCurrentCommitResolver.ts
Normal file
33
src/providers/DefaultCurrentCommitResolver.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import { cmd } from "../CommandRunner";
|
||||||
|
import { CurrentCommitResolver } from "./CurrentCommitResolver";
|
||||||
|
|
||||||
|
|
||||||
|
export class DefaultCurrentCommitResolver implements CurrentCommitResolver {
|
||||||
|
|
||||||
|
private branch: string;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.branch = config.branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ResolveAsync(): Promise<string> {
|
||||||
|
if (this.branch === 'HEAD') {
|
||||||
|
return (await cmd('git', 'rev-parse', 'HEAD')).trim();
|
||||||
|
}
|
||||||
|
return this.branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IsEmptyRepoAsync(): Promise<boolean> {
|
||||||
|
let lastCommitAll = (await cmd('git', 'rev-list', '-n1', '--all')).trim();
|
||||||
|
return lastCommitAll === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ResolveBranchNameAsync(): Promise<string> {
|
||||||
|
const branchName =
|
||||||
|
this.branch == 'HEAD' ? await cmd('git', 'rev-parse', '--abbrev-ref', 'HEAD') : this.branch;
|
||||||
|
|
||||||
|
|
||||||
|
return branchName.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/providers/DefaultLastReleaseResolver.ts
Normal file
82
src/providers/DefaultLastReleaseResolver.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { cmd } from "../CommandRunner";
|
||||||
|
import { TagFormatter } from "../formatting/TagFormatter";
|
||||||
|
import { LastReleaseResolver } from "./LastReleaseResolver";
|
||||||
|
import { ReleaseInformation } from "./ReleaseInformation";
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
|
||||||
|
export class DefaultLastReleaseResolver implements LastReleaseResolver {
|
||||||
|
|
||||||
|
private changePath: string;
|
||||||
|
private useBranches: boolean;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
this.changePath = config.changePath;
|
||||||
|
this.useBranches = config.useBranches;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ResolveAsync(current: string, tagFormatter: TagFormatter): Promise<ReleaseInformation> {
|
||||||
|
const releasePattern = tagFormatter.GetPattern();
|
||||||
|
|
||||||
|
let currentTag = (await cmd(
|
||||||
|
`git tag --points-at ${current} ${releasePattern}`
|
||||||
|
)).trim();
|
||||||
|
|
||||||
|
currentTag = tagFormatter.IsValid(currentTag) ? currentTag : '';
|
||||||
|
const isTagged = currentTag !== '';
|
||||||
|
|
||||||
|
const [currentMajor, currentMinor, currentPatch] = !!currentTag ? tagFormatter.Parse(currentTag) : [null, null, null];
|
||||||
|
|
||||||
|
let tagsCount = 0;
|
||||||
|
|
||||||
|
let tag = '';
|
||||||
|
try {
|
||||||
|
const refPrefixPattern = this.useBranches ? 'refs/heads/' : 'refs/tags/';
|
||||||
|
if (!!currentTag) {
|
||||||
|
// If we already have the current branch tagged, we are checking for the previous one
|
||||||
|
// so that we will have an accurate increment (assuming the new tag is the expected one)
|
||||||
|
const command = `git for-each-ref --sort=-v:*refname --format=%(refname:short) --merged=${current} ${refPrefixPattern}${releasePattern}`;
|
||||||
|
const tags = (await cmd(command)).split('\n')
|
||||||
|
tagsCount = tags.length;
|
||||||
|
tag = tags
|
||||||
|
.find(t => tagFormatter.IsValid(t) && t !== currentTag) || '';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const command = `git for-each-ref --sort=-v:*refname --format=%(refname:short) --merged=${current} ${refPrefixPattern}${releasePattern}`;
|
||||||
|
const tags = (await cmd(command)).split('\n')
|
||||||
|
tagsCount = tags.length;
|
||||||
|
tag = tags
|
||||||
|
.find(t => tagFormatter.IsValid(t)) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = tag.trim();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
tag = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag === '') {
|
||||||
|
if (await cmd('git', 'remote') !== '') {
|
||||||
|
|
||||||
|
// Since there is no remote, we assume that there are no other tags to pull. In
|
||||||
|
// practice this isn't likely to happen, but it keeps the test output from being
|
||||||
|
// polluted with a bunch of warnings.
|
||||||
|
|
||||||
|
if (tagsCount > 0) {
|
||||||
|
core.warning(`None of the ${tagsCount} tags(s) found were valid version tags for the present configuration. If this is unexpected, check to ensure that the configuration is correct and matches the tag format you are using.`);
|
||||||
|
} else {
|
||||||
|
core.warning('No tags are present for this repository. If this is unexpected, check to ensure that tags have been pulled from the remote.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [major, minor, patch] = tagFormatter.Parse('');
|
||||||
|
// no release tags yet, use the initial commit as the root
|
||||||
|
return new ReleaseInformation(major, minor, patch, '', currentMajor, currentMinor, currentPatch, isTagged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the version tag
|
||||||
|
const [major, minor, patch] = tagFormatter.Parse(tag);
|
||||||
|
const root = await cmd('git', `merge-base`, tag, current);
|
||||||
|
return new ReleaseInformation(major, minor, patch, root.trim(), currentMajor, currentMinor, currentPatch, isTagged);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
src/providers/DefaultVersionClassifier.test.ts
Normal file
24
src/providers/DefaultVersionClassifier.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { expect, test } from "@jest/globals";
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
import { DefaultVersionClassifier } from "./DefaultVersionClassifier";
|
||||||
|
import { ReleaseInformation } from "./ReleaseInformation";
|
||||||
|
|
||||||
|
test('Regular expressions can be used as minor tag direct', async () => {
|
||||||
|
|
||||||
|
const classifier = new DefaultVersionClassifier({ ...new ActionConfig(), ...{ tagPrefix: '', minorPattern: '/S[a-z]+Value/' }});
|
||||||
|
|
||||||
|
const releaseInfo =new ReleaseInformation(0,0,1,"",null,null,null,false);
|
||||||
|
const commitSet = new CommitInfoSet(false, [
|
||||||
|
new CommitInfo("", "Second Commit SomeValue", "", "","", new Date(), "", "", new Date(), []),
|
||||||
|
new CommitInfo("", "Initial Commit", "", "","", new Date(), "", "", new Date(), []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await classifier.ClassifyAsync(releaseInfo, commitSet);
|
||||||
|
|
||||||
|
expect(result.major).toBe(0);
|
||||||
|
expect(result.minor).toBe(1);
|
||||||
|
expect(result.patch).toBe(0);
|
||||||
|
expect(result.increment).toBe(0);
|
||||||
|
});
|
||||||
117
src/providers/DefaultVersionClassifier.ts
Normal file
117
src/providers/DefaultVersionClassifier.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { ActionConfig } from "../ActionConfig";
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
import { ReleaseInformation } from "./ReleaseInformation";
|
||||||
|
import { VersionClassification } from "./VersionClassification";
|
||||||
|
import { VersionClassifier } from "./VersionClassifier";
|
||||||
|
import { VersionType } from "./VersionType";
|
||||||
|
|
||||||
|
export class DefaultVersionClassifier implements VersionClassifier {
|
||||||
|
|
||||||
|
protected majorPattern: (commit: CommitInfo) => boolean;
|
||||||
|
protected minorPattern: (commit: CommitInfo) => boolean;
|
||||||
|
protected enablePrereleaseMode: boolean;
|
||||||
|
|
||||||
|
constructor(config: ActionConfig) {
|
||||||
|
const searchBody = config.searchCommitBody;
|
||||||
|
this.majorPattern = this.parsePattern(config.majorPattern, config.majorFlags, searchBody);
|
||||||
|
this.minorPattern = this.parsePattern(config.minorPattern, config.minorFlags, searchBody);
|
||||||
|
this.enablePrereleaseMode = config.enablePrereleaseMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected parsePattern(pattern: string, flags: string, searchBody: boolean): (pattern: CommitInfo) => boolean {
|
||||||
|
if (/^\/.+\/[i]*$/.test(pattern)) {
|
||||||
|
const regexEnd = pattern.lastIndexOf('/');
|
||||||
|
const parsedFlags = pattern.slice(pattern.lastIndexOf('/') + 1);
|
||||||
|
const regex = new RegExp(pattern.slice(1, regexEnd), parsedFlags || flags);
|
||||||
|
return searchBody ?
|
||||||
|
(commit: CommitInfo) => regex.test(commit.subject) || regex.test(commit.body) :
|
||||||
|
(commit: CommitInfo) => regex.test(commit.subject);
|
||||||
|
} else {
|
||||||
|
const matchString = pattern;
|
||||||
|
return searchBody ?
|
||||||
|
(commit: CommitInfo) => commit.subject.includes(matchString) || commit.body.includes(matchString) :
|
||||||
|
(commit: CommitInfo) => commit.subject.includes(matchString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getNextVersion(current: ReleaseInformation, type: VersionType): ({ major: number, minor: number, patch: number }) {
|
||||||
|
|
||||||
|
if (this.enablePrereleaseMode && current.major === 0) {
|
||||||
|
switch (type) {
|
||||||
|
case VersionType.Major:
|
||||||
|
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
||||||
|
case VersionType.Minor:
|
||||||
|
case VersionType.Patch:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch + 1 };
|
||||||
|
case VersionType.None:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown change type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case VersionType.Major:
|
||||||
|
return { major: current.major + 1, minor: 0, patch: 0 };
|
||||||
|
case VersionType.Minor:
|
||||||
|
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
||||||
|
case VersionType.Patch:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch + 1 };
|
||||||
|
case VersionType.None:
|
||||||
|
return { major: current.major, minor: current.minor, patch: current.patch };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown change type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveCommitType(commitsSet: CommitInfoSet): ({ type: VersionType, increment: number, changed: boolean }) {
|
||||||
|
if (commitsSet.commits.length === 0) {
|
||||||
|
return { type: VersionType.None, increment: 0, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
|
||||||
|
const commits = commitsSet.commits.reverse();
|
||||||
|
let index = 1;
|
||||||
|
for (let commit of commits) {
|
||||||
|
if (this.majorPattern(commit)) {
|
||||||
|
return { type: VersionType.Major, increment: commits.length - index, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = 1;
|
||||||
|
for (let commit of commits) {
|
||||||
|
if (this.minorPattern(commit)) {
|
||||||
|
return { type: VersionType.Minor, increment: commits.length - index, changed: commitsSet.changed };
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: VersionType.Patch, increment: commitsSet.commits.length - 1, changed: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ClassifyAsync(lastRelease: ReleaseInformation, commitSet: CommitInfoSet): Promise<VersionClassification> {
|
||||||
|
|
||||||
|
const { type, increment, changed } = this.resolveCommitType(commitSet);
|
||||||
|
|
||||||
|
const { major, minor, patch } = this.getNextVersion(lastRelease, type);
|
||||||
|
|
||||||
|
if (lastRelease.currentPatch !== null) {
|
||||||
|
// If the current commit is tagged, we must use that version. Here we check if the version we have resolved from the
|
||||||
|
// previous commits is the same as the current version. If it is, we will use the increment value, otherwise we reset
|
||||||
|
// to zero. For example:
|
||||||
|
|
||||||
|
// - commit 1 - v1.0.0+0
|
||||||
|
// - commit 2 - v1.0.0+1
|
||||||
|
// - commit 3 was tagged v2.0.0 - v2.0.0+0
|
||||||
|
// - commit 4 - v2.0.1+0
|
||||||
|
|
||||||
|
const versionsMatch = lastRelease.currentMajor === major && lastRelease.currentMinor === minor && lastRelease.currentPatch === patch;
|
||||||
|
const currentIncrement = versionsMatch ? increment : 0;
|
||||||
|
return new VersionClassification(VersionType.None, currentIncrement, false, <number>lastRelease.currentMajor, <number>lastRelease.currentMinor, <number>lastRelease.currentPatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new VersionClassification(type, increment, changed, major, minor, patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/providers/LastReleaseResolver.ts
Normal file
6
src/providers/LastReleaseResolver.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { TagFormatter } from '../formatting/TagFormatter';
|
||||||
|
import { ReleaseInformation } from './ReleaseInformation';
|
||||||
|
|
||||||
|
export interface LastReleaseResolver {
|
||||||
|
ResolveAsync(current: string, tagFormatter: TagFormatter): Promise<ReleaseInformation>;
|
||||||
|
}
|
||||||
23
src/providers/ReleaseInformation.ts
Normal file
23
src/providers/ReleaseInformation.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Finds the hash of the last commit
|
||||||
|
export class ReleaseInformation {
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
* @param major - the major version number
|
||||||
|
* @param minor - the minor version number
|
||||||
|
* @param patch - the patch version number
|
||||||
|
* @param hash - the hash of commit of the last release
|
||||||
|
* @param currentMajor - the major version number from the current commit
|
||||||
|
* @param currentMinor - the minor version number from the current commit
|
||||||
|
* @param currentPatch - the patch version number from the current commit
|
||||||
|
* @param isTagged - whether the current commit is tagged with a version
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public major: number,
|
||||||
|
public minor: number,
|
||||||
|
public patch: number,
|
||||||
|
public hash: string,
|
||||||
|
public currentMajor: number | null,
|
||||||
|
public currentMinor: number | null,
|
||||||
|
public currentPatch: number | null,
|
||||||
|
public isTagged: boolean,) { }
|
||||||
|
}
|
||||||
14
src/providers/UserInfo.ts
Normal file
14
src/providers/UserInfo.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
/** Represents information about a user (e.g. committer, author, tagger) */
|
||||||
|
export class UserInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
* @param name - User's name
|
||||||
|
* @param email - User's email
|
||||||
|
* @param commits - Number of commits in the scope evaluated
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public email: string,
|
||||||
|
public commits: number) { }
|
||||||
|
}
|
||||||
21
src/providers/VersionClassification.ts
Normal file
21
src/providers/VersionClassification.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { VersionType } from './VersionType';
|
||||||
|
|
||||||
|
/** The result of a version classification */
|
||||||
|
export class VersionClassification {
|
||||||
|
/**
|
||||||
|
* Creates a new version classification result instance
|
||||||
|
* @param type - The type of change the current range represents
|
||||||
|
* @param increment - The number of commits which have this version, usually zero-based
|
||||||
|
* @param changed - True if the version has changed, false otherwise
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public type: VersionType,
|
||||||
|
public increment: number,
|
||||||
|
public changed: boolean,
|
||||||
|
public major: number,
|
||||||
|
public minor: number,
|
||||||
|
public patch: number) { }
|
||||||
|
}
|
||||||
17
src/providers/VersionClassifier.ts
Normal file
17
src/providers/VersionClassifier.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { CommitInfoSet } from "./CommitInfoSet";
|
||||||
|
import { ReleaseInformation } from "./ReleaseInformation";
|
||||||
|
import { VersionClassification } from "./VersionClassification";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the 'business logic' to turn commits into parameters for creating
|
||||||
|
* output values
|
||||||
|
*/
|
||||||
|
export interface VersionClassifier {
|
||||||
|
/**
|
||||||
|
* Produces the version classification for a given commit set
|
||||||
|
* @param lastRelease - The last release information
|
||||||
|
* @param commitSet - The commits to classify, ordered from most recent to oldest
|
||||||
|
* @returns - The version classification
|
||||||
|
*/
|
||||||
|
ClassifyAsync(lastRelease: ReleaseInformation, commitSet: CommitInfoSet): Promise<VersionClassification>;
|
||||||
|
}
|
||||||
29
src/providers/VersionInformation.ts
Normal file
29
src/providers/VersionInformation.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { CommitInfo } from "./CommitInfo";
|
||||||
|
import { VersionType } from "./VersionType";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the "resolved" information about a version change, serves
|
||||||
|
* as the input to formatters that produce the final output
|
||||||
|
*/
|
||||||
|
export class VersionInformation {
|
||||||
|
/**
|
||||||
|
* Creates a new version information instance
|
||||||
|
* @param major - The major version number
|
||||||
|
* @param minor - The minor version number
|
||||||
|
* @param patch - The patch version number
|
||||||
|
* @param increment - The number of commits for this version
|
||||||
|
* @param type - The type of change the current range represents
|
||||||
|
* @param commits - The list of commits for this version
|
||||||
|
* @param changed - True if the version has changed, false otherwise
|
||||||
|
* @param isTagged - True if the current commit is a version-tagged commit
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
public major: number,
|
||||||
|
public minor: number,
|
||||||
|
public patch: number,
|
||||||
|
public increment: number,
|
||||||
|
public type: VersionType,
|
||||||
|
public commits: CommitInfo[],
|
||||||
|
public changed: boolean,
|
||||||
|
public isTagged: boolean) { }
|
||||||
|
}
|
||||||
12
src/providers/VersionType.ts
Normal file
12
src/providers/VersionType.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
/** Indicates the type of change a particular version change represents */
|
||||||
|
export enum VersionType {
|
||||||
|
/** Indicates a major version change */
|
||||||
|
Major = 'Major',
|
||||||
|
/** Indicates a minor version change */
|
||||||
|
Minor = 'Minor',
|
||||||
|
/** Indicates a patch version change */
|
||||||
|
Patch = 'Patch',
|
||||||
|
/** Indicates no change--generally this means that the current commit is already tagged with a version */
|
||||||
|
None = 'None'
|
||||||
|
}
|
||||||
205
tagging.drawio.svg
Normal file
205
tagging.drawio.svg
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="391px" height="293px" viewBox="-0.5 -0.5 391 293" content="<mxfile host="870b5583-737c-498f-a409-5e2781da980d" modified="2020-12-28T22:18:42.671Z" agent="5.0 (Macintosh; Intel Mac OS X 11_0_0) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="VQW0Hovv63nX1xUtBxw7" version="13.10.0" type="embed"><diagram id="gI28yuYEcBMMNC8PbDnz" name="Page-1">5VrbbqMwEP0aHlthCLk8pullV+pKK3W1++yAAW8NRsZpkn792mBDiEmLtikJTR8qc7AHz5kzMAOx3EWyeWAwi3/QABHLsYON5d5ajjMGI/FfAtsScCazEogYDkoI1MATfkUKtBW6wgHKGxM5pYTjrAn6NE2RzxsYZIyum9NCSppXzWCEDODJh8RE/+CAxyU69ewa/4ZwFOsrA1udWUL/OWJ0larrWY57X/yVpxOoban5eQwDut6B3DvLXTBKeTlKNgtEJLWatnLd/YGz1b4ZSnmXBSpOL5CskN5xsS++1VygQFCjDinjMY1oCsldjd4U/iJp0RZH9ZxHSjMBAgH+RZxvVZzhilMBxTwh6qy5a+VITlfMV/tQO+OQRUjNcktI7nBnmfL0AdEEcbYVExgikOOXZnCh0khUzat5EgNFVTttTgfaCBFqlfSsY8zRUwYLR9YiX5rOwzwrJRzijSTxMBsviHG0edNTfXas9KXyz3XV8bpWs4biHSFr7CPcjIcjKdeUlHcqSbkDk5Tj9Sep2XAk5ZmSmp5KUt7AJAUm/UlqOjBuZv1RA4DBzS8YRdKzuYTBtX0NDLaEX7xJS84ZfUYLSigTSEpTSWWICdmDIMFRKg4JCqUFyREWpdhcwQkOAnIoCs2kPgLvjr3/5PQM4qtqb5d55xjMm3VFwbbAbuxLYdzxTMYnn0W4+dStCL8YiYNJj4SbDUdFuHkH/qKEz3rke0DVOJhYRu0EVOnXf/Gkd3O2FcIInK7HA13Kp3OR1cyUlZ53All16WXOSVZ99nn6jjcEWVXvJXdl5ZxKVo5Zs5+3rPrs9TQXgyGnx2av5b3T5TR7I++EzV7Lu/ev3+ztM95ns9fyZrog3L0kwnvsPVresRm3li/b8+3z3tZkf96tpUORmccwk8OQoM1cfjIVfqM0UMNbn8A8x37HwujdgmfHa6/FaY11rovUFX5SLHZSN9rTPdKnXtNEWcCpVTWfhiF39o6hsuozDBWBqdzu9t3HrHlFlghgnmUEC0m2FcCPcInIXl2iZO6L2CD2lv4ZyvErXBb2pNoz6UXhl3djebetca4UtZ8A1Td4ZdDa/Y7dlhiinLBn4CjhvtJLVJSudM+uTdAwzNGHA9ShtL6IZLJH18dKJ9PUERPKrPa/JyKVfCx39BuxHNPUcsZEPiSWTIwiOVrEMI1Qfh7p5h563pxVujlGFP8n4cRh/XuXcnr9myL37h8=</diagram></mxfile>" style="background-color: rgb(255, 255, 255);">
|
||||||
|
<defs/>
|
||||||
|
<g>
|
||||||
|
<path d="M 15 262 L 15 218.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 213.12 L 18.5 220.12 L 15 218.37 L 11.5 220.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="277" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 15 182 L 15 138.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 133.12 L 18.5 140.12 L 15 138.37 L 11.5 140.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="197" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 15 102 L 15 58.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 53.12 L 18.5 60.12 L 15 58.37 L 11.5 60.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="117" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="37" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<rect x="40" y="267" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 277px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="281" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="187" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 197px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="201" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="107" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 117px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="121" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="27" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 37px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="41" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+2
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 265 262 L 265 218.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 265 213.12 L 268.5 220.12 L 265 218.37 L 261.5 220.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="265" cy="277" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 265 182 L 265 138.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 265 133.12 L 268.5 140.12 L 265 138.37 L 261.5 140.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="265" cy="197" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 265 102 L 265 58.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 265 53.12 L 268.5 60.12 L 265 58.37 L 261.5 60.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="265" cy="117" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<ellipse cx="265" cy="37" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<rect x="290" y="267" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 277px; margin-left: 292px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="292" y="281" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="290" y="187" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 197px; margin-left: 292px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="292" y="201" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="290" y="27" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 37px; margin-left: 292px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.3+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="292" y="41" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.3+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="290" y="107" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 117px; margin-left: 292px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Tagged: v1.0.2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="292" y="121" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Tagged: v1.0.2
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 120.5 122 L 120.5 112 L 210.5 112 L 210.5 101.5 L 229.5 117 L 210.5 132.5 L 210.5 122 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 101px; margin-left: 161px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||||
|
Tag Applied
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="161" y="104" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
Tag Applied
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 120.5 41.5 L 120.5 31.5 L 210.5 31.5 L 210.5 21 L 229.5 36.5 L 210.5 52 L 210.5 41.5 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 13px; margin-left: 161px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||||
|
Implicit Version
|
||||||
|
<br/>
|
||||||
|
Changes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="161" y="16" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
Implicit Version...
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<switch>
|
||||||
|
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||||
|
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||||
|
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||||
|
Viewer does not support full SVG 1.1
|
||||||
|
</text>
|
||||||
|
</a>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 17 KiB |
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/*.test.ts"]
|
||||||
|
}
|
||||||
109
versioning.drawio.svg
Normal file
109
versioning.drawio.svg
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="194px" height="271px" viewBox="-0.5 -0.5 194 271" content="<mxfile host="0a9c6f9a-c69b-46b3-a4d1-52050d73bab5" modified="2020-12-28T22:17:02.810Z" agent="5.0 (Macintosh; Intel Mac OS X 11_0_0) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="O5_NbqrsuXMcuouE6E1h" version="13.10.0" type="embed"><diagram id="gI28yuYEcBMMNC8PbDnz" name="Page-1">5ZjbcpswEIafhstmEAo+XCauk3amvXKnvVZhDaoFywjhQ5++EghjrDhxpz6MG1940K8Fdj/9wIJHJ9n6WbIi/YoxCC/w47VHP3pBMCD3+t8Im0YIhuNGSCSPG4l0woz/Biv6Vq14DGUvUCEKxYu+GGGeQ6R6GpMSV/2wOYr+WQuWgCPMIiZc9QePVdqoo9Dv9E/Ak7Q9M/HtzE8WLRKJVW7P5wX0qf410xlrj2Xjy5TFuNqR6NSjE4momq1sPQFh0LbYmv2eDsxu85aQq2N2sOu0ZKKCNuM6L7VpWUCs0dghSpVigjkT0059rOsFc0Rfj7qYL4iFFokWf4FSG7vOrFKopVRlws66WdtCSqxkZPOwmSkmE7BRtJFMhju72UqfATNQcqMDJAim+LK/uMx6JNnGdZz0hkX1MrbgCGxCaLcaPKuUK5gVrC5kpa+XfvGsLBoLz/naQDxMYwlSwfrVStvZgfWXvf4oteNV5+ZWSneM3Gr/wmZwO5airqXCa1mK3pilgvBylhrfjqVC11Kja1kqvDFLkeHlLDW6MTbjy6EhxGHzjSWJqezByOTOvyMOLV2X6mMplcQFTFCg1EqOuUE550LsSUzwJNdDAXNzBMOI61bswcoZj2NxaBX6F/UJuAf+/pMzdMBvu71d8sEpyLt9RU1ba4/+eyEehC7x4bmAu0/dLfB3Y3EyvCBw94VjC9y9A/+nwMcX5O32AJ+zQvCIK61+B1lyzMszgI80LpBXR0/J216/Pxf7I96E9Pt/YTajSorNo2TRwnSNbzUcHaZmpHTfiAbuBzI6lWeHe+B8twEJXgC3BfwX5PSw++JRz+18VaLTPw==</diagram></mxfile>" style="background-color: rgb(255, 255, 255);">
|
||||||
|
<defs/>
|
||||||
|
<g>
|
||||||
|
<path d="M 15 240 L 15 196.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 191.12 L 18.5 198.12 L 15 196.37 L 11.5 198.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="255" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 15 160 L 15 116.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 111.12 L 18.5 118.12 L 15 116.37 L 11.5 118.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="175" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<path d="M 15 80 L 15 36.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 15 31.12 L 18.5 38.12 L 15 36.37 L 11.5 38.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="95" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<ellipse cx="15" cy="15" rx="15" ry="15" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||||
|
<rect x="40" y="245" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 98px; height: 1px; padding-top: 255px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="259" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
Tagged: v1.0.1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="165" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 175px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="179" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+0
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="85" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 95px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="99" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+1
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="40" y="5" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 68px; height: 1px; padding-top: 15px; margin-left: 42px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
v1.0.2+2
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="42" y="19" fill="#000000" font-family="Helvetica" font-size="12px">
|
||||||
|
v1.0.2+2
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="150" y="85" width="40" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 95px; margin-left: 151px;">
|
||||||
|
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||||
|
Implicit Versions
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="170" y="99" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
Implic...
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 130 10 L 125 10 Q 120 10 120 20 L 120 87.5 Q 120 97.5 115 97.5 L 112.5 97.5 Q 110 97.5 115 97.5 L 117.5 97.5 Q 120 97.5 120 107.5 L 120 175 Q 120 185 125 185 L 130 185" fill="none" stroke="#000000" stroke-miterlimit="10" transform="rotate(-180,120,97.5)" pointer-events="all"/>
|
||||||
|
</g>
|
||||||
|
<switch>
|
||||||
|
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||||
|
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||||
|
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||||
|
Viewer does not support full SVG 1.1
|
||||||
|
</text>
|
||||||
|
</a>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.4 KiB |
Loading…
Reference in a new issue