From 62c41c3271d5a399cce076d2fde7a11adffa2ab4 Mon Sep 17 00:00:00 2001 From: shadcn Date: Wed, 3 Sep 2025 08:24:02 +0400 Subject: [PATCH] feat: add registries index (#8126) * feat: add registries index * ci: update workflow * ci: update * fix * debug * ci: debug * debug * fix: build * refactor --- .github/workflows/validate-registries.yml | 88 +++++++++++++++++++++++ apps/v4/package.json | 1 + apps/v4/public/r/registries.json | 3 + apps/v4/scripts/validate-registries.mts | 58 +++++++++++++++ package.json | 3 +- 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/validate-registries.yml create mode 100644 apps/v4/public/r/registries.json create mode 100644 apps/v4/scripts/validate-registries.mts diff --git a/.github/workflows/validate-registries.yml b/.github/workflows/validate-registries.yml new file mode 100644 index 0000000000..974b0d474f --- /dev/null +++ b/.github/workflows/validate-registries.yml @@ -0,0 +1,88 @@ +name: Validate Registries + +on: + pull_request: + paths: + - "apps/v4/public/r/registries.json" + push: + branches: + - main + paths: + - "apps/v4/public/r/registries.json" + +jobs: + validate: + runs-on: ubuntu-latest + name: pnpm validate:registries + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + + - uses: pnpm/action-setup@v4 + name: Install pnpm + id: pnpm-install + with: + version: 9.0.6 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install + + - name: Build packages + run: pnpm build --filter=shadcn + + - name: Validate registries + id: validate + run: | + pnpm --filter=v4 validate:registries + echo "validation_passed=$?" >> $GITHUB_ENV + continue-on-error: true + + - name: Add label to PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const validationPassed = '${{ env.validation_passed }}' === '0'; + const label = validationPassed ? 'registries: valid' : 'registries: invalid'; + const oppositeLabel = validationPassed ? 'registries: invalid' : 'registries: valid'; + + // Remove opposite label if it exists + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: oppositeLabel + }); + } catch (e) { + // Label might not exist, that's ok + } + + // Add the appropriate label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [label] + }); diff --git a/apps/v4/package.json b/apps/v4/package.json index 83b7824cd5..cf9b4c5e29 100644 --- a/apps/v4/package.json +++ b/apps/v4/package.json @@ -14,6 +14,7 @@ "format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache", "registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,json,mdx}\" --cache", "registry:capture": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/capture-registry.mts", + "validate:registries": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/validate-registries.mts", "postinstall": "fumadocs-mdx" }, "dependencies": { diff --git a/apps/v4/public/r/registries.json b/apps/v4/public/r/registries.json new file mode 100644 index 0000000000..e555db8771 --- /dev/null +++ b/apps/v4/public/r/registries.json @@ -0,0 +1,3 @@ +{ + "@alpine": "https://alpine-registry.vercel.app/r/{name}.json" +} diff --git a/apps/v4/scripts/validate-registries.mts b/apps/v4/scripts/validate-registries.mts new file mode 100644 index 0000000000..cf68e69e4e --- /dev/null +++ b/apps/v4/scripts/validate-registries.mts @@ -0,0 +1,58 @@ +import { promises as fs } from "fs" +import path from "path" +import { registrySchema } from "shadcn/schema" +import { z } from "zod" + +const registriesIndexSchema = z.record( + z.string().regex(/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/), + z.string().refine((url) => url.includes("{name}")) +) + +async function main() { + // 1. Validate the registries.json file. + const registriesFile = path.join(process.cwd(), "public/r/registries.json") + const content = await fs.readFile(registriesFile, "utf-8") + const data = JSON.parse(content) + const registries = registriesIndexSchema.parse(data) + + // 2. Validate each registry endpoint. + const errors: string[] = [] + for (const [name, url] of Object.entries(registries)) { + try { + const testUrl = url.replace("{name}", "registry") + const response = await fetch(testUrl) + + if (!response.ok) { + errors.push(`${name}: HTTP ${response.status}`) + continue + } + + const json = await response.json() + registrySchema.parse(json) + console.log(`āœ… ${name}`) + } catch (error) { + if (error instanceof z.ZodError) { + errors.push(`${name}: ${error.message}`) + continue + } + + errors.push( + `${name}: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + if (errors.length > 0) { + console.error("\nāŒ Validation failed:") + errors.forEach((err) => console.error(` ${err}`)) + process.exit(1) + } + + console.log("\nāœ… All registries passed validation.") + process.exit(0) +} + +main().catch((error) => { + console.error("āŒ Error:", error instanceof Error ? error.message : error) + process.exit(1) +}) diff --git a/package.json b/package.json index 04a22dfd62..413ed53df0 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "pub:beta": "cd packages/shadcn && pnpm pub:beta", "pub:release": "cd packages/shadcn && pnpm pub:release", "test:dev": "turbo run test --filter=!shadcn-ui --force", - "test": "start-server-and-test v4:dev http://localhost:4000 test:dev" + "test": "start-server-and-test v4:dev http://localhost:4000 test:dev", + "validate:registries": "pnpm --filter=v4 validate:registries" }, "packageManager": "pnpm@9.0.6", "dependencies": {