diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 597ad4d3a7..9ec6ede1a6 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -26,8 +26,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 93e20e7861..4f9d810bf3 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -32,8 +32,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98aa0b9c04..aabce74779 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - release push: branches: - main @@ -33,8 +34,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies (root only, no scripts) run: pnpm install --filter . --ignore-scripts @@ -55,8 +54,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -77,8 +74,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -105,8 +100,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -144,8 +137,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -174,8 +165,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -202,8 +191,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install @@ -227,8 +214,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies (root only, no scripts) run: pnpm install --filter . --ignore-scripts diff --git a/.github/workflows/lunaria.yml b/.github/workflows/lunaria.yml index 0ecb5af4f7..1c4238b221 100644 --- a/.github/workflows/lunaria.yml +++ b/.github/workflows/lunaria.yml @@ -34,8 +34,6 @@ jobs: - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - with: - cache: true - name: 📦 Install dependencies run: pnpm install diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000000..09b9ec45ed --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,152 @@ +name: release-pr + +on: + push: + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + release-pr: + name: 🚀 Create or update release PR + runs-on: ubuntu-slim + if: github.repository == 'npmx-dev/npmx.dev' + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: 🔍 Check for unreleased commits + id: check + run: | + git fetch origin release + COMMITS=$(git log origin/release..origin/main --oneline) + if [ -z "$COMMITS" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "No new commits to release" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "Found unreleased commits:" + echo "$COMMITS" + fi + + - name: 📝 Generate changelog body + if: steps.check.outputs.skip == 'false' + id: changelog + run: | + # Get the latest tag, or use initial commit if no tags exist + LATEST_TAG=$(git describe --tags --abbrev=0 origin/release 2>/dev/null || echo "") + + if [ -z "$LATEST_TAG" ]; then + FROM_REF=$(git rev-list --max-parents=0 HEAD) + CURRENT_VERSION="0.0.0" + else + FROM_REF="$LATEST_TAG" + CURRENT_VERSION="${LATEST_TAG#v}" + fi + + # Categorize commits + FEATURES="" + FIXES="" + CHORES="" + OTHER="" + + while IFS= read -r line; do + [ -z "$line" ] && continue + SHA=$(echo "$line" | cut -d' ' -f1) + MSG=$(echo "$line" | cut -d' ' -f2-) + ENTRY="- $MSG (\`$SHA\`)" + + if echo "$MSG" | grep -qE '^feat(\(|:)'; then + FEATURES="${FEATURES}${ENTRY}\n" + elif echo "$MSG" | grep -qE '^fix(\(|:)'; then + FIXES="${FIXES}${ENTRY}\n" + elif echo "$MSG" | grep -qE '^(chore|ci|build|perf|refactor|style|test|docs)(\(|:)'; then + CHORES="${CHORES}${ENTRY}\n" + else + OTHER="${OTHER}${ENTRY}\n" + fi + done <<< "$(git log "$FROM_REF"..origin/main --oneline --no-merges)" + + # Determine next version + HAS_BREAKING=$(git log "$FROM_REF"..origin/main --format='%B' | grep -c 'BREAKING CHANGE\|!:' || true) + HAS_FEAT=$(git log "$FROM_REF"..origin/main --oneline --no-merges | grep -cE '^[a-f0-9]+ feat(\(|:)' || true) + + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + + if [ "$HAS_BREAKING" -gt 0 ] && [ "$MAJOR" -gt 0 ]; then + NEXT_VERSION="$((MAJOR + 1)).0.0" + elif [ "$HAS_FEAT" -gt 0 ]; then + NEXT_VERSION="${MAJOR}.$((MINOR + 1)).0" + else + NEXT_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + fi + + echo "next_version=v${NEXT_VERSION}" >> "$GITHUB_OUTPUT" + + # Build the PR body + BODY="This PR will deploy the following changes to production (\`npmx.dev\`).\n\n" + BODY="${BODY}**Next version: \`v${NEXT_VERSION}\`** (current: \`v${CURRENT_VERSION}\`)\n\n" + + if [ -n "$FEATURES" ]; then + BODY="${BODY}### Features\n\n${FEATURES}\n" + fi + if [ -n "$FIXES" ]; then + BODY="${BODY}### Fixes\n\n${FIXES}\n" + fi + if [ -n "$CHORES" ]; then + BODY="${BODY}### Other Changes\n\n${CHORES}\n" + fi + if [ -n "$OTHER" ]; then + BODY="${BODY}### Uncategorized\n\n${OTHER}\n" + fi + + BODY="${BODY}---\n\n" + BODY="${BODY}> Merging this PR will:\n" + BODY="${BODY}> - Deploy to \`npmx.dev\` via Vercel\n" + BODY="${BODY}> - Create a \`v${NEXT_VERSION}\` tag and GitHub Release\n" + BODY="${BODY}> - Publish \`npmx-connector@${NEXT_VERSION}\` to npm" + + # Write body to file, truncating if needed (GitHub limits PR body to 65536 chars) + echo -e "$BODY" > /tmp/pr-body.md + if [ "$(wc -c < /tmp/pr-body.md)" -gt 60000 ]; then + COMMIT_COUNT=$(git log "$FROM_REF"..origin/main --oneline --no-merges | wc -l) + COMPARE_URL="https://github.com/npmx-dev/npmx.dev/compare/${FROM_REF}...main" + TRUNCATED="This PR will deploy the following changes to production (\`npmx.dev\`).\n\n" + TRUNCATED="${TRUNCATED}**Next version: \`v${NEXT_VERSION}\`** (current: \`v${CURRENT_VERSION}\`)\n\n" + TRUNCATED="${TRUNCATED}> **${COMMIT_COUNT} commits** are included in this release. The full changelog is too large to display here.\n>\n" + TRUNCATED="${TRUNCATED}> [View full diff on GitHub](${COMPARE_URL})\n\n" + TRUNCATED="${TRUNCATED}---\n\n" + TRUNCATED="${TRUNCATED}> Merging this PR will:\n" + TRUNCATED="${TRUNCATED}> - Deploy to \`npmx.dev\` via Vercel\n" + TRUNCATED="${TRUNCATED}> - Create a \`v${NEXT_VERSION}\` tag and GitHub Release\n" + TRUNCATED="${TRUNCATED}> - Publish \`npmx-connector@${NEXT_VERSION}\` to npm" + echo -e "$TRUNCATED" > /tmp/pr-body.md + fi + + - name: 🚀 Create or update release PR + if: steps.check.outputs.skip == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEXT_VERSION: ${{ steps.changelog.outputs.next_version }} + run: | + EXISTING_PR=$(gh pr list --base release --head main --state open --json number --jq '.[0].number') + + if [ -n "$EXISTING_PR" ]; then + gh pr edit "$EXISTING_PR" \ + --title "chore: release ${NEXT_VERSION}" \ + --body-file /tmp/pr-body.md + echo "Updated existing PR #${EXISTING_PR}" + else + gh pr create \ + --base release \ + --head main \ + --title "chore: release ${NEXT_VERSION}" \ + --body-file /tmp/pr-body.md \ + --label "release" + echo "Created new release PR" + fi diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 0000000000..af7ea9ef3e --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,158 @@ +name: release-tag + +on: + push: + branches: + - release + +permissions: {} + +jobs: + tag: + name: 🏷️ Tag release and create GitHub Release + runs-on: ubuntu-slim + if: github.repository == 'npmx-dev/npmx.dev' + permissions: + contents: write + outputs: + version: ${{ steps.version.outputs.next }} + skipped: ${{ steps.check.outputs.skip }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: 🔢 Determine next version + id: version + run: | + # Get the latest tag on this branch + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$LATEST_TAG" ]; then + CURRENT_VERSION="0.0.0" + FROM_REF=$(git rev-list --max-parents=0 HEAD) + else + CURRENT_VERSION="${LATEST_TAG#v}" + FROM_REF="$LATEST_TAG" + fi + + # Analyze conventional commits since last tag + HAS_BREAKING=$(git log "${FROM_REF}..HEAD" --format='%B' | grep -c 'BREAKING CHANGE\|!:' || true) + HAS_FEAT=$(git log "${FROM_REF}..HEAD" --oneline --no-merges | grep -cE '^[a-f0-9]+ feat(\(|:)' || true) + HAS_FIX=$(git log "${FROM_REF}..HEAD" --oneline --no-merges | grep -cE '^[a-f0-9]+ fix(\(|:)' || true) + + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + + if [ "$HAS_BREAKING" -gt 0 ] && [ "$MAJOR" -gt 0 ]; then + NEXT_VERSION="$((MAJOR + 1)).0.0" + elif [ "$HAS_FEAT" -gt 0 ]; then + NEXT_VERSION="${MAJOR}.$((MINOR + 1)).0" + elif [ "$HAS_FIX" -gt 0 ]; then + NEXT_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + else + # Only chore/docs/ci commits — still bump patch + NEXT_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + fi + + echo "current=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" + echo "next=v${NEXT_VERSION}" >> "$GITHUB_OUTPUT" + echo "from=$FROM_REF" >> "$GITHUB_OUTPUT" + echo "Bumping from v${CURRENT_VERSION} to v${NEXT_VERSION}" + + - name: 🔍 Check if tag already exists + id: check + env: + VERSION: ${{ steps.version.outputs.next }} + run: | + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Tag $VERSION already exists, skipping" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: 🏷️ Create and push tag + if: steps.check.outputs.skip == 'false' + env: + VERSION: ${{ steps.version.outputs.next }} + run: | + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + if: steps.check.outputs.skip == 'false' + with: + node-version: lts/* + + - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c + if: steps.check.outputs.skip == 'false' + name: 🟧 Install pnpm + + - name: 📦 Install dependencies + if: steps.check.outputs.skip == 'false' + run: pnpm install --filter . --ignore-scripts + + - name: 📝 Generate release notes + if: steps.check.outputs.skip == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FROM_REF: ${{ steps.version.outputs.from }} + run: node scripts/release-notes.ts "$FROM_REF" > /tmp/release-notes.md + + - name: 🚀 Create GitHub Release + if: steps.check.outputs.skip == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.next }} + run: | + gh release create "$VERSION" \ + --notes-file /tmp/release-notes.md \ + --title "$VERSION" + + publish-connector: + name: 📦 Publish npmx-connector to npm + runs-on: ubuntu-slim + needs: tag + if: needs.tag.outputs.skipped == 'false' + permissions: + contents: read + id-token: write + environment: npm-publish + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: release + + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org + + - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # 4e1c8eafbd745f64b1ef30a7d7ed7965034c486c + name: 🟧 Install pnpm + with: + cache: false + + - name: 📦 Install dependencies + run: pnpm install --filter npmx-connector... + + - name: 🔢 Set connector version + env: + VERSION: ${{ needs.tag.outputs.version }} + run: | + # Strip the 'v' prefix for package.json + PKG_VERSION="${VERSION#v}" + cd cli + npm version "$PKG_VERSION" --no-git-tag-version + echo "Publishing npmx-connector@${PKG_VERSION}" + + - name: 🏗️ Build connector + run: pnpm --filter npmx-connector build + + - name: 📤 Publish to npm with provenance + # Uses OIDC trusted publishing — no NPM_TOKEN needed. + # Configure on npmjs.com: repo npmx-dev/npmx.dev, workflow release-tag.yml, environment npm-publish + run: npm publish --provenance --access public + working-directory: cli diff --git a/README.md b/README.md index 1717303dd2..ca49a74f76 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ What npmx offers: ## Shortcuts - [chat.npmx.dev](https://chat.npmx.dev) - Discord Server +- [build.npmx.dev](https://build.npmx.dev) - Builders Discord Server - [social.npmx.dev](https://social.npmx.dev) - Bluesky Profile - [repo.npmx.dev](https://repo.npmx.dev) - GitHub Repository - [issues.npmx.dev](https://issues.npmx.dev) - GitHub Issues diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 49fcdf3a78..d89d3cf3d3 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -4,6 +4,7 @@ import { NPMX_DOCS_SITE } from '#shared/utils/constants' const route = useRoute() const isHome = computed(() => route.name === 'index') +const discord = useDiscordLink() const modalRef = useTemplateRef('modalRef') const showModal = () => modalRef.value?.showModal?.() const closeModal = () => modalRef.value?.close?.() @@ -124,8 +125,8 @@ const closeModal = () => modalRef.value?.close?.() {{ $t('footer.social') }} - - {{ $t('footer.chat') }} + + {{ discord.label }} diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 18801fca35..5febbd6fa7 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -5,6 +5,7 @@ import { isEditableElement } from '~/utils/input' import { NPMX_DOCS_SITE } from '#shared/utils/constants' const keyboardShortcuts = useKeyboardShortcuts() +const discord = useDiscordLink() withDefaults( defineProps<{ @@ -122,8 +123,8 @@ const mobileLinks = computed(() => [ }, { name: 'Chat', - label: $t('footer.chat'), - href: 'https://chat.npmx.dev', + label: discord.value.label, + href: discord.value.url, target: '_blank', type: 'link', external: true, diff --git a/app/components/CallToAction.vue b/app/components/CallToAction.vue index 8ed0f0c158..ba79780c9e 100644 --- a/app/components/CallToAction.vue +++ b/app/components/CallToAction.vue @@ -1,4 +1,5 @@