From e216f69860553213701d8afffc106b2327e16a61 Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Sun, 9 Aug 2026 14:07:25 +0700 Subject: [PATCH 1/3] ci(cla): add CLA check workflow --- .github/workflows/cla-check.yml | 129 ++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .github/workflows/cla-check.yml diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml new file mode 100644 index 0000000..d189a4b --- /dev/null +++ b/.github/workflows/cla-check.yml @@ -0,0 +1,129 @@ +name: CLA Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + pull-requests: write + contents: read + statuses: write + +jobs: + cla-check: + runs-on: ubuntu-latest + steps: + - name: Fetch & check CLA signature + id: check + run: | + # Fetch signatures.json directly from raw GitHub (public repo) + # Use wget as primary (handles 200-with-404-body better than curl) + wget -q -O signatures.json "https://raw.githubusercontent.com/codecoradev/.github/main/.cla/signatures.json" 2>/dev/null \ + || curl -sfL "https://raw.githubusercontent.com/codecoradev/.github/main/.cla/signatures.json" -o signatures.json 2>/dev/null \ + || echo '{"signatures":[]}' > signatures.json + # Validate JSON — if invalid, use empty + python3 -c "import json; json.load(open('signatures.json'))" 2>/dev/null \ + || echo '{"signatures":[]}' > signatures.json + python3 - << 'EOF' + import json, os + + author = os.environ["PR_AUTHOR"] + with open("signatures.json") as f: + data = json.load(f) + + signatures = data.get("signatures", []) + found = any( + s.get("github_username", "").lower() == author.lower() + for s in signatures + ) + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"signed={'true' if found else 'false'}\n") + + print(f"CLA signed by @{author}: {found}") + EOF + env: + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const signed = '${{ steps.check.outputs.signed }}' === 'true'; + const author = '${{ github.event.pull_request.user.login }}'; + const prNumber = ${{ github.event.pull_request.number }}; + + // Find existing CLA bot comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const botComment = comments.data.find(c => + c.user.login === 'github-actions[bot]' && + c.body.includes('CodeCoraDev CLA Bot') + ); + + const body = signed + ? [ + '## ✅ CodeCoraDev CLA Bot', + '', + `Thank you @${author}! Your CLA is on file. 🎉`, + '', + 'Your contribution can now be reviewed.', + ].join('\n') + : [ + '## ⚠️ CodeCoraDev CLA Bot', + '', + `Hi @${author}! Thanks for your contribution.`, + '', + 'Before this PR can be reviewed, please sign our Contributor License Agreement:', + '', + '- 📋 **Individual?** → [Sign CLA Individual](https://codecoradev.github.io/cla/?type=individual)', + '- 🏢 **Corporate?** → [Sign CLA Corporate](https://codecoradev.github.io/cla/?type=corporate)', + '', + '---', + 'By signing, you agree to the terms in [CLA_INDIVIDUAL.md](https://github.com/codecoradev/.github/blob/main/CLA_INDIVIDUAL.md) or [CLA_CORPORATE.md](https://github.com/codecoradev/.github/blob/main/CLA_CORPORATE.md).', + ].join('\n'); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + } + + - name: Set commit status + uses: actions/github-script@v7 + with: + script: | + const signed = '${{ steps.check.outputs.signed }}' === 'true'; + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ github.event.pull_request.head.sha }}', + state: signed ? 'success' : 'failure', + context: 'CLA Check', + description: signed + ? '✅ CLA signed' + : '❌ CLA not signed — sign at https://codecoradev.github.io/cla', + target_url: signed + ? 'https://github.com/codecoradev/.github/blob/main/.cla/signatures.json' + : 'https://codecoradev.github.io/cla', + }); + + - name: Fail if not signed + if: steps.check.outputs.signed != 'true' + run: | + echo "❌ CLA not signed by ${{ github.event.pull_request.user.login }}" + exit 1 From bc1c14cce90426641b3fefbf279b69fe6d29fbca Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Sun, 9 Aug 2026 15:12:44 +0700 Subject: [PATCH 2/3] ci(cla): skip comment for signed contributors (#37) --- .github/workflows/cla-check.yml | 38 +++++++++++++-------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index d189a4b..f0fa96a 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -45,35 +45,15 @@ jobs: env: PR_AUTHOR: ${{ github.event.pull_request.user.login }} - - name: Comment on PR + - name: Comment on PR (unsigned only) + if: steps.check.outputs.signed != 'true' uses: actions/github-script@v7 with: script: | - const signed = '${{ steps.check.outputs.signed }}' === 'true'; const author = '${{ github.event.pull_request.user.login }}'; const prNumber = ${{ github.event.pull_request.number }}; - // Find existing CLA bot comment - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - - const botComment = comments.data.find(c => - c.user.login === 'github-actions[bot]' && - c.body.includes('CodeCoraDev CLA Bot') - ); - - const body = signed - ? [ - '## ✅ CodeCoraDev CLA Bot', - '', - `Thank you @${author}! Your CLA is on file. 🎉`, - '', - 'Your contribution can now be reviewed.', - ].join('\n') - : [ + const body = [ '## ⚠️ CodeCoraDev CLA Bot', '', `Hi @${author}! Thanks for your contribution.`, @@ -87,6 +67,18 @@ jobs: 'By signing, you agree to the terms in [CLA_INDIVIDUAL.md](https://github.com/codecoradev/.github/blob/main/CLA_INDIVIDUAL.md) or [CLA_CORPORATE.md](https://github.com/codecoradev/.github/blob/main/CLA_CORPORATE.md).', ].join('\n'); + // Find existing CLA bot comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const botComment = comments.data.find(c => + c.user.login === 'github-actions[bot]' && + c.body.includes('CodeCoraDev CLA Bot') + ); + if (botComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, From eb93a7b29c6364f9266a63048ab3b81cc811ab0d Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Mon, 5 Oct 2026 10:44:51 +0700 Subject: [PATCH 3/3] seo(docs): keyword-rich title, description, OG/Twitter meta, canonical (#38) - config.ts: site title 'Covecto' (7 chars) -> 'Covecto: Image to SVG Vectorizer CLI | CodeCora' (49 chars, keyword-first); description rewritten intent-matched (colon values quoted - bare YAML values with ': ' break VitePress frontmatter parsing) - index.md frontmatter head: og:url, og:title, og:description, twitter:card/title/description, rel=canonical (exact indexed form, no trailing slash) - root page only, pages derive from titleTemplate - Do NOT pass head via createConfig(): @codecora/theme drops opts.head (never merged); theme fix tracked separately - Live site still serves the 2026-07-23 main build; this branch goes to develop first, fast-forward-merge to main to trigger deploy-website.yml Validated: vitepress build clean; dist/index.html carries title, canonical, og:url, og:title, og:description, twitter:*; subpages get ' | ' via titleTemplate. Co-authored-by: ajianaz --- docs/.vitepress/config.ts | 12 +++++------- docs/index.md | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 1d33bff..d7d2948 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -2,15 +2,13 @@ import { createConfig } from '@codecora/theme/vitepress/config' export default createConfig({ product: 'covecto', - title: 'Covecto', - description: 'Dual-engine image vectorization — pixel-exact for icons, smooth curves for art.', + title: 'Covecto: Image to SVG Vectorizer CLI | CodeCora', + description: 'Covecto converts images to SVG with a dual-engine vectorizer CLI: pixel-exact for icons, smooth Bézier curves for art. SVG, PDF, EPS output. Zero dependencies.', accent: 'orange', repo: 'covecto', - head: [ - ['meta', { property: 'og:title', content: 'Covecto — Image to SVG Vectorization' }], - ['meta', { property: 'og:description', content: 'Dual-engine image vectorizer: pixel-exact for icons, Bézier splines for art. CLI + HTTP API.' }], - ['meta', { name: 'twitter:card', content: 'summary_large_image' }], - ], + // NOTE: do NOT pass `head` here — @codecora/theme createConfig() drops + // opts.head (never merged into its hardcoded head array). OG/Twitter meta + // lives in docs/index.md frontmatter instead; theme fix tracked separately. ignoreDeadLinks: true, sidebar: [ { diff --git a/docs/index.md b/docs/index.md index 5c10057..b8164cc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,27 @@ --- layout: home +head: + - - meta + - property: og:url + content: 'https://codecora.dev/covecto/docs' + - - meta + - property: og:title + content: 'Covecto: Image to SVG Vectorizer CLI | CodeCora' + - - meta + - property: og:description + content: 'Covecto converts images to SVG with a dual-engine vectorizer CLI: pixel-exact for icons, smooth Bézier curves for art. SVG, PDF, EPS output. Zero dependencies.' + - - meta + - name: twitter:card + content: 'summary_large_image' + - - meta + - name: twitter:title + content: 'Covecto: Image to SVG Vectorizer CLI | CodeCora' + - - meta + - name: twitter:description + content: 'Covecto converts images to SVG with a dual-engine vectorizer CLI: pixel-exact for icons, smooth Bézier curves for art. SVG, PDF, EPS output. Zero dependencies.' + - - link + - rel: canonical + href: 'https://codecora.dev/covecto/docs' hero: name: covecto