Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .commitlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"chore",
"ci",
"revert"
]
],
"type-case": [2, "always", "lowercase"],
"type-empty": [2, "never"],
"subject-empty": [2, "never"],
"subject-full-stop": [2, "never", "."],
"subject-case": [2, "never", ["start-case", "pascal-case", "upper-case"]],
"header-max-length": [2, "always", 100]
}
}
141 changes: 141 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# CI — NossoCRM
#
# Jobs:
# check — lint + typecheck + tests (runs on every PR and push to main/feature branches)
# build — Next.js production build (runs only on push to main)
#
# Cache strategy:
# - pnpm store: keyed on pnpm-lock.yaml hash via actions/setup-node cache: "pnpm"
# - .next/cache: keyed on source hash, restores on prefix match
#
# Security: no untrusted event inputs are interpolated into run: commands.

name: CI

on:
push:
branches:
- main
- "feature/**"
- "fix/**"
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
NODE_VERSION: "20"
PNPM_VERSION: "9"

jobs:
# ─── commitlint ────────────────────────────────────────────────────────────
# Validates commit messages follow conventional commits format
# Runs only on PRs to catch format issues before merge
commitlint:
name: Validate Conventional Commits
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install commitlint
run: npm install --save-dev @commitlint/cli @commitlint/config-conventional --legacy-peer-deps

- name: Validate commits
run: |
npx commitlint \
--from ${{ github.event.pull_request.base.sha }} \
--to ${{ github.event.pull_request.head.sha }}

# ─── check ────────────────────────────────────────────────────────────────
# Runs precheck:fast: ESLint (--max-warnings 0) + tsc --noEmit + vitest run
# Fast path — no Next.js build required. Runs on all triggering events.
check:
name: Lint / Typecheck / Tests
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}

- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Lint (ESLint — zero warnings)
run: pnpm lint

- name: Typecheck (tsc --noEmit)
run: pnpm typecheck

- name: Tests (Vitest)
run: pnpm test:run

# ─── build ────────────────────────────────────────────────────────────────
# Full Next.js production build. Runs only on pushes to main.
# Depends on check passing to avoid wasting build minutes on broken code.
build:
name: Build (Next.js)
runs-on: ubuntu-latest
timeout-minutes: 15
needs: check
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}

- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"

- name: Restore Next.js build cache
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.css') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
${{ runner.os }}-nextjs-

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build
env:
# Next.js build requires these to be present even if not used at runtime.
# Real values come from Vercel project settings — these are build-time only.
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY }}
50 changes: 50 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Preview — NossoCRM
#
# Triggers a Vercel Preview deployment on every PR.
# The deployment URL is posted as a PR comment via Vercel's GitHub integration.
#
# Prerequisites (set in repo Settings → Secrets and variables → Actions):
# VERCEL_TOKEN — personal access token from vercel.com/account/tokens
# VERCEL_ORG_ID — from .vercel/project.json or `vercel env pull`
# VERCEL_PROJECT_ID — from .vercel/project.json or `vercel env pull`
#
# This job does NOT run the build itself — Vercel handles the build on its
# infrastructure using the same Next.js config. The CI check job (ci.yml)
# is the quality gate; preview deploy runs in parallel to save time.
#
# Security: no untrusted event inputs are interpolated into run: commands.

name: Preview Deploy

on:
pull_request:
branches:
- main

concurrency:
group: preview-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy-preview:
name: Deploy Preview to Vercel
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Deploy to Vercel (Preview)
id: deploy
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# Do not promote to production on this workflow
vercel-args: "--no-wait"
github-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
96 changes: 96 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Release — NossoCRM
#
# Triggered when a semantic version tag (vX.Y.Z) is pushed to main branch.
# Creates a GitHub Release with auto-generated release notes from merged PRs.
#
# Usage:
# npm run release:prepare # Analyze commits and suggest version
# npm run release:draft # Preview changelog
# npm run release:tag # Create tag and push
# git push origin main
# git push origin vX.Y.Z # This triggers this workflow

name: Release

on:
push:
tags:
- "v*.*.*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
# ─── release ──────────────────────────────────────────────────────────────
# Create GitHub Release from pushed semantic version tag
release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history for changelog generation

- name: Extract version from tag
id: version
env:
REF_NAME: ${{ github.ref_name }}
run: |
TAG="${REF_NAME}"
VERSION="${TAG#v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Release version: ${VERSION}"

- name: Get previous release
id: prev_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LATEST=$(gh release list --limit 1 --json tagName --jq -r '.[] | .tagName' 2>/dev/null || echo "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

gh CLI --jq flag syntax for extracting raw values from --json output

💡 Result:

The syntax for the gh CLI --jq flag to extract raw values from --json output is: gh --json <field1,field2,...> --jq '' Key points: - --json must be used first with a comma-separated list of fields (e.g., number,title,author). Run gh --json without fields to list available fields. - --jq follows, taking a string argument in standard jq query syntax (quoted with single quotes for shell safety). - The --jq expression processes the JSON array/object from --json and outputs matching raw values, without quotes for strings when possible. - No external jq tool is needed; gh has built-in support. - Output is pretty-printed in terminals. Examples: 1. Extract author logins as raw strings: gh pr list --json author --jq '.[].author.login' Output: monalisa codercat cli-maintainer 2. Complex filtering (issues with labels, first 3): gh issue list --json number,title,labels --jq 'map(select((.labels | length) > 0)) | map(.labels = (.labels | map(.name))) | .[:3]' For gh api, --jq works similarly on the API response: gh api repos/{owner}/{repo}/issues --jq '.[].title' To get raw values without formatting/quoting, use jq expressions like .field or .[].field that output primitives directly.

Citations:


gh --jq -r '…' is malformed — the step always falls back to git describe.

gh release list --jq takes a single jq expression as its argument. The -r flag does not exist for gh's built-in jq—it is a standalone jq tool flag. As written, gh receives --jq -r (treating -r as the jq expression) and .[] | .tagName as extraneous positional arguments, which fails. Because of 2>/dev/null || echo "", LATEST is always empty and the fallback git describe --tags --abbrev=0 is the only path actually used—so GitHub Releases is never consulted.

🔧 Proposed fix
-          LATEST=$(gh release list --limit 1 --json tagName --jq -r '.[] | .tagName' 2>/dev/null || echo "")
+          LATEST=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LATEST=$(gh release list --limit 1 --json tagName --jq -r '.[] | .tagName' 2>/dev/null || echo "")
LATEST=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml at line 55, The gh CLI invocation incorrectly
supplies -r as a separate argument; update the LATEST assignment so the jq
expression is passed as a single argument to gh (remove the standalone -r), e.g.
call gh release list --limit 1 --json tagName --jq '.[] | .tagName' (or select
the first element with --jq '.[0].tagName') and/or pipe the output to a real jq
-r if you need raw output, ensuring you don't pass extraneous positional args;
adjust the LATEST assignment around the variable name LATEST and the gh release
list command accordingly so the command returns the release tag instead of
always falling back to git describe.

if [ -z "$LATEST" ]; then
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
else
PREV_TAG="${LATEST}"
fi
echo "previous_tag=${PREV_TAG}" >> $GITHUB_OUTPUT

- name: Extract changelog for version
id: changelog
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if [ -f "CHANGELOG.md" ]; then
# Extract section from ## [VERSION] to next ## [
# Using a safe shell command to avoid injection
awk "/^## \[${VERSION}\]/,/^## \[/" CHANGELOG.md | head -n -1 > /tmp/changelog.txt
if [ -s /tmp/changelog.txt ]; then
# Convert to GitHub Actions multiline format
{
echo 'NOTES<<EOF'
cat /tmp/changelog.txt
echo 'EOF'
} >> $GITHUB_ENV
fi
fi

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.version.outputs.tag }}
name: Release ${{ steps.version.outputs.version }}
body: |
${{ env.NOTES }}

---

**Full Diff**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_release.outputs.previous_tag }}...${{ steps.version.outputs.tag }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading