From 7982c90805d6304218b775656b29b9b03933f359 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Tue, 3 Mar 2026 16:51:56 +0900 Subject: [PATCH 01/17] fix: uncheck "apply correction" when there is no anomaly data (#1864) --- app/components/Package/TrendsChart.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/Package/TrendsChart.vue b/app/components/Package/TrendsChart.vue index ea83d8469..d8391b4eb 100644 --- a/app/components/Package/TrendsChart.vue +++ b/app/components/Package/TrendsChart.vue @@ -1895,7 +1895,10 @@ watch(selectedMetric, value => { :class="{ 'opacity-50 pointer-events-none': !hasAnomalies }" > Date: Tue, 3 Mar 2026 02:53:16 -0500 Subject: [PATCH 02/17] chore: add link to Netlify post to alpha blog post (#1862) Co-authored-by: Daniel Roe --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index e5ebc448d..e858efd93 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -113,6 +113,12 @@ headline="Read more from the community" authorHandle: 'alexdln.com', description: 'Alex reflects on the project, warm stories, wonderful people, and a look into the future' }, + { + url: 'https://www.netlify.com/blog/sponsoring-npmx', + title: 'Sponsoring npmx', + authorHandle: 'netlify.com', + description: 'It’s more important than ever that companies come together across competitive boundaries to sponsor and support the open ecosystem that lifts all boats.', + }, { url: 'https://johnnyreilly.com/npmx-with-a-little-help-from-my-friends', title: 'npmx: With a Little Help From My Friends', From 787f4c136fa1852b73236cc4429e927c2b0f8464 Mon Sep 17 00:00:00 2001 From: Declan Chidlow <84255570+DeclanChidlow@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:39:38 +0800 Subject: [PATCH 03/17] chore: add link to Vale.Rocks post to alpha blog post (#1867) --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index e858efd93..f82c68253 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -190,6 +190,12 @@ headline="Read more from the community" title: 'From a Newsletter Link to My First Open Source Contribution', authorHandle: 'radosvet.dev', description: 'How discovering npmx through a newsletter led to a first meaningful open source contribution and a new perspective on community-driven development.' + }, + { + url: 'https://vale.rocks/micros/20260303-1200', + title: 'npmx Is Open-Source Done Right', + authorHandle: 'vale.rocks', + description: 'How the ethos and practices of npmx represent a healthy open-source ecosystem that should be the standard, not an exception.' } ]" /> From 251f9efa8b4bbb060abb6aab56b6ea093e65d4b1 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 09:25:48 +0000 Subject: [PATCH 04/17] ci: set up release pr workflow --- .github/workflows/autofix.yml | 2 - .github/workflows/chromatic.yml | 2 - .github/workflows/ci.yml | 17 +--- .github/workflows/lunaria.yml | 2 - .github/workflows/release-pr.yml | 138 ++++++++++++++++++++++++++ .github/workflows/release-tag.yml | 158 ++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 36 +++++++ scripts/release-notes.ts | 99 +++++++++++++++++++ 9 files changed, 433 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/release-pr.yml create mode 100644 .github/workflows/release-tag.yml create mode 100644 scripts/release-notes.ts diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 597ad4d3a..9ec6ede1a 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 93e20e786..4f9d810bf 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 98aa0b9c0..aabce7477 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 0ecb5af4f..1c4238b22 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 000000000..30fcd5a60 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,138 @@ +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 to avoid shell escaping issues + echo -e "$BODY" > /tmp/pr-body.md + + - 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 000000000..af7ea9ef3 --- /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/package.json b/package.json index 8478e63f2..b67267954 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "@vitest/coverage-v8": "4.0.18", "@vue/test-utils": "2.4.6", "axe-core": "4.11.1", + "changelogen": "0.6.2", "chromatic": "15.2.0", "defu": "6.1.4", "devalue": "5.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d00b35bf..e26695d1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -284,6 +284,9 @@ importers: axe-core: specifier: 4.11.1 version: 4.11.1 + changelogen: + specifier: 0.6.2 + version: 0.6.2(magicast@0.5.1) chromatic: specifier: 15.2.0 version: 15.2.0 @@ -5920,6 +5923,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + changelogen@0.6.2: + resolution: {integrity: sha512-QtC7+r9BxoUm+XDAwhLbz3CgU134J1ytfE3iCpLpA4KFzX2P1e6s21RrWDwUBzfx66b1Rv+6lOA2nS2btprd+A==} + hasBin: true + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -6094,6 +6101,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-gitmoji@0.1.5: + resolution: {integrity: sha512-4wqOafJdk2tqZC++cjcbGcaJ13BZ3kwldf06PTiAQRAB76Z1KJwZNL1SaRZMi2w1FM9RYTgZ6QErS8NUl/GBmQ==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -8292,6 +8302,10 @@ packages: '@vueuse/core': '>=10.0.0' vue: '>=3.0.0' + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -17096,6 +17110,24 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + changelogen@0.6.2(magicast@0.5.1): + dependencies: + c12: 3.3.3(magicast@0.5.1) + confbox: 0.2.4 + consola: 3.4.2 + convert-gitmoji: 0.1.5 + mri: 1.2.0 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + open: 10.2.0 + pathe: 2.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + semver: 7.7.4 + std-env: 3.10.0 + transitivePeerDependencies: + - magicast + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -17232,6 +17264,8 @@ snapshots: content-type@1.0.5: {} + convert-gitmoji@0.1.5: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -20010,6 +20044,8 @@ snapshots: - react - react-dom + mri@1.2.0: {} + mrmime@2.0.1: {} ms@2.1.3: {} diff --git a/scripts/release-notes.ts b/scripts/release-notes.ts new file mode 100644 index 000000000..3ff8a38ef --- /dev/null +++ b/scripts/release-notes.ts @@ -0,0 +1,99 @@ +/** + * Generates full release notes with changelogen + @username contributor mentions. + * + * Usage: node scripts/release-notes.ts [to-ref] + * + * Outputs the complete release notes markdown to stdout, including: + * - Changelog sections (features, fixes, etc.) via changelogen + * - Contributors section with GitHub @username mentions + * + * Set GITHUB_TOKEN for higher API rate limits. + */ + +import process from 'node:process' +import { $fetch } from 'ofetch' +import { getGitDiff, loadChangelogConfig, parseCommits, generateMarkDown } from 'changelogen' + +const REPO = 'npmx-dev/npmx.dev' + +interface Contributor { + name: string + username: string +} + +async function resolveContributors( + rawCommits: Awaited>, +): Promise { + const contributors: Contributor[] = [] + const seenEmails = new Set() + const seenUsernames = new Set() + const token = process.env.GITHUB_TOKEN + + for (const commit of rawCommits) { + if ( + seenEmails.has(commit.author.email) || + commit.author.name.endsWith('[bot]') || + commit.author.email === 'noreply@github.com' + ) { + continue + } + seenEmails.add(commit.author.email) + + try { + const data = await $fetch<{ author: { login: string } | null }>( + `https://api.github.com/repos/${REPO}/commits/${commit.shortHash}`, + { + headers: { + 'User-Agent': REPO, + 'Accept': 'application/vnd.github.v3+json', + ...(token ? { Authorization: `token ${token}` } : {}), + }, + }, + ) + + if (data.author?.login && !seenUsernames.has(data.author.login)) { + seenUsernames.add(data.author.login) + contributors.push({ name: commit.author.name, username: data.author.login }) + } + } catch { + // If API call fails (rate limit, etc.), skip this contributor + } + } + + return contributors +} + +async function main() { + const from = process.argv[2] + const to = process.argv[3] || 'HEAD' + + if (!from) { + console.error('Usage: node scripts/release-notes.ts [to-ref]') + process.exit(1) + } + + const config = await loadChangelogConfig(process.cwd(), { from, to, noAuthors: true }) + const rawCommits = await getGitDiff(from, to) + const commits = parseCommits(rawCommits, config) + + // Generate changelog markdown via changelogen + const markdown = await generateMarkDown(commits, config) + + // Resolve contributors to GitHub @username mentions + const contributors = await resolveContributors(rawCommits) + + let output = markdown + + if (contributors.length > 0) { + const lines = contributors.map(c => `- ${c.name} (@${c.username})`).join('\n') + + output += `\n\n### ❤️ Contributors\n\n${lines}` + } + + console.log(output) +} + +main().catch(err => { + console.error(err) + process.exit(1) +}) From 1eb99f33634c82c8eaf0492f87ac165fe611d48a Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 3 Mar 2026 17:25:14 +0800 Subject: [PATCH 05/17] feat(i18n): complete some `zh-CN` translation (#1868) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- i18n/locales/zh-CN.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 432573e8f..4d2dd6d76 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -13,6 +13,7 @@ "trademark_disclaimer": "npm 是 npm, Inc. 的注册商标。本网站不与 npm, Inc. 有任何隶属关系。", "footer": { "about": "关于", + "blog": "博客", "docs": "文档", "source": "源码", "social": "社交媒体", @@ -77,6 +78,29 @@ "links": "链接", "tap_to_search": "点击搜索" }, + "blog": { + "title": "博客", + "heading": "博客", + "meta_description": "来自 npmx 社区的见解与更新", + "author": { + "view_profile": "在 Bluesky 上查看 {name} 的资料" + }, + "atproto": { + "view_on_bluesky": "在 Bluesky 上查看", + "reply_on_bluesky": "在 Bluesky 上回复", + "likes_on_bluesky": "Bluesky 上的点赞", + "like_or_reply_on_bluesky": "在 Bluesky 上点赞此帖子或发表评论", + "no_comments_yet": "暂无评论。", + "could_not_load_comments": "无法加载评论。", + "comments": "评论", + "loading_comments": "正在加载评论...", + "updating": "更新中...", + "reply_count": "{count} 条回复 | {count} 条回复", + "like_count": "{count} 个赞 | {count} 个赞", + "repost_count": "{count} 次转发 | {count} 次转发", + "more_replies": "还有 {count} 条回复... | 还有 {count} 条回复..." + } + }, "settings": { "title": "设置", "tagline": "定制你的 npmx 体验", @@ -447,7 +471,8 @@ }, "downloads": { "title": "每周下载量", - "community_distribution": "查看社区采用分布" + "community_distribution": "查看社区采用分布", + "subtitle": "涵盖所有版本" }, "install_scripts": { "title": "安装脚本", @@ -1107,7 +1132,10 @@ "file_changes": "文件更改", "files_count": "{count} 个文件", "lines_hidden": "已隐藏 {count} 行", + "file_too_large": "文件过大,无法对比", + "file_size_warning": "{size} 超出了 250KB 的对比限制", "compare_versions": "差异", + "compare_versions_title": "与最新版本比较", "summary": "摘要", "deps_count": "{count} 个依赖", "dependencies": "直接依赖项", From 0e8f84f149fc0061a8da68fbc747e80b10d13f49 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:36:03 +0000 Subject: [PATCH 06/17] docs: add e18e blog post link (#1870) Co-authored-by: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com> --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index f82c68253..57144e7a2 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -196,6 +196,12 @@ headline="Read more from the community" title: 'npmx Is Open-Source Done Right', authorHandle: 'vale.rocks', description: 'How the ethos and practices of npmx represent a healthy open-source ecosystem that should be the standard, not an exception.' + }, + { + url: 'https://e18e.dev/blog/npmx-collaboration.html', + title: 'Collaborating with npmx', + authorHandle: '43081j.com', + description: 'How the e18e community is collaborating closely with npmx to make best practices more visible and accessible to everyone in the ecosystem.' } ]" /> From 242ff1d579faca975df4f14ea59a55834f7aa415 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 09:40:56 +0000 Subject: [PATCH 07/17] ci: handle enormous initial changelog --- .github/workflows/release-pr.yml | 16 +++++++++++++++- scripts/release-notes.ts | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 30fcd5a60..09b9ec45e 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -111,8 +111,22 @@ jobs: 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 to avoid shell escaping issues + # 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' diff --git a/scripts/release-notes.ts b/scripts/release-notes.ts index 3ff8a38ef..619bbdd4a 100644 --- a/scripts/release-notes.ts +++ b/scripts/release-notes.ts @@ -15,6 +15,8 @@ import { $fetch } from 'ofetch' import { getGitDiff, loadChangelogConfig, parseCommits, generateMarkDown } from 'changelogen' const REPO = 'npmx-dev/npmx.dev' +const MAX_RELEASE_BODY_LENGTH = 120_000 +const MAX_CONTRIBUTOR_LOOKUPS = 100 interface Contributor { name: string @@ -29,7 +31,9 @@ async function resolveContributors( const seenUsernames = new Set() const token = process.env.GITHUB_TOKEN + let lookups = 0 for (const commit of rawCommits) { + if (lookups >= MAX_CONTRIBUTOR_LOOKUPS) break if ( seenEmails.has(commit.author.email) || commit.author.name.endsWith('[bot]') || @@ -38,6 +42,7 @@ async function resolveContributors( continue } seenEmails.add(commit.author.email) + lookups++ try { const data = await $fetch<{ author: { login: string } | null }>( @@ -86,10 +91,22 @@ async function main() { if (contributors.length > 0) { const lines = contributors.map(c => `- ${c.name} (@${c.username})`).join('\n') - output += `\n\n### ❤️ Contributors\n\n${lines}` } + // Truncate if too long for GitHub Release body (125K limit) + if (output.length > MAX_RELEASE_BODY_LENGTH) { + const compareUrl = `https://github.com/${REPO}/compare/${from}...${to === 'HEAD' ? 'release' : to}` + output = + `> This release includes ${rawCommits.length} commits. The full changelog is too large to display here.\n>\n` + + `> [View full diff on GitHub](${compareUrl})\n` + + if (contributors.length > 0) { + const lines = contributors.map(c => `- ${c.name} (@${c.username})`).join('\n') + output += `\n### ❤️ Contributors\n\n${lines}` + } + } + console.log(output) } From 69a46d959ddc2c9de447ba237367f3b946a731ae Mon Sep 17 00:00:00 2001 From: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:47:04 +0000 Subject: [PATCH 08/17] chore: switch alpha release blog post to live (#1869) --- app/pages/blog/alpha-release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 57144e7a2..f97d3c541 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -12,7 +12,7 @@ excerpt: "Today we're releasing the alpha of npmx.dev – a fast, modern browser date: '2026-03-03' slug: 'alpha-release' description: "Today we're releasing the alpha of npmx.dev – a fast, modern browser for the npm registry, built in the open by a growing community." -draft: true +draft: false --- # Announcing npmx: a fast, modern browser for the npm registry From b6c7c1f9d3bd44a940937258ac0a01695508cab6 Mon Sep 17 00:00:00 2001 From: patak <583075+patak-cat@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:00:06 +0100 Subject: [PATCH 09/17] docs: add atprotocol.dev to alpha launch (#1850) Co-authored-by: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com> --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index f97d3c541..783c59c88 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -185,6 +185,12 @@ headline="Read more from the community" authorHandle: 'philippeserhal.com', description: 'How getting involved in npmx made me a better developer.' }, + { + url: 'https://news.atmosphereconf.org/3mg5b3zvktc2i', + title: 'npmx goes social with atproto', + authorHandle: 'atprotocol.dev', + description: 'Announcing npmx speakers, and congratulations on launch day!' + }, { url: 'https://www.radosvet.dev/posts/career/from-newsletter-to-open-source', title: 'From a Newsletter Link to My First Open Source Contribution', From e54e947d2b86e90760299c395ff3a713eeca5351 Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Tue, 3 Mar 2026 12:04:24 +0100 Subject: [PATCH 10/17] docs: add video to web ring (#1873) Co-authored-by: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com> --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 783c59c88..82f63b15d 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -208,6 +208,12 @@ headline="Read more from the community" title: 'Collaborating with npmx', authorHandle: '43081j.com', description: 'How the e18e community is collaborating closely with npmx to make best practices more visible and accessible to everyone in the ecosystem.' + }, + { + url: 'https://youtu.be/NoC5U6F6p4Y', + title: 'The npmjs.com that developers deserve - What is npmx? (video)', + authorHandle: 'thealexlichter.com', + description: 'An introductory video showcasing Alex\'s favorite features of npmx and the open-source idea behind it.' } ]" /> From 196e7958708e6c820f17602dc54b3859893bc243 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 3 Mar 2026 11:26:09 +0000 Subject: [PATCH 11/17] chore: update graphieros.npmx.social handle in blog post --- app/pages/blog/alpha-release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 82f63b15d..08ed79647 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -104,7 +104,7 @@ headline="Read more from the community" { url: 'https://graphieros.github.io/graphieros-blog/blog/2026/npmx.html', title: 'vue-data-ui is on npmx npmx is on vue-data-ui', - authorHandle: 'graphieros.com', + authorHandle: 'graphieros.npmx.social', description: 'Graphieros explores a minimal npm-based workflow and why it exists.' }, { From 2427057324d4c0dff063bfa6d89e172120ea0c3b Mon Sep 17 00:00:00 2001 From: Jaydip Sanghani <91427591+jellydeck@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:54:20 +0530 Subject: [PATCH 12/17] chore: add link of "jaydip.me" to alpha release blog (#1875) --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 08ed79647..20ef5d5e6 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -203,6 +203,12 @@ headline="Read more from the community" authorHandle: 'vale.rocks', description: 'How the ethos and practices of npmx represent a healthy open-source ecosystem that should be the standard, not an exception.' }, + { + url: 'https://jaydip.me/blog/joy-of-open-source', + title: 'Joy of open source', + authorHandle: 'jaydip.me', + description: 'childish fun of making things together' + }, { url: 'https://e18e.dev/blog/npmx-collaboration.html', title: 'Collaborating with npmx', From 4916f8552eb129f210ed0d6e06f94dfc0f123cbe Mon Sep 17 00:00:00 2001 From: Alex Korytskyi Date: Tue, 3 Mar 2026 11:33:35 +0000 Subject: [PATCH 13/17] fix: hotfix for complex versions urls (#1874) --- app/utils/router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/utils/router.ts b/app/utils/router.ts index 1b8ce5d40..0e7362a8f 100644 --- a/app/utils/router.ts +++ b/app/utils/router.ts @@ -9,7 +9,8 @@ export function packageRoute(packageName: string, version?: string | null): Rout params: { org, name, - version, + // remove spaces to be correctly resolved by router + version: version.replace(/\s+/g, ''), }, } } From b1d4b0537660b43701913044f1aed6fa7bc42abe Mon Sep 17 00:00:00 2001 From: Andy Bell Date: Tue, 3 Mar 2026 11:37:11 +0000 Subject: [PATCH 14/17] chore: abbey's picallili post (#1876) Co-authored-by: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com> --- app/pages/blog/alpha-release.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/pages/blog/alpha-release.md b/app/pages/blog/alpha-release.md index 20ef5d5e6..24780f367 100644 --- a/app/pages/blog/alpha-release.md +++ b/app/pages/blog/alpha-release.md @@ -220,6 +220,12 @@ headline="Read more from the community" title: 'The npmjs.com that developers deserve - What is npmx? (video)', authorHandle: 'thealexlichter.com', description: 'An introductory video showcasing Alex\'s favorite features of npmx and the open-source idea behind it.' + }, + { + url: 'https://piccalil.li/blog/finding-an-accessibility-first-culture-in-npmx/', + title: 'Finding an accessibility-first culture in npmx', + authorHandle: 'abbeyperini.dev', + description: 'Abbey Perini talks about how accessibility is a deep part of the npmx culture.' } ]" /> From 1b8421afb0fa5b3c5678c55b92730abf4604fc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Uriel=20Mart=C3=ADnez=20Castillo?= Date: Tue, 3 Mar 2026 12:39:33 +0100 Subject: [PATCH 15/17] feat: direct to different discord server on production, and update env settings (#1854) Co-authored-by: Daniel Roe --- README.md | 1 + app/components/AppFooter.vue | 5 +- app/components/AppHeader.vue | 5 +- app/components/CallToAction.vue | 9 +- app/composables/useDiscordLink.ts | 30 +++++ config/env.ts | 19 +-- i18n/locales/en.json | 6 + i18n/schema.json | 18 +++ shared/utils/constants.ts | 4 + test/nuxt/components/BuildEnvironment.spec.ts | 27 ++++ test/unit/config/env.spec.ts | 119 ++++++++++++++++++ 11 files changed, 227 insertions(+), 16 deletions(-) create mode 100644 app/composables/useDiscordLink.ts diff --git a/README.md b/README.md index 1717303dd..ca49a74f7 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 49fcdf3a7..d89d3cf3d 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 18801fca3..5febbd6fa 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 8ed0f0c15..ba79780c9 100644 --- a/app/components/CallToAction.vue +++ b/app/components/CallToAction.vue @@ -1,4 +1,5 @@