diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..9f70d20 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,22 @@ +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +jobs: + create-release: + name: "Create Release" + runs-on: "ubuntu-latest" + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Read Changelog + uses: ./ + id: read + - name: Release + uses: softprops/action-gh-release@v1 + with: + body: ${{ steps.read.outputs.changelog }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6704566..8d056c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,104 +1,2 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories +.vscode/ node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b74bd83 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# [1.0.0]() (2021-04-30) +## Initial release diff --git a/README.md b/README.md index f8db737..aa73173 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,68 @@ -# snapaddy-changelog-parser -Github action to parse changelog to create automatic Github releases +# snapADDY Changelog Parser + +A Github Action to parse manually created changelogs for automatic Github Releases. + +This action reads a markdown changelog file, and provides the latest versions release notes to steps further down the pipeline. + +### Simple example: +```yaml +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +jobs: + tagged-release: + name: "Tagged Release" + runs-on: "ubuntu-latest" + + steps: + - name: Read Changelog + uses: snapADDY/snapaddy-parse-changelog@1.0.0 + id: read + - name: Release + uses: softprops/action-gh-release@v1 + with: + body: ${{ steps.read.outputs.changelog }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +--- + +## Required changelog formatting: +Each section hast to start with a `#` followed by a space (markdown H1) and a semver formatted version number. Brackets around the version number are also supported, in case it's a hyperlink. +```markdown +# 1.0.0 +or +# [1.0.0](https://) +``` + +### Example: +This input: +```markdown +# [1.0.0](https://github.com/...) (2021-04-30) +## Features +- CI Magic 🧙‍♂️ + +# [0.1.0](https://github.com/...) (2021-04-27) +## Fixes +- more notes +``` +would return: +```markdown +## Features +- CI Magic 🧙‍♂️ +``` + +--- + +### Input +| Name | Type | Description | Default | +|---|---|---|---| +| changelog-path | string | Path to the changelog file | `CHANGELOG.md` | + +### Output +| Name | Type | Description | +|---|---|---| +| changelog | string | | \ No newline at end of file diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..f452391 --- /dev/null +++ b/action.yml @@ -0,0 +1,17 @@ +name: 'snapADDY Changelog Parser' +author : 'snapADDY GmbH' +description: 'Extract latest entry from markdown formatted changelog' +branding: + icon: 'file-text' + color: 'green' +inputs: + changelog-path: + description: 'path to changelog file' + required: true + default: 'CHANGELOG.md' +outputs: + changelog: + description: 'The changelog for the given tag' +runs: + using: 'node12' + main: 'dist/index.js' \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..1841496 --- /dev/null +++ b/dist/index.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={351:function(e,t,r){var n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r in e)if(Object.hasOwnProperty.call(e,r))t[r]=e[r];t["default"]=e;return t};Object.defineProperty(t,"__esModule",{value:true});const o=n(r(87));const i=r(278);function issueCommand(e,t,r){const n=new Command(e,t,r);process.stdout.write(n.toString()+o.EOL)}t.issueCommand=issueCommand;function issue(e,t=""){issueCommand(e,{},t)}t.issue=issue;const s="::";class Command{constructor(e,t,r){if(!e){e="missing.command"}this.command=e;this.properties=t;this.message=r}toString(){let e=s+this.command;if(this.properties&&Object.keys(this.properties).length>0){e+=" ";let t=true;for(const r in this.properties){if(this.properties.hasOwnProperty(r)){const n=this.properties[r];if(n){if(t){t=false}else{e+=","}e+=`${r}=${escapeProperty(n)}`}}}}e+=`${s}${escapeData(this.message)}`;return e}}function escapeData(e){return i.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function escapeProperty(e){return i.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}},186:function(e,t,r){var n=this&&this.__awaiter||function(e,t,r,n){function adopt(e){return e instanceof r?e:new r((function(t){t(e)}))}return new(r||(r=Promise))((function(r,o){function fulfilled(e){try{step(n.next(e))}catch(e){o(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){o(e)}}function step(e){e.done?r(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r in e)if(Object.hasOwnProperty.call(e,r))t[r]=e[r];t["default"]=e;return t};Object.defineProperty(t,"__esModule",{value:true});const i=r(351);const s=r(717);const u=r(278);const a=o(r(87));const c=o(r(622));var l;(function(e){e[e["Success"]=0]="Success";e[e["Failure"]=1]="Failure"})(l=t.ExitCode||(t.ExitCode={}));function exportVariable(e,t){const r=u.toCommandValue(t);process.env[e]=r;const n=process.env["GITHUB_ENV"]||"";if(n){const t="_GitHubActionsFileCommandDelimeter_";const n=`${e}<<${t}${a.EOL}${r}${a.EOL}${t}`;s.issueCommand("ENV",n)}else{i.issueCommand("set-env",{name:e},r)}}t.exportVariable=exportVariable;function setSecret(e){i.issueCommand("add-mask",{},e)}t.setSecret=setSecret;function addPath(e){const t=process.env["GITHUB_PATH"]||"";if(t){s.issueCommand("PATH",e)}else{i.issueCommand("add-path",{},e)}process.env["PATH"]=`${e}${c.delimiter}${process.env["PATH"]}`}t.addPath=addPath;function getInput(e,t){const r=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!r){throw new Error(`Input required and not supplied: ${e}`)}return r.trim()}t.getInput=getInput;function setOutput(e,t){process.stdout.write(a.EOL);i.issueCommand("set-output",{name:e},t)}t.setOutput=setOutput;function setCommandEcho(e){i.issue("echo",e?"on":"off")}t.setCommandEcho=setCommandEcho;function setFailed(e){process.exitCode=l.Failure;error(e)}t.setFailed=setFailed;function isDebug(){return process.env["RUNNER_DEBUG"]==="1"}t.isDebug=isDebug;function debug(e){i.issueCommand("debug",{},e)}t.debug=debug;function error(e){i.issue("error",e instanceof Error?e.toString():e)}t.error=error;function warning(e){i.issue("warning",e instanceof Error?e.toString():e)}t.warning=warning;function info(e){process.stdout.write(e+a.EOL)}t.info=info;function startGroup(e){i.issue("group",e)}t.startGroup=startGroup;function endGroup(){i.issue("endgroup")}t.endGroup=endGroup;function group(e,t){return n(this,void 0,void 0,(function*(){startGroup(e);let r;try{r=yield t()}finally{endGroup()}return r}))}t.group=group;function saveState(e,t){i.issueCommand("save-state",{name:e},t)}t.saveState=saveState;function getState(e){return process.env[`STATE_${e}`]||""}t.getState=getState},717:function(e,t,r){var n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r in e)if(Object.hasOwnProperty.call(e,r))t[r]=e[r];t["default"]=e;return t};Object.defineProperty(t,"__esModule",{value:true});const o=n(r(747));const i=n(r(87));const s=r(278);function issueCommand(e,t){const r=process.env[`GITHUB_${e}`];if(!r){throw new Error(`Unable to find environment variable for file command ${e}`)}if(!o.existsSync(r)){throw new Error(`Missing file at path: ${r}`)}o.appendFileSync(r,`${s.toCommandValue(t)}${i.EOL}`,{encoding:"utf8"})}t.issueCommand=issueCommand},278:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});function toCommandValue(e){if(e===null||e===undefined){return""}else if(typeof e==="string"||e instanceof String){return e}return JSON.stringify(e)}t.toCommandValue=toCommandValue},747:e=>{e.exports=require("fs")},87:e=>{e.exports=require("os")},622:e=>{e.exports=require("path")}};var t={};function __nccwpck_require__(r){var n=t[r];if(n!==undefined){return n.exports}var o=t[r]={exports:{}};var i=true;try{e[r].call(o.exports,o,o.exports,__nccwpck_require__);i=false}finally{if(i)delete t[r]}return o.exports}(()=>{__nccwpck_require__.r=e=>{if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(e,"__esModule",{value:true})}})();if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r={};(()=>{__nccwpck_require__.r(r);var e=__nccwpck_require__(186);var t=__nccwpck_require__(747);const n=require("readline");var o=undefined&&undefined.__awaiter||function(e,t,r,n){function adopt(e){return e instanceof r?e:new r((function(t){t(e)}))}return new(r||(r=Promise))((function(r,o){function fulfilled(e){try{step(n.next(e))}catch(e){o(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){o(e)}}function step(e){e.done?r(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var i=undefined&&undefined.__generator||function(e,t){var r={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},n,o,i,s;return s={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(s[Symbol.iterator]=function(){return this}),s;function verb(e){return function(t){return step([e,t])}}function step(s){if(n)throw new TypeError("Generator is already executing.");while(r)try{if(n=1,o&&(i=s[0]&2?o["return"]:s[0]?o["throw"]||((i=o["return"])&&i.call(o),0):o.next)&&!(i=i.call(o,s[1])).done)return i;if(o=0,i)s=[s[0]&2,i.value];switch(s[0]){case 0:case 1:i=s;break;case 4:r.label++;return{value:s[1],done:false};case 5:r.label++;o=s[1];s=[0];continue;case 7:s=r.ops.pop();r.trys.pop();continue;default:if(!(i=r.trys,i=i.length>0&&i[i.length-1])&&(s[0]===6||s[0]===2)){r=0;continue}if(s[0]===3&&(!i||s[1]>i[0]&&s[1]1){return[3,5]}}l.push(d);i.label=4;case 4:return[3,2];case 5:return[3,12];case 6:m=i.sent();r={error:m};return[3,12];case 7:i.trys.push([7,,10,11]);if(!(p&&!p.done&&(u=f.return)))return[3,9];return[4,u.call(f)];case 8:i.sent();i.label=9;case 9:return[3,11];case 10:if(r)throw r.error;return[7];case 11:return[7];case 12:return[2,l.slice(1).join("\n").trim()]}}))}))}run()})();module.exports=r})(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c2dcb74 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,31 @@ +{ + "name": "snapaddy-changelog-parser", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@actions/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.7.tgz", + "integrity": "sha512-kzLFD5BgEvq6ubcxdgPbRKGD2Qrgya/5j+wh4LZzqT915I0V3rED+MvjH6NXghbvk1MXknpNNQ3uKjXSEN00Ig==" + }, + "@types/node": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", + "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==", + "dev": true + }, + "@vercel/ncc": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.28.4.tgz", + "integrity": "sha512-vQe8WuBMiBgJbRM9TXMSb2zXmaoplH84K91nd2CmIlrXH0F3RjyiO9kdvaZbKbAQ66Mh/hMF2JPtDzbVvsx+Eg==", + "dev": true + }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7a648df --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "snapaddy-changelog-parser", + "version": "1.0.0", + "description": "Parser for CHANGELOG.md to create automatic Github Releases", + "main": "index.ts", + "scripts": { + "build": "npx ncc build -m src/index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/snapADDY/snapaddy-changelog-parser.git" + }, + "author": "snapADDY GmbH", + "license": "GNU GPL v3", + "bugs": { + "url": "https://github.com/snapADDY/snapaddy-changelog-parser/issues" + }, + "homepage": "https://github.com/snapADDY/snapaddy-changelog-parser#readme", + "dependencies": { + "@actions/core": "^1.2.7" + }, + "devDependencies": { + "@types/node": "^15.0.1", + "@vercel/ncc": "^0.28.4", + "typescript": "^4.2.4" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7ed4a79 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,38 @@ +import * as core from '@actions/core'; +import * as fs from 'fs'; +import * as readline from 'readline'; + +async function run(): Promise { + try { + const filepath = core.getInput('changelog-path'); + const changelog = await readChangelog(filepath); + core.setOutput('changelog', changelog); + + } catch (error) { + core.setFailed(error.message); + } +} + +async function readChangelog(filepath: string): Promise { + const fileStream = fs.createReadStream(filepath); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + + let versionCounter = 0; + const changelogLines: Array = []; + + for await (const line of rl) { + if (/^# \[?[0-9]+\.[0-9]+\.[0-9]+\]?/.test(line)) { + versionCounter++; + if (versionCounter > 1) { + break; + } + } + changelogLines.push(line); + } + return changelogLines.slice(1).join('\n').trim(); +} + +run(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fd7db0d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} \ No newline at end of file