├── .all-contributorsrc
├── .editorconfig
├── .eslintrc.json
├── .github
├── FUNDING.yml
└── workflows
│ └── test.yml
├── .gitignore
├── .prettierrc.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── SPONSORS.md
├── action.yml
├── dist
└── index.js
├── docs
└── how-to.md
├── index.js
├── package-lock.json
├── package.json
└── src
├── add-links.js
├── add-links.test.js
├── get-entries.js
├── get-entries.test.js
├── get-entry-by-version-id.js
├── get-entry-by-version-id.test.js
├── get-links.js
├── get-links.test.js
├── main.js
├── parse-entry-content.js
├── parse-entry.js
├── parse-entry.test.js
├── rules
├── has-chronological-order.js
├── has-chronological-order.test.js
├── has-correct-sections.js
├── has-correct-sections.test.js
├── has-sections.js
├── has-sections.test.js
├── is-semver.js
└── is-semver.test.js
├── validate-entry.js
└── validate-entry.test.js
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "CONTRIBUTORS.md"
4 | ],
5 | "skipCi": true,
6 | "contributors": [
7 | {
8 | "login": "mindsers",
9 | "name": "Nathanael CHERRIER",
10 | "avatar_url": "https://avatars0.githubusercontent.com/u/3090112?v=4",
11 | "profile": "https://nathanaelcherrier.com",
12 | "contributions": [
13 | "code",
14 | "doc",
15 | "review",
16 | "question",
17 | "test",
18 | "maintenance"
19 | ]
20 | },
21 | {
22 | "login": "aroemen",
23 | "name": "Alan Roemen",
24 | "avatar_url": "https://avatars1.githubusercontent.com/u/796505?v=4",
25 | "profile": "https://roemen.company",
26 | "contributions": [
27 | "code",
28 | "test",
29 | "ideas"
30 | ]
31 | },
32 | {
33 | "login": "endormi",
34 | "name": "Erno Salo",
35 | "avatar_url": "https://avatars3.githubusercontent.com/u/39559256?v=4",
36 | "profile": "http://endormi.io",
37 | "contributions": [
38 | "doc"
39 | ]
40 | },
41 | {
42 | "login": "andrekosak",
43 | "name": "Andre Kosak",
44 | "avatar_url": "https://avatars1.githubusercontent.com/u/6382243?v=4",
45 | "profile": "https://github.com/andrekosak",
46 | "contributions": [
47 | "example"
48 | ]
49 | },
50 | {
51 | "login": "svenstaro",
52 | "name": "Sven-Hendrik Haase",
53 | "avatar_url": "https://avatars0.githubusercontent.com/u/1664?v=4",
54 | "profile": "https://svenstaro.org",
55 | "contributions": [
56 | "example"
57 | ]
58 | },
59 | {
60 | "login": "alexesprit",
61 | "name": "Alexey",
62 | "avatar_url": "https://avatars1.githubusercontent.com/u/1119267?v=4",
63 | "profile": "https://alexesprit.com",
64 | "contributions": [
65 | "doc"
66 | ]
67 | },
68 | {
69 | "login": "farfromrefug",
70 | "name": "farfromrefuge",
71 | "avatar_url": "https://avatars.githubusercontent.com/u/655344?v=4",
72 | "profile": "https://github.com/farfromrefug",
73 | "contributions": [
74 | "code"
75 | ]
76 | },
77 | {
78 | "login": "xt0rted",
79 | "name": "Brian Surowiec",
80 | "avatar_url": "https://avatars.githubusercontent.com/u/831974?v=4",
81 | "profile": "http://onlypans.pizza",
82 | "contributions": [
83 | "bug"
84 | ]
85 | },
86 | {
87 | "login": "danilogco",
88 | "name": "Danilo Carolino",
89 | "avatar_url": "https://avatars.githubusercontent.com/u/3433530?v=4",
90 | "profile": "https://github.com/danilogco",
91 | "contributions": [
92 | "code",
93 | "bug"
94 | ]
95 | },
96 | {
97 | "login": "SwabianCoder",
98 | "name": "SwabianCoder",
99 | "avatar_url": "https://avatars.githubusercontent.com/u/43047586?v=4",
100 | "profile": "https://github.com/SwabianCoder",
101 | "contributions": [
102 | "bug"
103 | ]
104 | },
105 | {
106 | "login": "jon-whit",
107 | "name": "Jonathan Whitaker",
108 | "avatar_url": "https://avatars.githubusercontent.com/u/2899204?v=4",
109 | "profile": "http://jon-whit.me",
110 | "contributions": [
111 | "ideas"
112 | ]
113 | },
114 | {
115 | "login": "mrks115",
116 | "name": "Markus Brüx",
117 | "avatar_url": "https://avatars.githubusercontent.com/u/3636209?v=4",
118 | "profile": "https://github.com/mrks115",
119 | "contributions": [
120 | "code"
121 | ]
122 | },
123 | {
124 | "login": "guihkx",
125 | "name": "Guilherme Silva",
126 | "avatar_url": "https://avatars.githubusercontent.com/u/626206?v=4",
127 | "profile": "https://github.com/guihkx",
128 | "contributions": [
129 | "bug"
130 | ]
131 | },
132 | {
133 | "login": "stronk7",
134 | "name": "Eloy Lafuente",
135 | "avatar_url": "https://avatars.githubusercontent.com/u/167147?v=4",
136 | "profile": "https://github.com/stronk7",
137 | "contributions": [
138 | "doc"
139 | ]
140 | }
141 | ],
142 | "projectName": "changelog-reader-action",
143 | "projectOwner": "mindsers",
144 | "repoType": "github",
145 | "repoHost": "https://github.com",
146 | "contributorsPerLine": 7,
147 | "commitType": "docs",
148 | "commitConvention": "angular"
149 | }
150 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | insert_final_newline = true
8 | end_of_line = lf
9 | trim_trailing_whitespace = true
10 |
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
16 | [Dockerfile]
17 | indent_size = 4
18 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "commonjs": true,
4 | "es6": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "extends": "eslint:recommended",
9 | "globals": {
10 | "Atomics": "readonly",
11 | "SharedArrayBuffer": "readonly"
12 | },
13 | "parserOptions": {
14 | "ecmaVersion": 2018
15 | },
16 | "rules": {
17 | "arrow-body-style": ["error", "as-needed"],
18 | "arrow-parens": ["error", "as-needed"],
19 | "arrow-spacing": "error",
20 | "default-case": "error",
21 | "guard-for-in": "error",
22 | "generator-star-spacing": ["error", { "before": true, "after": false }],
23 | "getter-return": ["error", { "allowImplicit": true }],
24 | "no-compare-neg-zero": "error",
25 | "no-confusing-arrow": "error",
26 | "no-else-return": "error",
27 | "no-empty": "error",
28 | "no-implicit-coercion": "error",
29 | "no-useless-return": "error",
30 | "no-redeclare": "error",
31 | "no-var": "error",
32 | "prefer-arrow-callback": "error",
33 | "prefer-const": "error",
34 | "prefer-rest-params": "error",
35 | "prefer-spread": "error",
36 | "prefer-template": "error",
37 | "space-before-function-paren": ["error", {
38 | "anonymous": "always",
39 | "named": "never",
40 | "asyncArrow": "always"
41 | }]
42 | }
43 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [mindsers] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # mindsers # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: mindsers # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: mindsers # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # nathanaelcherrier.dev/en/support # Replace with a single custom sponsorship URL
13 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: 'units-test'
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - master
7 | - 'releases/*'
8 |
9 | jobs:
10 | # unit tests
11 | unit:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - run: npm ci
16 | - run: npm test
17 |
18 | # test action works running from the graph
19 | e2e:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v2
24 | - name: Run the changelog reader
25 | id: changelog_reader
26 | uses: ./
27 | - name: Display output
28 | run: |
29 | echo "Version: ${{ steps.changelog_reader.outputs.version }}"
30 | echo "Date: ${{ steps.changelog_reader.outputs.date }}"
31 | echo "Status: ${{ steps.changelog_reader.outputs.status }}"
32 | echo "Changes: ${{ steps.changelog_reader.outputs.changes }}"
33 | - name: Check output
34 | run: |
35 | test "Changes: ${{ steps.changelog_reader.outputs.changes }}" != ""
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | # Editors
4 | .vscode
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Other Dependency directories
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 |
64 | # next.js build output
65 | .next
66 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | trailingComma: "es5"
2 | tabWidth: 2
3 | semi: false
4 | singleQuote: true
5 | printWidth: 100
6 | jsxSingleQuote: false
7 | arrowParens: "avoid"
8 | endOfLine: "lf"
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [2.2.3] - 2024-03-10
11 |
12 | ### Fixed
13 |
14 | - Upgrade dependencies to solve deprecation issues.
15 | - Use node v20
16 | - Remove useless empty line between links in the body of a version
17 |
18 | ## [2.2.2] - 2022-11-23
19 |
20 | ### Fixed
21 |
22 | - Upgrade dependencies to solve deprecation issues.
23 |
24 | ## [2.2.1] - 2022-11-10
25 |
26 | ### Fixed
27 |
28 | - Change node engine for a non-deprecated version.
29 |
30 | ## [2.2.0] - 2022-09-01
31 |
32 | ### Changed
33 |
34 | - Allow more section types into prerelease versions. #67
35 |
36 | ### Fixed
37 |
38 | - Change the links' syntax to make them correctly recognized by GitHub.
39 |
40 | ## [2.1.1] - 2022-07-03
41 |
42 | ### Fixed
43 |
44 | - The action was returning empty data since the last version. Now correctly returns selected entries data.
45 |
46 | ## [2.1.0] - 2022-06-14
47 |
48 | ### Added
49 |
50 | - Introduced changelog validation to help keep the release version in line with [Semantic Versioning](https://semver.org/)
51 | - New input param of `validation_level` and `validation_depth` to allow for configuration of changelog validation.
52 | - Support Angular CHANGELOG format. (Doesn't force title emphasis)
53 |
54 | ### Changed
55 |
56 | - The project now implement the [All Contributors](https://allcontributors.org).
57 | _This is not a change in the code but a change in how the projet recognize the
58 | external contributions._
59 |
60 | ### Fixed
61 |
62 | - Retrieve links (external to the entry) and add them back in the related entry.
63 |
64 | ## [2.0.0] - 2020-08-30
65 |
66 | ### Added
67 |
68 | - New output properties:
69 | - The `version` number of the returned entry
70 | - The released `date` of the returned entry
71 | - The `status` of the release based on the version number and the title line of the entry.
72 | Could be equal to `unreleased`, `prereleased`, `released` or `yanked`.
73 | Please refer to https://semver.org/#semantic-versioning-specification-semver for more informations about this.
74 |
75 | ### Changed
76 |
77 | - **[BREAKING CHANGE]** If given a specific target version, action will now generate an error response if that version is not found in the changelog.
78 | - **[BREAKING CHANGE]** `log_entry` output property is renamed to `changes`.
79 |
80 | ## [1.3.1] - 2020-07-08
81 |
82 | ### Fixed
83 |
84 | - Allow developers to NOT use a date for each version entries.
85 |
86 | ## [1.3.0] - 2020-07-06
87 |
88 | ### Changed
89 |
90 | - Dates should follow the format used in the ["Keep a Changelog"](https://keepachangelog.com/en/1.0.0/) specification
91 | which is `YYYY-MM-DD`. Another format that may work is `YYYY-DD-MM`.
92 | Given the current disclaimer in the `README.md`, this change is **not** a _breaking_ change.
93 |
94 | ### Fixed
95 |
96 | - Improve SEMVER support. Now recognize complex version number based on https://semver.org.
97 |
98 | ## [1.2.0] - 2020-06-24
99 |
100 | ### Added
101 |
102 | - New support for "Unreleased" section. It is possible to ask for the "Unreleased" section
103 | by setting `version: "Unreleased"` in the `workflow.yml` file.
104 |
105 | ### Fixed
106 |
107 | - Improve the way the project is linted
108 |
109 | ## [1.1.0] - 2020-03-13
110 |
111 | ### Added
112 |
113 | - The project has now a CHANGELOG
114 | - Add logging message to make the debugging session easier
115 |
116 | ### Changed
117 |
118 | - When no log entry is found, the error state of the action doesn't break the workflow anymore
119 |
120 | ### Fixed
121 |
122 | - README now uses the correct version number in the examples
123 | - Support X.X.X version pattern
124 |
125 | ## [1.0.1] - 2020-02-12
126 |
127 | ### Fixed
128 |
129 | - Remove template's old behavior
130 |
131 | ## [1.0.0] - 2020-02-12
132 |
133 | ### Added
134 |
135 | - CHANGELOG can be parsed by the github action
136 |
137 | [unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v2.2.3...HEAD
138 | [2.2.3]: https://github.com/mindsers/changelog-reader-action/compare/v2.2.2...v2.2.3
139 | [2.2.2]: https://github.com/mindsers/changelog-reader-action/compare/v2.2.1...v2.2.2
140 | [2.2.1]: https://github.com/mindsers/changelog-reader-action/compare/v2.2.0...v2.2.1
141 | [2.2.0]: https://github.com/mindsers/changelog-reader-action/compare/v2.1.1...v2.2.0
142 | [2.1.1]: https://github.com/mindsers/changelog-reader-action/compare/v2.1.0...v2.1.1
143 | [2.1.0]: https://github.com/mindsers/changelog-reader-action/compare/v2.0.0...v2.1.0
144 | [2.0.0]: https://github.com/mindsers/changelog-reader-action/compare/v1.3.1...v2.0.0
145 | [1.3.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.3.0...v1.3.1
146 | [1.3.0]: https://github.com/mindsers/changelog-reader-action/compare/v1.2.0...v1.3.0
147 | [1.2.0]: https://github.com/mindsers/changelog-reader-action/compare/v1.1.0...v1.2.0
148 | [1.1.0]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.1...v1.1.0
149 | [1.0.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0...v1.0.1
150 | [1.0.0]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0
151 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute to Changelog Reader Action
2 |
3 | *First off, thanks for taking the time to contribute!*
4 |
5 | This file is a set of guilines for contributing to *Changelog Reader Action* project. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6 |
7 | **All the contributors are/will be added to our [Contributors Wall](CONTRIBUTORS.md).** If we forgot someone, please let us know. We'll be glad to add them to the list.
8 |
9 | #### Table of contents
10 |
11 | [How can I contribute?](#how-can-i-contribute)
12 |
13 | * [Did you find a bug?](#did-you-find-a-bug)
14 | * [Did you write a patch that fixes a bug?](#did-you-write-a-patch-that-fixes-a-bug)
15 | * [Did you fix whitespace, format code, or make a purely cosmetic patch?](#did-you-fix-whitespace-format-code-or-make-a-purely-cosmetic-patch)
16 | * [Do you intend to add a new feature or change an existing one?](#do-you-intend-to-add-a-new-feature-or-change-an-existing-one)
17 | * [Do you have questions about the source code?](#do-you-have-questions-about-the-source-code)
18 | * [Do you want to contribute to the Changelog Reader Action documentation?](#do-you-want-to-contribute-to-the-Configfile-documentation)
19 |
20 | [Styleguides](#styleguides)
21 |
22 | * [JavaScript styleguide](#javaScript-styleguide)
23 | * [Git commit messages](#git-commit-messages)
24 |
25 | ## How can I contribute?
26 |
27 | ### Did you find a bug?
28 |
29 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues][Issues].
30 |
31 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/mindsers/changelog-reader-action/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
32 |
33 | * If possible, use the relevant bug report templates to create the issue.
34 |
35 | ### Did you write a patch that fixes a bug?
36 |
37 | * Write new unit test(s) that match the bug case to limit future regression.
38 |
39 | * Open a new GitHub pull request with the patch.
40 |
41 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
42 |
43 | * Before submitting, please ensure your code follow the code convention.
44 |
45 | ### Did you fix whitespace, format code, or make a purely cosmetic patch?
46 |
47 | * Your changes must follow the coding convention.
48 |
49 | * Please ensure that your changes does not include regression.
50 |
51 | ### Do you intend to add a new feature or change an existing one?
52 |
53 | * Please ask first ([open an issue][Issues] or [talk about it on the community forum][Forum]) before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project.
54 |
55 | * Please adhere to the coding conventions used in this project (indentation, accurate comments, etc.) and any other requirements (such as test coverage, documentation).
56 |
57 | ### Do you have questions about the source code?
58 |
59 | * Ask any question about how to use Configfile in the [forum category][Forum] or on [Stack Overflow](https://stackoverflow.com).
60 |
61 | ### Do you want to contribute to the Configfile documentation?
62 |
63 | Documentation file are stored in the project source code.
64 |
65 | * Please refer to "Do you intend to add a new feature or change an existing one?" section.
66 |
67 | ## Styleguides
68 |
69 | ### JavaScript styleguide
70 |
71 | All JavaScript must adhere to [JavaScript Standard Style](https://standardjs.com).
72 |
73 | Exepctions to Standard style:
74 |
75 | * We do not want space after function name `function name(arg) { ... }`
76 |
77 | Additional rules:
78 |
79 | * Prefer spread operator (`prefer-spread`)
80 | * No useless brackets for arrow functions (`arrow-body-style`)
81 | * No useless parens for arrow functions (`arrow-parens`)
82 | * Require space before/after arrow function’s arrow (`arrow-spacing`)
83 | * `switch` must have a default case (`default-case`)
84 | * `for...in` loop must be guard by an `if` (`guard-for-in`)
85 | * Require space before the star of generator function (`generator-star-spacing`)
86 | * Getter properties must return a value (`getter-return`)
87 | * Compare to `-0` is an error (`no-compare-neg-zero`)
88 | * Use brackets if arrow function body could be confused with comparisons (`no-confusing-arrow`)
89 | * No `else` when return is used (`no-else-return`)
90 | * No empty block statements (`no-empty`)
91 | * No type conversion with shorter notations (`no-implicit-coercion`)
92 | * No useless `return` statement (`no-useless-return`)
93 | * No redeclare variables (`no-redeclare`)
94 | * Using `var` statement is an error (`no-var`)
95 | * Prefer arrow function for callback (`prefer-arrow-callback`)
96 | * Prefer using constant (`prefer-const`)
97 | * Prefer rest parameter (`prefer-rest-params`)
98 | * Prefer using template literals (`prefer-template`)
99 |
100 | All the rules are listed in `.eslintrc.json` on the root directory.
101 |
102 | ### Git commit messages
103 |
104 | * Use the present tense ("Add feature" not "Added feature").
105 |
106 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...").
107 |
108 | * Limit the first line to 72 characters or less.
109 |
110 | * Reference issues and pull requests liberally after the first line.
111 |
112 |
113 | [Issues]: https://github.com/mindsers/changelog-reader-action/issues
114 | [Forum]: https://community.nathanaelcherrier.com/c/open-source/changelog-reader-action/11
115 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributors ✨
3 |
4 | Thanks goes to these wonderful people
5 | ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
6 |
7 |
8 |
9 |
10 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | This project follows the
39 | [all-contributors](https://github.com/all-contributors/all-contributors)
40 | specification. Contributions of any kind are welcome!
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Nathanaël Cherrier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Changelog Reader
2 |
3 |
4 |
5 | A GitHub action to read and get data from the `CHANGELOG.md` file :rocket:
6 |
7 | **This action only works if your `CHANGELOG.md` file follows the [_Keep a Changelog_](https://github.com/olivierlacan/keep-a-changelog) standard for now.**
8 |
9 | ## Usage
10 |
11 | ### Pre-requisites
12 |
13 | Create a workflow `.yml` file in your repositories `.github/workflows` directory. An [example workflow](#example-workflow---upload-a-release-asset) is available below. For more information, reference the GitHub Help Documentation for [Creating a workflow file](https://help.github.com/en/articles/configuring-a-workflow#creating-a-workflow-file).
14 |
15 | ### Inputs
16 |
17 | - `path`: The path the action can find the CHANGELOG. Optional. Defaults to `./CHANGELOG.md`.
18 | - `version`: The [exact version](https://semver.org) of the log entry you want to retreive or "Unreleased" for the unreleased entry. Optional. Defaults to the last version number.
19 | - `validation_level`: Specifies at which level the validation system is set. Can be 'none', 'warn', 'error'. Optional. Defaults to `none`.
20 | - `validation_depth`: Specifies how many entries to validate in the CHANGELOG.md file. Optional. Defaults to `10`.
21 |
22 | ### Outputs
23 |
24 | - `version`: Version of the log entry found. Ex: `2.0.0`.
25 | - `date`: Release date of the log entry found. Ex: `2020-08-22`.
26 | - `status`: Status of the log entry found (`prereleased`, `released`, `unreleased`, or `yanked`).
27 | - `changes`: Description text of the log entry found.
28 |
29 | ### Validation / Linting
30 |
31 | A validation engine is available in _Changelog Reader_. It is by default disabled but can be enabled by setting `validation_level` to 'warn' or 'error'.
32 |
33 | The validation engine will enforce [Semantic Versioning 2.0.0](https://semver.org/) standards as well as [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) standards and formatting. **If your project doesn't follow Semantic Versioning 2.0.0 or Keep a Changelog, do not enable the validation engine.** It might break your build unnecessarily.
34 |
35 | You can utilize the `validation_depth` input param to specify how many entries to validate. Changelog Reader will by default validates only the last 10 changelog entries.
36 |
37 | When `validation_level` is set at 'warn', Changelog Reader will print check results as warnings in your logs without breaking your build.
38 | When `validation_level` is set at 'error', Changelog Reader will print check results as errors in your logs and will throw an error to prevent the build to go further.
39 |
40 | ### Example workflow - create a release from changelog
41 |
42 | On every `push` to a tag matching the pattern `v*`, [create a release](https://developer.github.com/v3/repos/releases/#create-a-release) using the CHANGELOG.md content.
43 | This Workflow example assumes you'll use the [`@actions/create-release`](https://www.github.com/actions/create-release) Action to create the release step:
44 |
45 | ```yaml
46 | on:
47 | push:
48 | # Sequence of patterns matched against refs/tags
49 | tags:
50 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
51 |
52 | name: Create Release
53 |
54 | jobs:
55 | build:
56 | name: Create Release
57 | runs-on: ubuntu-latest
58 | steps:
59 | - name: Get version from tag
60 | id: tag_name
61 | run: |
62 | echo "current_version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
63 | shell: bash
64 | - name: Checkout code
65 | uses: actions/checkout@v2
66 | - name: Get Changelog Entry
67 | id: changelog_reader
68 | uses: mindsers/changelog-reader-action@v2
69 | with:
70 | validation_level: warn
71 | version: ${{ steps.tag_name.outputs.current_version }}
72 | path: ./CHANGELOG.md
73 | - name: Create/update release
74 | uses: ncipollo/release-action@v1
75 | with:
76 | # This pulls from the "Get Changelog Entry" step above, referencing it's ID to get its outputs object.
77 | # See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
78 | tag: ${{ steps.changelog_reader.outputs.version }}
79 | name: Release ${{ steps.changelog_reader.outputs.version }}
80 | body: ${{ steps.changelog_reader.outputs.changes }}
81 | prerelease: ${{ steps.changelog_reader.outputs.status == 'prereleased' }}
82 | draft: ${{ steps.changelog_reader.outputs.status == 'unreleased' }}
83 | allowUpdates: true
84 | token: ${{ secrets.GITHUB_TOKEN }}
85 | ```
86 |
87 | ## Contribution
88 |
89 | Contributions to the source code of _Changelog Reader Action_ are welcomed and greatly appreciated.
90 | For help on how to contribute in this project, please refer to [How to contribute to Changelog Reader Action](CONTRIBUTING.md).
91 |
92 | To see the project's list of **awesome contributors**, please refer to our [Contributors Wall](CONTRIBUTORS.md).
93 |
94 | ## Support
95 |
96 | _Changelog Reader Action_ is licensed under an MIT license, which means that it's a completely free open source software. Unfortunately, _Changelog Reader Action_ doesn't make itself. Version 2.0.0 is the next step, which will result in many late, beer-filled nights of development.
97 |
98 | If you're using _Changelog Reader Action_ and want to support the development, you now have the chance! Go on my [GitHub Sponsor page](https://github.com/sponsors/mindsers) and become my joyful sponsor!!
99 |
100 | For more help on how to support Changelog Reader Action, please refer to [The awesome people who support Changelog Reader Action](SPONSORS.md).
101 |
102 |
103 |
104 | ## License
105 |
106 | The scripts and documentation in this project are released under the [MIT License](LICENSE)
107 |
--------------------------------------------------------------------------------
/SPONSORS.md:
--------------------------------------------------------------------------------
1 | # The awesome people who support Changelog Reader Action
2 |
3 | *First off, thanks for being a part of this project!*
4 |
5 | This file is an extension of the **Support** section in `README.md`. You can find here all the people who support *Changelog Reader Action* organized by their contribution.
6 |
7 | #### Table of content
8 |
9 | [How can I support this project?](#how-can-i-support-this-project)
10 |
11 | [Who are the people behind this project?](#who-are-the-people-behind-this-project)
12 |
13 | * [Sponsors](#sponsors)
14 | * [Supporters](#supporters)
15 |
16 | ## How can I support this project?
17 |
18 | *Changelog Reader Action* is licensed under an Apache-2.0 license, which means that it's a completely free open source software. Unfortunately, *Changelog Reader Action* doesn't make itself and I'm happy to build it on my free time.
19 |
20 | If you're using *Changelog Reader Action* and want to support the development, you can do it via the GitHub Sponsor service. On this platform you can choose exactly what you want to give from $5 to whatever you want... You can click on the "Become a sponsor" link bellow to see Mindsers' GitHub Sponsor page.
21 |
22 | Be sure your effort to support this project will be greatly appreciated.
23 |
24 | [**_Become a Sponsor!_**](https://github.com/sponsors/mindsers)
25 |
26 | ## Who are the people behind this project?
27 |
28 | ### Sponsors
29 |
30 | No one is in the **Sponsors** section for now. Be the first! Read the **How can I support this project** section above.
31 |
32 | ### Supporters
33 |
34 | No one is in the **Supporters** section for now. Be the first! Read the **How can I support this project** section above.
35 |
36 |
39 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Changelog Reader'
2 | description: 'Read and parse the CHANGELOG file of the project'
3 | branding:
4 | icon: 'align-right'
5 | color: 'gray-dark'
6 | inputs:
7 | path:
8 | description: 'Path to the CHANGELOG file containing the log entries'
9 | required: false
10 | default: './CHANGELOG.md'
11 | version:
12 | description: 'Version of the log entry wanted'
13 | required: false
14 | validation_level:
15 | description: 'Specifies if the CHANGELOG.md file should be validated and the behavior of the action'
16 | required: false
17 | default: 'none'
18 | validation_depth:
19 | description: 'Specifies how many entries to validate in the CHANGELOG.md file'
20 | required: false
21 | default: '10'
22 | outputs:
23 | version:
24 | description: 'Version of the log entry found'
25 | date:
26 | description: 'Release date of the log entry found'
27 | status:
28 | description: 'Status of the log entry found. Possibly one of the following: prereleased, released, unreleased, or yanked'
29 | changes:
30 | description: 'Description text of the log entry found'
31 | runs:
32 | using: 'node20'
33 | main: 'dist/index.js'
34 |
--------------------------------------------------------------------------------
/docs/how-to.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Create a JavaScript Action
6 |
7 | ## Code in Develop
8 |
9 | Install the dependencies:
10 |
11 | ```bash
12 | $ npm install
13 | ```
14 |
15 | Run the tests:
16 |
17 | ```bash
18 | $ npm test
19 |
20 | PASS ./index.test.js
21 | ✓ throws invalid number (3ms)
22 | ✓ wait 500 ms (504ms)
23 | ✓ test runs (95ms)
24 |
25 | ...
26 | ```
27 |
28 | ## Change the Code
29 |
30 | Most toolkit and CI/CD operations involve async operations so the action is run in an async function.
31 |
32 | ```javascript
33 | const core = require('@actions/core');
34 | ...
35 |
36 | async function run() {
37 | try {
38 | ...
39 | }
40 | catch (error) {
41 | core.setFailed(error.message);
42 | }
43 | }
44 |
45 | run()
46 | ```
47 |
48 | See the [toolkit documentation](https://github.com/actions/toolkit/blob/master/README.md#packages) for the various packages.
49 |
50 | ## Package for distribution
51 |
52 | GitHub Actions will run the entry point from the action.yml. Packaging assembles the code into one file that can be checked in to Git, enabling fast and reliable execution and preventing the need to check in node_modules.
53 |
54 | Actions are run from GitHub repos. Packaging the action will create a packaged action in the `dist` folder.
55 |
56 | Run package:
57 |
58 | ```bash
59 | npm run package
60 | ```
61 |
62 | Since the packaged `index.js` is ran from the `dist` folder.
63 |
64 | ```bash
65 | git add dist
66 | ```
67 |
68 | ## Create a release tags
69 |
70 | Changelog Reader follow semantic versionning. So you have to create your tag.
71 |
72 | ```bash
73 | $ git tag -s v1.0.3
74 | ```
75 |
76 | To ease the work of maintainer we also create (or update) shortcut tags:
77 |
78 | ```bash
79 | $ git tag -d v1
80 | $ git push --delete origin v1
81 | $ git tag -a v1
82 | ```
83 |
84 | ```bash
85 | $ git push --tags
86 | ```
87 |
88 | Now you have to create the GitHub Release for this new version (v1.0.3) and check "marketplace". This action is now published! :rocket:
89 |
90 | See the [versioning documentation](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md)
91 |
92 | ## Usage
93 |
94 | You can now consume the action by referencing the v1 branch:
95 |
96 | ```yaml
97 | uses: actions/javascript-action@v1
98 | with:
99 | milliseconds: 1000
100 | ```
101 |
102 | See the [actions tab](https://github.com/actions/javascript-action/actions) for runs of this action! :rocket:
103 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { main } = require('./src/main')
2 |
3 | main()
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "changelog-reader-action",
3 | "version": "2.2.3",
4 | "description": "A GitHub Action to read your CHANGELOG",
5 | "main": "index.js",
6 | "homepage": "https://github.com/mindsers/changelog-reader-action#readme",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/mindsers/changelog-reader-action"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/mindsers/changelog-reader-action/issues"
14 | },
15 | "author": {
16 | "name": "Nathanaël Cherrier",
17 | "email": "dev@nathanaelcherrier.com",
18 | "url": "https://nathanaelcherrier.com"
19 | },
20 | "scripts": {
21 | "lint": "eslint src/**/*.js",
22 | "package": "NODE_OPTIONS=--openssl-legacy-provider ncc build index.js -o dist",
23 | "test": "npm run lint && jest"
24 | },
25 | "dependencies": {
26 | "@actions/core": "^1.10.0"
27 | },
28 | "devDependencies": {
29 | "@types/jest": "^29.2.2",
30 | "@types/semver": "^7.3.13",
31 | "@zeit/ncc": "^0.22.3",
32 | "eslint": "^8.27.0",
33 | "jest": "^29.3.1",
34 | "semver": "^7.3.8"
35 | },
36 | "keywords": [
37 | "GitHub",
38 | "Actions",
39 | "Changelog"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/src/add-links.js:
--------------------------------------------------------------------------------
1 | exports.addLinks = links => entry => {
2 | const { text } = entry
3 | const linkRegex = /(\[.+\])\[\]/gi
4 |
5 | let tempText = `${text}`
6 | let results = null
7 | while ((results = linkRegex.exec(text)) != null) {
8 | const link = links.find(element => element.includes(results[1]))
9 | tempText = `${tempText}\n${link}`
10 | }
11 |
12 | return { ...entry, text: tempText.trim() }
13 | }
14 |
--------------------------------------------------------------------------------
/src/add-links.test.js:
--------------------------------------------------------------------------------
1 | const { addLinks } = require('./add-links')
2 |
3 | test('retreive add correct links to entry', () => {
4 | const output = addLinks([
5 | `[1.1.2+meta]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.1-rc.1+build.123...1.1.2+meta`,
6 | `[1.1.1-rc.1+build.123]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-rc.1+build.123`,
7 | `[1.1.1-DEV-SNAPSHOT]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-DEV-SNAPSHOT`,
8 | `[1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay`,
9 | `[github]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay`,
10 | ])({
11 | text: `### Added
12 | - CHANGELOG can be parsed by the [github][] action
13 | - [1.1.1-DEV-SNAPSHOT][] can be parsed by the github action`,
14 | })
15 |
16 | expect(output.text).toEqual(
17 | `### Added
18 | - CHANGELOG can be parsed by the [github][] action
19 | - [1.1.1-DEV-SNAPSHOT][] can be parsed by the github action
20 | [github]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
21 | [1.1.1-DEV-SNAPSHOT]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-DEV-SNAPSHOT`
22 | )
23 | })
24 |
25 | test('add nothing when there is no references', () => {
26 | const output = addLinks([
27 | `[1.1.2+meta]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.1-rc.1+build.123...1.1.2+meta`,
28 | `[1.1.1-rc.1+build.123]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-rc.1+build.123`,
29 | `[1.1.1-DEV-SNAPSHOT]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-DEV-SNAPSHOT`,
30 | `[1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay`,
31 | ])({
32 | text: `### Added
33 | - CHANGELOG can be parsed by the github action`,
34 | })
35 |
36 | expect(output.text).toEqual(`### Added
37 | - CHANGELOG can be parsed by the github action`)
38 | })
39 |
--------------------------------------------------------------------------------
/src/get-entries.js:
--------------------------------------------------------------------------------
1 | const core = require('@actions/core')
2 |
3 | const versionSeparator = '\n## '
4 | const semverLinkRegex =
5 | /^\[v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?\]/
6 | const unreleasedLinkRegex = /^\[unreleased\]/i
7 | const avoidNonVersionData = version =>
8 | semverLinkRegex.test(version) || unreleasedLinkRegex.test(version)
9 |
10 | exports.getEntries = rawData => {
11 | const content = String(rawData)
12 |
13 | core.debug(`CHANGELOG content: ${content}`)
14 |
15 | return content.split(versionSeparator).filter(avoidNonVersionData)
16 | }
17 |
--------------------------------------------------------------------------------
/src/get-entries.test.js:
--------------------------------------------------------------------------------
1 | const { getEntries } = require('./get-entries')
2 |
3 | const DATA_v = `
4 | # Changelog
5 | All notable changes to this project will be documented in this file.
6 |
7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9 |
10 | ## [Unreleased]
11 | ### Added
12 | - The project has now a CHANGELOG
13 |
14 | ### Fixed
15 | - README now uses the correct version number in the examples
16 |
17 | ## [v1.0.1] - 2020-02-12
18 | ### Fixed
19 | - Remove template's old behavior
20 |
21 | ## [v1.0.0] - 2020-02-12
22 | ### Added
23 | - CHANGELOG can be parsed by the github action
24 |
25 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.1...HEAD
26 | [1.0.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0...v1.0.1
27 | [1.0.0]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0
28 | `
29 |
30 | const DATA_complex = `
31 | # Changelog
32 | All notable changes to this project will be documented in this file.
33 |
34 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
35 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
36 |
37 | ## [1.1.2+meta]
38 | ### Added
39 | - The project has now a CHANGELOG
40 |
41 | ### Fixed
42 | - README now uses the correct version number in the examples
43 |
44 | ## [1.1.1-rc.1+build.123] - 2020-02-12
45 | ### Fixed
46 | - Remove template's old behavior
47 |
48 | ## [1.1.1-DEV-SNAPSHOT] - 2020-02-12
49 | ### Added
50 | - CHANGELOG can be parsed by the github action
51 |
52 | ## [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay] - 2019-12-09
53 | ### Added
54 | - CHANGELOG can be parsed by the github action
55 |
56 | [1.1.2+meta]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.1-rc.1+build.123...1.1.2+meta
57 | [1.1.1-rc.1+build.123]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-rc.1+build.123
58 | [1.1.1-DEV-SNAPSHOT]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-DEV-SNAPSHOT
59 | [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
60 | `
61 |
62 | const DATA = `
63 | # Changelog
64 | All notable changes to this project will be documented in this file.
65 |
66 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
67 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
68 |
69 | ## [Unreleased]
70 | ### Added
71 | - The project has now a CHANGELOG
72 |
73 | ### Fixed
74 | - README now uses the correct version number in the examples
75 |
76 | ## [1.0.1] - 2020-02-12
77 | ### Fixed
78 | - Remove template's old behavior
79 |
80 | ## [1.0.0] - 2020-02-12
81 | ### Added
82 | - CHANGELOG can be parsed by the github action
83 |
84 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.1...HEAD
85 | [1.0.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0...v1.0.1
86 | [1.0.0]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0
87 | `
88 |
89 | test('retreive entries from test (tag patern: vX.X.X)', () => {
90 | const output = getEntries(DATA_v)
91 | const versionRegex = /^\[(v[0-1]+|unreleased)/i
92 |
93 | expect(output.length).toEqual(3)
94 | expect(output[0]).toMatch(versionRegex)
95 | expect(output[1]).toMatch(versionRegex)
96 | expect(output[2]).toMatch(versionRegex)
97 | })
98 |
99 | test('retreive entries from test (tag patern: X.X.X)', () => {
100 | const output = getEntries(DATA)
101 | const versionRegex = /^\[([0-1]+|unreleased)/i
102 |
103 | expect(output.length).toEqual(3)
104 | expect(output[0]).toMatch(versionRegex)
105 | expect(output[1]).toMatch(versionRegex)
106 | expect(output[2]).toMatch(versionRegex)
107 | })
108 |
109 | // https://github.com/mindsers/changelog-reader-action/issues/8
110 | test('retreive entries from test (complex SEMVER)', () => {
111 | const output = getEntries(DATA_complex)
112 | const versionRegex =
113 | /^\[(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/
114 |
115 | expect(output.length).toEqual(4)
116 | expect(output[0]).toMatch(versionRegex)
117 | expect(output[1]).toMatch(versionRegex)
118 | expect(output[2]).toMatch(versionRegex)
119 | expect(output[3]).toMatch(versionRegex)
120 | })
121 |
--------------------------------------------------------------------------------
/src/get-entry-by-version-id.js:
--------------------------------------------------------------------------------
1 | exports.getEntryByVersionID = (versions, id) => {
2 | if (id != null) {
3 | return versions.find(version => version.id === id)
4 | }
5 |
6 | return [...versions].filter(version => !['Unreleased', 'unreleased'].includes(version.id)).shift()
7 | }
8 |
--------------------------------------------------------------------------------
/src/get-entry-by-version-id.test.js:
--------------------------------------------------------------------------------
1 | const { getEntryByVersionID } = require('./get-entry-by-version-id')
2 |
3 | test('get latest if no version provided', () => {
4 | const input = [
5 | {
6 | id: 'Unreleased',
7 | text: `blablabla`,
8 | },
9 | {
10 | id: 'v2.0.2',
11 | date: '2019-12-08',
12 | text: `blablabla`,
13 | },
14 | {
15 | id: 'v2.0.1',
16 | date: '2019-12-02',
17 | text: `blablabla`,
18 | },
19 | ]
20 | const output = getEntryByVersionID(input)
21 |
22 | expect(output.id).toEqual(input[1].id)
23 | })
24 |
25 | test('return null if bad version provided', () => {
26 | const input = [
27 | {
28 | id: 'Unreleased',
29 | text: `blablabla`,
30 | },
31 | {
32 | id: 'v2.0.2',
33 | date: '2019-12-08',
34 | text: `blablabla`,
35 | },
36 | {
37 | id: 'v2.0.1',
38 | date: '2019-12-02',
39 | text: `blablabla`,
40 | },
41 | ]
42 | const output = getEntryByVersionID(input, 'v1.2.12')
43 |
44 | expect(output).toBeUndefined()
45 | })
46 |
47 | test('support X.X.X version patern', () => {
48 | const input = [
49 | {
50 | id: 'Unreleased',
51 | text: `blablabla`,
52 | },
53 | {
54 | id: 'v2.0.2',
55 | date: '2019-12-08',
56 | text: `blablabla`,
57 | },
58 | {
59 | id: '2.0.1',
60 | date: '2019-12-02',
61 | text: `blablabla`,
62 | },
63 | {
64 | id: '1.13.2',
65 | date: '2019-12-02',
66 | text: `blablabla`,
67 | },
68 | ]
69 | const output = getEntryByVersionID(input, '2.0.1')
70 |
71 | expect(output.id).toEqual('2.0.1')
72 | })
73 |
74 | test('get the correct version', () => {
75 | const input = [
76 | {
77 | id: 'Unreleased',
78 | text: `blablabla`,
79 | },
80 | {
81 | id: 'v2.1.1',
82 | date: '2019-12-08',
83 | text: `blablabla`,
84 | },
85 | {
86 | id: 'v2.1.0',
87 | date: '2019-12-02',
88 | text: `blablabla`,
89 | },
90 | {
91 | id: 'v2.0.0',
92 | date: '2019-12-02',
93 | text: `blablabla`,
94 | },
95 | {
96 | id: 'v1.2.12',
97 | date: '2019-12-02',
98 | text: `blablabla`,
99 | },
100 | ]
101 | const output = getEntryByVersionID(input, 'v2.1.0')
102 |
103 | expect(output.id).toEqual(input[2].id)
104 | })
105 |
106 | test('get the unreleased version', () => {
107 | const input = [
108 | {
109 | id: 'Unreleased',
110 | text: `blablabla`,
111 | },
112 | {
113 | id: 'v2.1.1',
114 | date: '2019-12-08',
115 | text: `blablabla`,
116 | },
117 | {
118 | id: 'v2.1.0',
119 | date: '2019-12-02',
120 | text: `blablabla`,
121 | },
122 | {
123 | id: 'v2.0.0',
124 | date: '2019-12-02',
125 | text: `blablabla`,
126 | },
127 | {
128 | id: 'v1.2.12',
129 | date: '2019-12-02',
130 | text: `blablabla`,
131 | },
132 | ]
133 | const output = getEntryByVersionID(input, 'Unreleased')
134 |
135 | expect(output.id).toEqual(input[0].id)
136 | })
137 |
--------------------------------------------------------------------------------
/src/get-links.js:
--------------------------------------------------------------------------------
1 | const core = require('@actions/core')
2 |
3 | const linkRegex =
4 | /^\[.+\]:\s?(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/
5 | const avoidNonVersionData = text => linkRegex.test(text)
6 |
7 | exports.getLinks = rawData => {
8 | const content = String(rawData)
9 |
10 | core.debug(`CHANGELOG content: ${content}`)
11 |
12 | return content.trim().split('\n').filter(avoidNonVersionData)
13 | }
14 |
--------------------------------------------------------------------------------
/src/get-links.test.js:
--------------------------------------------------------------------------------
1 | const { getLinks } = require('./get-links')
2 |
3 | const DATA_v = `
4 | # Changelog
5 | All notable changes to this project will be documented in this file.
6 |
7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9 |
10 | ## [Unreleased]
11 | ### Added
12 | - The project has now a CHANGELOG
13 |
14 | ### Fixed
15 | - README now uses the correct version number in the examples
16 |
17 | ## [v1.0.1] - 2020-02-12
18 | ### Fixed
19 | - Remove template's old behavior
20 |
21 | ## [v1.0.0] - 2020-02-12
22 | ### Added
23 | - CHANGELOG can be parsed by the github action
24 |
25 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.1...HEAD
26 | [1.0.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0...v1.0.1
27 | [1.0.0]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0
28 | `
29 |
30 | const DATA_complex = `
31 | # Changelog
32 | All notable changes to this project will be documented in this file.
33 |
34 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
35 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
36 |
37 | ## [1.1.2+meta]
38 | ### Added
39 | - The project has now a CHANGELOG
40 |
41 | ### Fixed
42 | - README now uses the correct version number in the examples
43 |
44 | ## [1.1.1-rc.1+build.123] - 2020-02-12
45 | ### Fixed
46 | - Remove template's old behavior
47 |
48 | ## [1.1.1-DEV-SNAPSHOT] - 2020-02-12
49 | ### Added
50 | - CHANGELOG can be parsed by the github action
51 |
52 | ## [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay] - 2019-12-09
53 | ### Added
54 | - CHANGELOG can be parsed by the github action
55 |
56 | [1.1.2+meta]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.1-rc.1+build.123...1.1.2+meta
57 | [1.1.1-rc.1+build.123]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-rc.1+build.123
58 | [1.1.1-DEV-SNAPSHOT]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay...v1.1.1-DEV-SNAPSHOT
59 | [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
60 | `
61 |
62 | const DATA = `
63 | # Changelog
64 | All notable changes to this project will be documented in this file.
65 |
66 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
67 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
68 |
69 | ## [Unreleased]
70 | ### Added
71 | - The project has now a CHANGELOG
72 |
73 | ### Fixed
74 | - README now uses the correct version number in the examples
75 |
76 | ## [1.0.1] - 2020-02-12
77 | ### Fixed
78 | - Remove template's old behavior
79 |
80 | ## [1.0.0] - 2020-02-12
81 | ### Added
82 | - CHANGELOG can be parsed by the github action
83 |
84 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.1...HEAD
85 | [1.0.1]: https://github.com/mindsers/changelog-reader-action/compare/v1.0.0...v1.0.1
86 | [1.0.0]: https://github.com/mindsers/changelog-reader-action/releases/tag/v1.0.0
87 | `
88 |
89 | const linkRegex =
90 | /^\[.+\]:\s?(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/
91 |
92 | test('retreive links from test (tag patern: vX.X.X)', () => {
93 | const output = getLinks(DATA_v)
94 |
95 | expect(output.length).toEqual(3)
96 | for (const text of output) {
97 | expect(text).toMatch(linkRegex)
98 | }
99 | })
100 |
101 | test('retreive links from test (tag patern: X.X.X)', () => {
102 | const output = getLinks(DATA)
103 |
104 | expect(output.length).toEqual(3)
105 | for (const text of output) {
106 | expect(text).toMatch(linkRegex)
107 | }
108 | })
109 |
110 | // https://github.com/mindsers/changelog-reader-action/issues/8
111 | test('retreive links from test (complex SEMVER)', () => {
112 | const output = getLinks(DATA_complex)
113 |
114 | expect(output.length).toEqual(4)
115 | for (const text of output) {
116 | expect(text).toMatch(linkRegex)
117 | }
118 | })
119 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | const utils = require('util')
2 | const fs = require('fs')
3 | const core = require('@actions/core')
4 |
5 | const { validateEntry } = require('./validate-entry')
6 | const { parseEntry } = require('./parse-entry')
7 | const { getEntries } = require('./get-entries')
8 | const { getEntryByVersionID } = require('./get-entry-by-version-id')
9 | const { getLinks } = require('./get-links')
10 | const { addLinks } = require('./add-links')
11 |
12 | const readFile = utils.promisify(fs.readFile)
13 |
14 | exports.main = async function main() {
15 | try {
16 | const changelogPath = core.getInput('path') || './CHANGELOG.md'
17 | const targetVersion = core.getInput('version') || null
18 | const validationLevel = core.getInput('validation_level') || 'none'
19 |
20 | if (targetVersion == null) {
21 | core.warning(
22 | `No target version specified. Will try to return the most recent one in the changelog file.`
23 | )
24 | }
25 |
26 | // Parse data
27 | core.startGroup('Parse data')
28 | const rawData = await readFile(changelogPath)
29 | const linkList = getLinks(rawData)
30 | const versions = getEntries(rawData).map(parseEntry).map(addLinks(linkList))
31 |
32 | core.debug(`${versions.length} version logs found`)
33 | core.endGroup()
34 |
35 | // Validate data
36 | core.startGroup('Validate data')
37 | if (validationLevel === 'none') {
38 | core.info(`Validation level set to 'none'. Skipping validation.`)
39 | }
40 |
41 | if (validationLevel !== 'none') {
42 | const validationDepth = parseInt(core.getInput('validation_depth'), 10)
43 | const releasedVersions = versions.filter(version => version.status != 'unreleased')
44 | releasedVersions
45 | .reverse()
46 | .slice(Math.max(0, releasedVersions.length - validationDepth))
47 | .forEach(validateEntry(validationLevel))
48 | }
49 | core.endGroup()
50 |
51 | // Return data
52 | const entry = getEntryByVersionID(versions, targetVersion)
53 |
54 | if (entry == null) {
55 | throw new Error(
56 | `No log entry found${targetVersion != null ? ` for version ${targetVersion}` : ''}`
57 | )
58 | }
59 |
60 | core.setOutput('version', entry.id)
61 | core.setOutput('date', entry.date)
62 | core.setOutput('status', entry.status)
63 | core.setOutput('changes', entry.text)
64 | } catch (error) {
65 | core.setFailed(error.message)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/parse-entry-content.js:
--------------------------------------------------------------------------------
1 | exports.parseEntryContent = function (text) {
2 | return text
3 | .split(/^###\s*/gm)
4 | .filter(content => content.replace(/\s+/g, '') != '')
5 | .map(content => {
6 | const [type, ...items] = content.trim().split(/\r*\n/)
7 |
8 | return { type: type.toLowerCase().trim(), items }
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/parse-entry.js:
--------------------------------------------------------------------------------
1 | const { prerelease } = require('semver')
2 |
3 | exports.parseEntry = entry => {
4 | const [title, ...other] = entry.trim().split('\n')
5 |
6 | const [versionPart, datePart] = title.split(' - ')
7 | const [versionNumber] = versionPart.match(/[a-zA-Z0-9.\-+]+/)
8 | const [versionDate] = (datePart != null && datePart.match(/[0-9-]+/)) || []
9 |
10 | return {
11 | id: versionNumber,
12 | date: versionDate,
13 | status: computeStatus(versionNumber, title),
14 | text: other.filter(item => !/\[.*\]: http/.test(item)).join('\n'),
15 | }
16 | }
17 |
18 | function computeStatus(version, title) {
19 | if (prerelease(version)) {
20 | return 'prereleased'
21 | }
22 |
23 | if (title.match(/\[yanked\]/i)) {
24 | return 'yanked'
25 | }
26 |
27 | if (title.match(/\[unreleased\]/i)) {
28 | return 'unreleased'
29 | }
30 |
31 | return 'released'
32 | }
33 |
--------------------------------------------------------------------------------
/src/parse-entry.test.js:
--------------------------------------------------------------------------------
1 | const { parseEntry } = require('./parse-entry')
2 |
3 | const entryDescription = `
4 | ### Added
5 | - New attribute isOrdered on TextFieldList. Basicaly, becomes when not ordered.
6 | - It is possible to change the row count of the TextField in TextFieldList.
7 | - New attribute maxFieldCount on TextFieldList. It hides the add button when the value of maxFieldCount is reached.
8 | - New DatePickerField component to display a date input.
9 | - New attribute isSmall on TextField to display a smaller version of he TextField.
10 |
11 | ### Changed
12 | - All form components can be updated using the value attribute.
13 | - **BREACKING CHANGES** The default behavior of TextFieldList is to be "not ordered".
14 | You'll have to add the new isOrdered attribute on your existing components.
15 | - FormErrors become FormErrorMessages.
16 | - FormErrorMessages try to translate the error key automatically.
17 | - ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.
18 | - FormErrorMessages filter error that are equal to false
19 | - If an error is equal to en object instead of true, the object is passed to the translator as variables by FormErrorMessages.
20 | - RadioButton and CheckboxButton support other type than string for their children attribute.
21 |
22 | ### Fixed
23 | - Update types to allow no theme data into ThemeProvider.
24 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.
25 | `
26 |
27 | test('get readable data from text entry', () => {
28 | const input = `
29 | ## [v0.0.26] - 2019-02-10
30 | ${entryDescription}
31 | `
32 | const output = parseEntry(input)
33 |
34 | expect(output.id).toEqual('v0.0.26')
35 | expect(output.date).toEqual('2019-02-10')
36 | expect(output.status).toEqual('released')
37 | expect(output.text).toContain(`### Added`)
38 | expect(output.text).toContain(
39 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
40 | )
41 | expect(output.text).toContain(
42 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
43 | )
44 | })
45 |
46 | test('get readable data from text entry | no title for version', () => {
47 | const input = `
48 | [v0.0.26] - 2019-02-10
49 | ${entryDescription}
50 | `
51 | const output = parseEntry(input)
52 |
53 | expect(output.id).toEqual('v0.0.26')
54 | expect(output.date).toEqual('2019-02-10')
55 | expect(output.status).toEqual('released')
56 | expect(output.text).toContain(`### Added`)
57 | expect(output.text).toContain(
58 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
59 | )
60 | expect(output.text).toContain(
61 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
62 | )
63 | })
64 |
65 | test('get readable data from text entry | version patern X.X.X', () => {
66 | const input = `
67 | [0.0.26] - 2019-02-10
68 | ${entryDescription}
69 | `
70 | const output = parseEntry(input)
71 |
72 | expect(output.id).toEqual('0.0.26')
73 | expect(output.date).toEqual('2019-02-10')
74 | expect(output.status).toEqual('released')
75 | expect(output.text).toContain(`### Added`)
76 | expect(output.text).toContain(
77 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
78 | )
79 | expect(output.text).toContain(
80 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
81 | )
82 | })
83 |
84 | test('get readable data from text entry | raw version number', () => {
85 | const input = `
86 | v0.0.26 - 2019-02-10
87 | ${entryDescription}
88 | `
89 | const output = parseEntry(input)
90 |
91 | expect(output.id).toEqual('v0.0.26')
92 | expect(output.date).toEqual('2019-02-10')
93 | expect(output.status).toEqual('released')
94 | expect(output.text).toContain(`### Added`)
95 | expect(output.text).toContain(
96 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
97 | )
98 | expect(output.text).toContain(
99 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
100 | )
101 | })
102 |
103 | // https://github.com/mindsers/changelog-reader-action/issues/4
104 | test('get readable data from text entry | unreleased version', () => {
105 | const input = `
106 | ## [Unreleased]
107 | ${entryDescription}
108 | `
109 | const output = parseEntry(input)
110 |
111 | expect(output.id).toEqual('Unreleased')
112 | expect(output.date).toBeUndefined()
113 | expect(output.status).toEqual('unreleased')
114 | expect(output.text).toContain(`### Added`)
115 | expect(output.text).toContain(
116 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
117 | )
118 | expect(output.text).toContain(
119 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
120 | )
121 | })
122 |
123 | // https://github.com/mindsers/changelog-reader-action/issues/8
124 | test('get readable data from text entry | unreleased version', () => {
125 | const input = `
126 | ## [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay] - 2019-02-10
127 | ${entryDescription}
128 | `
129 | const output = parseEntry(input)
130 |
131 | expect(output.id).toEqual('1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay')
132 | expect(output.date).toEqual('2019-02-10')
133 | expect(output.status).toEqual('prereleased')
134 | expect(output.text).toContain(`### Added`)
135 | expect(output.text).toContain(
136 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
137 | )
138 | expect(output.text).toContain(
139 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
140 | )
141 | })
142 |
143 | test('get readable data from text entry | unexpected characters between version and date', () => {
144 | const input = `
145 | ## [1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay] |+. - \\\\ 2019-02-10
146 | ${entryDescription}
147 | `
148 | const output = parseEntry(input)
149 |
150 | expect(output.id).toEqual('1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay')
151 | expect(output.date).toEqual('2019-02-10')
152 | expect(output.status).toEqual('prereleased')
153 | expect(output.text).toContain(`### Added`)
154 | expect(output.text).toContain(
155 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
156 | )
157 | expect(output.text).toContain(
158 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
159 | )
160 | })
161 |
162 | // https://github.com/mindsers/changelog-reader-action/issues/10
163 | test('get readable data from text entry | Fake date', () => {
164 | const input = `
165 | ## [1.0.0] - SomeWords instead of a Date
166 | ${entryDescription}
167 | `
168 | const output = parseEntry(input)
169 |
170 | expect(output.id).toEqual('1.0.0')
171 | expect(output.date).toBeUndefined()
172 | expect(output.status).toEqual('released')
173 | expect(output.text).toContain(`### Added`)
174 | expect(output.text).toContain(
175 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
176 | )
177 | expect(output.text).toContain(
178 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
179 | )
180 | })
181 |
182 | test('get readable data from text entry | build number with plus sign', () => {
183 | const input = `
184 | ## [2.0.0+build.1848] - SomeWords instead of a Date
185 | ${entryDescription}
186 | `
187 | const output = parseEntry(input)
188 |
189 | expect(output.id).toEqual('2.0.0+build.1848')
190 | expect(output.date).toBeUndefined()
191 | expect(output.status).toEqual('released')
192 | expect(output.text).toContain(`### Added`)
193 | expect(output.text).toContain(
194 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
195 | )
196 | expect(output.text).toContain(
197 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
198 | )
199 | })
200 |
201 | test('get readable data from text entry | yanked release', () => {
202 | const input = `
203 | ## [2.0.0] - 2019-02-10 [YANKED]
204 | ${entryDescription}
205 | `
206 | const output = parseEntry(input)
207 |
208 | expect(output.id).toEqual('2.0.0')
209 | expect(output.date).toEqual('2019-02-10')
210 | expect(output.status).toEqual('yanked')
211 | expect(output.text).toContain(`### Added`)
212 | expect(output.text).toContain(
213 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
214 | )
215 | expect(output.text).toContain(
216 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
217 | )
218 | })
219 |
220 | test('get readable data from text entry | yanked release with no date', () => {
221 | const input = `
222 | ## [2.0.0] [YANKED]
223 | ${entryDescription}
224 | `
225 | const output = parseEntry(input)
226 |
227 | expect(output.id).toEqual('2.0.0')
228 | expect(output.date).toBeUndefined()
229 | expect(output.status).toEqual('yanked')
230 | expect(output.text).toContain(`### Added`)
231 | expect(output.text).toContain(
232 | `ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.`
233 | )
234 | expect(output.text).toContain(
235 | `**SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`
236 | )
237 | })
238 |
--------------------------------------------------------------------------------
/src/rules/has-chronological-order.js:
--------------------------------------------------------------------------------
1 | const { lt, valid } = require('semver')
2 |
3 | exports.hasChronologicalOrder = function (entries, currentIndex) {
4 | const currentEntry = entries[currentIndex]
5 | const previousEntry = entries[currentIndex - 1]
6 |
7 | if (previousEntry == null) {
8 | return {}
9 | }
10 |
11 | if (!valid(previousEntry.id) || !valid(currentEntry.id)) {
12 | return {}
13 | }
14 |
15 | if (lt(previousEntry.id, currentEntry.id)) {
16 | return {}
17 | }
18 |
19 | return {
20 | 'has-chronological-order': {
21 | previous: previousEntry.id,
22 | current: currentEntry.id,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/rules/has-chronological-order.test.js:
--------------------------------------------------------------------------------
1 | const { hasChronologicalOrder } = require('./has-chronological-order')
2 |
3 | test('should not throw error', () => {
4 | const outputError = () => hasChronologicalOrder([{ id: '1.0.1' }, { id: '1.0.0' }], 1)
5 | const output = () => hasChronologicalOrder([{ id: '1.0.0' }, { id: '2.0.0' }], 1)
6 |
7 | expect(output).not.toThrow()
8 | expect(outputError).not.toThrow()
9 | })
10 |
11 | test('should return error when not chronological order', () => {
12 | const output = hasChronologicalOrder([{ id: '2.0.0' }, { id: '1.0.0' }], 1)
13 |
14 | expect(output['has-chronological-order']).toBeTruthy()
15 | })
16 |
17 | test('should not return error when chronological order', () => {
18 | const output = hasChronologicalOrder([{ id: '1.0.0' }, { id: '2.0.0' }], 1)
19 |
20 | expect(output['has-chronological-order']).toBeFalsy()
21 | })
22 |
--------------------------------------------------------------------------------
/src/rules/has-correct-sections.js:
--------------------------------------------------------------------------------
1 | const { diff, valid } = require('semver')
2 |
3 | const { parseEntryContent } = require('../parse-entry-content')
4 |
5 | exports.hasCorrectSections = function (entries, currentIndex) {
6 | const currentEntry = entries[currentIndex]
7 | const previousEntry = entries[currentIndex - 1]
8 |
9 | if (previousEntry == null) {
10 | return {}
11 | }
12 |
13 | if (!valid(previousEntry.id) || !valid(currentEntry.id)) {
14 | return {}
15 | }
16 |
17 | const entryTypes = parseEntryContent(currentEntry.changes || currentEntry.text).map(
18 | change => change.type
19 | )
20 | const allowedTypes = getAllowedTypes(previousEntry.id, currentEntry.id)
21 |
22 | if (entryTypes.some(type => allowedTypes.indexOf(type) === -1)) {
23 | // Validates that only certain allowed types are in the change set
24 | return {
25 | 'has-correct-sections': {
26 | entryID: currentEntry.id,
27 | types: allowedTypes,
28 | },
29 | }
30 | }
31 |
32 | return {}
33 | }
34 |
35 | function getAllowedTypes(v1, v2) {
36 | const versionDiff = diff(v1, v2)
37 |
38 | switch (versionDiff) {
39 | case 'prepatch':
40 | case 'patch':
41 | return ['fixed', 'security']
42 | case 'minor':
43 | case 'preminor':
44 | return ['added', 'changed', 'deprecated', 'fixed', 'security']
45 | case 'premajor':
46 | case 'major':
47 | default:
48 | return ['added', 'removed', 'changed', 'deprecated', 'fixed', 'security']
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/rules/has-correct-sections.test.js:
--------------------------------------------------------------------------------
1 | const { hasCorrectSections } = require('./has-correct-sections')
2 |
3 | const entryDescriptionMajor = `
4 | ### Added
5 | - New attribute isOrdered on TextFieldList. Basicaly, becomes when not ordered.
6 | - It is possible to change the row count of the TextField in TextFieldList.
7 | - New attribute maxFieldCount on TextFieldList. It hides the add button when the value of maxFieldCount is reached.
8 | - New DatePickerField component to display a date input.
9 | - New attribute isSmall on TextField to display a smaller version of he TextField.
10 |
11 | ### Removed
12 | - Legacy DatePicker library.
13 |
14 | ### Changed
15 | - All form components can be updated using the value attribute.
16 | - **BREACKING CHANGES** The default behavior of TextFieldList is to be "not ordered".
17 | You'll have to add the new isOrdered attribute on your existing components.
18 | - FormErrors become FormErrorMessages.
19 | - FormErrorMessages try to translate the error key automatically.
20 | - ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.
21 | - FormErrorMessages filter error that are equal to false
22 | - If an error is equal to en object instead of true, the object is passed to the translator as variables by FormErrorMessages.
23 | - RadioButton and CheckboxButton support other type than string for their children attribute.
24 |
25 | ### Fixed
26 | - Update types to allow no theme data into ThemeProvider.
27 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.
28 | `
29 |
30 | test('should not throw error', () => {
31 | const outputError = () =>
32 | hasCorrectSections(
33 | [
34 | { id: '1.0.0', changes: entryDescriptionMajor },
35 | { id: '1.0.1', changes: entryDescriptionMajor },
36 | ],
37 | 1
38 | )
39 | const output = () =>
40 | hasCorrectSections(
41 | [
42 | { id: '1.0.0', changes: entryDescriptionMajor },
43 | { id: '2.0.0', changes: entryDescriptionMajor },
44 | ],
45 | 1
46 | )
47 |
48 | expect(output).not.toThrow()
49 | expect(outputError).not.toThrow()
50 | })
51 |
52 | test('should return error when added section in patch release', () => {
53 | const output = hasCorrectSections(
54 | [
55 | { id: '1.0.0', changes: entryDescriptionMajor },
56 | { id: '1.0.1', changes: entryDescriptionMajor },
57 | ],
58 | 1
59 | )
60 |
61 | expect(output['has-correct-sections']).toBeTruthy()
62 | })
63 |
64 | test('should return error when removed section in minor release should throw error', () => {
65 | const output = hasCorrectSections(
66 | [
67 | { id: '1.0.0', changes: entryDescriptionMajor },
68 | { id: '1.1.0', changes: entryDescriptionMajor },
69 | ],
70 | 1
71 | )
72 |
73 | expect(output['has-correct-sections']).toBeTruthy()
74 | })
75 |
--------------------------------------------------------------------------------
/src/rules/has-sections.js:
--------------------------------------------------------------------------------
1 | const { parseEntryContent } = require('../parse-entry-content')
2 |
3 | exports.hasSections = function (entry) {
4 | const changes = parseEntryContent(entry.changes || entry.text)
5 |
6 | for (const change of changes) {
7 | if (change.items.length > 0) {
8 | // Validate that there are changes listed under each section
9 | continue
10 | }
11 |
12 | return {
13 | 'has-section': {
14 | type: change.type,
15 | entryID: entry.id,
16 | },
17 | }
18 | }
19 |
20 | return {}
21 | }
22 |
--------------------------------------------------------------------------------
/src/rules/has-sections.test.js:
--------------------------------------------------------------------------------
1 | const { hasSections } = require('./has-sections')
2 |
3 | test('should not throw error', () => {
4 | const outputError = () => hasSections({ id: '1.0.0', changes: '### Added\r\n' })
5 | const output = () =>
6 | hasSections({
7 | id: '1.0.0',
8 | changes: `### Fixed
9 | - Update types to allow no theme data into ThemeProvider.
10 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`,
11 | })
12 |
13 | expect(outputError).not.toThrow()
14 | expect(output).not.toThrow()
15 | })
16 |
17 | test('should return error when no listed changes under the heading', () => {
18 | const output = hasSections({ id: '1.0.0', changes: '### Added\r\n' })
19 |
20 | expect(output['has-section']).toBeTruthy()
21 | })
22 |
23 | test('should not return error when listed changes under the heading', () => {
24 | const output = hasSections({
25 | id: '1.0.0',
26 | changes: `### Fixed
27 | - Update types to allow no theme data into ThemeProvider.
28 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.`,
29 | })
30 |
31 | expect(output['has-section']).toBeFalsy()
32 | })
33 |
--------------------------------------------------------------------------------
/src/rules/is-semver.js:
--------------------------------------------------------------------------------
1 | const { valid } = require('semver')
2 |
3 | exports.isSemVer = function (entry) {
4 | if (valid(entry.id)) {
5 | return {}
6 | }
7 |
8 | return {
9 | 'is-semver': true,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/rules/is-semver.test.js:
--------------------------------------------------------------------------------
1 | const { isSemVer } = require('./is-semver')
2 |
3 | test('should not throw error', () => {
4 | const outputError = () => isSemVer({ id: 'a.b.c' })
5 | const outputNoError = () => isSemVer({ id: '2.0.0' })
6 |
7 | expect(outputError).not.toThrow()
8 | expect(outputNoError).not.toThrow()
9 | })
10 |
11 | test('should return error on version that is not semantic', () => {
12 | const output = isSemVer({ id: 'a.b.c' })
13 |
14 | expect(output['is-semver']).toBeTruthy()
15 | })
16 |
17 | test('should not return error on version that is not semantic', () => {
18 | const output = isSemVer({ id: '2.0.0' })
19 |
20 | expect(output['is-semver']).toBeFalsy()
21 | })
22 |
--------------------------------------------------------------------------------
/src/validate-entry.js:
--------------------------------------------------------------------------------
1 | const core = require('@actions/core')
2 |
3 | const { hasChronologicalOrder } = require('./rules/has-chronological-order')
4 | const { hasCorrectSections } = require('./rules/has-correct-sections')
5 | const { hasSections } = require('./rules/has-sections')
6 | const { isSemVer } = require('./rules/is-semver')
7 |
8 | exports.validateEntry = validationLevel => (entry, index, entries) => {
9 | if (entry.status == 'unreleased') return // no validation on unreleased versions
10 |
11 | const validationResults = {
12 | ...isSemVer(entry),
13 | ...hasChronologicalOrder(entries, index),
14 | ...hasSections(entry),
15 | ...hasCorrectSections(entries, index),
16 | }
17 |
18 | const errors = Object.keys(validationResults)
19 | .filter(key => validationResults[key] != false)
20 | .map(key => {
21 | if (key === 'is-semver') {
22 | return new Error(`${entry.id} is not a valid semantic version.`)
23 | }
24 |
25 | if (key === 'has-chronological-order') {
26 | const { current, previous } = validationResults[key]
27 |
28 | return new Error(
29 | `Changelog versions out of order. Version ${current} cannot come after ${previous}.`
30 | )
31 | }
32 |
33 | if (key === 'has-section') {
34 | const { type, entryID } = validationResults[key]
35 |
36 | return new Error(
37 | `The '${type}' section under version ${entryID} does not contain any listed changes under the heading.`
38 | )
39 | }
40 |
41 | if (key === 'has-correct-sections') {
42 | const { entryID, types } = validationResults[key]
43 |
44 | return new Error(
45 | `Only '${types.join(', ')}' section${
46 | types.length == 1 ? '' : 's'
47 | } are allowed for version ${entryID}.`
48 | )
49 | }
50 | })
51 |
52 | const shouldBreakTheBuild = validationLevel === 'error'
53 | const log = shouldBreakTheBuild ? core.error : core.warning
54 | for (const error of errors) {
55 | log(error)
56 | }
57 |
58 | if (errors.length > 0 && shouldBreakTheBuild) {
59 | throw new AggregateError(errors, `${entry.id} entry is invalid.`)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/validate-entry.test.js:
--------------------------------------------------------------------------------
1 | const { validateEntry } = require('./validate-entry')
2 |
3 | const entryDescriptionMajor = `
4 | ### Added
5 | - New attribute isOrdered on TextFieldList. Basicaly, becomes when not ordered.
6 | - It is possible to change the row count of the TextField in TextFieldList.
7 | - New attribute maxFieldCount on TextFieldList. It hides the add button when the value of maxFieldCount is reached.
8 | - New DatePickerField component to display a date input.
9 | - New attribute isSmall on TextField to display a smaller version of he TextField.
10 |
11 | ### Removed
12 | - Legacy DatePicker library.
13 |
14 | ### Changed
15 | - All form components can be updated using the value attribute.
16 | - **BREACKING CHANGES** The default behavior of TextFieldList is to be "not ordered".
17 | You'll have to add the new isOrdered attribute on your existing components.
18 | - FormErrors become FormErrorMessages.
19 | - FormErrorMessages try to translate the error key automatically.
20 | - ThemeProvider doesn't loads the font anymore. We created a more generic component (UIKitInitializer) that'll do it.
21 | - FormErrorMessages filter error that are equal to false
22 | - If an error is equal to en object instead of true, the object is passed to the translator as variables by FormErrorMessages.
23 | - RadioButton and CheckboxButton support other type than string for their children attribute.
24 |
25 | ### Fixed
26 | - Update types to allow no theme data into ThemeProvider.
27 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.
28 | `
29 |
30 | const entryDescriptionMinor = `
31 | ### Added
32 | - New attribute isOrdered on TextFieldList. Basicaly, becomes when not ordered.
33 | - It is possible to change the row count of the TextField in TextFieldList.
34 | - New attribute maxFieldCount on TextFieldList. It hides the add button when the value of maxFieldCount is reached.
35 | - New DatePickerField component to display a date input.
36 | - New attribute isSmall on TextField to display a smaller version of he TextField.
37 |
38 | ### Fixed
39 | - Update types to allow no theme data into ThemeProvider.
40 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.
41 | `
42 |
43 | const entryDescriptionPatch = `
44 | ### Fixed
45 | - Update types to allow no theme data into ThemeProvider.
46 | - **SECURITY** The list components don't use the nth-child CSS attributes in favor of nth-of-type.
47 | `
48 |
49 | test('validate multiple versions', () => {
50 | const input = [
51 | { id: '1.0.0', changes: entryDescriptionMajor },
52 | { id: '1.0.1', changes: entryDescriptionPatch },
53 | { id: '1.1.0', changes: entryDescriptionMinor },
54 | { id: '2.0.0', changes: entryDescriptionMajor },
55 | ]
56 | const output = () => input.forEach(validateEntry('error'))
57 |
58 | expect(output).not.toThrow()
59 | })
60 |
61 | test('throw error on error', () => {
62 | const input = [
63 | { id: '1.0.0', changes: entryDescriptionMajor },
64 | { id: '1.0.1', changes: entryDescriptionPatch },
65 | { id: 'a.b.c', changes: entryDescriptionMinor },
66 | { id: '2.0.0', changes: entryDescriptionMajor },
67 | ]
68 | const output = () => input.forEach(validateEntry('error'))
69 |
70 | expect(output).toThrow()
71 | })
72 |
73 | test('not throw error on error [warn]', () => {
74 | const input = [
75 | { id: '1.0.0', changes: entryDescriptionMajor },
76 | { id: '1.0.1', changes: entryDescriptionPatch },
77 | { id: 'a.b.c', changes: entryDescriptionMinor },
78 | { id: '2.0.0', changes: entryDescriptionMajor },
79 | ]
80 | const output = () => input.forEach(validateEntry('warn'))
81 |
82 | expect(output).not.toThrow()
83 | })
84 |
--------------------------------------------------------------------------------