Mirror of
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ci: sync template files [skip ci] (#4)

* ci: update GitHub template files

* [autofix.ci] apply automated fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

+1153 -621
+34
.github/labeler.yaml
··· 1 + # See https://github.com/actions/labeler/tree/v5 2 + 3 + "🚨 action": 4 + - changed-files: 5 + - any-glob-to-any-file: .github/workflows/** 6 + 7 + "📝 changeset": 8 + - changed-files: 9 + - any-glob-to-any-file: "**/.changeset/**.{md,mdx}" 10 + 11 + "🚧 config": 12 + - changed-files: 13 + - any-glob-to-any-file: "**/*config*.{js,ts,jsx,tsx,mjs,mts,json,yml,yaml,toml,cjs,cts}" 14 + 15 + "✒️ documentation": 16 + - changed-files: 17 + - any-glob-to-any-file: "**/README.md" 18 + 19 + "🌏 i18n": 20 + - changed-files: 21 + - all-globs-to-any-file: ["**/docs/**", "!**/docs/en/**"] 22 + 23 + "🚀 manifest": 24 + - changed-files: 25 + - any-glob-to-any-file: "manifest*/**" 26 + 27 + "📦 package": 28 + - changed-files: 29 + - any-glob-to-any-file: "**/packages/**" 30 + - any-glob-to-any-file: "**/package.json" 31 + 32 + "🏯 styles": 33 + - changed-files: 34 + - any-glob-to-any-file: "**/*.{css,scss,sass,less,styl}"
+35
.github/renovate.json
··· 1 + { 2 + "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 + "extends": [ 4 + ":disableDependencyDashboard", 5 + ":disablePeerDependencies", 6 + ":semanticPrefixFixDepsChoreOthers", 7 + ":ignoreModulesAndTests", 8 + "workarounds:all", 9 + "helpers:pinGitHubActionDigestsToSemver", 10 + "docker:disable" 11 + ], 12 + "rangeStrategy": "bump", 13 + "ignorePaths": ["**/node_modules/**"], 14 + "commitMessageSuffix": "[skip ci]", 15 + "assignees": [], 16 + "reviewers": [], 17 + "packageRules": [ 18 + { 19 + "groupName": "github-actions", 20 + "matchManagers": ["github-actions"] 21 + }, 22 + { 23 + "groupName": "npm-packages", 24 + "matchManagers": ["npm"], 25 + "matchDepTypes": ["dependencies", "devDependencies"], 26 + "separateMajorMinor": true 27 + }, 28 + { 29 + "description": "Disable package manager version updates", 30 + "matchPackageNames": ["pnpm"], 31 + "matchDepTypes": ["packageManager"], 32 + "enabled": false 33 + } 34 + ] 35 + }
+42
.github/workflows/format.yaml
··· 1 + name: autofix.ci 2 + on: 3 + pull_request: 4 + push: 5 + branches: [main] 6 + permissions: 7 + contents: read 8 + 9 + env: 10 + NODE_VERSION: 24.10.0 11 + 12 + jobs: 13 + autofix: 14 + runs-on: ubuntu-latest 15 + steps: 16 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 17 + with: 18 + persist-credentials: false 19 + 20 + - name: Setup PNPM 21 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 22 + 23 + - name: Setup Node 24 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 25 + with: 26 + node-version: ${{ env.NODE_VERSION }} 27 + cache: "pnpm" 28 + 29 + - name: Install Dependencies 30 + run: pnpm i 31 + 32 + - name: Run prettier 33 + run: pnpm exec prettier . --write 34 + 35 + # Optimize all PNGs with https://pngquant.org/ 36 + - run: sudo apt-get update && sudo apt-get install -y pngquant 37 + - name: Run pngquant 38 + run: | 39 + shopt -s globstar 40 + find . -name '*.png' -exec pngquant --ext .png 256 {} \; 41 + 42 + - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8
+57
.github/workflows/labeler.yaml
··· 1 + name: "Pull Request Labeler" 2 + on: 3 + - pull_request_target 4 + 5 + jobs: 6 + labeler: 7 + permissions: 8 + contents: read 9 + pull-requests: write 10 + runs-on: ubuntu-latest 11 + steps: 12 + - name: Generate GitHub App token 13 + id: generate_token 14 + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 15 + with: 16 + app_id: ${{ secrets.BOT_APP_ID }} 17 + private_key: ${{ secrets.BOT_PRIVATE_KEY }} 18 + 19 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 20 + with: 21 + persist-credentials: false 22 + - name: Ensure labels exist 23 + env: 24 + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 25 + run: | 26 + gh label delete "bug" --yes || true 27 + gh label delete "documentation" --yes || true 28 + gh label delete "duplicate" --yes || true 29 + gh label delete "enhancement" --yes || true 30 + gh label delete "good first issue" --yes || true 31 + gh label delete "help wanted" --yes || true 32 + gh label delete "invalid" --yes || true 33 + gh label delete "question" --yes || true 34 + gh label delete "wontfix" --yes || true 35 + 36 + gh label create "🚨 action" --description "Changes in GitHub workflows or actions" --color "A75AD5" --force 37 + gh label create "🤖 bot" --description "Automatically generated pull request" --color "0075CA" --force 38 + gh label create "🐛 bug" --description "Something isn't working" --color "D73A4A" --force 39 + gh label create "📝 changeset" --description "Contains changeset files" --color "304EF9" --force 40 + gh label create "🚧 config" --description "Configuration file updates" --color "C0ED4F" --force 41 + gh label create "✒️ documentation" --description "Documentation updates, like README changes" --color "66741D" --force 42 + gh label create "🔁 duplicate" --description "This issue or pull request already exists" --color "008672" --force 43 + gh label create "⏫ enhancement" --description "New feature or request" --color "3C11FD" --force 44 + gh label create "🥇 good first issue" --description "Good for newcomers" --color "7057FF" --force 45 + gh label create "🆘 help wanted" --description "Extra attention is needed" --color "BFD4F2" --force 46 + gh label create "🌏 i18n" --description "Updates to internationalized docs, excluding English" --color "006B75" --force 47 + gh label create "👀 invalid" --description "This doesn't seem right" --color "E4E669" --force 48 + gh label create "🚀 manifest" --description "Manifest-related changes" --color "96D3D7" --force 49 + gh label create "📦 package" --description "Updates in package structure or package.json" --color "F34A37" --force 50 + gh label create "❓ question" --description "Further information is requested" --color "D876E3" --force 51 + gh label create "🏯 styles" --description "Stylesheets or design updates" --color "550F5A" --force 52 + gh label create "🔒 wontfix" --description "This will not be worked on" --color "FFFFFF" --force 53 + 54 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 55 + with: 56 + configuration-path: .github/labeler.yaml 57 + sync-labels: true
+45
.github/workflows/welcome-bot.yaml
··· 1 + name: WelcomeBot 2 + 3 + on: 4 + pull_request_target: 5 + branches: [main] 6 + types: [opened] 7 + 8 + permissions: 9 + pull-requests: write 10 + 11 + jobs: 12 + welcome: 13 + name: Welcome First-Time Contributors 14 + runs-on: ubuntu-latest 15 + steps: 16 + - name: Generate GitHub App token 17 + id: generate_token 18 + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 19 + with: 20 + app_id: ${{ secrets.BOT_APP_ID }} 21 + private_key: ${{ secrets.BOT_PRIVATE_KEY }} 22 + 23 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 24 + with: 25 + persist-credentials: false 26 + - name: Convert Repository Name to Title Case 27 + id: convert_repo_name 28 + run: | 29 + REPO_NAME="${{ github.event.repository.name }}" 30 + TITLE_CASE_REPO_NAME=$(echo "$REPO_NAME" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))} 1') 31 + echo "title_case_repo_name=$TITLE_CASE_REPO_NAME" >> $GITHUB_ENV 32 + - uses: zephyrproject-rtos/action-first-interaction@58853996b1ac504b8e0f6964301f369d2bb22e5c 33 + with: 34 + repo-token: ${{ steps.generate_token.outputs.token }} 35 + pr-opened-message: | 36 + Hello! Thank you for opening your **first PR** to ${{ env.title_case_repo_name }}! ✨ 37 + 38 + Here’s what will happen next: 39 + 40 + 1. Our GitHub bots will run to check your changes. 41 + If they spot any issues you will see some error messages on this PR. 42 + Don’t hesitate to ask any questions if you’re not sure what these mean! 43 + 44 + 2. One or more of our maintainers will take a look and may ask you to make changes. 45 + We try to be responsive, but don’t worry if this takes a few days.
+44
.prettierignore
··· 1 + # Dependency directories 2 + node_modules/ 3 + dist/ 4 + build/ 5 + out/ 6 + 7 + # Lock files 8 + pnpm-lock.yaml 9 + package-lock.json 10 + yarn.lock 11 + 12 + # Build and generated files 13 + *.min.* 14 + *.bundle.* 15 + *.map 16 + 17 + # Git and version control 18 + **/.git 19 + 20 + # Framework-specific files 21 + next-env.d.ts 22 + __generated__/ 23 + 24 + # Test and coverage files 25 + coverage/ 26 + *.spec.* 27 + *.test.* 28 + 29 + # Editor-specific files 30 + .vscode/ 31 + .idea/ 32 + *.sublime-project 33 + *.sublime-workspace 34 + 35 + # OS generated files 36 + .DS_Store 37 + Thumbs.db 38 + 39 + # Markdown and MDX files 40 + *.md 41 + *.mdx 42 + 43 + # Changelog 44 + .changeset
+29
.prettierrc
··· 1 + { 2 + "plugins": ["prettier-plugin-astro", "@trivago/prettier-plugin-sort-imports"], 3 + "trailingComma": "es5", 4 + "tabWidth": 2, 5 + "semi": true, 6 + "singleQuote": false, 7 + "endOfLine": "lf", 8 + "htmlWhitespaceSensitivity": "css", 9 + "overrides": [ 10 + { 11 + "files": "*.astro", 12 + "options": { 13 + "parser": "astro" 14 + } 15 + } 16 + ], 17 + "importOrder": [ 18 + "<THIRD_PARTY_MODULES>", 19 + "^@internal/(.*)$", 20 + "^@/models/(.*)$", 21 + "^@/utils/(.*)$", 22 + "^@/components/(?!ui/)(.*)$", 23 + "^@/components/ui/(.*)$", 24 + "^[./].*(?<!\\.(c|le|sc)ss)$", 25 + "^[.]/[-a-zA-Z0-9_]+[.](module)[.](css|scss|less)$" 26 + ], 27 + "importOrderSeparation": true, 28 + "importOrderSortSpecifiers": true 29 + }
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2026-present, trueberryless 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+1 -1
astro.config.mjs
··· 1 1 // @ts-check 2 - import { defineConfig } from 'astro/config'; 2 + import { defineConfig } from "astro/config"; 3 3 4 4 // https://astro.build/config 5 5 export default defineConfig({});
+29 -23
package.json
··· 1 1 { 2 2 "name": "npmx-digest", 3 - "type": "module", 4 3 "version": "0.0.1", 4 + "private": true, 5 5 "description": "Get the latest news around npmx.", 6 + "keywords": [ 7 + "npmx", 8 + "newsletter", 9 + "blog", 10 + "automation", 11 + "daily", 12 + "nightly" 13 + ], 14 + "homepage": "https://github.com/trueberryless-org/npmx-digest", 15 + "bugs": { 16 + "url": "https://github.com/trueberryless-org/npmx-digest/issues" 17 + }, 18 + "repository": { 19 + "type": "git", 20 + "url": "https://github.com/trueberryless-org/npmx-digest.git" 21 + }, 22 + "license": "MIT", 6 23 "author": "trueberryless <trueberryless@gmail.com> (https://trueberryless.org)", 24 + "type": "module", 7 25 "scripts": { 8 - "dev": "astro dev", 9 - "build": "astro check && astro build", 10 - "preview": "astro preview", 11 26 "astro": "astro", 12 - "generate": "tsx scripts/generate-digest.ts", 13 - "local:generate": "tsx --env-file=.env scripts/generate-digest.ts", 14 27 "backfill": "tsx scripts/backfill.ts", 28 + "build": "astro check && astro build", 29 + "dev": "astro dev", 30 + "generate": "tsx scripts/generate-digest.ts", 15 31 "local:backfill": "tsx --env-file=.env scripts/backfill.ts", 16 - "local:quota": "tsx --env-file=.env scripts/quota.ts" 32 + "local:generate": "tsx --env-file=.env scripts/generate-digest.ts", 33 + "local:quota": "tsx --env-file=.env scripts/quota.ts", 34 + "preview": "astro preview" 17 35 }, 18 36 "dependencies": { 19 37 "@astrojs/check": "^0.9.6", ··· 21 39 "typescript": "^5.9.3" 22 40 }, 23 41 "devDependencies": { 42 + "@trivago/prettier-plugin-sort-imports": "6.0.2", 24 43 "@types/node": "^22.0.0", 44 + "prettier": "3.8.1", 45 + "prettier-plugin-astro": "0.14.1", 25 46 "tsx": "^4.19.0", 26 47 "zod": "^3.25.76" 27 48 }, 28 - "packageManager": "pnpm@10.10.0", 29 - "private": true, 30 - "keywords": [ 31 - "npmx", 32 - "newsletter", 33 - "blog", 34 - "automation", 35 - "daily", 36 - "nightly" 37 - ], 38 - "homepage": "https://github.com/trueberryless-org/npmx-digest", 39 - "repository": { 40 - "type": "git", 41 - "url": "https://github.com/trueberryless-org/npmx-digest.git" 42 - }, 43 - "bugs": "https://github.com/trueberryless-org/npmx-digest/issues" 49 + "packageManager": "pnpm@10.10.0" 44 50 }
+206 -4
pnpm-lock.yaml
··· 10 10 dependencies: 11 11 '@astrojs/check': 12 12 specifier: ^0.9.6 13 - version: 0.9.6(prettier@3.8.1)(typescript@5.9.3) 13 + version: 0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3) 14 14 astro: 15 15 specifier: ^5.17.1 16 16 version: 5.17.1(@types/node@22.19.7)(rollup@4.57.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) ··· 18 18 specifier: ^5.9.3 19 19 version: 5.9.3 20 20 devDependencies: 21 + '@trivago/prettier-plugin-sort-imports': 22 + specifier: 6.0.2 23 + version: 6.0.2(prettier@3.8.1) 21 24 '@types/node': 22 25 specifier: ^22.0.0 23 26 version: 22.19.7 27 + prettier: 28 + specifier: 3.8.1 29 + version: 3.8.1 30 + prettier-plugin-astro: 31 + specifier: 0.14.1 32 + version: 0.14.1 24 33 tsx: 25 34 specifier: ^4.19.0 26 35 version: 4.21.0 ··· 68 77 '@astrojs/yaml2ts@0.2.2': 69 78 resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} 70 79 80 + '@babel/code-frame@7.29.0': 81 + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} 82 + engines: {node: '>=6.9.0'} 83 + 84 + '@babel/generator@7.29.0': 85 + resolution: {integrity: sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==} 86 + engines: {node: '>=6.9.0'} 87 + 88 + '@babel/helper-globals@7.28.0': 89 + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 90 + engines: {node: '>=6.9.0'} 91 + 71 92 '@babel/helper-string-parser@7.27.1': 72 93 resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 73 94 engines: {node: '>=6.9.0'} ··· 80 101 resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} 81 102 engines: {node: '>=6.0.0'} 82 103 hasBin: true 104 + 105 + '@babel/template@7.28.6': 106 + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} 107 + engines: {node: '>=6.9.0'} 108 + 109 + '@babel/traverse@7.29.0': 110 + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} 111 + engines: {node: '>=6.9.0'} 83 112 84 113 '@babel/types@7.29.0': 85 114 resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} ··· 562 591 cpu: [x64] 563 592 os: [win32] 564 593 594 + '@jridgewell/gen-mapping@0.3.13': 595 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 596 + 597 + '@jridgewell/resolve-uri@3.1.2': 598 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 599 + engines: {node: '>=6.0.0'} 600 + 565 601 '@jridgewell/sourcemap-codec@1.5.5': 566 602 resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 603 + 604 + '@jridgewell/trace-mapping@0.3.31': 605 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 567 606 568 607 '@oslojs/encoding@1.1.0': 569 608 resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} ··· 723 762 '@shikijs/vscode-textmate@10.0.2': 724 763 resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} 725 764 765 + '@trivago/prettier-plugin-sort-imports@6.0.2': 766 + resolution: {integrity: sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==} 767 + engines: {node: '>= 20'} 768 + peerDependencies: 769 + '@vue/compiler-sfc': 3.x 770 + prettier: 2.x - 3.x 771 + prettier-plugin-ember-template-tag: '>= 2.0.0' 772 + prettier-plugin-svelte: 3.x 773 + svelte: 4.x || 5.x 774 + peerDependenciesMeta: 775 + '@vue/compiler-sfc': 776 + optional: true 777 + prettier-plugin-ember-template-tag: 778 + optional: true 779 + prettier-plugin-svelte: 780 + optional: true 781 + svelte: 782 + optional: true 783 + 726 784 '@types/debug@4.1.12': 727 785 resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} 728 786 ··· 837 895 bail@2.0.2: 838 896 resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} 839 897 898 + balanced-match@1.0.2: 899 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 900 + 840 901 base-64@1.0.0: 841 902 resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} 842 903 ··· 846 907 boxen@8.0.1: 847 908 resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} 848 909 engines: {node: '>=18'} 910 + 911 + brace-expansion@2.0.2: 912 + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 849 913 850 914 camelcase@8.0.0: 851 915 resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} ··· 1167 1231 resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} 1168 1232 engines: {node: '>=16'} 1169 1233 1234 + javascript-natural-sort@0.7.1: 1235 + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} 1236 + 1237 + js-tokens@4.0.0: 1238 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1239 + 1170 1240 js-yaml@4.1.1: 1171 1241 resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 1242 + hasBin: true 1243 + 1244 + jsesc@3.1.0: 1245 + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 1246 + engines: {node: '>=6'} 1172 1247 hasBin: true 1173 1248 1174 1249 json-schema-traverse@1.0.0: ··· 1188 1263 resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 1189 1264 engines: {node: '>=6'} 1190 1265 1266 + lodash-es@4.17.23: 1267 + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} 1268 + 1191 1269 lodash@4.17.21: 1192 1270 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1193 1271 ··· 1336 1414 micromark@4.0.2: 1337 1415 resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} 1338 1416 1417 + minimatch@9.0.5: 1418 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1419 + engines: {node: '>=16 || 14 >=14.17'} 1420 + 1339 1421 mrmime@2.0.1: 1340 1422 resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} 1341 1423 engines: {node: '>=10'} ··· 1398 1480 package-manager-detector@1.6.0: 1399 1481 resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} 1400 1482 1483 + parse-imports-exports@0.2.4: 1484 + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} 1485 + 1401 1486 parse-latin@7.0.0: 1402 1487 resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} 1488 + 1489 + parse-statements@1.0.11: 1490 + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} 1403 1491 1404 1492 parse5@7.3.0: 1405 1493 resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} ··· 1425 1513 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1426 1514 engines: {node: ^10 || ^12 || >=14} 1427 1515 1516 + prettier-plugin-astro@0.14.1: 1517 + resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} 1518 + engines: {node: ^14.15.0 || >=16.0.0} 1519 + 1428 1520 prettier@3.8.1: 1429 1521 resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} 1430 1522 engines: {node: '>=14'} ··· 1523 1615 engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1524 1616 hasBin: true 1525 1617 1618 + s.color@0.0.15: 1619 + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} 1620 + 1621 + sass-formatter@0.7.9: 1622 + resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} 1623 + 1526 1624 sax@1.4.4: 1527 1625 resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} 1528 1626 engines: {node: '>=11.0.0'} ··· 1571 1669 strip-ansi@7.1.2: 1572 1670 resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} 1573 1671 engines: {node: '>=12'} 1672 + 1673 + suf-log@2.5.3: 1674 + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} 1574 1675 1575 1676 svgo@4.0.0: 1576 1677 resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} ··· 1962 2063 1963 2064 snapshots: 1964 2065 1965 - '@astrojs/check@0.9.6(prettier@3.8.1)(typescript@5.9.3)': 2066 + '@astrojs/check@0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)': 1966 2067 dependencies: 1967 - '@astrojs/language-server': 2.16.3(prettier@3.8.1)(typescript@5.9.3) 2068 + '@astrojs/language-server': 2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3) 1968 2069 chokidar: 4.0.3 1969 2070 kleur: 4.1.5 1970 2071 typescript: 5.9.3 ··· 1977 2078 1978 2079 '@astrojs/internal-helpers@0.7.5': {} 1979 2080 1980 - '@astrojs/language-server@2.16.3(prettier@3.8.1)(typescript@5.9.3)': 2081 + '@astrojs/language-server@2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)': 1981 2082 dependencies: 1982 2083 '@astrojs/compiler': 2.13.0 1983 2084 '@astrojs/yaml2ts': 0.2.2 ··· 1999 2100 vscode-uri: 3.1.0 2000 2101 optionalDependencies: 2001 2102 prettier: 3.8.1 2103 + prettier-plugin-astro: 0.14.1 2002 2104 transitivePeerDependencies: 2003 2105 - typescript 2004 2106 ··· 2048 2150 dependencies: 2049 2151 yaml: 2.8.2 2050 2152 2153 + '@babel/code-frame@7.29.0': 2154 + dependencies: 2155 + '@babel/helper-validator-identifier': 7.28.5 2156 + js-tokens: 4.0.0 2157 + picocolors: 1.1.1 2158 + 2159 + '@babel/generator@7.29.0': 2160 + dependencies: 2161 + '@babel/parser': 7.29.0 2162 + '@babel/types': 7.29.0 2163 + '@jridgewell/gen-mapping': 0.3.13 2164 + '@jridgewell/trace-mapping': 0.3.31 2165 + jsesc: 3.1.0 2166 + 2167 + '@babel/helper-globals@7.28.0': {} 2168 + 2051 2169 '@babel/helper-string-parser@7.27.1': {} 2052 2170 2053 2171 '@babel/helper-validator-identifier@7.28.5': {} ··· 2055 2173 '@babel/parser@7.29.0': 2056 2174 dependencies: 2057 2175 '@babel/types': 7.29.0 2176 + 2177 + '@babel/template@7.28.6': 2178 + dependencies: 2179 + '@babel/code-frame': 7.29.0 2180 + '@babel/parser': 7.29.0 2181 + '@babel/types': 7.29.0 2182 + 2183 + '@babel/traverse@7.29.0': 2184 + dependencies: 2185 + '@babel/code-frame': 7.29.0 2186 + '@babel/generator': 7.29.0 2187 + '@babel/helper-globals': 7.28.0 2188 + '@babel/parser': 7.29.0 2189 + '@babel/template': 7.28.6 2190 + '@babel/types': 7.29.0 2191 + debug: 4.4.3 2192 + transitivePeerDependencies: 2193 + - supports-color 2058 2194 2059 2195 '@babel/types@7.29.0': 2060 2196 dependencies: ··· 2346 2482 '@img/sharp-win32-x64@0.34.5': 2347 2483 optional: true 2348 2484 2485 + '@jridgewell/gen-mapping@0.3.13': 2486 + dependencies: 2487 + '@jridgewell/sourcemap-codec': 1.5.5 2488 + '@jridgewell/trace-mapping': 0.3.31 2489 + 2490 + '@jridgewell/resolve-uri@3.1.2': {} 2491 + 2349 2492 '@jridgewell/sourcemap-codec@1.5.5': {} 2493 + 2494 + '@jridgewell/trace-mapping@0.3.31': 2495 + dependencies: 2496 + '@jridgewell/resolve-uri': 3.1.2 2497 + '@jridgewell/sourcemap-codec': 1.5.5 2350 2498 2351 2499 '@oslojs/encoding@1.1.0': {} 2352 2500 ··· 2466 2614 2467 2615 '@shikijs/vscode-textmate@10.0.2': {} 2468 2616 2617 + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.1)': 2618 + dependencies: 2619 + '@babel/generator': 7.29.0 2620 + '@babel/parser': 7.29.0 2621 + '@babel/traverse': 7.29.0 2622 + '@babel/types': 7.29.0 2623 + javascript-natural-sort: 0.7.1 2624 + lodash-es: 4.17.23 2625 + minimatch: 9.0.5 2626 + parse-imports-exports: 0.2.4 2627 + prettier: 3.8.1 2628 + transitivePeerDependencies: 2629 + - supports-color 2630 + 2469 2631 '@types/debug@4.1.12': 2470 2632 dependencies: 2471 2633 '@types/ms': 2.1.0 ··· 2688 2850 2689 2851 bail@2.0.2: {} 2690 2852 2853 + balanced-match@1.0.2: {} 2854 + 2691 2855 base-64@1.0.0: {} 2692 2856 2693 2857 boolbase@1.0.0: {} ··· 2702 2866 type-fest: 4.41.0 2703 2867 widest-line: 5.0.0 2704 2868 wrap-ansi: 9.0.2 2869 + 2870 + brace-expansion@2.0.2: 2871 + dependencies: 2872 + balanced-match: 1.0.2 2705 2873 2706 2874 camelcase@8.0.0: {} 2707 2875 ··· 3073 3241 dependencies: 3074 3242 is-inside-container: 1.0.0 3075 3243 3244 + javascript-natural-sort@0.7.1: {} 3245 + 3246 + js-tokens@4.0.0: {} 3247 + 3076 3248 js-yaml@4.1.1: 3077 3249 dependencies: 3078 3250 argparse: 2.0.1 3079 3251 3252 + jsesc@3.1.0: {} 3253 + 3080 3254 json-schema-traverse@1.0.0: {} 3081 3255 3082 3256 jsonc-parser@2.3.1: {} ··· 3086 3260 kleur@3.0.3: {} 3087 3261 3088 3262 kleur@4.1.5: {} 3263 + 3264 + lodash-es@4.17.23: {} 3089 3265 3090 3266 lodash@4.17.21: {} 3091 3267 ··· 3420 3596 transitivePeerDependencies: 3421 3597 - supports-color 3422 3598 3599 + minimatch@9.0.5: 3600 + dependencies: 3601 + brace-expansion: 2.0.2 3602 + 3423 3603 mrmime@2.0.1: {} 3424 3604 3425 3605 ms@2.1.3: {} ··· 3473 3653 3474 3654 package-manager-detector@1.6.0: {} 3475 3655 3656 + parse-imports-exports@0.2.4: 3657 + dependencies: 3658 + parse-statements: 1.0.11 3659 + 3476 3660 parse-latin@7.0.0: 3477 3661 dependencies: 3478 3662 '@types/nlcst': 2.0.3 ··· 3481 3665 unist-util-modify-children: 4.0.0 3482 3666 unist-util-visit-children: 3.0.0 3483 3667 vfile: 6.0.3 3668 + 3669 + parse-statements@1.0.11: {} 3484 3670 3485 3671 parse5@7.3.0: 3486 3672 dependencies: ··· 3502 3688 picocolors: 1.1.1 3503 3689 source-map-js: 1.2.1 3504 3690 3691 + prettier-plugin-astro@0.14.1: 3692 + dependencies: 3693 + '@astrojs/compiler': 2.13.0 3694 + prettier: 3.8.1 3695 + sass-formatter: 0.7.9 3696 + 3505 3697 prettier@3.8.1: {} 3506 3698 3507 3699 prismjs@1.30.0: {} ··· 3661 3853 '@rollup/rollup-win32-x64-msvc': 4.57.1 3662 3854 fsevents: 2.3.3 3663 3855 3856 + s.color@0.0.15: {} 3857 + 3858 + sass-formatter@0.7.9: 3859 + dependencies: 3860 + suf-log: 2.5.3 3861 + 3664 3862 sax@1.4.4: {} 3665 3863 3666 3864 semver@7.7.3: {} ··· 3740 3938 strip-ansi@7.1.2: 3741 3939 dependencies: 3742 3940 ansi-regex: 6.2.2 3941 + 3942 + suf-log@2.5.3: 3943 + dependencies: 3944 + s.color: 0.0.15 3743 3945 3744 3946 svgo@4.0.0: 3745 3947 dependencies:
+8 -8
scripts/backfill.ts
··· 1 - import { z } from "zod"; 2 1 import { spawn } from "child_process"; 2 + import { z } from "zod"; 3 3 4 4 const END_TIME_CONSTANT = "2026-02-03T14:00:00Z"; 5 5 const WINDOW_HOURS = 8; ··· 38 38 return new Promise((resolve, reject) => { 39 39 const child = spawn(command, args); 40 40 child.on("error", () => 41 - reject(new Error(`Failed to use ${command}. Is it installed?`)), 41 + reject(new Error(`Failed to use ${command}. Is it installed?`)) 42 42 ); 43 43 child.stdin.write(text); 44 44 child.stdin.end(); ··· 54 54 55 55 export async function fetchGitHubEvents( 56 56 start: Date, 57 - end: Date, 57 + end: Date 58 58 ): Promise<Event[]> { 59 59 const owner = "npmx-dev"; 60 60 const repo = "npmx.dev"; ··· 65 65 const endIso = end.toISOString().split(".")[0] + "Z"; 66 66 67 67 const query = encodeURIComponent( 68 - `repo:${owner}/${repo} is:closed reason:completed -is:unmerged closed:${startIso}..${endIso}`, 68 + `repo:${owner}/${repo} is:closed reason:completed -is:unmerged closed:${startIso}..${endIso}` 69 69 ); 70 70 71 71 const headers = { ··· 77 77 try { 78 78 const response = await fetch( 79 79 `https://api.github.com/search/issues?q=${query}`, 80 - { headers }, 80 + { headers } 81 81 ); 82 82 if (response.ok) { 83 83 const data = await response.json(); ··· 100 100 101 101 export async function fetchBlueskyEvents( 102 102 start: Date, 103 - end: Date, 103 + end: Date 104 104 ): Promise<Event[]> { 105 105 const handle = "npmx.dev"; 106 106 const events: Event[] = []; ··· 109 109 110 110 try { 111 111 const resolve = await fetch( 112 - `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`, 112 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}` 113 113 ); 114 114 if (!resolve.ok) return []; 115 115 const { did } = await resolve.json(); 116 116 117 117 while (!reachedBeforeStart) { 118 118 const url = new URL( 119 - "https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed", 119 + "https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed" 120 120 ); 121 121 url.searchParams.set("actor", did); 122 122 url.searchParams.set("limit", "100");
+14 -9
scripts/generate-digest.ts
··· 1 - import { writeFile, mkdir } from "node:fs/promises"; 1 + import { mkdir, writeFile } from "node:fs/promises"; 2 2 import { join } from "node:path"; 3 - import { PostSchema, type Topic } from "../src/lib/schema"; 3 + 4 4 import { 5 + fetchBlueskyEvents, 5 6 fetchGitHubEvents, 6 - fetchBlueskyEvents, 7 - generateSmartDigest, 8 7 generateCatchyTitle, 8 + generateSmartDigest, 9 9 } from "../src/lib/events"; 10 + import { PostSchema, type Topic } from "../src/lib/schema"; 10 11 11 12 const POST_DIR = join(process.cwd(), "src/content/posts"); 12 13 ··· 39 40 40 41 const now = new Date(); 41 42 const marks = [6, 14, 22]; 42 - const windowSize = 8 * 60 * 60 * 1000; // 8 hours: event fetch window 43 - const snapWindow = 2 * 60 * 60 * 1000; // 2 hours: snap-to-future threshold 43 + const windowSize = 8 * 60 * 60 * 1000; // 8 hours: event fetch window 44 + const snapWindow = 2 * 60 * 60 * 1000; // 2 hours: snap-to-future threshold 44 45 45 46 const candidates: Date[] = []; 46 47 [-1, 0, 1].forEach((dayOffset) => { ··· 69 70 70 71 const startTime = new Date(nearestMark.getTime() - windowSize); 71 72 72 - console.log(`\x1b[34m[INFO]\x1b[0m Target Mark: ${nearestMark.toISOString()}`); 73 - console.log(`\x1b[34m[INFO]\x1b[0m Window: ${startTime.toISOString()} -> ${nearestMark.toISOString()}`); 73 + console.log( 74 + `\x1b[34m[INFO]\x1b[0m Target Mark: ${nearestMark.toISOString()}` 75 + ); 76 + console.log( 77 + `\x1b[34m[INFO]\x1b[0m Window: ${startTime.toISOString()} -> ${nearestMark.toISOString()}` 78 + ); 74 79 75 80 try { 76 81 const [gh, bs] = await Promise.all([ ··· 105 110 await mkdir(POST_DIR, { recursive: true }); 106 111 await writeFile( 107 112 join(POST_DIR, `${slug}.json`), 108 - JSON.stringify(validatedPost, null, 2), 113 + JSON.stringify(validatedPost, null, 2) 109 114 ); 110 115 111 116 console.log(`\x1b[32m✅ Digest complete: ${slug}.json\x1b[0m`);
+5 -5
scripts/quota.ts
··· 16 16 17 17 if (!token) { 18 18 console.log( 19 - "\x1b[31mError: GITHUB_TOKEN is not set in your environment\x1b[0m", 19 + "\x1b[31mError: GITHUB_TOKEN is not set in your environment\x1b[0m" 20 20 ); 21 21 return; 22 22 } ··· 44 44 console.log("\n\x1b[32m[MATCH FOUND]\x1b[0m"); 45 45 console.log(`Resource: ${resources.models ? "Models" : "Marketplace"}`); 46 46 console.log( 47 - `Remaining: ${targetResource.remaining} / ${targetResource.limit}`, 47 + `Remaining: ${targetResource.remaining} / ${targetResource.limit}` 48 48 ); 49 49 console.log( 50 - `Resets: ${resetDate.toLocaleTimeString()} (${waitMins}m)`, 50 + `Resets: ${resetDate.toLocaleTimeString()} (${waitMins}m)` 51 51 ); 52 52 } else { 53 53 console.log( 54 - "\n\x1b[33m[DEBUG] Available resources found on this token:\x1b[0m", 54 + "\n\x1b[33m[DEBUG] Available resources found on this token:\x1b[0m" 55 55 ); 56 56 Object.keys(resources).forEach((key) => console.log(` - ${key}`)); 57 57 console.log( 58 - "\n\x1b[31mAction Required:\x1b[0m Enable 'Models' or 'Copilot' scopes in your PAT settings.", 58 + "\n\x1b[31mAction Required:\x1b[0m Enable 'Models' or 'Copilot' scopes in your PAT settings." 59 59 ); 60 60 } 61 61 } catch (error) {
+2 -1
src/content.config.ts
··· 1 + import { glob } from "astro/loaders"; 1 2 import { defineCollection } from "astro:content"; 2 - import { glob } from "astro/loaders"; 3 + 3 4 import { PostSchema } from "./lib/schema"; 4 5 5 6 const posts = defineCollection({
+1 -1
src/content/posts/2026-02-03-nightly.json
··· 138 138 ] 139 139 } 140 140 ] 141 - } 141 + }
+1 -1
src/content/posts/2026-02-04-daily.json
··· 126 126 ] 127 127 } 128 128 ] 129 - } 129 + }
+102 -99
src/layouts/Layout.astro
··· 1 1 --- 2 2 interface Props { 3 - title: string; 3 + title: string; 4 4 } 5 5 6 6 const { title } = Astro.props; ··· 8 8 9 9 <!doctype html> 10 10 <html lang="en"> 11 - <head> 12 - <meta charset="UTF-8" /> 13 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 14 - <link rel="icon" type="image/png" href="/favicon.png" /> 15 - <title>{title}</title> 16 - </head> 17 - <body> 18 - <div class="wrapper"> 19 - <header> 20 - <nav> 21 - <a href="/" class="logo">npmx.<span>digest</span></a> 22 - <div class="links"> 23 - <a href="/archive">Archive</a> 24 - <a href="https://github.com/npmx-dev/npmx.dev">GitHub</a> 25 - </div> 26 - </nav> 27 - </header> 28 - <main> 29 - <slot /> 30 - </main> 31 - <footer> 32 - <p>AI generated updates for the npmx ecosystem.</p> 33 - <a href="https://github.com/trueberryless-org/npmx-digest" class="footer-link">Source Code</a> 34 - </footer> 35 - </div> 36 - </body> 11 + <head> 12 + <meta charset="UTF-8" /> 13 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 14 + <link rel="icon" type="image/png" href="/favicon.png" /> 15 + <title>{title}</title> 16 + </head> 17 + <body> 18 + <div class="wrapper"> 19 + <header> 20 + <nav> 21 + <a href="/" class="logo">npmx.<span>digest</span></a> 22 + <div class="links"> 23 + <a href="/archive">Archive</a> 24 + <a href="https://github.com/npmx-dev/npmx.dev">GitHub</a> 25 + </div> 26 + </nav> 27 + </header> 28 + <main> 29 + <slot /> 30 + </main> 31 + <footer> 32 + <p>AI generated updates for the npmx ecosystem.</p> 33 + <a 34 + href="https://github.com/trueberryless-org/npmx-digest" 35 + class="footer-link">Source Code</a 36 + > 37 + </footer> 38 + </div> 39 + </body> 37 40 </html> 38 41 39 42 <style is:global> 40 - :root { 41 - --bg: #050505; 42 - --text: #e5e5e5; 43 - --text-muted: #737373; 44 - --accent: #fff; 45 - } 43 + :root { 44 + --bg: #050505; 45 + --text: #e5e5e5; 46 + --text-muted: #737373; 47 + --accent: #fff; 48 + } 46 49 47 - * { 48 - margin: 0; 49 - padding: 0; 50 - box-sizing: border-box; 51 - } 50 + * { 51 + margin: 0; 52 + padding: 0; 53 + box-sizing: border-box; 54 + } 52 55 53 - body { 54 - background: var(--bg); 55 - color: var(--text); 56 - font-family: ui-sans-serif, system-ui, sans-serif; 57 - -webkit-font-smoothing: antialiased; 58 - } 56 + body { 57 + background: var(--bg); 58 + color: var(--text); 59 + font-family: ui-sans-serif, system-ui, sans-serif; 60 + -webkit-font-smoothing: antialiased; 61 + } 59 62 60 - .wrapper { 61 - max-width: 650px; 62 - margin: 0 auto; 63 - padding: 2rem 1.5rem; 64 - } 63 + .wrapper { 64 + max-width: 650px; 65 + margin: 0 auto; 66 + padding: 2rem 1.5rem; 67 + } 65 68 66 - header { 67 - margin-bottom: 4rem; 68 - } 69 + header { 70 + margin-bottom: 4rem; 71 + } 69 72 70 - nav { 71 - display: flex; 72 - justify-content: space-between; 73 - align-items: baseline; 74 - } 73 + nav { 74 + display: flex; 75 + justify-content: space-between; 76 + align-items: baseline; 77 + } 75 78 76 - .logo { 77 - font-weight: 700; 78 - text-decoration: none; 79 - color: var(--accent); 80 - font-size: 1.2rem; 81 - letter-spacing: -0.02em; 82 - } 79 + .logo { 80 + font-weight: 700; 81 + text-decoration: none; 82 + color: var(--accent); 83 + font-size: 1.2rem; 84 + letter-spacing: -0.02em; 85 + } 83 86 84 - .logo span { 85 - color: var(--text-muted); 86 - font-weight: 400; 87 - } 87 + .logo span { 88 + color: var(--text-muted); 89 + font-weight: 400; 90 + } 88 91 89 - .links { 90 - display: flex; 91 - gap: 1.5rem; 92 - } 92 + .links { 93 + display: flex; 94 + gap: 1.5rem; 95 + } 93 96 94 - .links a { 95 - color: var(--text-muted); 96 - text-decoration: none; 97 - font-size: 0.9rem; 98 - transition: color 0.2s; 99 - } 97 + .links a { 98 + color: var(--text-muted); 99 + text-decoration: none; 100 + font-size: 0.9rem; 101 + transition: color 0.2s; 102 + } 100 103 101 - .links a:hover { 102 - color: var(--accent); 103 - } 104 + .links a:hover { 105 + color: var(--accent); 106 + } 104 107 105 - footer { 106 - display: flex; 107 - justify-content: space-between; 108 - align-items: center; 109 - margin-top: 6rem; 110 - padding-top: 2rem; 111 - border-top: 1px solid #1a1a1a; 112 - font-size: 0.8rem; 113 - color: var(--text-muted); 114 - } 108 + footer { 109 + display: flex; 110 + justify-content: space-between; 111 + align-items: center; 112 + margin-top: 6rem; 113 + padding-top: 2rem; 114 + border-top: 1px solid #1a1a1a; 115 + font-size: 0.8rem; 116 + color: var(--text-muted); 117 + } 115 118 116 - .footer-link { 117 - color: var(--text-muted); 118 - text-decoration: none; 119 - transition: color 0.2s; 120 - } 119 + .footer-link { 120 + color: var(--text-muted); 121 + text-decoration: none; 122 + transition: color 0.2s; 123 + } 121 124 122 - .footer-link:hover { 123 - color: var(--accent); 124 - } 125 + .footer-link:hover { 126 + color: var(--accent); 127 + } 125 128 </style>
+23 -12
src/lib/events.ts
··· 1 1 import { z } from "astro/zod"; 2 - import { TopicSchema, type Topic } from "../lib/schema"; 2 + 3 + import { type Topic, TopicSchema } from "../lib/schema"; 3 4 4 5 const INFERENCE_URL = "https://models.inference.ai.azure.com/chat/completions"; 5 6 ··· 54 55 return response.json(); 55 56 } 56 57 57 - export async function fetchGitHubEvents(since: Date, end: Date): Promise<Event[]> { 58 + export async function fetchGitHubEvents( 59 + since: Date, 60 + end: Date 61 + ): Promise<Event[]> { 58 62 const owner = "npmx-dev"; 59 63 const repo = "npmx.dev"; 60 64 const token = getRequiredEnv("GITHUB_TOKEN"); ··· 64 68 const endIso = end.toISOString().split(".")[0] + "Z"; 65 69 66 70 const query = encodeURIComponent( 67 - `repo:${owner}/${repo} is:closed reason:completed -is:unmerged closed:${startIso}..${endIso}`, 71 + `repo:${owner}/${repo} is:closed reason:completed -is:unmerged closed:${startIso}..${endIso}` 68 72 ); 69 73 70 74 try { ··· 76 80 "User-Agent": "npmx-digest-bot", 77 81 Authorization: `Bearer ${token}`, 78 82 }, 79 - }, 83 + } 80 84 ); 81 85 82 86 if (response.ok) { ··· 101 105 return events; 102 106 } 103 107 104 - export async function fetchBlueskyEvents(since: Date, end: Date): Promise<Event[]> { 108 + export async function fetchBlueskyEvents( 109 + since: Date, 110 + end: Date 111 + ): Promise<Event[]> { 105 112 const handle = "npmx.dev"; 106 113 const events: Event[] = []; 107 114 108 115 try { 109 116 const resolve = await fetch( 110 - `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`, 117 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}` 111 118 ); 112 119 if (!resolve.ok) return events; 113 120 const { did } = await resolve.json(); 114 121 115 122 const feedRes = await fetch( 116 - `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${did}&limit=50&filter=posts_with_replies`, 123 + `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${did}&limit=50&filter=posts_with_replies` 117 124 ); 118 125 119 126 if (feedRes.ok) { ··· 148 155 if (events.length === 0) return []; 149 156 150 157 LOG.ai( 151 - `Clustering ${events.length} signals into topics (Prioritizing Bluesky)...`, 158 + `Clustering ${events.length} signals into topics (Prioritizing Bluesky)...` 152 159 ); 153 160 154 161 const prompt = `You are a technical analyst for npmx. Group these events into 5-6 logical "Topics". ··· 188 195 const validated = TopicSchema.parse(t); 189 196 190 197 const hasBluesky = validated.sources.some( 191 - (s) => s.platform === "bluesky", 198 + (s) => s.platform === "bluesky" 192 199 ); 193 200 194 201 return { ··· 202 209 }); 203 210 204 211 LOG.success( 205 - `Successfully clustered into ${topics.length} topics with Bluesky priority.`, 212 + `Successfully clustered into ${topics.length} topics with Bluesky priority.` 206 213 ); 207 214 return topics.sort((a, b) => b.relevanceScore - a.relevanceScore); 208 215 } catch (err: any) { ··· 218 225 try { 219 226 const data = await requestInference({ 220 227 messages: [ 221 - { role: "system", content: "You provide raw text headlines without any quotation marks or wrapping characters." }, 222 - { role: "user", content: prompt } 228 + { 229 + role: "system", 230 + content: 231 + "You provide raw text headlines without any quotation marks or wrapping characters.", 232 + }, 233 + { role: "user", content: prompt }, 223 234 ], 224 235 model: "gpt-4o-mini", 225 236 temperature: 0.7,
+93 -80
src/pages/archive.astro
··· 5 5 const posts = await getCollection("posts"); 6 6 7 7 const sortedPosts = posts.sort( 8 - (a, b) => b.data.date.valueOf() - a.data.date.valueOf(), 8 + (a, b) => b.data.date.valueOf() - a.data.date.valueOf() 9 9 ); 10 10 11 11 const formatDate = (date: Date) => date.toISOString().split("T")[0]; 12 12 --- 13 13 14 14 <Layout title="Archive | npmx.digest"> 15 - <h1 class="page-title">Archives</h1> 15 + <h1 class="page-title">Archives</h1> 16 16 17 - <ul class="archive-list"> 18 - { 19 - sortedPosts.map(({ data, id }) => { 20 - const displayDate = formatDate(data.date); 21 - const postUrl = `/posts/${id}`; 17 + <ul class="archive-list"> 18 + { 19 + sortedPosts.map(({ data, id }) => { 20 + const displayDate = formatDate(data.date); 21 + const postUrl = `/posts/${id}`; 22 22 23 - return ( 24 - <li class="archive-row"> 25 - <a href={postUrl} class="archive-item"> 26 - <span class={`type ${data.type}`} aria-label={`Post type: ${data.type}`}>{data.type}</span> 27 - <span class="title">{data.title}</span> 28 - <span class="dot" aria-hidden="true" /> 29 - <time datetime={data.date.toISOString()} class="date">{displayDate}</time> 30 - </a> 31 - </li> 32 - ); 33 - }) 34 - } 35 - </ul> 23 + return ( 24 + <li class="archive-row"> 25 + <a href={postUrl} class="archive-item"> 26 + <span 27 + class={`type ${data.type}`} 28 + aria-label={`Post type: ${data.type}`} 29 + > 30 + {data.type} 31 + </span> 32 + <span class="title">{data.title}</span> 33 + <span class="dot" aria-hidden="true" /> 34 + <time datetime={data.date.toISOString()} class="date"> 35 + {displayDate} 36 + </time> 37 + </a> 38 + </li> 39 + ); 40 + }) 41 + } 42 + </ul> 36 43 </Layout> 37 44 38 45 <style> 39 - .page-title { 40 - font-size: 1.2rem; 41 - margin-bottom: 2rem; 42 - color: var(--text-muted); 43 - } 46 + .page-title { 47 + font-size: 1.2rem; 48 + margin-bottom: 2rem; 49 + color: var(--text-muted); 50 + } 44 51 45 - .archive-list { 46 - display: flex; 47 - flex-direction: column; 48 - list-style: none; 49 - padding: 0; 50 - margin: 0; 51 - } 52 + .archive-list { 53 + display: flex; 54 + flex-direction: column; 55 + list-style: none; 56 + padding: 0; 57 + margin: 0; 58 + } 52 59 53 - .archive-row { 54 - width: 100%; 55 - } 60 + .archive-row { 61 + width: 100%; 62 + } 56 63 57 - .archive-item { 58 - display: flex; 59 - align-items: baseline; 60 - padding: 0.8rem 0; 61 - border-bottom: 1px solid #151515; 62 - text-decoration: none; 63 - color: var(--text-muted); 64 - font-size: 0.95rem; 65 - } 64 + .archive-item { 65 + display: flex; 66 + align-items: baseline; 67 + padding: 0.8rem 0; 68 + border-bottom: 1px solid #151515; 69 + text-decoration: none; 70 + color: var(--text-muted); 71 + font-size: 0.95rem; 72 + } 66 73 67 - .archive-item:hover { 68 - color: var(--accent); 69 - } 74 + .archive-item:hover { 75 + color: var(--accent); 76 + } 70 77 71 - .type { 72 - width: 65px; 73 - flex-shrink: 0; 74 - font-size: 0.75rem; 75 - text-transform: capitalize; 76 - font-weight: 600; 77 - margin-right: 0.5rem; 78 - } 78 + .type { 79 + width: 65px; 80 + flex-shrink: 0; 81 + font-size: 0.75rem; 82 + text-transform: capitalize; 83 + font-weight: 600; 84 + margin-right: 0.5rem; 85 + } 79 86 80 - .daily { color: #fbbf24; } 81 - .midday { color: #f97316; } 82 - .nightly { color: #818cf8; } 87 + .daily { 88 + color: #fbbf24; 89 + } 90 + .midday { 91 + color: #f97316; 92 + } 93 + .nightly { 94 + color: #818cf8; 95 + } 83 96 84 - .title { 85 - color: var(--text); 86 - white-space: nowrap; 87 - overflow: hidden; 88 - text-overflow: ellipsis; 89 - flex-shrink: 1; 90 - min-width: 0; 91 - } 97 + .title { 98 + color: var(--text); 99 + white-space: nowrap; 100 + overflow: hidden; 101 + text-overflow: ellipsis; 102 + flex-shrink: 1; 103 + min-width: 0; 104 + } 92 105 93 - .dot { 94 - align-self: center; 95 - flex-grow: 1; 96 - min-width: 20px; 97 - border-bottom: 1px dotted #333; 98 - margin: 0.2rem 0.5rem 0; 99 - } 106 + .dot { 107 + align-self: center; 108 + flex-grow: 1; 109 + min-width: 20px; 110 + border-bottom: 1px dotted #333; 111 + margin: 0.2rem 0.5rem 0; 112 + } 100 113 101 - .date { 102 - font-family: monospace; 103 - width: 90px; 104 - text-align: right; 105 - flex-shrink: 0; 106 - font-size: 0.85rem; 107 - } 114 + .date { 115 + font-family: monospace; 116 + width: 90px; 117 + text-align: right; 118 + flex-shrink: 0; 119 + font-size: 0.85rem; 120 + } 108 121 </style>
+140 -150
src/pages/index.astro
··· 4 4 5 5 const allPosts = await getCollection("posts"); 6 6 const posts = allPosts.sort( 7 - (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(), 7 + (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime() 8 8 ); 9 9 10 10 const MAX_DISPLAY_POSTS = 5; ··· 15 15 --- 16 16 17 17 <Layout title="npmx.digest"> 18 - <section class="intro"> 19 - <h1>Latest Intelligence</h1> 20 - <p> 21 - A semi-daily report on the progress of npmx across GitHub and 22 - Bluesky. 23 - </p> 24 - </section> 18 + <section class="intro"> 19 + <h1>Latest Intelligence</h1> 20 + <p> 21 + A semi-daily report on the progress of npmx across GitHub and Bluesky. 22 + </p> 23 + </section> 25 24 26 - <div class="post-list"> 27 - { 28 - latestPosts.map((post) => { 29 - const postDate = new Date(post.data.date); 30 - const displayDate = postDate.toLocaleDateString("en-US", { 31 - month: "short", 32 - day: "numeric", 33 - }); 25 + <div class="post-list"> 26 + { 27 + latestPosts.map((post) => { 28 + const postDate = new Date(post.data.date); 29 + const displayDate = postDate.toLocaleDateString("en-US", { 30 + month: "short", 31 + day: "numeric", 32 + }); 34 33 35 - const visibleTopics = post.data.topics.slice(0, TOPIC_LIMIT); 36 - const remainingCount = post.data.topics.length - TOPIC_LIMIT; 34 + const visibleTopics = post.data.topics.slice(0, TOPIC_LIMIT); 35 + const remainingCount = post.data.topics.length - TOPIC_LIMIT; 37 36 38 - return ( 39 - <a href={`/posts/${post.id}`} class="post-entry"> 40 - <div class="post-meta"> 41 - <time datetime={post.data.date.toString()}> 42 - {displayDate} 43 - </time> 44 - <span class={`type-tag ${post.data.type}`}> 45 - {post.data.type} 46 - </span> 47 - </div> 48 - <div class="post-content"> 49 - <h2>{post.data.title}</h2> 50 - <div class="topic-preview"> 51 - {visibleTopics.map((topic, i) => ( 52 - <span> 53 - {topic.title} 54 - {i < visibleTopics.length - 1 55 - ? " • " 56 - : ""} 57 - </span> 58 - ))} 59 - {remainingCount > 0 && ( 60 - <span class="more"> 61 - {" "} 62 - +{remainingCount} more 63 - </span> 64 - )} 65 - </div> 66 - </div> 67 - </a> 68 - ); 69 - }) 70 - } 71 - </div> 37 + return ( 38 + <a href={`/posts/${post.id}`} class="post-entry"> 39 + <div class="post-meta"> 40 + <time datetime={post.data.date.toString()}>{displayDate}</time> 41 + <span class={`type-tag ${post.data.type}`}>{post.data.type}</span> 42 + </div> 43 + <div class="post-content"> 44 + <h2>{post.data.title}</h2> 45 + <div class="topic-preview"> 46 + {visibleTopics.map((topic, i) => ( 47 + <span> 48 + {topic.title} 49 + {i < visibleTopics.length - 1 ? " • " : ""} 50 + </span> 51 + ))} 52 + {remainingCount > 0 && ( 53 + <span class="more"> +{remainingCount} more</span> 54 + )} 55 + </div> 56 + </div> 57 + </a> 58 + ); 59 + }) 60 + } 61 + </div> 72 62 73 - { 74 - hasArchive && ( 75 - <a href="/archive" class="archive-link"> 76 - Explore the archives → 77 - </a> 78 - ) 79 - } 63 + { 64 + hasArchive && ( 65 + <a href="/archive" class="archive-link"> 66 + Explore the archives → 67 + </a> 68 + ) 69 + } 80 70 </Layout> 81 71 82 72 <style> 83 - .intro { 84 - margin-bottom: 3rem; 85 - } 73 + .intro { 74 + margin-bottom: 3rem; 75 + } 86 76 87 - h1 { 88 - font-size: 1.5rem; 89 - font-weight: 600; 90 - margin-bottom: 0.5rem; 91 - color: var(--accent); 92 - } 77 + h1 { 78 + font-size: 1.5rem; 79 + font-weight: 600; 80 + margin-bottom: 0.5rem; 81 + color: var(--accent); 82 + } 93 83 94 - .intro p { 95 - color: var(--text-muted); 96 - line-height: 1.5; 97 - } 84 + .intro p { 85 + color: var(--text-muted); 86 + line-height: 1.5; 87 + } 98 88 99 - .post-list { 100 - display: flex; 101 - flex-direction: column; 102 - gap: 3rem; 103 - } 89 + .post-list { 90 + display: flex; 91 + flex-direction: column; 92 + gap: 3rem; 93 + } 104 94 105 - .post-entry { 106 - text-decoration: none; 107 - color: inherit; 108 - display: grid; 109 - grid-template-columns: 100px 1fr; 110 - gap: 1.5rem; 111 - transition: transform 0.2s; 112 - } 95 + .post-entry { 96 + text-decoration: none; 97 + color: inherit; 98 + display: grid; 99 + grid-template-columns: 100px 1fr; 100 + gap: 1.5rem; 101 + transition: transform 0.2s; 102 + } 103 + 104 + .post-entry:hover { 105 + transform: translateX(4px); 106 + } 113 107 114 - .post-entry:hover { 115 - transform: translateX(4px); 116 - } 108 + .post-meta { 109 + font-size: 0.85rem; 110 + color: var(--text-muted); 111 + display: flex; 112 + flex-direction: column; 113 + gap: 0.4rem; 114 + font-family: monospace; 115 + } 117 116 118 - .post-meta { 119 - font-size: 0.85rem; 120 - color: var(--text-muted); 121 - display: flex; 122 - flex-direction: column; 123 - gap: 0.4rem; 124 - font-family: monospace; 125 - } 117 + .type-tag { 118 + text-transform: uppercase; 119 + font-size: 0.7rem; 120 + letter-spacing: 0.05em; 121 + font-weight: 700; 122 + } 126 123 127 - .type-tag { 128 - text-transform: uppercase; 129 - font-size: 0.7rem; 130 - letter-spacing: 0.05em; 131 - font-weight: 700; 132 - } 124 + .daily { 125 + color: #fbbf24; 126 + } 127 + .midday { 128 + color: #f97316; 129 + } 130 + .nightly { 131 + color: #818cf8; 132 + } 133 133 134 - .daily { 135 - color: #fbbf24; 136 - } 137 - .midday { 138 - color: #f97316; 139 - } 140 - .nightly { 141 - color: #818cf8; 142 - } 134 + h2 { 135 + font-size: 1.1rem; 136 + font-weight: 600; 137 + margin-bottom: 0.4rem; 138 + color: var(--text); 139 + } 143 140 144 - h2 { 145 - font-size: 1.1rem; 146 - font-weight: 600; 147 - margin-bottom: 0.4rem; 148 - color: var(--text); 149 - } 141 + .topic-preview { 142 + font-size: 0.9rem; 143 + color: var(--text-muted); 144 + line-height: 1.4; 145 + } 150 146 151 - .topic-preview { 152 - font-size: 0.9rem; 153 - color: var(--text-muted); 154 - line-height: 1.4; 155 - } 147 + .topic-preview span { 148 + display: inline; 149 + } 156 150 157 - .topic-preview span { 158 - display: inline; 159 - } 151 + .topic-preview .more { 152 + opacity: 0.5; 153 + font-style: italic; 154 + } 160 155 161 - .topic-preview .more { 162 - opacity: 0.5; 163 - font-style: italic; 164 - } 156 + .archive-link { 157 + display: block; 158 + margin-top: 5rem; 159 + color: var(--text-muted); 160 + text-decoration: none; 161 + font-size: 0.9rem; 162 + font-weight: 500; 163 + } 165 164 166 - .archive-link { 167 - display: block; 168 - margin-top: 5rem; 169 - color: var(--text-muted); 170 - text-decoration: none; 171 - font-size: 0.9rem; 172 - font-weight: 500; 173 - } 165 + .archive-link:hover { 166 + color: var(--accent); 167 + } 174 168 175 - .archive-link:hover { 176 - color: var(--accent); 169 + @media (max-width: 500px) { 170 + .post-entry { 171 + grid-template-columns: 1fr; 172 + gap: 0.5rem; 177 173 } 178 - 179 - @media (max-width: 500px) { 180 - .post-entry { 181 - grid-template-columns: 1fr; 182 - gap: 0.5rem; 183 - } 184 - .post-meta { 185 - flex-direction: row; 186 - align-items: center; 187 - gap: 1rem; 188 - } 174 + .post-meta { 175 + flex-direction: row; 176 + align-items: center; 177 + gap: 1rem; 189 178 } 179 + } 190 180 </style>
+221 -227
src/pages/posts/[id].astro
··· 3 3 import Layout from "../../layouts/Layout.astro"; 4 4 5 5 export async function getStaticPaths() { 6 - const posts = await getCollection("posts"); 7 - return posts.map((post) => ({ 8 - params: { id: post.id }, 9 - })); 6 + const posts = await getCollection("posts"); 7 + return posts.map((post) => ({ 8 + params: { id: post.id }, 9 + })); 10 10 } 11 11 12 12 const { id } = Astro.params; 13 13 const postEntry = await getEntry("posts", id); 14 14 15 15 if (!postEntry) { 16 - return Astro.redirect("/404"); 16 + return Astro.redirect("/404"); 17 17 } 18 18 19 19 const { data } = postEntry; 20 20 21 21 const formattedDate = data.date.toLocaleDateString("en-US", { 22 - weekday: "short", 23 - year: "numeric", 24 - month: "long", 25 - day: "numeric", 22 + weekday: "short", 23 + year: "numeric", 24 + month: "long", 25 + day: "numeric", 26 26 }); 27 27 28 28 const getGithubId = (url?: string) => { 29 - if (!url) return ""; 30 - const match = url.match(/\/(pull|issues)\/(\d+)$/); 31 - return match ? ` (#${match[2]})` : ""; 29 + if (!url) return ""; 30 + const match = url.match(/\/(pull|issues)\/(\d+)$/); 31 + return match ? ` (#${match[2]})` : ""; 32 32 }; 33 33 --- 34 34 35 35 <Layout title={`${data.title} | npmx.digest`}> 36 - <article class="report"> 37 - <header class="report-header"> 38 - <div class="meta"> 39 - <time datetime={data.date.toISOString()}>{formattedDate}</time> 40 - <span class="slash">/</span> 41 - <span class={`tag ${data.type}`}>{data.type}</span> 42 - </div> 43 - <h1>{data.title}</h1> 44 - </header> 36 + <article class="report"> 37 + <header class="report-header"> 38 + <div class="meta"> 39 + <time datetime={data.date.toISOString()}>{formattedDate}</time> 40 + <span class="slash">/</span> 41 + <span class={`tag ${data.type}`}>{data.type}</span> 42 + </div> 43 + <h1>{data.title}</h1> 44 + </header> 45 45 46 - <section class="topics-section"> 47 - <div class="section-divider"> 48 - <span>Intelligence Topics</span> 49 - <hr /> 50 - </div> 46 + <section class="topics-section"> 47 + <div class="section-divider"> 48 + <span>Intelligence Topics</span> 49 + <hr /> 50 + </div> 51 51 52 - <div class="topic-list"> 53 - { 54 - data.topics.map((topic, index) => { 55 - const displayIndex = (index + 1) 56 - .toString() 57 - .padStart(2, "0"); 52 + <div class="topic-list"> 53 + { 54 + data.topics.map((topic, index) => { 55 + const displayIndex = (index + 1).toString().padStart(2, "0"); 58 56 59 - return ( 60 - <div class="topic-item"> 61 - <div class="topic-meta"> 62 - <span class="index">{displayIndex}</span> 63 - <span class="score"> 64 - Signal: {topic.relevanceScore}/10 65 - </span> 66 - </div> 67 - <div class="topic-body"> 68 - <h2>{topic.title}</h2> 69 - <p>{topic.summary}</p> 70 - <div class="topic-sources"> 71 - {topic.sources.map((source) => ( 72 - <a 73 - href={source.url} 74 - target="_blank" 75 - rel="noopener noreferrer" 76 - class={`source-link ${source.platform}`} 77 - > 78 - {source.platform} 79 - {source.platform === "github" 80 - ? getGithubId(source.url) 81 - : ""} 82 - 83 - </a> 84 - ))} 85 - </div> 86 - </div> 87 - </div> 88 - ); 89 - }) 90 - } 91 - </div> 92 - </section> 57 + return ( 58 + <div class="topic-item"> 59 + <div class="topic-meta"> 60 + <span class="index">{displayIndex}</span> 61 + <span class="score">Signal: {topic.relevanceScore}/10</span> 62 + </div> 63 + <div class="topic-body"> 64 + <h2>{topic.title}</h2> 65 + <p>{topic.summary}</p> 66 + <div class="topic-sources"> 67 + {topic.sources.map((source) => ( 68 + <a 69 + href={source.url} 70 + target="_blank" 71 + rel="noopener noreferrer" 72 + class={`source-link ${source.platform}`} 73 + > 74 + {source.platform} 75 + {source.platform === "github" 76 + ? getGithubId(source.url) 77 + : ""} 78 + 79 + </a> 80 + ))} 81 + </div> 82 + </div> 83 + </div> 84 + ); 85 + }) 86 + } 87 + </div> 88 + </section> 93 89 94 - <footer class="report-footer"> 95 - <a href="/" class="back-btn">← Back to Index</a> 96 - <p class="bot-note"> 97 - Automated cluster analysis performed by npmx-bot 98 - </p> 99 - </footer> 100 - </article> 90 + <footer class="report-footer"> 91 + <a href="/" class="back-btn">← Back to Index</a> 92 + <p class="bot-note">Automated cluster analysis performed by npmx-bot</p> 93 + </footer> 94 + </article> 101 95 </Layout> 102 96 103 97 <style> 104 - .report { 105 - margin-top: 2rem; 106 - } 98 + .report { 99 + margin-top: 2rem; 100 + } 107 101 108 - .report-header { 109 - margin-bottom: 4rem; 110 - } 102 + .report-header { 103 + margin-bottom: 4rem; 104 + } 111 105 112 - .meta { 113 - font-family: monospace; 114 - font-size: 0.8rem; 115 - text-transform: uppercase; 116 - color: var(--text-muted); 117 - display: flex; 118 - gap: 0.6rem; 119 - margin-bottom: 0.8rem; 120 - } 106 + .meta { 107 + font-family: monospace; 108 + font-size: 0.8rem; 109 + text-transform: uppercase; 110 + color: var(--text-muted); 111 + display: flex; 112 + gap: 0.6rem; 113 + margin-bottom: 0.8rem; 114 + } 121 115 122 - .slash { 123 - color: #333; 124 - } 116 + .slash { 117 + color: #333; 118 + } 125 119 126 - .tag.daily { 127 - color: #fbbf24; 128 - } 129 - .tag.midday { 130 - color: #f97316; 131 - } 132 - .tag.nightly { 133 - color: #818cf8; 134 - } 120 + .tag.daily { 121 + color: #fbbf24; 122 + } 123 + .tag.midday { 124 + color: #f97316; 125 + } 126 + .tag.nightly { 127 + color: #818cf8; 128 + } 135 129 136 - h1 { 137 - font-size: 2.5rem; 138 - font-weight: 700; 139 - letter-spacing: -0.04em; 140 - line-height: 1.1; 141 - color: var(--accent); 142 - } 130 + h1 { 131 + font-size: 2.5rem; 132 + font-weight: 700; 133 + letter-spacing: -0.04em; 134 + line-height: 1.1; 135 + color: var(--accent); 136 + } 143 137 144 - .section-divider { 145 - display: flex; 146 - align-items: center; 147 - gap: 1rem; 148 - margin-bottom: 3rem; 149 - } 138 + .section-divider { 139 + display: flex; 140 + align-items: center; 141 + gap: 1rem; 142 + margin-bottom: 3rem; 143 + } 150 144 151 - .section-divider span { 152 - font-family: monospace; 153 - font-size: 0.75rem; 154 - text-transform: uppercase; 155 - color: var(--text-muted); 156 - white-space: nowrap; 157 - } 145 + .section-divider span { 146 + font-family: monospace; 147 + font-size: 0.75rem; 148 + text-transform: uppercase; 149 + color: var(--text-muted); 150 + white-space: nowrap; 151 + } 158 152 159 - .section-divider hr { 160 - flex-grow: 1; 161 - border: 0; 162 - border-top: 1px solid #1a1a1a; 163 - } 153 + .section-divider hr { 154 + flex-grow: 1; 155 + border: 0; 156 + border-top: 1px solid #1a1a1a; 157 + } 164 158 165 - .topic-list { 166 - display: flex; 167 - flex-direction: column; 168 - gap: 4rem; 169 - } 159 + .topic-list { 160 + display: flex; 161 + flex-direction: column; 162 + gap: 4rem; 163 + } 170 164 171 - .topic-item { 172 - display: grid; 173 - grid-template-columns: 120px 1fr; 174 - gap: 2rem; 175 - } 165 + .topic-item { 166 + display: grid; 167 + grid-template-columns: 120px 1fr; 168 + gap: 2rem; 169 + } 176 170 177 - .topic-meta { 178 - display: flex; 179 - flex-direction: column; 180 - gap: 0.5rem; 181 - font-family: monospace; 182 - font-size: 0.8rem; 183 - } 171 + .topic-meta { 172 + display: flex; 173 + flex-direction: column; 174 + gap: 0.5rem; 175 + font-family: monospace; 176 + font-size: 0.8rem; 177 + } 184 178 185 - .index { 186 - color: var(--accent); 187 - font-weight: 700; 188 - font-size: 1.2rem; 189 - } 179 + .index { 180 + color: var(--accent); 181 + font-weight: 700; 182 + font-size: 1.2rem; 183 + } 190 184 191 - .score { 192 - color: var(--text-muted); 193 - font-size: 0.7rem; 194 - text-transform: uppercase; 195 - } 185 + .score { 186 + color: var(--text-muted); 187 + font-size: 0.7rem; 188 + text-transform: uppercase; 189 + } 196 190 197 - .topic-body h2 { 198 - font-size: 1.4rem; 199 - font-weight: 600; 200 - margin-bottom: 0.75rem; 201 - color: var(--text); 202 - letter-spacing: -0.02em; 203 - } 191 + .topic-body h2 { 192 + font-size: 1.4rem; 193 + font-weight: 600; 194 + margin-bottom: 0.75rem; 195 + color: var(--text); 196 + letter-spacing: -0.02em; 197 + } 204 198 205 - .topic-body p { 206 - font-size: 1.05rem; 207 - color: var(--text-muted); 208 - line-height: 1.6; 209 - margin-bottom: 1.5rem; 210 - } 199 + .topic-body p { 200 + font-size: 1.05rem; 201 + color: var(--text-muted); 202 + line-height: 1.6; 203 + margin-bottom: 1.5rem; 204 + } 211 205 212 - .topic-sources { 213 - display: flex; 214 - flex-wrap: wrap; 215 - gap: 0.75rem; 216 - } 206 + .topic-sources { 207 + display: flex; 208 + flex-wrap: wrap; 209 + gap: 0.75rem; 210 + } 217 211 218 - .source-link { 219 - font-size: 0.75rem; 220 - font-family: monospace; 221 - text-decoration: none; 222 - padding: 0.2rem 0.5rem; 223 - border: 1px solid #222; 224 - color: var(--text-muted); 225 - text-transform: uppercase; 226 - transition: all 0.2s; 227 - } 212 + .source-link { 213 + font-size: 0.75rem; 214 + font-family: monospace; 215 + text-decoration: none; 216 + padding: 0.2rem 0.5rem; 217 + border: 1px solid #222; 218 + color: var(--text-muted); 219 + text-transform: uppercase; 220 + transition: all 0.2s; 221 + } 228 222 229 - .source-link:hover { 230 - border-color: var(--accent); 231 - color: var(--accent); 232 - background: #111; 233 - } 223 + .source-link:hover { 224 + border-color: var(--accent); 225 + color: var(--accent); 226 + background: #111; 227 + } 234 228 235 - .source-link.github:hover { 236 - color: #fff; 237 - border-color: #fff; 238 - } 239 - .source-link.bluesky:hover { 240 - color: #0285ff; 241 - border-color: #0285ff; 242 - } 229 + .source-link.github:hover { 230 + color: #fff; 231 + border-color: #fff; 232 + } 233 + .source-link.bluesky:hover { 234 + color: #0285ff; 235 + border-color: #0285ff; 236 + } 243 237 244 - .report-footer { 245 - margin-top: 6rem; 246 - padding-top: 2rem; 247 - border-top: 1px solid #1a1a1a; 248 - display: flex; 249 - justify-content: space-between; 250 - align-items: center; 251 - } 238 + .report-footer { 239 + margin-top: 6rem; 240 + padding-top: 2rem; 241 + border-top: 1px solid #1a1a1a; 242 + display: flex; 243 + justify-content: space-between; 244 + align-items: center; 245 + } 252 246 253 - .back-btn { 254 - text-decoration: none; 255 - color: var(--text-muted); 256 - font-size: 0.9rem; 257 - } 247 + .back-btn { 248 + text-decoration: none; 249 + color: var(--text-muted); 250 + font-size: 0.9rem; 251 + } 258 252 259 - .back-btn:hover { 260 - color: var(--accent); 261 - } 253 + .back-btn:hover { 254 + color: var(--accent); 255 + } 262 256 263 - .bot-note { 264 - font-family: monospace; 265 - font-size: 0.7rem; 266 - color: #333; 267 - } 257 + .bot-note { 258 + font-family: monospace; 259 + font-size: 0.7rem; 260 + color: #333; 261 + } 268 262 269 - @media (max-width: 600px) { 270 - .topic-item { 271 - grid-template-columns: 1fr; 272 - gap: 1rem; 273 - } 274 - .topic-meta { 275 - flex-direction: row; 276 - align-items: center; 277 - gap: 1rem; 278 - } 279 - h1 { 280 - font-size: 2rem; 281 - } 263 + @media (max-width: 600px) { 264 + .topic-item { 265 + grid-template-columns: 1fr; 266 + gap: 1rem; 282 267 } 268 + .topic-meta { 269 + flex-direction: row; 270 + align-items: center; 271 + gap: 1rem; 272 + } 273 + h1 { 274 + font-size: 2rem; 275 + } 276 + } 283 277 </style>