diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ff49a95 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: Continuous integration icons + +on: + push: + branches: + - master + paths: + - icons/** + +jobs: + create-release: + if: github.repository == 'lucide-icons/lucide' + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Create new version + id: new-version + run: echo "::set-output name=NEW_VERSION::$(.github/workflows/version-up.sh --patch)" + + - name: Create change log + id: change-log + run: echo "::set-output name=CHANGE_LOG::$(yarn generate:changelog)" + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ steps.new-version.outputs.NEW_VERSION }} + release_name: Release ${{ github.ref }} + body: ${{ steps.change-log.outputs.CHANGE_LOG }} + diff --git a/.github/workflows/font.yml b/.github/workflows/font.yml index 4cf8305..5520dee 100644 --- a/.github/workflows/font.yml +++ b/.github/workflows/font.yml @@ -2,6 +2,7 @@ name: Build Lucide on: push: branches: [ master ] + pull_request: branches: [ master ] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47fd577..1a1eb34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,10 +28,6 @@ jobs: needs: pre-build steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '12.x' - - uses: actions/cache@v2 id: yarn-cache with: @@ -59,13 +55,10 @@ jobs: - name: Publish run: yarn workspace lucide publish - - name: Commit package.json - run: | - git add packages/lucide/package.json - git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \ - commit -m ":package: Bump version lucide to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet - git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git - git push origin HEAD:master + - name: Upload package.json + uses: actions/upload-artifact@v2 + with: + path: packages/lucide/package.json lucide-react: if: github.repository == 'lucide-icons/lucide' @@ -73,10 +66,6 @@ jobs: needs: pre-build steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '12.x' - - uses: actions/cache@v2 id: yarn-cache with: @@ -104,13 +93,10 @@ jobs: - name: Publish run: yarn workspace lucide-react publish - - name: Commit package.json - run: | - git add packages/lucide-react/package.json - git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \ - commit -m ":package: Bump version lucide-react to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet - git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git - git push origin HEAD:master + - name: Upload package.json + uses: actions/upload-artifact@v2 + with: + path: packages/lucide-react/package.json lucide-vue: if: github.repository == 'lucide-icons/lucide' @@ -149,10 +135,23 @@ jobs: - name: Publish run: yarn workspace lucide-vue publish - - name: Commit package.json - run: | - git add packages/lucide-vue/package.json - git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \ - commit -m ":package: Bump version lucide-vue to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet - git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git - git push origin HEAD:master + - name: Upload package.json + uses: actions/upload-artifact@v2 + with: + path: packages/lucide-vue/package.json + + upload-package-jsons: + if: github.repository == 'lucide-icons/lucide' + runs-on: ubuntu-latest + needs: [lucide, lucide-react, lucide-vue] + + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2 + - name: Commit package.jsons + run: | + git add packages/*/package.json + git -c user.name="Lucide Bot" -c user.email="lucide-bot@users.noreply.github.com" \ + commit -m ":package: Bump lucide package versions to ${{ needs.pre-build.outputs.VERSION }}" --no-verify --quiet + git remote set-url --push origin https://lucide-bot:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY.git + git push origin HEAD:master diff --git a/.github/workflows/version-up.sh b/.github/workflows/version-up.sh new file mode 100755 index 0000000..1a9631b --- /dev/null +++ b/.github/workflows/version-up.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +## Copyright (C) 2017, Oleksandr Kucherenko +## Last revisit: 2017-09-29 + +## get highest version tag for all branches +function highest_tag(){ + local TAG=$(git tag --list 2>/dev/null | tail -n1 2>/dev/null) + echo "$TAG" +} + +## extract current branch name +function current_branch(){ + ## expected: heads/{branch_name} + ## expected: {branch_name} + local BRANCH=$(git rev-parse --abbrev-ref HEAD | cut -d"/" -f2) + echo "$BRANCH" +} + +## get latest/head commit hash number +function head_hash(){ + local COMMIT_HASH=$(git rev-parse --verify HEAD) + echo "$COMMIT_HASH" +} + +## extract tag commit hash code, tag name provided by argument +function tag_hash(){ + local TAG_HASH=$(git log -1 --format=format:"%H" $1 2>/dev/null | tail -n1) + echo "$TAG_HASH" +} + +## get latest tag in specified branch +function latest_tag(){ + #local TAG=$(git describe --tags $(git rev-list --tags --max-count=1 2>/dev/null) 2>/dev/null) + local TAG=$(git describe --tags --abbrev=0 2>/dev/null) + echo "$TAG" +} + +## get latest revision number +function latest_revision(){ + local REV=$(git rev-list --count HEAD 2>/dev/null) + echo "$REV" +} + +## parse last found tag, extract it PARTS +function parse_last(){ + local position=$(($1-1)) + + # two parts found only + local SUBS=( ${PARTS[$position]//-/ } ) + #echo ${SUBS[@]}, size: ${#SUBS} + + # found NUMBER + PARTS[$position]=${SUBS[0]} + #echo ${PARTS[@]} + + # found SUFFIX + if [[ ${#SUBS} -ge 1 ]]; then + PARTS[4]=${SUBS[1],,} #lowercase + #echo ${PARTS[@]}, ${SUBS[@]} + fi +} + +## increment REVISION part, don't touch STAGE +function increment_revision(){ + PARTS[3]=$(( PARTS[3] + 1 )) + IS_DIRTY=1 +} + +## increment PATCH part, reset all other lower PARTS, don't touch STAGE +function increment_patch(){ + PARTS[2]=$(( PARTS[2] + 1 )) + PARTS[3]=0 + IS_DIRTY=1 +} + +## increment MINOR part, reset all other lower PARTS, don't touch STAGE +function increment_minor(){ + PARTS[1]=$(( PARTS[1] + 1 )) + PARTS[2]=0 + PARTS[3]=0 + IS_DIRTY=1 +} + +## increment MAJOR part, reset all other lower PARTS, don't touch STAGE +function incremet_major(){ + PARTS[0]="v$(( PARTS[0] + 1 ))" + PARTS[1]=0 + PARTS[2]=0 + PARTS[3]=0 + IS_DIRTY=1 +} + +## increment the number only of last found PART: REVISION --> PATCH --> MINOR. don't touch STAGE +function increment_last_found(){ + if [[ "${#PARTS[3]}" == 0 || "${PARTS[3]}" == "0" ]]; then + if [[ "${#PARTS[2]}" == 0 || "${PARTS[2]}" == "0" ]]; then + increment_minor + else + increment_patch + fi + else + increment_revision + fi + + # stage part is not EMPTY + if [[ "${#PARTS[4]}" != 0 ]]; then + IS_SHIFT=1 + fi +} + +## compose version from PARTS +function compose(){ + MAJOR="${PARTS[0]}" + MINOR=".${PARTS[1]}" + PATCH=".${PARTS[2]}" + REVISION=".${PARTS[3]}" + SUFFIX="-${PARTS[4]}" + + if [[ "${#PATCH}" == 1 ]]; then # if empty {PATCH} + PATCH="" + fi + + if [[ "${#REVISION}" == 1 ]]; then # if empty {REVISION} + REVISION="" + fi + + if [[ "${PARTS[3]}" == "0" ]]; then # if revision is ZERO + REVISION="" + fi + + # shrink patch and revision + if [[ -z "${REVISION// }" ]]; then + if [[ "${PARTS[2]}" == "0" ]]; then + PATCH=".0" + fi + else # revision is not EMPTY + if [[ "${#PATCH}" == 0 ]]; then + PATCH=".0" + fi + fi + + # remove suffix if we don't have a alpha/beta/rc + if [[ "${#SUFFIX}" == 1 ]]; then + SUFFIX="" + fi + + + echo "${MAJOR}${MINOR}${PATCH}${REVISION}${SUFFIX}" #full format +} + +# initial version used for repository without tags +INIT_VERSION=0.0.0.0-alpha + +# do GIT data extracting +TAG=$(latest_tag) +REVISION=$(latest_revision) +BRANCH=$(current_branch) +TOP_TAG=$(highest_tag) +TAG_HASH=$(tag_hash $TAG) +HEAD_HASH=$(head_hash) + +# if tag and branch commit hashes are different, than print info about that +#echo $HEAD_HASH vs $TAG_HASH +if [[ "$@" == "" ]]; then + if [[ "$TAG_HASH" == "$HEAD_HASH" ]]; then + echo "Tag $TAG and HEAD are aligned. We will stay on the TAG version." + echo "" + NO_ARGS_VALUE='--stay' + else + PATTERN="^[0-9]+.[0-9]+(.[0-9]+)*(-(alpha|beta|rc))*$" + + if [[ "$BRANCH" =~ $PATTERN ]]; then + echo "Detected version branch '$BRANCH'. We will auto-increment the last version PART." + echo "" + NO_ARGS_VALUE='--default' + else + echo "Detected branch name '$BRANCH' than does not match version pattern. We will increase MINOR." + echo "" + NO_ARGS_VALUE='--minor' + fi + fi +fi + +# +# {MAJOR}.{MINOR}[.{PATCH}[.{REVISION}][-(.*)] +# +# Suffix: alpha, beta, rc +# No Suffix --> {NEW_VERSION}-alpha +# alpha --> beta +# beta --> rc +# rc --> {VERSION} +# +PARTS=( ${TAG//./ } ) +parse_last ${#PARTS[@]} # array size as argument +#echo ${PARTS[@]} + +# if no parameters than emulate --default parameter +if [[ "$@" == "" ]]; then + set -- $NO_ARGS_VALUE +fi + +# parse input parameters +for i in "$@" +do + key="$i" + + case $key in + -a|--alpha) # switched to ALPHA + PARTS[4]="alpha" + IS_SHIFT=1 + ;; + -b|--beta) # switched to BETA + PARTS[4]="beta" + IS_SHIFT=1 + ;; + -c|--release-candidate) # switched to RC + PARTS[4]="rc" + IS_SHIFT=1 + ;; + -r|--release) # switched to RELEASE + PARTS[4]="" + IS_SHIFT=1 + ;; + -p|--patch) # increment of PATCH + increment_patch + ;; + -e|--revision) # increment of REVISION + increment_revision + ;; + -g|--git-revision) # use git revision number as a revision part§ + PARTS[3]=$(( REVISION )) + IS_DIRTY=1 + ;; + -i|--minor) # increment of MINOR by default + increment_minor + ;; + --default) # stay on the same stage, but increment only last found PART of version code + increment_last_found + ;; + -m|--major) # increment of MAJOR + incremet_major + ;; + -s|--stay) # extract version info + IS_DIRTY=1 + NO_APPLY_MSG=1 + ;; + -t|--tag-only) # extract version info + TAG_ONLY=1 + ;; + --apply) + DO_APPLY=1 + ;; + -h|--help) + help + ;; + esac + shift +done + +# detected shift, but no increment +if [[ "$IS_SHIFT" == "1" ]]; then + # temporary disable stage shift + stage=${PARTS[4]} + PARTS[4]='' + + # detect first run on repository, INIT_VERSION was used + if [[ "$(compose)" == "0.0" ]]; then + increment_minor + fi + + PARTS[4]=$stage +fi + +# no increment applied yet and no shift of state, do minor increase +if [[ "$IS_DIRTY$IS_SHIFT" == "" ]]; then + increment_minor +fi + +compose + +# is proposed tag in conflict with any other TAG +PROPOSED_HASH=$(tag_hash $(compose)) +if [[ "${#PROPOSED_HASH}" -gt 0 && "$NO_APPLY_MSG" == "" ]]; then + echo -e "\033[31mERROR:\033[0m " + echo -e "\033[31mERROR:\033[0m Found conflict with existing tag \033[32m$(compose)\033[0m / $PROPOSED_HASH" + echo -e "\033[31mERROR:\033[0m Only manual resolving is possible now." + echo -e "\033[31mERROR:\033[0m " + echo -e "\033[31mERROR:\033[0m To Resolve try to add --revision or --patch modifier." + echo -e "\033[31mERROR:\033[0m " + echo "" + exit 1 +fi diff --git a/icons/code-2.svg b/icons/code-2.svg new file mode 100644 index 0000000..04bb183 --- /dev/null +++ b/icons/code-2.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/package.json b/package.json index d362729..c2372f3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "lucide-vue": "yarn workspace lucide-vue", "build:icons": "babel-node ./scripts/buildIcons.js --presets @babel/env", "optimize": "babel-node ./scripts/optimizeSvgs.js --presets @babel/env", - "addtags": "babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env" + "addtags": "babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env", + "generate:changelog": "babel-node ./scripts/generateChangelog.js --presets @babel/env" }, "devDependencies": { "@ampproject/rollup-plugin-closure-compiler": "^0.25.2", @@ -38,6 +39,7 @@ "jest": "^26.4.2", "lint-staged": "^10.5.3", "minimist": "^1.2.5", + "node-fetch": "^2.6.1", "prettier": "1.17.1", "rollup": "^2.7.3", "rollup-plugin-license": "^2.0.0", diff --git a/scripts/generateChangelog.js b/scripts/generateChangelog.js new file mode 100644 index 0000000..97dbc1f --- /dev/null +++ b/scripts/generateChangelog.js @@ -0,0 +1,76 @@ +import githubApi from './githubApi'; + +const fetchCompareTags = oldTag => + githubApi(`https://api.github.com/repos/lucide-icons/lucide/compare/${oldTag}...master`); + +const iconRegex = /icons\/(.*)\.svg/g; +const iconTemplate = ({ name, pullNumber, author }) => + `- \`${name}\` (${pullNumber}) by @${author}`; + +const topics = [ + { + title: 'New icons 🎨', + template: iconTemplate, + filter: ({ status, filename }) => status === 'added' && filename.match(iconRegex), + }, + { + title: 'Modified Icons 🔨', + template: iconTemplate, + filter: ({ status, filename }) => status === 'modified' && filename.match(iconRegex), + }, + { + title: 'Code improvements ⚡', + template: ({ title, pullNumber, author }) => `- ${title} (${pullNumber}) by @${author}`, + filter: ({ filename }, index, self) => + !filename.match(iconRegex) && self.indexOf(filename) === index, + }, +]; + +const fetchCommits = async file => { + const commits = await githubApi( + `https://api.github.com/repos/lucide-icons/lucide/commits?path=${file.filename}`, + ); + + return { ...file, commits }; +}; + +// eslint-disable-next-line func-names +(async function() { + const output = await fetchCompareTags('v0.13.0'); + const changedFiles = output.files.filter( + ({ filename }) => !filename.match(/site\/(.*)|(.*)package\.json|tags.json/g), + ); + + const commits = await Promise.all(changedFiles.map(fetchCommits)); + + const mappedCommits = commits + .map(({ commits: [pr], filename, sha, status }) => { + const pullNumber = /(.*)\((#[0-9]*)\)/gm.exec(pr.commit.message); + const nameRegex = /^\/?(.+\/)*(.+)\.(.+)$/g.exec(filename); + + return { + filename, + name: nameRegex && nameRegex[2] ? nameRegex[2] : null, + title: pullNumber && pullNumber[1] ? pullNumber[1].trim() : null, + pullNumber: pullNumber && pullNumber[2] ? pullNumber[2].trim() : null, + author: pr.author.login, + sha, + status, + }; + }) + .filter(({ pullNumber }) => !!pullNumber); + + const changelog = topics.map(({ title, filter, template }) => { + const lines = mappedCommits.filter(filter).map(template); + + if (lines.length) { + return [`## ${title}`, ' ', ...lines, ' ']; + } + + return ['']; + }); + + const changelogMarkown = changelog.flat().join('\n'); + + console.log(changelogMarkown); +})(); diff --git a/scripts/githubApi.js b/scripts/githubApi.js new file mode 100644 index 0000000..de5c558 --- /dev/null +++ b/scripts/githubApi.js @@ -0,0 +1,22 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import fetch, { Headers } from 'node-fetch'; + +const githubApi = async endpoint => { + const headers = new Headers(); + const username = 'ericfennis'; + const password = process.env.GITHUB_API_KEY; + + headers.set( + 'Authorization', + `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + ); + + const res = await fetch(endpoint, { + method: 'GET', + headers, + }); + + return res.json(); +}; + +export default githubApi; diff --git a/yarn.lock b/yarn.lock index a3050db..893457e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7548,7 +7548,7 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2.6.1: +node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==