Repository: ieedan/jsrepo Branch: main Commit: aa1753d4a043 Files: 459 Total size: 1.1 MB Directory structure: gitextract__1f43wdr/ ├── .changeset/ │ └── config.json ├── .github/ │ └── workflows/ │ ├── build-example-registries.yml │ ├── bundle-analyze.yml │ ├── ci.yml │ ├── cli-preview.yml │ ├── publish.yml │ └── test-external-projects.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps/ │ └── docs/ │ ├── .gitignore │ ├── README.md │ ├── biome.json │ ├── cli.json │ ├── content/ │ │ └── docs/ │ │ ├── cli/ │ │ │ ├── add.mdx │ │ │ ├── auth.mdx │ │ │ ├── build.mdx │ │ │ ├── config/ │ │ │ │ ├── language.mdx │ │ │ │ ├── mcp.mdx │ │ │ │ ├── meta.json │ │ │ │ ├── provider.mdx │ │ │ │ └── transform.mdx │ │ │ ├── meta.json │ │ │ ├── publish.mdx │ │ │ └── update.mdx │ │ ├── create-a-registry.mdx │ │ ├── index.mdx │ │ ├── jsrepo-com.mdx │ │ ├── jsrepo-config.mdx │ │ ├── languages/ │ │ │ ├── css.mdx │ │ │ ├── html.mdx │ │ │ ├── index.mdx │ │ │ ├── js.mdx │ │ │ ├── svelte.mdx │ │ │ └── vue.mdx │ │ ├── legacy.mdx │ │ ├── mcp.mdx │ │ ├── migrate.mdx │ │ ├── outputs/ │ │ │ ├── distributed.mdx │ │ │ ├── index.mdx │ │ │ ├── repository.mdx │ │ │ └── shadcn.mdx │ │ ├── providers/ │ │ │ ├── azure.mdx │ │ │ ├── bitbucket.mdx │ │ │ ├── fs.mdx │ │ │ ├── github.mdx │ │ │ ├── gitlab.mdx │ │ │ ├── http.mdx │ │ │ ├── index.mdx │ │ │ ├── jsrepo.mdx │ │ │ └── shadcn.mdx │ │ └── transforms/ │ │ ├── biome.mdx │ │ ├── filecasing.mdx │ │ ├── index.mdx │ │ ├── javascript.mdx │ │ ├── oxfmt.mdx │ │ └── prettier.mdx │ ├── instrumentation-client.js │ ├── jsrepo.config.mts │ ├── middleware.ts │ ├── next.config.mjs │ ├── og-image.d.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── source.config.ts │ ├── src/ │ │ ├── app/ │ │ │ ├── (home)/ │ │ │ │ ├── code-block.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── providers-section.tsx │ │ │ ├── api/ │ │ │ │ └── search/ │ │ │ │ └── route.ts │ │ │ ├── app-client.tsx │ │ │ ├── docs/ │ │ │ │ ├── [[...slug]]/ │ │ │ │ │ └── page.tsx │ │ │ │ └── layout.tsx │ │ │ ├── global.css │ │ │ ├── layout.tsx │ │ │ ├── llms-full.txt/ │ │ │ │ └── route.ts │ │ │ ├── llms.mdx/ │ │ │ │ └── [[...slug]]/ │ │ │ │ └── route.ts │ │ │ ├── og/ │ │ │ │ ├── docs/ │ │ │ │ │ └── [...slug]/ │ │ │ │ │ └── route.tsx │ │ │ │ └── route.tsx │ │ │ └── robots.txt │ │ ├── components/ │ │ │ ├── PrismaticBurst.tsx │ │ │ ├── badges-table.tsx │ │ │ ├── feature-tabs.tsx │ │ │ ├── files.tsx │ │ │ ├── language-toggle.tsx │ │ │ ├── layout/ │ │ │ │ ├── home/ │ │ │ │ │ ├── client.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── shared/ │ │ │ │ ├── client.tsx │ │ │ │ └── index.tsx │ │ │ ├── logos/ │ │ │ │ ├── antigravity.tsx │ │ │ │ ├── azure-devops.tsx │ │ │ │ ├── biome.tsx │ │ │ │ ├── bitbucket.tsx │ │ │ │ ├── claude.tsx │ │ │ │ ├── css.tsx │ │ │ │ ├── cursor.tsx │ │ │ │ ├── github.tsx │ │ │ │ ├── gitlab.tsx │ │ │ │ ├── html.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── javascript.tsx │ │ │ │ ├── jsrepo-com.tsx │ │ │ │ ├── npm.tsx │ │ │ │ ├── openai.tsx │ │ │ │ ├── oxfmt.tsx │ │ │ │ ├── prettier.tsx │ │ │ │ ├── registry-kit.tsx │ │ │ │ ├── shadcn.tsx │ │ │ │ ├── svelte.tsx │ │ │ │ ├── typescript.tsx │ │ │ │ ├── vscode.tsx │ │ │ │ └── vue.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── page-actions.tsx │ │ │ ├── registry-index.tsx │ │ │ ├── registry-kit/ │ │ │ │ ├── demo-example.tsx │ │ │ │ └── demo.tsx │ │ │ ├── search-toggle.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── theme-toggle.tsx │ │ │ └── ui/ │ │ │ ├── accordion.tsx │ │ │ ├── animated-beam.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── field.tsx │ │ │ ├── github-button.tsx │ │ │ ├── index.ts │ │ │ ├── input-group.tsx │ │ │ ├── input.tsx │ │ │ ├── item.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── separator.tsx │ │ │ ├── sonner.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── terminal.tsx │ │ │ ├── textarea.tsx │ │ │ ├── tooltip.tsx │ │ │ └── underline-tabs.tsx │ │ ├── hooks/ │ │ │ ├── use-copy-to-clipboard.tsx │ │ │ └── use-scroll-to-top.tsx │ │ ├── lib/ │ │ │ ├── cn.ts │ │ │ ├── is-active.ts │ │ │ ├── layout.shared.tsx │ │ │ ├── og.ts │ │ │ ├── source.ts │ │ │ └── utils.ts │ │ └── mdx-components.tsx │ └── tsconfig.json ├── biome.json ├── examples/ │ ├── react/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── biome.json │ │ ├── jsrepo.config.mts │ │ ├── next.config.ts │ │ ├── package.json │ │ ├── postcss.config.mjs │ │ ├── public/ │ │ │ └── r/ │ │ │ ├── button.json │ │ │ ├── registry.json │ │ │ ├── shadcn/ │ │ │ │ ├── button.json │ │ │ │ ├── registry.json │ │ │ │ └── utils.json │ │ │ └── utils.json │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── demos/ │ │ │ │ │ └── button-demo/ │ │ │ │ │ └── page.tsx │ │ │ │ ├── globals.css │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ └── registry/ │ │ │ ├── lib/ │ │ │ │ └── utils.ts │ │ │ └── ui/ │ │ │ └── button.tsx │ │ └── tsconfig.json │ └── svelte/ │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── biome.json │ ├── jsrepo.config.ts │ ├── package.json │ ├── src/ │ │ ├── app.css │ │ ├── app.d.ts │ │ ├── app.html │ │ ├── lib/ │ │ │ └── registry/ │ │ │ ├── lib/ │ │ │ │ └── utils.ts │ │ │ └── ui/ │ │ │ └── button/ │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ └── routes/ │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ └── demos/ │ │ └── button-demo/ │ │ └── +page.svelte │ ├── static/ │ │ ├── r/ │ │ │ ├── button.json │ │ │ ├── registry.json │ │ │ └── utils.json │ │ └── robots.txt │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── package.json ├── packages/ │ ├── bun/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── workspace.ts │ │ ├── tests/ │ │ │ ├── bun.test.ts │ │ │ └── fixtures/ │ │ │ ├── bun-workspace/ │ │ │ │ ├── package.json │ │ │ │ └── packages/ │ │ │ │ ├── bar/ │ │ │ │ │ └── package.json │ │ │ │ └── foo/ │ │ │ │ └── package.json │ │ │ └── bun-workspace-object/ │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── baz/ │ │ │ └── package.json │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── jsrepo/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── api/ │ │ │ │ ├── config.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── index.ts │ │ │ │ ├── langs/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── js.ts │ │ │ │ ├── outputs.ts │ │ │ │ ├── providers.ts │ │ │ │ ├── utils.ts │ │ │ │ └── warnings.ts │ │ │ ├── bin.ts │ │ │ ├── cli.ts │ │ │ ├── commands/ │ │ │ │ ├── add.ts │ │ │ │ ├── auth.ts │ │ │ │ ├── build.ts │ │ │ │ ├── config/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── language.ts │ │ │ │ │ ├── mcp.ts │ │ │ │ │ ├── provider.ts │ │ │ │ │ └── transform.ts │ │ │ │ ├── index.ts │ │ │ │ ├── init.ts │ │ │ │ ├── publish.ts │ │ │ │ ├── update.ts │ │ │ │ └── utils.ts │ │ │ ├── langs/ │ │ │ │ ├── css.ts │ │ │ │ ├── html.ts │ │ │ │ ├── index.ts │ │ │ │ ├── js.ts │ │ │ │ ├── svelte.ts │ │ │ │ ├── types.ts │ │ │ │ └── vue.ts │ │ │ ├── outputs/ │ │ │ │ ├── distributed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── repository.ts │ │ │ │ └── types.ts │ │ │ ├── providers/ │ │ │ │ ├── azure.ts │ │ │ │ ├── bitbucket.ts │ │ │ │ ├── fs.ts │ │ │ │ ├── github.ts │ │ │ │ ├── gitlab.ts │ │ │ │ ├── http.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jsrepo.ts │ │ │ │ └── types.ts │ │ │ └── utils/ │ │ │ ├── add.ts │ │ │ ├── build.ts │ │ │ ├── casing.ts │ │ │ ├── compat/ │ │ │ │ └── shadcn.ts │ │ │ ├── config/ │ │ │ │ ├── index.ts │ │ │ │ ├── mods/ │ │ │ │ │ ├── add-plugins.ts │ │ │ │ │ ├── add-registries.ts │ │ │ │ │ └── update-paths.ts │ │ │ │ └── utils.ts │ │ │ ├── diff.ts │ │ │ ├── env.ts │ │ │ ├── errors.ts │ │ │ ├── fs.ts │ │ │ ├── glob.ts │ │ │ ├── hooks.ts │ │ │ ├── json.ts │ │ │ ├── lines.ts │ │ │ ├── package.ts │ │ │ ├── pad.ts │ │ │ ├── parse-package-name.ts │ │ │ ├── path.ts │ │ │ ├── persisted.ts │ │ │ ├── prompts.ts │ │ │ ├── roles.ts │ │ │ ├── strings.ts │ │ │ ├── token-manager.ts │ │ │ ├── tsconfig.ts │ │ │ ├── types.ts │ │ │ ├── url.ts │ │ │ ├── utils.ts │ │ │ ├── validate-npm-package-name.ts │ │ │ ├── warnings.ts │ │ │ └── zod.ts │ │ ├── tests/ │ │ │ ├── __mocks__/ │ │ │ │ ├── fs/ │ │ │ │ │ └── promises.cjs │ │ │ │ └── fs.cjs │ │ │ ├── build.test.ts │ │ │ ├── config.test.ts │ │ │ ├── fixtures/ │ │ │ │ ├── build/ │ │ │ │ │ ├── .npmignore │ │ │ │ │ ├── .npmrc │ │ │ │ │ ├── jsrepo.config.ts │ │ │ │ │ ├── package.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── ui/ │ │ │ │ │ │ ├── button.tsx │ │ │ │ │ │ ├── counter.svelte │ │ │ │ │ │ ├── empty/ │ │ │ │ │ │ │ ├── empty-content.svelte │ │ │ │ │ │ │ ├── empty.svelte │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── lanyard/ │ │ │ │ │ │ ├── Lanyard.tsx │ │ │ │ │ │ └── card.glb │ │ │ │ │ ├── routes/ │ │ │ │ │ │ ├── demo/ │ │ │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ │ │ └── +page.svelte │ │ │ │ │ │ └── demos/ │ │ │ │ │ │ ├── button-default.tsx │ │ │ │ │ │ ├── button-loading.tsx │ │ │ │ │ │ └── subdir/ │ │ │ │ │ │ ├── button-subdir.tsx │ │ │ │ │ │ └── nested/ │ │ │ │ │ │ └── button-nested.tsx │ │ │ │ │ ├── utils/ │ │ │ │ │ │ ├── math/ │ │ │ │ │ │ │ ├── add.test.ts │ │ │ │ │ │ │ ├── add.ts │ │ │ │ │ │ │ ├── answer-format.test.ts │ │ │ │ │ │ │ └── answer-format.ts │ │ │ │ │ │ ├── shiki.ts │ │ │ │ │ │ └── stdout.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── config/ │ │ │ │ │ ├── basic/ │ │ │ │ │ │ └── jsrepo.config.ts │ │ │ │ │ ├── mts/ │ │ │ │ │ │ └── jsrepo.config.mts │ │ │ │ │ └── nested/ │ │ │ │ │ └── jsrepo.config.ts │ │ │ │ └── langs/ │ │ │ │ ├── js/ │ │ │ │ │ ├── logger.ts │ │ │ │ │ ├── math/ │ │ │ │ │ │ ├── add.ts │ │ │ │ │ │ └── subtract.ts │ │ │ │ │ ├── print-answer.ts │ │ │ │ │ └── stdout.ts │ │ │ │ ├── js-arbitrary-extensions/ │ │ │ │ │ ├── component.svelte │ │ │ │ │ ├── config.json │ │ │ │ │ ├── index.ts │ │ │ │ │ └── svelte-entry.ts │ │ │ │ ├── js-baseurl-bare-imports/ │ │ │ │ │ ├── package.json │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── types.ts │ │ │ │ ├── js-subpath-imports/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── package.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── meta.ts │ │ │ │ │ └── utils/ │ │ │ │ │ └── print.ts │ │ │ │ └── svelte/ │ │ │ │ └── page.svelte │ │ │ ├── langs/ │ │ │ │ ├── js.test.ts │ │ │ │ └── svelte.test.ts │ │ │ ├── providers/ │ │ │ │ ├── azure.test.ts │ │ │ │ ├── bitbucket.test.ts │ │ │ │ ├── fs.test.ts │ │ │ │ ├── github.test.ts │ │ │ │ ├── gitlab.test.ts │ │ │ │ └── http.test.ts │ │ │ └── utils/ │ │ │ ├── add-plugins-to-config.test.ts │ │ │ ├── add-registries.test.ts │ │ │ ├── add.test.ts │ │ │ ├── casing.test.ts │ │ │ ├── config-mcp.test.ts │ │ │ ├── config-utils.test.ts │ │ │ ├── env.test.ts │ │ │ ├── package.test.ts │ │ │ ├── parse-package-name.test.ts │ │ │ ├── roles.test.ts │ │ │ ├── update-paths.test.ts │ │ │ └── zod.test.ts │ │ ├── tsconfig.json │ │ ├── tsdown.config.ts │ │ └── vitest.config.ts │ ├── mcp/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── server.ts │ │ │ └── utils.ts │ │ ├── tests/ │ │ │ └── mcp.test.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── migrate/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── bin.ts │ │ │ ├── cli.ts │ │ │ ├── commands/ │ │ │ │ ├── index.ts │ │ │ │ ├── utils.ts │ │ │ │ └── v3.ts │ │ │ └── utils/ │ │ │ ├── errors.ts │ │ │ ├── fs.ts │ │ │ ├── json.ts │ │ │ ├── package.ts │ │ │ ├── parse-package-name.ts │ │ │ ├── path.ts │ │ │ ├── prompts.ts │ │ │ ├── strings.ts │ │ │ ├── types.ts │ │ │ ├── v2/ │ │ │ │ └── config.ts │ │ │ └── zod.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── pnpm/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── catalog.ts │ │ │ ├── index.ts │ │ │ └── workspace.ts │ │ ├── tests/ │ │ │ ├── fixtures/ │ │ │ │ └── pnpm-workspace/ │ │ │ │ ├── packages/ │ │ │ │ │ ├── pkg-a/ │ │ │ │ │ │ └── package.json │ │ │ │ │ ├── pkg-b/ │ │ │ │ │ │ └── package.json │ │ │ │ │ └── pkg-c/ │ │ │ │ │ └── package.json │ │ │ │ └── pnpm-workspace.yaml │ │ │ └── pnpm.test.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── shadcn/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── output.ts │ │ │ ├── provider.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── transform-biome/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── transform-filecasing/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tests/ │ │ │ └── filecasing.test.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── transform-javascript/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tests/ │ │ │ └── strip-types.test.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ ├── transform-oxfmt/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── biome.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── tsdown.config.ts │ └── transform-prettier/ │ ├── CHANGELOG.md │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── src/ │ │ └── index.ts │ ├── tsconfig.json │ └── tsdown.config.ts ├── pnpm-workspace.yaml └── task.config.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", "changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "jsrepojs/jsrepo" }], "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": ["@jsrepo/docs", "@example/react", "@example/svelte"], "prettier": false } ================================================ FILE: .github/workflows/build-example-registries.yml ================================================ name: build-example-registries on: pull_request: branches: [main] jobs: build-example-registries: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install dependencies run: pnpm install - name: Build example registries run: pnpm build:example-registries ================================================ FILE: .github/workflows/bundle-analyze.yml ================================================ name: Bundle Analyze on: pull_request: branches: [next, main] jobs: bundle-analyze: runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install dependencies run: pnpm install - name: Build packages run: pnpm build:packages - name: Bundle Analyze run: | pnpm bundle-analyze ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: branches: [main] jobs: CI: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install dependencies run: pnpm install - name: Check Types run: pnpm check - name: Test run: pnpm test ================================================ FILE: .github/workflows/cli-preview.yml ================================================ name: Package Previews on: pull_request: branches: [main] paths: - 'pnpm-lock.yaml' - 'packages/**' jobs: release-previews: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install dependencies run: pnpm install # packages automatically built on postinstall - name: publish preview run: pnpm pkg-pr-new publish --pnpm './packages/jsrepo' './packages/mcp' './packages/transform-prettier' './packages/transform-biome' './packages/transform-javascript' './packages/migrate' './packages/transform-oxfmt' './packages/transform-filecasing' --packageManager=pnpm ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: push: branches: - main concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: permissions: contents: write # to create release (changesets/action) pull-requests: write # to create pull request (changesets/action) id-token: write # Required for OIDC name: Build & Publish Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: 24 cache: 'pnpm' - run: npm install -g npm@latest - name: Install dependencies run: pnpm install - name: Create Release Pull Request or Publish to npm id: changesets uses: changesets/action@v1 with: commit: "chore(release): version package" title: "chore(release): version package" publish: pnpm ci:release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_CONFIG_PROVENANCE: true ================================================ FILE: .github/workflows/test-external-projects.yml ================================================ name: Test External Projects on: pull_request: branches: [main] jobs: test-external-projects: runs-on: ubuntu-latest strategy: matrix: project: - name: shadcn-svelte-extras repo: ieedan/shadcn-svelte-extras pm: pnpm build: pnpm registry:build - name: react-bits repo: DavidHDev/react-bits pm: npm build: npm run registry:build steps: - name: Checkout jsrepo uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install jsrepo dependencies run: pnpm install - name: Build jsrepo run: pnpm build:packages - name: Pack jsrepo id: pack run: | cd packages/jsrepo && pnpm pack TARBALL="$(ls jsrepo-*.tgz)" echo "tarball_path=${{ github.workspace }}/packages/jsrepo/$TARBALL" >> $GITHUB_OUTPUT - name: Clone ${{ matrix.project.name }} uses: actions/checkout@v4 with: repository: ${{ matrix.project.repo }} path: external-project - name: Cache ${{ matrix.project.name }} dependencies uses: actions/cache@v4 with: path: external-project/node_modules key: ${{ matrix.project.name }}-deps-${{ hashFiles('external-project/package-lock.json', 'external-project/pnpm-lock.yaml') }} - name: Install local jsrepo in ${{ matrix.project.name }} working-directory: external-project env: TARBALL: ${{ steps.pack.outputs.tarball_path }} run: | if [ "${{ matrix.project.pm }}" = "pnpm" ]; then pnpm add "jsrepo@file:$TARBALL" else npm install "jsrepo@file:$TARBALL" fi - name: Build ${{ matrix.project.name }} registry working-directory: external-project run: ${{ matrix.project.build }} ================================================ FILE: .gitignore ================================================ node_modules # out dist # testing temp-test **/tests/fixtures/**/node_modules **/tests/fixtures/**/package-lock.json **/tests/fixtures/**/pnpm-lock.yaml .DS_Store # ralphex progress logs progress*.txt ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": ["fuzzysort", "onwarn", "packlist"] } ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to jsrepo Looking to contribute to jsrepo? Great! You're in the right place. This guide will help you get started. ## AI Assistance Notice > [!IMPORTANT] > > If you are using **any kind of AI assistance** to contribute to jsrepo, > it must be disclosed in the pull request. If you are using any kind of AI assistance while contributing to jsrepo, **this must be disclosed in the pull request**, along with the extent to which AI assistance was used (e.g. docs only vs. code generation). If PR responses are being generated by an AI, disclose that as well. As a small exception, trivial tab-completion doesn't need to be disclosed, so long as it is limited to single keywords or short phrases. An example disclosure: > This PR was written primarily by Claude Code. Or a more detailed disclosure: > I consulted ChatGPT to understand the codebase but the solution > was fully authored manually by myself. Failure to disclose this is first and foremost rude to the human operators on the other end of the pull request, but it also makes it difficult to determine how much scrutiny to apply to the contribution. When using AI assistance, we expect contributors to understand the code that is produced and be able to answer critical questions about it. It isn't a maintainers job to review a PR so broken that it requires significant rework to be acceptable. Please be respectful to maintainers and disclose AI assistance. ## Development First install dependencies: ```sh pnpm install ``` Then run the dev script: ```sh pnpm dev ``` This will start the development server and watch for changes to packages. The docs are available at [http://localhost:3000](http://localhost:3000). To run the tests, run the following command: ```sh pnpm test ``` To test jsrepo manually you can use the playground projects in the [`playground/`](./playground/) directory. ### Project Structure - `packages/jsrepo` - jsrepo CLI - `packages/mcp` - MCP server for jsrepo - `playground/` - Playground projects to manually test jsrepo - `apps/docs` - Contains the documentation hosted at [https://jsrepo.dev](https://jsrepo.dev). ## Before opening a PR ### Open an issue If your PR is more than a one line fix please open an issue first to discuss the changes you want to make. ### Checklist - Run `pnpm format` - Run `pnpm lint` (Ensure passing) - Run `pnpm check` (Ensure passing) - Run `pnpm test` (Ensure passing) - Run `pnpm changeset` and add a changeset for your changes if it effects any of the released packages (under packages/*) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 jsrepo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ CleanShot 2025-11-24 at 09 35 57 [![npm version](https://flat.badgen.net/npm/v/jsrepo?color=yellow)](https://npmjs.com/package/jsrepo) [![npm downloads](https://flat.badgen.net/npm/dm/jsrepo?color=yellow)](https://npmjs.com/package/jsrepo) # jsrepo The modern registry toolchain. Distribute your code in any language anywhere with one CLI. ## Get Started ```sh npx jsrepo init ``` ================================================ FILE: apps/docs/.gitignore ================================================ # deps /node_modules # generated content .contentlayer .content-collections .source # test & build /coverage /.next/ /out/ /build *.tsbuildinfo # misc .DS_Store *.pem /.pnp .pnp.js npm-debug.log* yarn-debug.log* yarn-error.log* # others .env*.local .vercel next-env.d.ts public/registry-kit ================================================ FILE: apps/docs/README.md ================================================ # docs This is a Next.js application generated with [Create Fumadocs](https://github.com/fuma-nama/fumadocs). Run development server: ```bash npm run dev # or pnpm dev # or yarn dev ``` Open http://localhost:3000 with your browser to see the result. ## Explore In the project, you can see: - `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content. - `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep. | Route | Description | | ------------------------- | ------------------------------------------------------ | | `app/(home)` | The route group for your landing page and other pages. | | `app/docs` | The documentation layout and pages. | | `app/api/search/route.ts` | The Route Handler for search. | ### Fumadocs MDX A `source.config.ts` config file has been included, you can customise different options like frontmatter schema. Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details. ## Learn More To learn more about Next.js and Fumadocs, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - [Fumadocs](https://fumadocs.dev) - learn about Fumadocs ================================================ FILE: apps/docs/biome.json ================================================ { "root": false, "extends": "//", "files": { "includes": [] } } ================================================ FILE: apps/docs/cli.json ================================================ { "aliases": { "uiDir": "./components/ui", "componentsDir": "./components", "blockDir": "./components", "cssDir": "./styles", "libDir": "./lib" }, "baseDir": "src", "commands": {} } ================================================ FILE: apps/docs/content/docs/cli/add.mdx ================================================ --- title: add description: Add items from registries to your project. --- ```sh jsrepo add ``` ## Usage Add an item from the registries in your `jsrepo.config` file: ```sh jsrepo add button ``` If the item exists in multiple registries you will be prompted to select which one you want to add: ``` ◆ Multiple registries contain button. Please select one: │ ● button (@ieedan/shadcn-svelte-extras) │ ○ button (@ai/elements) └ ``` Add an item from a specific registry: ```sh jsrepo add @ieedan/shadcn-svelte-extras/button ``` Add all items from a specific registry: ```sh jsrepo add @ieedan/shadcn-svelte-extras --all ``` Add all items from every registry in your `jsrepo.config` file: ```sh jsrepo add --all ``` ## Options ### `--all` Add all items from every registry: ```sh jsrepo add --all ``` ### `--cwd` The current working directory: ```sh jsrepo add button --cwd ./my-project ``` ### `--expand` Expands the diff so you see the entire file: ```sh jsrepo add button --expand ``` ### `--max-unchanged` Maximum unchanged lines that will show without being collapsed: ```sh jsrepo add button --max-unchanged 20 ``` ### `--overwrite` Overwrite files without prompting: ```sh jsrepo add button --overwrite ``` ### `--registry` The registry to add items from: ```sh jsrepo add button --registry @ieedan/shadcn-svelte-extras ``` ### `--verbose` Include debug logs: ```sh jsrepo add button --verbose ``` ### `--with` Include files with the given roles: ```sh jsrepo add --with example test storybook ``` ### `--with-docs` (Deprecated) Use `--with doc`: ```sh jsrepo add --with-docs ``` ### `--with-examples` (Deprecated) Use `--with example`: ```sh jsrepo add --with-examples ``` ### `--with-tests` (Deprecated) Use `--with test`: ```sh jsrepo add --with-tests ``` ### `--yes` Skip the confirmation prompt: ```sh jsrepo add button --yes ``` ================================================ FILE: apps/docs/content/docs/cli/auth.mdx ================================================ --- title: auth description: Authenticate to a provider or registry. --- ```sh jsrepo auth ``` ## Usage Authenticate to a provider. You will be prompted to select a provider if multiple are available: ```sh jsrepo auth ``` Authenticate to a specific provider: ```sh jsrepo auth github ``` If the provider requires registry-specific authentication, you will be prompted to select a registry: ``` ◆ Select a registry to authenticate to. │ ● @ieedan/shadcn-svelte-extras │ ○ @ai/elements │ ○ Other └ ``` Logout from a provider: ```sh jsrepo auth github --logout ``` If the provider uses registry-specific tokens, you will be prompted to select which registry to logout from: ``` ◆ Select a registry to logout of. │ ● @ieedan/shadcn-svelte-extras │ ○ @ai/elements └ ``` Authenticate using a token directly: ```sh jsrepo auth github --token ``` Authenticate non-interactively for a specific registry: ```sh jsrepo auth --registry https://private-registry.example.com --token ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo auth --cwd ./my-project ``` ### `--logout` Execute the logout flow: ```sh jsrepo auth github --logout ``` ### `--registry` The registry to authenticate to. This is especially useful in non-interactive environments where prompts are unavailable: ```sh jsrepo auth --registry https://private-registry.example.com --token ``` ### `--token` The token to use for authenticating to this provider: ```sh jsrepo auth github --token ``` ### `--verbose` Include debug logs: ```sh jsrepo auth github --verbose ``` ================================================ FILE: apps/docs/content/docs/cli/build.mdx ================================================ --- title: build description: Build your registry. --- ```sh jsrepo build ``` ## Usage Build all blocks from the registries in your `jsrepo.config` file: ```sh jsrepo build ``` Build blocks and watch for changes: ```sh jsrepo build --watch ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo build --cwd ./my-project ``` ### `--debounce`, `-d` How long to wait before building again after a change is detected (watch mode only): ```sh jsrepo build --watch --debounce 200 ``` ### `--watch`, `-w` Watch for changes and rebuild automatically: ```sh jsrepo build --watch ``` ================================================ FILE: apps/docs/content/docs/cli/config/language.mdx ================================================ --- title: language description: Add a language to your config. --- ```sh jsrepo config language ``` ## Usage Add a language to your config. You will be prompted to install dependencies after adding: ```sh jsrepo config language jsrepo-language-go ``` Add multiple languages at once: ```sh jsrepo config language jsrepo-language-go jsrepo-language-rust ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo config language jsrepo-language-go --cwd ./my-project ``` ### `--yes` Skip the confirmation prompt: ```sh jsrepo config language jsrepo-language-go --yes ``` ================================================ FILE: apps/docs/content/docs/cli/config/mcp.mdx ================================================ --- title: mcp description: Configure the jsrepo MCP server for your environment. --- ```sh jsrepo config mcp ``` ## Usage Configure the jsrepo MCP server. You will be prompted to select which clients to configure: ```sh jsrepo config mcp ``` You will be prompted to select which clients you want to configure: ``` ◆ Which clients would you like to configure? │ ■ Cursor │ □ Claude Code │ □ VS Code │ □ Codex └ ``` Configure a specific client: ```sh jsrepo config mcp --client cursor ``` Configure multiple clients: ```sh jsrepo config mcp --client cursor vscode ``` Configure all supported MCP clients: ```sh jsrepo config mcp --all ``` ## Options ### `--all` Configure all supported MCP clients: ```sh jsrepo config mcp --all ``` ### `--client` The MCP client(s) to configure. Supported clients: `cursor`, `claude`, `vscode`, `codex`: ```sh jsrepo config mcp --client cursor ``` Configure multiple clients: ```sh jsrepo config mcp --client cursor vscode ``` ### `--cwd` The current working directory: ```sh jsrepo config mcp --cwd ./my-project ``` ================================================ FILE: apps/docs/content/docs/cli/config/meta.json ================================================ { "title": "config" } ================================================ FILE: apps/docs/content/docs/cli/config/provider.mdx ================================================ --- title: provider description: Add a provider to your config. --- ```sh jsrepo config provider ``` ## Usage Add a provider to your config. You will be prompted to install dependencies after adding: ```sh jsrepo config provider jsrepo-provider-jsr ``` Add multiple providers at once: ```sh jsrepo config provider jsrepo-provider-jsr jsrepo-provider-npm ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo config provider jsrepo-provider-jsr --cwd ./my-project ``` ### `--yes` Skip the confirmation prompt: ```sh jsrepo config provider jsrepo-provider-jsr --yes ``` ================================================ FILE: apps/docs/content/docs/cli/config/transform.mdx ================================================ --- title: transform description: Add a transform to your config. --- ```sh jsrepo config transform ``` ## Usage Add a transform to your config. You will be prompted to install dependencies after adding: ```sh jsrepo config transform @jsrepo/transform-prettier ``` Add multiple transforms at once: ```sh jsrepo config transform @jsrepo/transform-prettier @jsrepo/transform-biome ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo config transform @jsrepo/transform-prettier --cwd ./my-project ``` ### `--yes` Skip the confirmation prompt: ```sh jsrepo config transform @jsrepo/transform-prettier --yes ``` ================================================ FILE: apps/docs/content/docs/cli/meta.json ================================================ { "title": "CLI" } ================================================ FILE: apps/docs/content/docs/cli/publish.mdx ================================================ --- title: publish description: Publish your registry to jsrepo.com. --- ```sh jsrepo publish ``` ## Usage Publish all registries in your `jsrepo.config` file: ```sh jsrepo publish ``` Publish a specific registry: ```sh jsrepo publish @jsrepo/playground ``` Publish in dry run mode: ```sh jsrepo publish --dry-run ``` ## Options ### `--cwd` The current working directory: ```sh jsrepo build --cwd ./my-project ``` ### `--dry-run` Publish in dry run mode. This will check with jsrepo.com to see if the registry can be published in it's current state: ```sh jsrepo publish --dry-run ``` ### `--verbose` Include debug logs: ```sh jsrepo publish --verbose ``` ================================================ FILE: apps/docs/content/docs/cli/update.mdx ================================================ --- title: update description: Update items in your project. --- ```sh jsrepo update ``` ## Usage Update an item in your project: ```sh jsrepo update button ``` Select which items from your project that you want to update: ```sh jsrepo update ``` ``` ◆ Which items would you like to update? │ ■ button │ □ math │ □ logger └ ``` Update an item from a specific registry: ```sh jsrepo update @ieedan/shadcn-svelte-extras/button ``` Update all items in your project: ```sh jsrepo update --all ``` ## Options ### `--all` Update all items in your project: ```sh jsrepo update --all ``` ### `--cwd` The current working directory: ```sh jsrepo update button --cwd ./my-project ``` ### `--expand` Expands the diff so you see the entire file: ```sh jsrepo update button --expand ``` ### `--max-unchanged` Maximum unchanged lines that will show without being collapsed: ```sh jsrepo update button --max-unchanged 20 ``` ### `--overwrite` Overwrite files without prompting: ```sh jsrepo update button --overwrite ``` ### `--registry` The registry to update items from: ```sh jsrepo update button --registry @ieedan/shadcn-svelte-extras ``` ### `--verbose` Include debug logs: ```sh jsrepo update button --verbose ``` ### `--with` Include files with the given roles: ```sh jsrepo update --with example test storybook ``` ### `--with-docs` (Deprecated) Use `--with doc`: ```sh jsrepo update --with-docs ``` ### `--with-examples` (Deprecated) Use `--with example`: ```sh jsrepo update --with-examples ``` ### `--with-tests` (Deprecated) Use `--with test`: ```sh jsrepo update --with-tests ``` ### `--yes` Skip the confirmation prompt: ```sh jsrepo update button --yes ``` ================================================ FILE: apps/docs/content/docs/create-a-registry.mdx ================================================ --- title: Create a registry description: A complete guide to creating your own registry with jsrepo. --- In this guide we will show you how to setup a registry with **jsrepo**. ## Creating a registry To create a registry start by running: ```npm npx jsrepo init ``` This will initialize a blank config in your project and install **jsrepo** as a dev dependency. Before we continue let's create some items for our registry. ```ts tab="src/stdout.ts" export function print(msg: string) { console.log(msg); } ``` ```ts tab="src/logger.ts" import { print } from './stdout'; export function createLogger() { return { log: print, } } ``` Next we can configure the registry with the `registry` key. Let's start by giving the registry a name: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { name: 'my-first-registry', // [!code ++] }, }); ``` Next let's add the items we just created to the registry: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { // ... items: [ // [!code ++] { // [!code ++] name: 'logger', // [!code ++] type: 'utils', // [!code ++] files: [ // [!code ++] { // [!code ++] path: 'src/logger.ts', // [!code ++] }, // [!code ++] ] // [!code ++] }, // [!code ++] { // [!code ++] name: 'stdout', // [!code ++] type: 'utils', // [!code ++] add: 'when-needed', // [!code ++] this will prevent the item from being listed by the `add` command files: [ // [!code ++] { // [!code ++] path: 'src/stdout.ts', // [!code ++] }, // [!code ++] ] // [!code ++] } // [!code ++] ], // [!code ++] } }); ``` For now we will use the `repository` output for our registry so let's add it to our config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { // ... outputs: [repository()], // [!code ++] }, }); ``` Now we can build our registry with the `jsrepo build` command: ```sh jsrepo build ``` This will create a `registry.json` file at the root of our project that contains everything we need to start adding items from our registry to other projects. ### Testing the registry To test our registry locally we can use the `fs` provider. Let's add it to our config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; import { fs } from "jsrepo/providers"; // [!code ++] export default defineConfig({ // ... providers: [fs()], // [!code ++] }); ``` Now let's initialize our registry with the `jsrepo init` command: ```sh jsrepo init fs://./ ``` This will add the registry to the `registries` key in our config file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; import { fs } from "jsrepo/providers"; export default defineConfig({ // ... registries: ["fs://./"], // [!code ++] }); ``` Now we can run `jsrepo add` to add an item to our project: ```sh jsrepo add logger ``` ## Deploying your registry This is the end of the basic guide. Now that you have a working registry you can deploy it wherever you want! Take a look at the [providers](/docs/providers) docs for the full list of hosting options. For more advanced usage you can continue reading below... ## Advanced Usage Now that we have covered the basics of creating a registry we can start to explore some of the features that make **jsrepo** so powerful. ### Resolving Dependencies **jsrepo** automatically resolves dependencies both to other items in the registry and to remote packages. This ensures that when building your registry it will work out of the box for end users. #### Excluding dependencies Many times you may not want certain dependencies to be installed with your registry items. For instance if you import `useState` from `react` you probably don't want to force users to install `react` with your registry. For this you can use the `excludeDeps` key of your registry config. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { // ... excludeDeps: ["react"], // [!code ++] }, }); ``` It's good practice to put your framework in the `excludeDeps` list whether that be `react`, `vue`, `svelte` etc. #### Opting out of automatic dependency resolution Occasionally you may want to opt out of automatic dependency resolution for a particular item or file. To do this you can set the `dependencyResolution` key to `manual` on the item or on specific files within the item: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { // ... dependencyResolution: "manual", // [!code ++] } ] }, }); ``` #### Resolving `workspace:` and `catalog:` dependencies By default `workspace:` and `catalog:` dependencies won't be handled by **jsrepo**, however if you are using `pnpm` or `bun` you can use the `@jsrepo/pnpm` or `@jsrepo/bun` packages to automatically resolve these dependencies. pnpm: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { pnpm } from "@jsrepo/pnpm"; // [!code ++] export default defineConfig({ // ... build: { remoteDependencyResolver: pnpm(), // [!code ++] }, }); ``` bun: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { bun } from "@jsrepo/bun"; // [!code ++] export default defineConfig({ // ... build: { remoteDependencyResolver: bun(), // [!code ++] }, }); ``` Now when you build your registry the `workspace:` and `catalog:` dependencies will be resolved to concrete versions. ### Files We showed you how to include files in the registry earlier by referencing them by their path but there's much more to know! #### File types Files can have any type that an item can. If you don't provide a type the file will simply inherit the type from the parent item. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { // ... files: [ { path: "src/example.ts", type: "utils", // [!code ++] } ] } ] } }); ``` #### File dependencies Just like items you can set the `dependencyResolution` to `manual` and manually specify the dependencies of a file. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { // ... files: [ { path: "src/example.ts", dependencyResolution: "manual", // [!code ++] dependencies: ["chalk"], // [!code ++] } ] } ] } }); ``` #### File roles File roles classify files into different categories that can be optionally installed by the user. The built-in roles are: - `file` - The default (always installed) - `example` - An example file - `doc` - A documentation file - `test` - A test file - Custom roles - Any other role you want to give files in your registry By default only files with the `file` role are included. Users can include files with specific roles using the `--with ` flag: ```sh jsrepo add --with story doc ``` These files are also made available to LLMs when using the `@jsrepo/mcp` server. You can specify the role of a file when you define it on an item: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { // ... items: [ { // ... files: [ { path: "src/example.ts", role: "example", // [!code ++] }, ], } ] }, }); ``` Files with any non-`file` role will only install their dependencies when the file is included. For example if you have a file with `role: "test"` that depends on `vitest`. `vitest` will only be installed to the user's project when the user provides `--with test`. Similarly if that same file was to depend on another item in the registry. Then that item will only be installed to the user's project when the test file is added. This allows you to add **documentation**, **examples**, **tests**, or any other optional role to your registry without forcing the user to install them. #### Folders When using frameworks like **Svelte** you are forced to define *one component per file* which will require you to bundle all your files into a folder. In cases like this you need to be able to include folders in your registry. Doing this is intuitive in **jsrepo**. Let's take for example, this **Empty** component: import { Files, Folder, File } from "@/components/files"; We can simply reference the folder path and **jsrepo** will automatically include all the files in the folder: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { name: 'empty', type: 'ui', files: [ { path: 'src/components/ui/empty', // [!code ++] }, ], }, ], }, }); ``` All the files in the folder will be included in the registry automatically and when users add them they will be added together under the `empty` folder. If you need to configure the files that are included in a folder you can use the `files` key on folder. This is also useful if you need to change properties like the `role` or `dependencyResolution` of a particular file. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { // ... files: [ { path: 'src/components/ui/empty', // [!code ++] files: [ // [!code ++] { // [!code ++] // file paths are relative to the parent folder path so this turns into `src/components/ui/empty/empty-content.svelte` // [!code ++] path: 'empty-content.svelte', // [!code ++] // you can also configure other properties like the `role` or `dependencyResolution` of a particular file. // [!code ++] dependencyResolution: 'manual', // [!code ++] }, // [!code ++] { // [!code ++] path: 'empty-description.svelte', // [!code ++] }, // [!code ++] { // [!code ++] path: 'empty-header.svelte', // [!code ++] }, // [!code ++] { // [!code ++] path: 'empty-media.svelte', // [!code ++] }, // [!code ++] { // [!code ++] path: 'empty-title.svelte', // [!code ++] }, // [!code ++] { // [!code ++] path: 'empty.svelte', // [!code ++] }, // [!code ++] { // [!code ++] path: 'index.ts', // [!code ++] }, // [!code ++] ], // [!code ++] }, ], }, ], }); }); ``` #### Glob patterns in file paths You can also use glob patterns in file paths to include all files that match the pattern. For example lets say I want to include all my demos for the button component as examples: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { name: 'button', type: 'ui', files: [ { path: 'src/lib/components/button', }, { path: "src/lib/demos/button-*.svelte", // [!code ++] role: 'example', dependencyResolution: 'manual', }, ], }, ], }); }); ``` This will match all files that match the pattern `button-*.svelte` in the `src/lib/demos` directory. **Recursive glob patterns:** Match files in subdirectories while preserving the directory structure: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { name: 'button', type: 'ui', files: [ { path: 'src/lib/components/button', }, { path: "src/lib/demos/**/button-*.svelte", // [!code ++] role: 'example', }, ], }, ], }); }); ``` This will match all files (including those in subdirectories) that match the pattern `button-*.svelte` while maintaining the directory structure. For instance: | Source Path | Path Relative to Item | |-------------|----------------------| | `src/lib/demos/button-default.svelte` | `button-default.svelte` | | `src/lib/demos/variants/button-outlined.svelte` | `variants/button-outlined.svelte` | | `src/lib/demos/variants/themes/button-dark.svelte` | `variants/themes/button-dark.svelte` | ### Configure when an item is added We mentioned this briefly above but you can configure when an item is added in the user's project by setting the `add` key an item. - `"on-init"` - Added on registry init or when it's needed by another item - `"optionally-on-init"` - Users are prompted to add the item when initializing the registry - `"when-needed"` - Not listed and only added when another item is added that depends on it - `"when-added"` - Added when the user selects it to be added ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { // ... add: "when-added", // [!code ++] } ] }, }); ``` ### Configuring the user's project There are a few common things you may want to automatically configure in the user's project when they first initialize your registry. #### Default Paths Default paths are just that, the default locations for which items types or specific items should be added to in the user's project. You can configure the `defaultPaths` key of your registry config to configure the default paths for items or item types to be added to in the user's project. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { // ... defaultPaths: { // [!code ++] component: "src/components/ui", // [!code ++] // you can of course also configure a specific item by referencing it by `/` // [!code ++] "ui/button": "src/components/ui/button", // [!code ++] }, // [!code ++] }, }); ``` #### Plugins You can configure the `plugins` key to automatically install plugins to the user's project when they initialize your registry. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { plugins: { languages: [{ package: "jsrepo-language-go" }], // [!code ++] // by setting the optional key to true the user will be prompted to install the plugin if it is not already installed. // [!code ++] transforms: [{ package: "@jsrepo/transform-prettier", optional: true }], // [!code ++] }, }, }); ``` ### Environment Variables Sometimes your registry items may require environment variables to work. For this you can define the `envVars` key of that particular item: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { // ... items: [ // ... { // ... name: 'db', type: 'lib', files: [ { path: 'src/db.ts', } ], envVars: { // [!code ++] DATABASE_URL: "https://example.com/database", // [!code ++] DATABASE_SECRET_TOKEN: "", // [!code ++] }, // [!code ++] } ] }, }); ``` Environment variables will be added to the users `.env.local` or `.env` file. If you leave an environment variable blank the user will be prompted to add a value for it. Values you configure here will ***never*** overwrite existing values in the user's env file. ### Distributing multiple registries It's become common to distribute multiple registries to allow users to optionally use different variants of your registry for example JavaScript or TypeScript. However until now there wasn't an easy way to do this. **jsrepo** solves this by allowing you to define multiple registries in the same config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: [ { name: '@my-registry/typescript', // ... }, { name: '@my-registry/javascript', // ... } ] }); ``` You can then use the `outputs` api to define where each registry should be output to: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { distributed } from "jsrepo/outputs"; export default defineConfig({ registry: [ { name: '@my-registry/vanilla', outputs: [distributed({ dir: "./public/r/v" })], // [!code ++] // ... }, { name: '@my-registry/tailwind', outputs: [distributed({ dir: "./public/r/tw" })], // [!code ++] // ... } ] }); ``` ### Dynamically generating registries You don't always want to have to manually define your entire registry in your config file. AI has made this less cumbersome but it's still annoying to have a 1k LOC file just to define your registry. In **jsrepo v2** we automatically generated your registry based on a bunch of complicated options and this wasn't the best experience. In **jsrepo v3** we are giving the control back to you allowing you to write your own code to generate your registry. To do this simply pass a function to the `registry` key that returns a registry config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; // define your own custom function import { getItems } from "./getItems"; export default defineConfig({ registry: ({ cwd }) => { return { name: 'my-registry', items: getItems(cwd) } } }); ``` Oh and of course you can also pass an array of functions: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; // define your own custom function import { getItems } from "./getItems"; export default defineConfig({ registry: [ ({ cwd }) => { return { name: '@my-registry/typescript', items: getItems(path.join(cwd, 'src/registry/ts')) } }, ({ cwd }) => { return { name: '@my-registry/javascript', items: getItems(path.join(cwd, 'src/registry/js')) } } ] }); ``` Dynamically generated registries will still work with the `--watch` flag. ### Supporting JavaScript and TypeScript Thanks to the **jsrepo** transforms API it's extremely straightforward to allow your users to choose between JavaScript and TypeScript when using your registry. Users can simply initialize your registry with the `--js` flag to use JavaScript: ```sh jsrepo init @example/registry --js # or add the javascript plugin at any time jsrepo config transform javascript ``` Or add the `@jsrepo/transform-javascript` transform to their config manually: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import javascript from "@jsrepo/transform-javascript"; // [!code ++] export default defineConfig({ transforms: [javascript()], // [!code ++] }); ``` This will automatically strip the types from TypeScript files and rename them to JavaScript files. You can see the full documentation for the `@jsrepo/transform-javascript` transform [here](/docs/transforms/javascript). This only works for [erasable syntax](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option) if you are expecting for your users to use TypeScript ensure you are using the `--erasableSyntaxOnly` option when writing your TypeScript code. ### Depending on items from other registries Often times you may want to depend on items from other registries. In `shadcn/ui` you would add the registry URL to the item you are dependent on. **jsrepo** doesn't support this for a few reasons: 1. You may have made changes to the component in your project that are not tracked by the upstream registry. This can break the users code or cause it to function incorrectly. 2. It's possible that the author of the upstream registry may make changes to the component that break your code in users projects. For this reason **jsrepo** doesn't support depending on items from other registries. The alternative is to simply copy the components from the upstream registry into your own registry and serve them from your own registry. We recommend when you do this to follow a few best practices: 1. Credit the upstream registry in some way so users know where those items came from. 2. Ensure to set `add: "when-needed"` on items from external registries to prevent them from being listed by the `add` command. ### Modifying items in your registry before build Sometimes you may want to modify the content of registry items before they are built to an output. For example you may want to build multiple different types of your registry for each style you offer. You can do this with the `build.transforms` option: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ build: { transforms: [ { transform: async (content, opts) => { return { content: content.replaceAll('{{PROJECT_NAME}}', 'jsrepo') }; }, } ], }, }); ``` This will run the transform on the content of each file in your registry before it's added to the output. This way you can easily modify the content at build time without needing to write another registry to the filesystem. ================================================ FILE: apps/docs/content/docs/index.mdx ================================================ --- title: Introduction description: Why we built jsrepo. --- import Link from "next/link"; [shadcn/ui](https://ui.shadcn.com) proved that distributing code in a way that allows consumers to own the source is an extremely powerful pattern. Since then registries have become a common way to distribute reusable components. As more and more registries start to emerge it became clear that there was a need for a more powerful toolchain to help manage and distribute these registries. That's where **jsrepo** comes in... **jsrepo** is a toolchain for distributing your code. It gives powerful tools to both registry owners and consumers. **Registry owners...** - Don't want to have to write their own CLI and build system to distribute their code - Want to be able to test and verify that their registry will work for their users before they publish it - Want to be able to host their registries privately without having to write their own hosting solution **Registry consumers...** - Want to quickly add items from a registry to their project and start using them without having to even look at the code - Want to be able to easily update their items with visibility to what as changed - Want to be able to install and manage code from multiple registries in a single project - Want an easy way to authenticate to private registries **jsrepo...** - Provides a powerful CLI for building and distributing your registry - Prevents you from publishing broken or unusable registries - Provides provider adapters to allow you to host your registry publicly or privately anywhere you want - Automatically resolves dependencies to other items so your items always come ready to use - Provides an interactive update command to easily update your items with visibility to what as changed - Allows you to authenticate to private registries with `jsrepo auth` Is this trying to replace npm? No, **jsrepo** won't replace npm, but it's perfect for distributing code like leftPad that probably shouldn't be a package but also shouldn't have to be rewritten every time. Is this shadcn compatible? Yes **jsrepo** is fully shadcn compatible. You can add and update items from **shadcn** with zero configuration needed. ================================================ FILE: apps/docs/content/docs/jsrepo-com.mdx ================================================ --- title: jsrepo.com description: A registry for your registries. --- import { BadgesTable } from "@/components/badges-table"; [jsrepo.com](https://jsrepo.com) is a centralized source registry for registries. Much like npm is for packages, **jsrepo.com** is for registries. Like npm, you publish your registry to [jsrepo.com](https://jsrepo.com) with the `jsrepo publish` command and then other developers can add your code to their projects with the `jsrepo add` command. ## The benefits of jsrepo.com - Semver support - You can publish multiple versions of the same registry (including pre-release versions) just like you would on npm. - Easy installation - You can add your registry to your project with the simple `@/` syntax. - Private registries - You can easily publish private registries and share them with your team for free. - Marketplace - You can monetize your registry by selling it on the marketplace. - Discoverability - Your registry will be discoverable through the **jsrepo.com** website and the jsrepo mcp server. ## Publishing your registry If you already have a registry that can be built with **jsrepo** then there are only a few steps to publishing it to **jsrepo.com**. If not then checkout the [create a registry](/docs/create-a-registry) guide to get started. ### Create an account Go to [jsrepo.com](https://jsrepo.com) and create an account. ### Claim a scope Once you have an account navigate to `/account/scopes/new` to [claim a scope](https://www.jsrepo.com/account/scopes/new). Scopes are used to group your registries together and are required to publish your registry. When users add your registry the scope will be the first part of the registry name: `@/`. ### Authenticate with jsrepo.com Run the following command to authenticate with jsrepo.com: ```sh jsrepo auth jsrepo ``` You will be prompted to finish the sign in in your browser and then you will be authenticated. ### Prepare your registry Decide on a name for your registry and add it to your `jsrepo.config.ts` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { name: "@my-scope/my-registry", // [!code ++] // ... }, }); ``` Next you need to provide the version or your registry that will be published. If you want **jsrepo** to pull from the version in your `package.json` file just provide `package` as the version: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { name: "@my-scope/my-registry", version: "1.0.0", // [!code ++] // or take the version from the `package.json` file version: "package", // [!code ++] // ... }, }); ``` (Optional) Now is also a good time to add any other metadata you want to your registry: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { name: "@my-scope/my-registry", description: "My first registry", // [!code ++] homepage: "https://my-registry.com", // [!code ++] repository: "https://github.com/my-scope/my-registry", // [!code ++] bugs: "https://github.com/my-scope/my-registry/issues", // [!code ++] authors: ["Aidan Bleser"], // [!code ++] tags: ["first-registry"], // [!code ++] // ... }, }); ``` (Optional) By default your registry will be published as public so anyone can see and use it. You can change the access level to `"private"` or `"marketplace"` by setting the `access` property: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { name: "@my-scope/my-registry", access: "private", // [!code ++] }, }); ``` ### Publish your registry Run the following command to publish your registry: ```sh jsrepo publish ``` ### Start adding items to your project Run the following command to add items to your project: ```sh jsrepo add --registry @my-scope/my-registry # or jsrepo init @my-scope/my-registry ``` ## Badges ================================================ FILE: apps/docs/content/docs/jsrepo-config.mdx ================================================ --- title: jsrepo.config description: The configuration file for jsrepo. --- The `jsrepo.config.(ts|js|mts|mjs)` file is used to configure **jsrepo** projects and registries. Having a js based config allows you far more flexibility when configuring your project or registry allowing you to abstract out the reusable parts of your config. ## Creating a config To create a new config in your project you can run the following command or copy the code below: ```sh jsrepo init ``` This will initialize a blank config in your project. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ // configure where stuff comes from here registries: [], // configure where stuff goes here paths: {}, }); ``` We default to `.mts` to prevent errors when you don't have `"type": "module"` in your `package.json`. But you can rename this to `.ts` if you prefer. ### Create with a registry To create your config with a registry you can run the following command: ```sh jsrepo init [registry] ``` Let's look at an example... You might run: ```sh jsrepo init https://example.com/registry ``` First you will be prompted to install any plugins specified by the registry author: ```plaintext ┌ jsrepo │ ◆ Would you like to add the @jsrepo/transform-prettier transform plugin? │ ● Yes / ○ No ``` Next you can configure the paths for the items you want to add. (These are the types of the items you can add from the registry) ```plaintext ◆ Which paths would you like to configure? │ ◻ block (Default: src/components) │ ◻ component │ ◻ lib └ ``` Once you have configured the paths any items that were specified by the registry author to add upon initialization will be added to your project. And the resulting config should look like this: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import prettier from "@jsrepo/transform-prettier"; export default defineConfig({ registries: ["https://example.com/registry"], transforms: [prettier()], paths: { block: "src/components", component: "src/components/ui", lib: "src/lib", }, }); ``` ## Adding plugins Plugins are a big part of what makes jsrepo so powerful but having to manually add them to your config kinda sucks. So we've added a way to automatically install and add plugins to your config. To add a plugin run the following command: ```sh # add a transform plugin jsrepo config transform @jsrepo/transform-prettier # add a provider plugin jsrepo config provider jsrepo-provider- # add a language plugin jsrepo config language jsrepo-language- ``` This will automatically install the plugin and add it to your config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import prettier from "@jsrepo/transform-prettier"; // [!code ++] export default defineConfig({ registries: ["https://example.com/registry"], transforms: [prettier()], // [!code ++] paths: { block: "src/components", component: "src/components/ui", lib: "src/lib", }, }); ``` As your config grows this will continue to work (so long as you don't use some really contrived syntax for your config). ## Options ### languages Languages are how **jsrepo** knows how to parse and transform code. You can read more about the supported languages [here](/docs/languages). ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { js } from "jsrepo/languages"; export default defineConfig({ languages: [js()], // [!code highlight] }); ``` ### paths Paths are how **jsrepo** knows where to put items in your project. Paths can either reference a type of item or a specific item. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ paths: { // [!code highlight] block: "src/components", // [!code highlight] // control where a specific item goes by referencing it by `/` // [!code highlight] "ui/button": "src/components/ui/button", // [!code highlight] }, // [!code highlight] }); ``` ### providers Providers are how **jsrepo** knows where to fetch items from. You can read more about providers [here](/docs/providers). ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { fs } from "jsrepo/providers"; export default defineConfig({ providers: [fs()], // [!code highlight] }); ``` ### registries Registries are the default locations that items will be fetched from when you run **jsrepo** commands. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registries: ["https://example.com/registry"], // [!code highlight] }); ``` ### registry The `registry` option allows you to define your own registry or registries. You can learn more about creating your own registry [here](/docs/create-a-registry). ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: ..., // [!code highlight] }); ``` ### transforms Transforms allow you to make modifications to code before it is added to your project this is where you might add formatting, and other code modifications. You can read more about transforms [here](/docs/transforms). ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import prettier from "@jsrepo/transform-prettier"; export default defineConfig({ transforms: [prettier()], // [!code highlight] }); ``` ### onwarn **Deprecated:** The top-level `onwarn` option is deprecated. Use [`build.onwarn`](#buildonwarn) instead. ### build.onwarn The `build.onwarn` option allows you to customize how warnings are handled during the build process. You can suppress specific warnings, transform them, or use the default logging behavior. The handler receives two arguments: - `warning`: The warning instance (extends the base `Warning` class) - `handler`: A function to log the warning using the default format ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { InvalidImportWarning, LanguageNotFoundWarning } from "jsrepo/warnings"; export default defineConfig({ // ... build: { onwarn: (warning, handler) => { // [!code highlight] // Suppress warnings for SvelteKit internal imports if (warning instanceof InvalidImportWarning) { // [!code highlight] if (['$app/server', '$app/navigation'].includes(warning.specifier)) { // [!code highlight] return; // Don't log this warning // [!code highlight] } } // Suppress language warnings for specific file types if (warning instanceof LanguageNotFoundWarning) { // [!code highlight] if (warning.path.endsWith('.glb') || warning.path.endsWith('.png')) { // [!code highlight] return; // Don't log this warning // [!code highlight] } } // Log all other warnings using the default handler handler(warning); // [!code highlight] }, }, }); ``` Available warning types: - `InvalidImportWarning`: Triggered when an import is skipped because it's not a valid package name or path alias - `LanguageNotFoundWarning`: Triggered when a language cannot be found to resolve dependencies for a file - `UnresolvableDynamicImportWarning`: Triggered when a dynamic import cannot be resolved due to unresolvable syntax All warnings extend the base `Warning` class which has a `message` property. Each specific warning type includes additional properties relevant to that warning type. ### build.remoteDependencyResolver The `build.remoteDependencyResolver` option lets you rewrite each detected remote dependency before it is added to the built registry output. This is useful when your source `package.json` uses version protocols like `workspace:*` or `catalog:` and you want to replace them with concrete versions during build. For pnpm workspaces (workspace and catalog protocols), use `@jsrepo/pnpm`: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { pnpm } from "@jsrepo/pnpm"; export default defineConfig({ build: { remoteDependencyResolver: pnpm(), }, }); ``` For bun workspaces, use `@jsrepo/bun`: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { bun } from "@jsrepo/bun"; export default defineConfig({ build: { remoteDependencyResolver: bun(), }, }); ``` Or implement a custom resolver: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ build: { remoteDependencyResolver: async (dep) => { if (dep.version === "workspace:*") { return { ...dep, version: "1.2.3" }; } if (dep.version === "catalog:") { return { ...dep, version: "^4.0.0" }; } return dep; }, }, }); ``` ### build.transforms The `build.transforms` option allows you to transform the content of files before they are added to your project. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ build: { transforms: [ { transform: async (content, opts) => { return { content: content.replaceAll('{{PROJECT_NAME}}', 'jsrepo') }; }, } ], }, }); ``` ### hooks Hooks allow you to run custom logic before and after CLI commands. - `before` hooks run before the command executes. - `after` hooks run after the command completes. Hooks can be a function, a shell command string, or an array of either. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ hooks: { before: ({ command }) => { console.log(`Running ${command}...`); }, after: "echo done", }, }); ``` Function hooks receive typed arguments with a `command` discriminant. Use it to narrow and access command-specific data: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ hooks: { after: (args) => { if (args.command === "add") { console.log(`Added ${args.result.items.length} items`); } }, }, }); ``` You can also use an array of hooks: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ hooks: { before: [ ({ command }) => console.log(`Starting ${command}...`), "echo 'before hook done'", ], }, }); ``` ================================================ FILE: apps/docs/content/docs/languages/css.mdx ================================================ --- title: css description: CSS language support for jsrepo. --- The `css` language plugin supports dependency resolution for CSS, SCSS, and Sass files as well as installing dependencies with `ecosystem: "js"`. ## Supported syntax The CSS plugin uses the [css-dependency](https://www.npmjs.com/package/css-dependency) library under the hood to detect dependencies. So it supports all the following syntax: - `@import` statements - `@plugin` statements (if `allowTailwindDirectives` is `true`) - `@config` statements (if `allowTailwindDirectives` is `true`) - `@reference` statements (if `allowTailwindDirectives` is `true`) By default `allowTailwindDirectives` is `true` so it will also detect Tailwind directives as imports. To opt out of this behavior you can set `allowTailwindDirectives` to `false`. ```ts import { defineConfig } from "jsrepo"; import { css } from "jsrepo/langs"; export default defineConfig({ languages: [css({ allowTailwindDirectives: false })], // [!code ++] }); ``` ## Supported File Extensions The CSS language plugin supports the following file extensions: | Extension | | --------- | | `.css` | | `.scss` | | `.sass` | ================================================ FILE: apps/docs/content/docs/languages/html.mdx ================================================ --- title: html description: HTML language support for jsrepo. --- The `html` language plugin supports dependency resolution for HTML files as well as installing dependencies with `ecosystem: "js"`. ## Supported syntax The HTML plugin will detect dependencies from the following syntax: - `script` tags with `src` attributes - `script` tags with inline code - `link` tags with `href` attributes For example the following code: ```html ``` Will result in the following dependencies: - `./app.css` - From `link` tag - `@/utils/stdout` - From `script` tag with inline code ## Supported File Extensions The HTML language plugin supports the following file extensions: | Extension | | --------- | | `.html` | ================================================ FILE: apps/docs/content/docs/languages/index.mdx ================================================ --- title: Languages description: Language support for jsrepo. --- **jsrepo** uses languages to determine the dependencies of registry items when you run the `build` command and also how to add and install dependencies. **jsrepo** can distribute code of any language or file type but these are the languages that support [dependency resolution](#what-is-dependency-resolution). ## Available Languages By default **jsrepo** supports the following languages: } title="JavaScript"> Support for `*.js`, `*.ts`, `*.jsx`, `*.tsx`, `*.mjs`, `*.mts` files. } title="Svelte"> Support for `*.svelte` files. } title="Vue"> Support for `*.vue` files. } title="CSS"> Support for `*.css`, `*.scss`, `*.sass` files. } title="HTML"> Support for `*.html` files. ## What is dependency resolution? Dependency resolution is the process of determining the dependencies of a registry item. This process ensures that when you add a registry item that all of it's dependencies are also added. Without dependency resolution you need to manually specify dependencies of a registry item which is cumbersome and error prone. ### How to manually specify dependencies If your language of choice doesn't support dependency resolution or your registry items have dependencies that cannot be automatically detected you can manually specify them like so: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { js } from "jsrepo/langs"; export default defineConfig({ registry: { items: [ { name: "button", type: "component", files: [ { path: "src/components/button.tsx", }, ], registryDependencies: ["utils"], // [!code ++] dependencies: [ // [!code ++] { // [!code ++] ecosystem: "js", // [!code ++] name: "radix-ui", // [!code ++] version: "1.4.3", // [!code ++] }, // [!code ++] ], // [!code ++] }, ], }, }); ``` ### How to prevent automatic dependency resolution If you want to opt out of automatic dependency resolution you can do so by setting the `dependencyResolution` option to `manual` on the registry item or on the file itself. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { js } from "jsrepo/langs"; export default defineConfig({ registry: { items: [ { name: "button", type: "component", // for the entire item dependencyResolution: "manual", // [!code ++] files: [ { path: "src/components/button.tsx", // for individual files dependencyResolution: "manual", // [!code ++] }, ], }, ], }, }); ``` ### Strict mode By default **jsrepo** will error if it cannot resolve all dependencies of a registry item. You can opt out of this behavior by setting the `strict` option to `false`. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { js } from "jsrepo/langs"; export default defineConfig({ registry: { items: [ { name: "button", type: "component", strict: false, // [!code ++] }, ], }, }); ``` ================================================ FILE: apps/docs/content/docs/languages/js.mdx ================================================ --- title: js description: JavaScript language support for jsrepo. --- The `js` language plugin supports dependency resolution for both JavaScript and TypeScript files as well as installing dependencies with `ecosystem: "js"`. ## Supported syntax The JavaScript plugin will detect dependencies from the following syntax: - Static imports - Dynamic imports - Export from statements For example the following code: ```ts import 'dotenv/config'; import { print } from "@/utils/stdout"; async function printDynamic() { const data = await import("./data.json", { with: { type: "json" } }); print(data.message); } export { logger } from "@/utils/logger"; ``` Will result in the following dependencies: - `dotenv` - From side effect import - `@/utils/stdout` - From static import - `./data.json` - From dynamic import - `@/utils/logger` - From export from statement ## Supported File Extensions The JavaScript language plugin supports the following file extensions: | Extension | | --------- | | `.js` | | `.ts` | | `.jsx` | | `.tsx` | | `.mjs` | | `.mts` | ================================================ FILE: apps/docs/content/docs/languages/svelte.mdx ================================================ --- title: svelte description: Svelte language support for jsrepo. --- The `svelte` language plugin supports dependency resolution for Svelte files as well as installing dependencies with `ecosystem: "js"`. This language plugin requires `svelte` to be installed in your project. ## Supported syntax The Svelte plugin parses all script tags and passes the code to the [JavaScript plugin](/docs/languages/js) to resolve dependencies so all the same syntax is supported. ## Supported File Extensions The JavaScript language plugin supports the following file extensions: | Extension | | --------- | | `.svelte` | ================================================ FILE: apps/docs/content/docs/languages/vue.mdx ================================================ --- title: vue description: Vue language support for jsrepo. --- The `vue` language plugin supports dependency resolution for Vue files as well as installing dependencies with `ecosystem: "js"`. This language plugin requires `vue` to be installed in your project. ## Supported syntax The Vue plugin parses all script tags and passes the code to the [JavaScript plugin](/docs/languages/js) to resolve dependencies so all the same syntax is supported. ## Supported File Extensions The Vue language plugin supports the following file extensions: | Extension | | --------- | | `.vue` | ================================================ FILE: apps/docs/content/docs/legacy.mdx ================================================ --- title: Legacy Docs description: Legacy documentation for jsrepo. --- All documentation for older versions of **jsrepo** can be found below: } title="jsrepo v2"> Legacy documentation for jsrepo v2. ================================================ FILE: apps/docs/content/docs/mcp.mdx ================================================ --- title: MCP Server description: The jsrepo MCP server. --- The **jsrepo** MCP server has been built alongside the **jsrepo** CLI to ensure agents have first class support for interacting with jsrepo registries. The **jsrepo** MCP ships separate to the **jsrepo** CLI to reduce the overall size of the CLI and can be found on npm as `@jsrepo/mcp`. All **jsrepo** registries are supported out of the box by the MCP server, including registries hosted on custom providers (provided the custom provider is present in your `jsrepo.config`). ## Configuration Cursor Claude Code VS Code Codex Antigravity You can quickly configure the MCP server for Cursor by running the following command: ```sh jsrepo config mcp --client cursor ``` This will automatically configure the MCP server for Cursor in the correct file: ```json title=".cursor/mcp.json" { "mcpServers": { "jsrepo": { "command": "npx", "args": ["@jsrepo/mcp"] } } } ``` You can quickly configure the MCP server for Claude Code by running the following command: ```sh jsrepo config mcp --client claude ``` This will automatically configure the MCP server for Claude Code in the correct file: ```json title=".mcp.json" { "mcpServers": { "jsrepo": { "command": "npx", "args": ["@jsrepo/mcp"] } } } ``` You can quickly configure the MCP server for VSCode by running the following command: ```sh jsrepo config mcp --client vscode ``` This will automatically configure the MCP server for VSCode in the correct file: ```json title=".vscode/mcp.json" { "servers": { "jsrepo": { "command": "npx", "args": ["@jsrepo/mcp"] } } } ``` You can quickly configure the MCP server for Codex by running the following command: ```sh jsrepo config mcp --client codex ``` This will automatically configure the MCP server for Codex in the correct file: ```toml title="~/.codex/config.toml" [mcp_servers.jsrepo] command = "npx" args = ["@jsrepo/mcp"] ``` You can quickly configure the MCP server for Antigravity by running the following command: ```sh jsrepo config mcp --client antigravity ``` This will automatically configure the MCP server for Antigravity in the correct file: ```json title="~/.gemini/antigravity/mcp_config.json" { "mcpServers": { "jsrepo": { "command": "npx", "args": ["@jsrepo/mcp"] } } } ``` ## Tools The jsrepo MCP server provides the following tools: | Tool | Description | |------|-------------| | `add_item_to_project` | Add a registry item or items directly to the users project | | `view_registry_item` | View the code and information about a registry item | | `list_items_in_registry` | List registry items in one or more registries and optionally fuzzy search | ## Registry Authors Your registry will automatically be compatible with the **jsrepo** MCP server, but here are a few tips to improve the agents usage of your registry: - include all the registry metadata in your `jsrepo.config` to allow agents to find your registry more easily when searching - include a `description` with each item in your registry - include files with `role: "example"` that show how to use each item - include files with `role: "doc"` that document each item ================================================ FILE: apps/docs/content/docs/migrate.mdx ================================================ --- title: Migration description: Migrate from jsrepo v2 to jsrepo v3. --- **jsrepo** v3 is a complete rewrite of the **jsrepo** CLI and we haven't been shy about making breaking changes. If you're coming from **jsrepo** v2 and want to quickly get started with **jsrepo** v3 you can run the following command in your existing **jsrepo** project: ```sh pnpm dlx @jsrepo/migrate v3 ``` The migration tool will: - Migrate your `jsrepo-build-config.json` and `jsrepo.json` files into the new `jsrepo.config.ts` file. - Install `jsrepo` and the correct formatting transform (if you were using one) - Build your registry using both v2 and v3 to ensure compatibility You can put this in your upgrade guide for your users. ## Breaking changes ### 1. A new js based config In **jsrepo** v2 you used a `jsrepo-build-config.json` file to configure your registry and a `jsrepo.json` file to configure your project. This wasn't great for obvious reasons. In **jsrepo** v3 we use a `jsrepo.config.ts` file to configure your registry and project: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ // ... }); ``` This affords us more flexibility and allows us to support plugins. If you're like me you may be worried about having to automatically make modifications to a js based config. Not to worry! The blood, sweat, and (mostly) tears have been shed to make it as seamless as possible. ### 2. No more categories In **jsrepo** v2 we had item categories. This allowed you to group items together and add them by running `jsrepo add /`. In **jsrepo** v3 you can add items by running `jsrepo add `. "categories" are now the `type` of the item. The type of an item is used to determine the path that the item will be added to in the user's project: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { name: "button", type: "component", // instead of category now it's type // [!code highlight] files: [ { path: "src/components/button.tsx", }, ], }, ], }, }); ``` ### 3. No backwards compatibility with v2 registries You probably could've guessed this by some of the changes we have already mentioned but unfortunately we don't support adding items from v2 registries from the v3 CLI. If you still want to use a v2 registry you can of course continue to use the v2 CLI to add items to your project: ```sh pnpm dlx jsrepo@2 add ui/button ``` ### 4. Deprecated commands The following commands have been deprecated in **jsrepo** v3: - `jsrepo test` - `jsrepo execute` - `jsrepo tokens` - Now all auth functions are handled by the `jsrepo auth` command. - `jsrepo mcp` - The MCP server has been moved to the `@jsrepo/mcp` package. ### 5. No more `Update with AI` While we were excited by the `Update with AI` feature it didn't feel worth the added bundle cost. We'll likely explore ways to improve the workflow of updating items with AI without the added bundle cost. ### 6. Formatting functionality has been externalized In **jsrepo** v2 you could format code before it was added to your project by configuring the formatter in your `jsrepo.json` file. With **jsrepo** v3 we introduced the concept of "transforms" and in so doing we realized that the formatting functionality should be externalized to reduce the bundle size of the CLI and allow for more flexibility for users. You can now configure formatting for your project by adding one of the officially supported transforms to your config: ```sh # prettier jsrepo config transform prettier # biome jsrepo config transform biome ``` ### 7. Output configuration changes In **jsrepo** v2, registries could specify an `outputDir` in their build config. In **jsrepo** v3, this has been replaced with output plugins: - Registries that had an `outputDir` are migrated to use the [distributed](/docs/outputs/distributed) output - Registries without an `outputDir` use the [repository](/docs/outputs/repository) output by default ### 8. Removed peer dependencies Right now this is just a choice to reduce API surface for language plugins. If this becomes something the community wants we can add it back in the future. ## The wins Now let's talk about the reasons we are so excited to break everyone's code for **jsrepo** v3. ### Plugins In **jsrepo** v3 we have introduced plugins. There are currently three types of plugins: - [Languages](/docs/languages) - Extend jsrepo dependency resolution support to include additional languages. - [Outputs](/docs/outputs) - Customize the way your registry is distributed. - [Providers](/docs/providers) - Allow users to install items from a custom source. - [Transforms](/docs/transforms) - Modify code before it's added to your project. ### Shadcn compatibility **jsrepo** v3 is now **shadcn** compatible. You can now add and update items from **shadcn** meaning **jsrepo** now has access to an entire ecosystem of **shadcn** registries. Try it out by running: ```sh pnpm jsrepo add --registry https://ui.shadcn.com/r/styles/new-york-v4 ``` ### Greatly improved MCP responses Thanks to adopting the more manual approach for defining a registry it's now possible to add metadata to your registry items greatly improving the responses you will get when using the **jsrepo** MCP server. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; export default defineConfig({ registry: { items: [ { name: "button", title: "Button", // [!code ++] description: "A button component", // [!code ++] files: [ { path: "src/components/button.tsx", }, // add examples for LLMs to use { // [!code ++] path: 'src/demos/button-demo.tsx', // [!code ++] role: 'example', // [!code ++] } // [!code ++] ] }, ], }, }); ``` ================================================ FILE: apps/docs/content/docs/outputs/distributed.mdx ================================================ --- title: Distributed description: Output your registry as json files in a directory. --- import { Files, Folder, File } from "@/components/files"; The `distributed` output is used for maximizing performance when serving your registry as a static asset. ## Usage ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { distributed } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { outputs: [distributed({ dir: "./public/r" })], // [!code ++] }, }); ``` Now running `jsrepo build` will output your registry to the `./public/r` directory. Unlike the `repository` output, the `distributed` output will output each item as a separate json file. For example: Since each item file contains all the code for all the files required for that item this is much more performant, and portable than the `repository` output. ================================================ FILE: apps/docs/content/docs/outputs/index.mdx ================================================ --- title: Outputs description: Output your registry however you want. --- Outputs allow you to customize the way your registry is distributed, or (more generally) what to do with the registry after it's built. This isn't limited into building your registry into a format that **jsrepo** can understand you could use it to output documentation or a manifest for your own CLI application. By default **jsrepo** ships with two output types: - [Distributed](/docs/outputs/distributed) - Output your registry as json files in a directory. - [Repository](/docs/outputs/repository) - Output your registry as a single json file in the root of your repository. Here are all the officially available outputs: } title="Distributed"> Output your registry as json files in a directory. } title="Repository"> Output your registry as a single json file in the root of your repository. } title="shadcn"> Output your registry as a shadcn registry. ## Creating a custom output You can create your own output either inline in your config file or as a standalone package. For this example we will create a standalone package that outputs your registry as a markdown file called `REGISTRY.md`. Let's create our `output.ts` file and import the `Output` type from `jsrepo/outputs`: ```ts import type { Output } from "jsrepo/outputs"; export function output(): Output { return { } } ``` Next let's define the `output` key to define how the registry should be output: ```ts title="src/output.ts" import type { Output } from "jsrepo/outputs"; export const OUTPUT_FILE = "REGISTRY.md"; export function output(): Output { return { output: async (buildResult, { cwd }) => { // [!code ++] let content = `# ${buildResult.name}\n\n`; // [!code ++] // [!code ++] for (const item of buildResult.items) { // [!code ++] content += `## ${item.name}\n\n${item.description}\n\n`; // [!code ++] } // [!code ++] // [!code ++] fs.writeFileSync(path.join(cwd, OUTPUT_FILE), content); // [!code ++] }, // [!code ++] } } ``` The `buildResult` object contains a bunch of useful information about the registry. You can find the full type [here](https://github.com/jsrepojs/jsrepo/blob/next/packages/jsrepo/src/utils/build.ts#L31). Finally we can define the `clean` key to define how to remove the output file before the next build: ```ts title="src/output.ts" import type { Output } from "jsrepo/outputs"; export const OUTPUT_FILE = "REGISTRY.md"; export function output(): Output { return { output: async (buildResult, { cwd }) => { let content = `# ${buildResult.name}\n\n`; for (const item of buildResult.items) { content += `## ${item.name}\n\n${item.description}\n\n`; } fs.writeFileSync(path.join(cwd, OUTPUT_FILE), content); }, clean: async ({ cwd }) => { // [!code ++] const manifestPath = path.join(cwd, OUTPUT_FILE); // [!code ++] if (!fs.existsSync(manifestPath)) return; // [!code ++] fs.rmSync(manifestPath); // [!code ++] }, // [!code ++] } } ``` Now we can use the output in our config file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { output } from "./src/output"; export default defineConfig({ registry: { name: "my-registry", outputs: [output()], // ... } }); ``` And the result should look like this: ```markdown title="REGISTRY.md" # my-registry ## button A button component. ``` ================================================ FILE: apps/docs/content/docs/outputs/repository.mdx ================================================ --- title: Repository description: Output your registry as a single json file in the root of your repository. --- The `repository` output is used when you are serving your registry from a repository and want to minimize code duplication and tracked files. ## Usage ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { outputs: [repository()], // [!code ++] }, }); ``` Now running `jsrepo build` will output a `registry.json` file to the root of your repository. ================================================ FILE: apps/docs/content/docs/outputs/shadcn.mdx ================================================ --- title: shadcn description: Output your registry as a shadcn registry. --- The `@jsrepo/shadcn` is a package that helps you distribute your jsrepo registry as a shadcn registry. ## Usage To get started install the package: ```npm npm install @jsrepo/shadcn -D ``` Next let's add the output to our config file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { output } from "@jsrepo/shadcn/output"; export default defineConfig({ registry: { // ... outputs: [output({ dir: "./public/r/shadcn" })], // [!code ++] }, }); ``` Now running `jsrepo build` will output a **shadcn** registry to the `./public/r/shadcn` directory. ## defineShadcnRegistry **shadcn** registries are defined a bit differently than **jsrepo** registries, generally this plugin should correctly resolve those differences or warn you if it is unable to. However if you are only distributing your registry as a **shadcn** registry and don't care about the other features of **jsrepo** you can use the `defineShadcnRegistry` function to define your registry the *"shadcn"* way. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { defineShadcnRegistry, output } from "@jsrepo/shadcn"; export default defineConfig({ registry: defineShadcnRegistry({ // make sure you still include the output outputs: [output({ dir: "./public/r/shadcn" })], }), }); ``` When using the `defineShadcnRegistry` function it's be possible to just paste the contents of the **shadcn** `registry.json` file into your config file and start building your registry with **jsrepo**. ## Outputting jsrepo registries alongside shadcn registries Currently the **jsrepo** and **shadcn** outputs are not compatible to be used in the same directory. Because of this you will need to serve each registry from a different URL. The preferred way to avoid this conflict would be to publish your registry to [jsrepo.com](https://jsrepo.com) (which also has it's own benefits). In absence of that, the easiest way to do this and our recommendation is to postfix the **shadcn** registry output `dir` option with `/shadcn`. For example: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { distributed } from "jsrepo/outputs"; import { output } from "@jsrepo/shadcn/output"; export default defineConfig({ registry: { outputs: [ distributed({ dir: "./public/r" }), output({ dir: "./public/r" }) // [!code --] output({ dir: "./public/r/shadcn" }) // [!code ++] ], }, }); ``` ================================================ FILE: apps/docs/content/docs/providers/azure.mdx ================================================ --- title: azure description: Download and add registry items from an Azure DevOps repository. --- ## Usage To start adding registry items from an Azure DevOps repository you can run the following command: ```sh jsrepo add azure/// ``` The Azure provider understands refs as either branches or tags: ```sh jsrepo add azure////heads/ jsrepo add azure////tags/ ``` ### Custom base urls By default the Azure provider will use `https://dev.azure.com` as the base url. You can configure this behavior by configuring `azure` in your `jsrepo.config` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { azure } from "jsrepo/providers"; export default defineConfig({ providers: [azure({ baseUrl: "https://dev.azure.com" })], // [!code highlight] }); ``` Alternatively you can use the `azure:https://` shorthand to use a custom base url without having to touch your config file: ```sh jsrepo add azure:https://dev.azure.com////heads/ ``` This syntax tells the Azure provider that it can resolve this URL as an Azure DevOps repository. ## Deploying your registry to Azure DevOps When deploying your registry to Azure DevOps you will want to use the `repository` output type: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { // ... outputs: [repository()], // [!code ++] }, }); ``` This will create a `registry.json` file that will contain everything users need to use your registry. The `registry.json` file must be at the **root** of your repository otherwise users won't be able to use your registry. ## Authentication To authenticate with Azure DevOps you can run the following command: ```sh jsrepo auth azure ``` You can logout of your account by running: ```sh jsrepo auth azure --logout ``` ### Environment Variable If you prefer to use an environment variable or are in an environment where you can't use the `jsrepo auth` command you can provide the token via the `AZURE_TOKEN` environment variable. ```sh AZURE_TOKEN=... jsrepo add ``` ## Options ================================================ FILE: apps/docs/content/docs/providers/bitbucket.mdx ================================================ --- title: bitbucket description: Download and add registry items from a Bitbucket repository. --- ## Usage To start adding registry items from a Bitbucket repository you can run the following command: ```sh jsrepo add https://bitbucket.org/// ``` The Bitbucket provider can parse Bitbucket URLs so that you can just copy and paste a link to a Bitbucket repository and it will just work: ```sh jsrepo add https://bitbucket.org/// jsrepo add https://bitbucket.org///src/ ``` ### The `bitbucket/` shorthand You can also just use the `bitbucket/` shorthand in place of `https://bitbucket.org/`. ```sh jsrepo add bitbucket/// jsrepo add bitbucket///src/ ``` If you configured a custom `baseUrl` in your `jsrepo.config` file then using the `bitbucket/` shorthand will use your custom base url instead of `https://bitbucket.org/`. ### Custom base urls By default the Bitbucket provider will use `https://bitbucket.org` as the base url for the `bitbucket/` shorthand. You can configure this behavior by configuring `bitbucket` in your `jsrepo.config` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { bitbucket } from "jsrepo/providers"; export default defineConfig({ providers: [bitbucket()], // [!code ++] }); ``` Alternatively you can use the `bitbucket:https://` shorthand to use a custom base url without having to touch your config file: ```sh jsrepo add bitbucket:https://bitbucket.org///src/ ``` This syntax tells the Bitbucket provider that it can resolve this URL as a Bitbucket repository. ## Deploying your registry to Bitbucket When deploying your registry to Bitbucket you will want to use the `repository` output type: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { // ... outputs: [repository()], // [!code ++] }, }); ``` This will create a `registry.json` file that will contain everything users need to use your registry. The `registry.json` file must be at the **root** of your repository otherwise users won't be able to use your registry. ## Authentication To authenticate with Bitbucket you can run the following command: ```sh jsrepo auth bitbucket ``` You can logout of your account by running: ```sh jsrepo auth bitbucket --logout ``` ### Environment Variable If you prefer to use an environment variable or are in an environment where you can't use the `jsrepo auth` command you can provide the token via the `BITBUCKET_TOKEN` environment variable. ```sh BITBUCKET_TOKEN=... jsrepo add ``` ## Options ================================================ FILE: apps/docs/content/docs/providers/fs.mdx ================================================ --- title: fs description: Download and add registry items from your local filesystem. --- The `fs` provider allows you to distribute code from your local filesystem. This is useful when you want to test out how your registries behave without having to deploy them somewhere first. ## Usage To start adding registry items from your local filesystem you first need to configure the provider in your `jsrepo.config` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { fs } from "jsrepo/providers"; // [!code ++] export default defineConfig({ providers: [fs()], // [!code ++] }); ``` Now you can start adding registry items from your local filesystem: ```sh jsrepo add fs://../registries/my-registry ``` You may also want to supply a base directory to the provider: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { fs } from "jsrepo/providers"; export default defineConfig({ providers: [fs({ baseDir: "../registries" })], // [!code highlight] }); ``` Now your commands will be relative to the base directory you defined: ```sh jsrepo add fs://./my-registry ``` ## Options ================================================ FILE: apps/docs/content/docs/providers/github.mdx ================================================ --- title: github description: Download and add registry items from a GitHub repository. --- ## Usage To start adding registry items from a GitHub repository you can run the following command: ```sh jsrepo add https://github.com/// ``` The GitHub provider can parse GitHub URLs so that you can just copy and paste a link to a GitHub repository and it will just work: ```sh jsrepo add https://github.com/// jsrepo add https://github.com///tree/ ``` ### The `github/` shorthand You can also just use the `github/` shorthand in place of `https://github.com/`. ```sh jsrepo add github/// jsrepo add github///tree/ ``` If you configured a custom `baseUrl` in your `jsrepo.config` file then using the `github/` shorthand will use your custom base url instead of `https://github.com/`. ### Custom base urls By default the GitHub provider will use `https://github.com` as the base url for the `github/` shorthand. You can configure this behavior by configuring `github` in your `jsrepo.config` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { github } from "jsrepo/providers"; export default defineConfig({ providers: [github({ baseUrl: "https://my-github-instance.com" })], // [!code highlight] }); ``` Alternatively you can use the `github:https://` shorthand to use a custom base url without having to touch your config file: ```sh jsrepo add github:https://my-github-instance.com/// ``` This syntax tells the GitHub provider that it can resolve this URL as a GitHub repository. ## Authentication To authenticate with GitHub you can run the following command: ```sh jsrepo auth github ``` You can logout of your account by running: ```sh jsrepo auth github --logout ``` ### Environment Variable If you prefer to use an environment variable or are in an environment where you can't use the `jsrepo auth` command you can provide the token via the `GITHUB_TOKEN` environment variable. ```sh GITHUB_TOKEN=... jsrepo add ``` ## Deploying your registry to GitHub When deploying your registry to GitHub you will want to use the `repository` output type: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { // ... outputs: [repository()], // [!code ++] }, }); ``` This will create a `registry.json` file that will contain everything users need to use your registry. The `registry.json` file must be at the **root** of your repository otherwise users won't be able to use your registry. ### Workflow If you want to use GitHub Actions to automatically build your registry when you push changes you can use this workflow: ```yml title=".github/workflows/build-registry.yml" name: build-registry on: push: branches: - main permissions: contents: write pull-requests: write id-token: write jobs: build: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm install - name: Build registry.json run: jsrepo build - name: Create pull request with changes uses: peter-evans/create-pull-request@v7 with: title: 'chore: update `registry.json`' body: | - Update `registry.json` --- This PR was auto generated branch: build-registry commit-message: build `registry.json` ``` ## Options ================================================ FILE: apps/docs/content/docs/providers/gitlab.mdx ================================================ --- title: gitlab description: Download and add registry items from a GitLab repository. --- ## Usage To start adding registry items from a GitLab repository you can run the following command: ```sh jsrepo add https://gitlab.com/// ``` The GitLab provider can parse GitLab URLs so that you can just copy and paste a link to a GitLab repository and it will just work: ```sh jsrepo add https://gitlab.com/// jsrepo add https://gitlab.com///-/tree/ ``` ### The `gitlab/` shorthand You can also just use the `gitlab/` shorthand in place of `https://gitlab.com/`. ```sh jsrepo add gitlab/// jsrepo add gitlab////-/tree/ ``` If you configured a custom `baseUrl` in your `jsrepo.config` file then using the `gitlab/` shorthand will use your custom base url instead of `https://gitlab.com/`. ### Custom base urls By default the GitLab provider will use `https://gitlab.com` as the base url for the `gitlab/` shorthand. You can configure this behavior by configuring `gitlab` in your `jsrepo.config` file: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { gitlab } from "jsrepo/providers"; export default defineConfig({ providers: [gitlab({ baseUrl: "https://my-gitlab-instance.com" })], // [!code highlight] }); ``` Alternatively you can use the `gitlab:https://` shorthand to use a custom base url without having to touch your config file: ```sh jsrepo add gitlab:https://my-gitlab-instance.com/// ``` This syntax tells the GitLab provider that it can resolve this URL as a GitLab repository. ## Deploying your registry to GitLab When deploying your registry to GitLab you will want to use the `repository` output type: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { repository } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ registry: { // ... outputs: [repository()], // [!code ++] }, }); ``` This will create a `registry.json` file that will contain everything users need to use your registry. The `registry.json` file must be at the **root** of your repository otherwise users won't be able to use your registry. ## Authentication To authenticate with GitLab you can run the following command: ```sh jsrepo auth gitlab ``` You can logout of your account by running: ```sh jsrepo auth gitlab --logout ``` ### Environment Variable If you prefer to use an environment variable or are in an environment where you can't use the `jsrepo auth` command you can provide the token via the `GITLAB_TOKEN` environment variable. ```sh GITLAB_TOKEN=... jsrepo add ``` ## Options ================================================ FILE: apps/docs/content/docs/providers/http.mdx ================================================ --- title: http description: Download and add registry items from an arbitrary HTTP endpoint. --- import { Files, Folder, File } from "@/components/files"; The http provider is the simplest provider by far. To add registry items simply provide the base url of the `registry.json` file: ```sh jsrepo add https://example.com/registry ``` **jsrepo** will then find the `registry.json` file from `https://example.com/registry/registry.json`. ## Deploying a registry to your own website When deploying a registry to your own website you will want to use the `distributed` output type: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { distributed } from "jsrepo/outputs"; // [!code ++] export default defineConfig({ // `dir` is the directory to output the files to outputs: [distributed({ dir: "./public/r" })], // [!code ++] }); ``` This will output your registry to the `public/r` directory which might look something like this: Users will then be able to add registry items to their project by running: ```sh jsrepo add https://your-website.com/r ``` ## Authentication To authenticate with the http provider you can run the following command: ```sh jsrepo auth http ``` You will then be prompted to select or enter the name of a registry to authenticate to. Once authenticated that token will continue to be used for that registry until you logout. You can logout of your account by running: ```sh jsrepo auth http --logout ``` ## Options ### `baseUrl` The `baseUrl` option allows you to configure what registries this provider will match. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { http } from "jsrepo/providers"; export default defineConfig({ // only use this provider for registries starting with https://myregistry.com providers: [ http({ baseUrl: "https://myregistry.com" // [!code ++] }) ], }); ``` ### `authHeader` The `authHeader` function allows you to set headers for every request using the provided token. This is useful for when you need to authenticate to a registry that doesn't support the Bearer token format. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { http } from "jsrepo/providers"; export default defineConfig({ providers: [ http({ // set your custom headers here authHeader: (token) => ({ 'X-API-Key': token }) // [!code ++] }) ], }); ``` ### `headers` You can set custom headers on every request by providing the `headers` option: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { http } from "jsrepo/providers"; export default defineConfig({ providers: [ http({ headers: { // [!code ++] 'X-Custom-Header': 'custom value' // [!code ++] } // [!code ++] }) ], }); ``` One way you might use headers is by providing an authorization token via an environment variable: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { http } from "jsrepo/providers"; export default defineConfig({ providers: [ http({ baseUrl: "https://myregistry.com", // [!code ++] headers: { // [!code ++] Authorization: `Bearer ${process.env.MYREGISTRY_TOKEN}` // [!code ++] } // [!code ++] }) ], }); ``` Now anyone running `jsrepo add` will have the `Authorization` header set to the value of the `MYREGISTRY_TOKEN` environment variable when making requests to `https://myregistry.com/registry`. ================================================ FILE: apps/docs/content/docs/providers/index.mdx ================================================ --- title: Providers description: Host your code anywhere with jsrepo. --- Providers are how **jsrepo** knows where to find registry items. When you provide a registry identifier to a **jsrepo** command the provider is responsible for resolving that to a usable URL. For example you might run the following command: ```sh jsrepo init github/ieedan/std ``` In this case the `github` provider will be used to resolve the path to the `registry.json` file which might look something like this: ```plaintext https://api.github.com/repos/ieedan/std/contents/registry.json?ref=main ``` This makes **jsrepo** more flexible than any other registry because you can host your registry anywhere. ## Available providers } title="jsrepo"> Download and add registry items from jsrepo.com } title="GitHub"> Download and add registry items from a GitHub repository. } title="GitLab"> Download and add registry items from a GitLab repository. } title="Bitbucket"> Download and add registry items from a Bitbucket repository. } title="AzureDevops"> Download and add registry items from an Azure DevOps repository. } title="Your Website"> Download and add registry items from your own website. } title="Local Filesystem"> Download and add registry items from your local filesystem. You can customize the providers that **jsrepo** uses by adding them to your `jsrepo.config` file. ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import { github } from "jsrepo/providers"; // [!code ++] export default defineConfig({ providers: [github()], // [!code ++] }); ``` ## Provider plugins Provider plugins are a way to add support for additional providers to **jsrepo**. ### Adding provider plugins You can add provider plugins by running the following command: ```sh jsrepo config provider ``` This will automatically install the plugin and add it to your config file: ```ts title="jsrepo.config.ts" import { defineConfig, DEFAULT_PROVIDERS } from "jsrepo"; // [!code ++] import myProvider from "jsrepo-provider-myprovider"; // [!code ++] export default defineConfig({ providers: [...DEFAULT_PROVIDERS, myProvider()], // [!code ++] }); ``` You will notice the addition of the `DEFAULT_PROVIDERS` object in the example above. This way you can continue to use all other providers alongside your provider plugin. ================================================ FILE: apps/docs/content/docs/providers/jsrepo.mdx ================================================ --- title: jsrepo description: Download and add registry items from jsrepo.com --- **jsrepo.com** is a centralized source registry for registries. Much like npm is for packages, **jsrepo.com** is for registries. **jsrepo.com** comes with a few added benefits over other registry providers: - Semver support - First class support for private registries with the `jsrepo auth` command - Discoverability of your registry through the **jsrepo.com** website (also by the jsrepo mcp agent) - Easier installation for your users with simple `@/` syntax ## Usage To start adding registry items from **jsrepo.com** you can run the following command: ```sh jsrepo add @/ ``` You can also add registries at a specific version: ```sh jsrepo add @/@1.0.0 ``` ## Authentication To authenticate with jsrepo.com you can run the following command: ```sh jsrepo auth jsrepo ``` Once authenticated you will have access to all registries associated with your account public and private as well as the ability to publish registries. You can logout of your account by running: ```sh jsrepo auth jsrepo --logout ``` ### Environment Variable If you prefer to use an environment variable or are in an environment where you can't use the `jsrepo auth` command you can provide the token via the `JSREPO_TOKEN` environment variable. ```sh JSREPO_TOKEN=... jsrepo publish ``` ## Publishing your registry to jsrepo.com Once you have authenticated you can publish your registry with the `publish` command: ```sh jsrepo publish ``` For a more detailed guide on publishing your registry to jsrepo.com checkout the guide [here](/docs/jsrepo-com#publishing-your-registry). ================================================ FILE: apps/docs/content/docs/providers/shadcn.mdx ================================================ --- title: shadcn description: Download and add items from the shadcn registry index. --- import { RegistryDirectory } from "@/components/registry-index"; ## Usage The **shadcn** registry index is a nice way to shorten shadcn registry urls. If you are making use of shadcn registries in your project you can use this provider to use registries by their shortened name. Since this doesn't come by default you will need to add it to your `jsrepo.config.ts` file. You can do this with a command: ```sh jsrepo config provider @jsrepo/shadcn ``` ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import shadcn from "@jsrepo/shadcn"; // [!code ++] export default defineConfig({ providers: [shadcn()], // [!code ++] }); ``` Now you can simply use the `shadcn:` prefix followed by the registry namespace: ```sh jsrepo add --registry shadcn:@react-bits ``` ## Options ## Registry Directory All of these registries are available to use with the **shadcn** provider. ================================================ FILE: apps/docs/content/docs/transforms/biome.mdx ================================================ --- title: Biome description: Format code before it's added to your project using Biome. --- The [@jsrepo/transform-biome](https://npmjs.com/package/@jsrepo/transform-biome) package is a transform plugin for **jsrepo** that formats code using your local biome configuration before it's added to your project using [Biome](https://biomejs.dev). This is especially useful when you are making use of the `jsrepo update` command as it will format the code the same way as it is in your project preventing any formatting changes from being shown in the diff. ## Installation To add the **@jsrepo/transform-biome** transform to your config run the following command: ```sh jsrepo config transform biome ``` This will automatically install the transform and add it to your config: ```ts import { defineConfig } from "jsrepo"; import biome from "@jsrepo/transform-biome"; // [!code ++] export default defineConfig({ transforms: [biome()], // [!code ++] }); ``` ================================================ FILE: apps/docs/content/docs/transforms/filecasing.mdx ================================================ --- title: File Casing description: Transform file and folder names to different case formats before adding them to your project. --- The [@jsrepo/transform-filecasing](https://npmjs.com/package/@jsrepo/transform-filecasing) package is a transform plugin for **jsrepo** that transforms file and folder names to different case formats before adding them to your project. This is useful when your project follows a specific file or folder naming convention and you want registry items to automatically conform to it. The transform only affects the item's relative path, not the base path configured in your jsrepo.config. For example, if your config specifies `src/lib/components/ui` as the path for components, and you add a button component, only the part after that base path is transformed: `button/index.ts` → `Button/Index.ts`, resulting in `src/lib/components/ui/Button/Index.ts`. ## Installation To add the **@jsrepo/transform-filecasing** transform to your config run the following command: ```sh jsrepo config transform filecasing ``` This will automatically install the transform and add it to your config: ```ts import { defineConfig } from "jsrepo"; import fileCasing from "@jsrepo/transform-filecasing"; // [!code ++] export default defineConfig({ transforms: [fileCasing({ to: "camel" })], // [!code ++] }); ``` Now when a file is added to your project its file and folder names will be transformed to the target case format. Note that only the item's relative path is transformed, not the base path from your config: ```ts title="Config path: 'src/lib/components/ui', Item: 'button/index.ts' → Result: 'src/lib/components/ui/Button/Index.ts'" // Config path: 'src/lib/components/ui' // Item path: 'button/index.ts' // Result: 'src/lib/components/ui/Button/Index.ts' ``` ## Configuration The transform accepts an options object with the following properties: | Option | Type | Description | |--------|------|-------------| | `to` | `"kebab" \| "camel" \| "snake" \| "pascal"` | The target case format for file and folder names | | `transformDirectories` | `boolean` | Whether to transform directory segments in the path. When `false`, only the filename baseName is transformed. Defaults to `true` | ### Transform Directories and Filenames (Default) By default, both directory segments and filenames are transformed: ```ts import { defineConfig } from "jsrepo"; import fileCasing from "@jsrepo/transform-filecasing"; export default defineConfig({ // Config path: 'src/lib/components/ui' transforms: [fileCasing({ to: "pascal" })], }); ``` This will transform the item's relative path: - Item: `button/index.ts` → Result: `src/lib/components/ui/Button/Index.ts` - Item: `my-components/use-hook.ts` → Result: `src/lib/components/ui/MyComponents/UseHook.ts` Note that the config path (`src/lib/components/ui`) is never transformed. ### Transform Only Filenames To preserve directory names and only transform filenames, set `transformDirectories` to `false`: ```ts import { defineConfig } from "jsrepo"; import fileCasing from "@jsrepo/transform-filecasing"; export default defineConfig({ transforms: [fileCasing({ to: "camel", transformDirectories: false })], }); ``` This will transform only the filename in the item's relative path: - Item: `my-components/use-hook.ts` → Result: `src/lib/components/ui/my-components/useHook.ts` - Item: `button/index.ts` → Result: `src/lib/components/ui/button/index.ts` Note that directory names in the item path are preserved, and the config path is never transformed. This only transforms directories and files within added items and will never rename existing files or directories in your project. ================================================ FILE: apps/docs/content/docs/transforms/index.mdx ================================================ --- title: Transforms description: Modify code before it is added to your project. --- Transforms are a way to modify code before it's added to your project. You can use transforms to format code, add comments, or any other code modification you can think of. Transforms are also pluggable so you can create your own and share them with the community. Here are a few of the officially supported transforms: } title="@jsrepo/transform-prettier"> Format code before it's added to your project using **Prettier**. } title="@jsrepo/transform-biome"> Format code before it's added to your project using **Biome**. } title="@jsrepo/transform-oxfmt"> Format code before it's added to your project using **oxfmt**. } title="@jsrepo/transform-javascript"> Transform TypeScript registry items into JavaScript before adding them to your project. Transform file and folder names to different case formats before adding them to your project. ## Adding transforms You can add transforms to your config by running the following command: ```sh jsrepo config transform jsrepo-transform-my-transform ``` This will automatically install the transform and add it to your config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import myTransform from "jsrepo-transform-my-transform"; // [!code ++] export default defineConfig({ transforms: [myTransform()], // [!code ++] }); ``` ## Transform authors When creating transforms we recommend you follow a few rules: 1. Name your transform package with the following naming convention: `jsrepo-transform-` or `@/jsrepo-transform-` 2. Export the transform as the `default` export The name of your transform in the config will be the name of your package without the `jsrepo-transform-` prefix converted to camel case. For example: ```plaintext jsrepo-transform-my-transform -> myTransform @my-org/jsrepo-transform-my-transform -> myTransform ``` Official plugins are an exception to this rule to prevent having to name packages like `@jsrepo/jsrepo-transform-prettier`. For instance: ```plaintext @jsrepo/transform-prettier -> prettier @jsrepo/transform-faster-prettier -> fasterPrettier ``` ## Creating your own transform To demonstrate the power of transforms let's create a simple transform that adds a comment at the top of each file letting the user know what registry the item was added from. ```ts title="src/transform.ts" import type { Transform } from "jsrepo"; export default function(): Transform { return { transform: async (code, fileName, { registryUrl }) => { if (fileName.endsWith('.ts') || fileName.endsWith('.js')) { return { code: `// Added from ${registryUrl}\n\n${code}` }; } // return nothing since no changes were made return { }; }, }; } ``` Now we can use the transform in our config: ```ts title="jsrepo.config.ts" import { defineConfig } from "jsrepo"; import addComment from "./src/transform"; // [!code ++] export default defineConfig({ transforms: [addComment()], // [!code ++] }); ``` Now when users add items from the registry they will include the comment at the top of the file letting them know what registry the item was added from: ```ts title="src/math/add.ts" // Added from https://example.com/registry // [!code highlight] export function add(a: number, b: number): number { return a + b; } ``` ================================================ FILE: apps/docs/content/docs/transforms/javascript.mdx ================================================ --- title: JavaScript description: Transform TypeScript registry items into JavaScript before adding them to your project. --- The [@jsrepo/transform-javascript](https://npmjs.com/package/@jsrepo/transform-javascript) package is a transform plugin for **jsrepo** that converts TypeScript registry items into JavaScript before adding them to your project. This only works for [erasable syntax](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#the---erasablesyntaxonly-option). ## Installation ```sh jsrepo config transform javascript # or initialize any jsrepo project with the --js flag jsrepo init @ieedan/std --js ``` This will automatically install the transform and add it to your config: ```ts import { defineConfig } from "jsrepo"; import javascript from "@jsrepo/transform-javascript"; // [!code ++] export default defineConfig({ // ... transforms: [javascript()], // [!code ++] }); ``` This plugin will then transform any registry items written in TypeScript into JavaScript before adding them to your project. **Before:** ```ts title="math/add.ts" export function add(a: number, b: number): number { return a + b; } ``` **After:** ```ts title="math/add.js" export function add(a: number, b: number): number { // [!code --] export function add(a, b) { // [!code ++] return a + b; } ``` ## Supported Languages If your language of choice is not supported here feel free to contribute it! - ✅ Full support - ⚠️ Partial support - ❌ No support | Language | Support | |------------|---------| | TypeScript | ✅ | | Svelte | ✅ | ================================================ FILE: apps/docs/content/docs/transforms/oxfmt.mdx ================================================ --- title: oxfmt description: Format code before it's added to your project using oxfmt. --- The [@jsrepo/transform-oxfmt](https://npmjs.com/package/@jsrepo/transform-oxfmt) package is a transform plugin for **jsrepo** that formats code using [oxfmt](https://github.com/oxc-project/oxfmt) before it's added to your project. This is especially useful when you are making use of the `jsrepo update` command as it will format the code the same way as it is in your project preventing any formatting changes from being shown in the diff. ## Installation To add the **@jsrepo/transform-oxfmt** transform to your config run the following command: ```sh jsrepo config transform oxfmt ``` This will automatically install the transform and add it to your config: ```ts import { defineConfig } from "jsrepo"; import oxfmt from "@jsrepo/transform-oxfmt"; // [!code ++] export default defineConfig({ transforms: [oxfmt()], // [!code ++] }); ``` ================================================ FILE: apps/docs/content/docs/transforms/prettier.mdx ================================================ --- title: Prettier description: Format code before it's added to your project using Prettier. --- The [@jsrepo/transform-prettier](https://npmjs.com/package/@jsrepo/transform-prettier) package is a transform plugin for **jsrepo** that formats code using your local Prettier configuration before it's added to your project using [Prettier](https://prettier.io). This is especially useful when you are making use of the `jsrepo update` command as it will format the code the same way as it is in your project preventing any formatting changes from being shown in the diff. ## Installation To add the **@jsrepo/transform-prettier** transform to your config run the following command: ```sh jsrepo config transform prettier ``` This will automatically install the transform and add it to your config: ```ts import { defineConfig } from "jsrepo"; import prettier from "@jsrepo/transform-prettier"; // [!code ++] export default defineConfig({ transforms: [prettier()], // [!code ++] }); ``` ================================================ FILE: apps/docs/instrumentation-client.js ================================================ import posthog from "posthog-js"; if (process.env.NODE_ENV === 'production') { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, defaults: "2025-05-24" }); } ================================================ FILE: apps/docs/jsrepo.config.mts ================================================ import { defineConfig, DEFAULT_PROVIDERS } from "jsrepo"; import shadcn from '@jsrepo/shadcn'; export default defineConfig({ registries: ["https://reactbits.dev/r", "https://magicui.design/r", "https://ui.shadcn.com/r/styles/new-york-v4"], paths: { component: '@/components', util: '@/lib/utils', ui: '@/components/ui', lib: '@/lib', hook: '@/hooks', }, providers: [...DEFAULT_PROVIDERS, shadcn()] }); ================================================ FILE: apps/docs/middleware.ts ================================================ import { NextRequest, NextResponse } from "next/server"; import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation"; const { rewrite: rewriteLLM } = rewritePath("/docs/*path", "/llms.mdx/*path"); export function middleware(request: NextRequest) { if (isMarkdownPreferred(request)) { const result = rewriteLLM(request.nextUrl.pathname); if (result) { return NextResponse.rewrite(new URL(result, request.nextUrl)); } } return NextResponse.next(); } ================================================ FILE: apps/docs/next.config.mjs ================================================ import { createMDX } from "fumadocs-mdx/next"; const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, async rewrites() { return [ { source: "/docs/:path*.mdx", destination: "/llms.mdx/:path*", }, ]; }, }; export default withMDX(config); ================================================ FILE: apps/docs/og-image.d.ts ================================================ /** * Type declarations for @vercel/og ImageResponse. * The `tw` prop allows Tailwind-style class names for OG image generation. * @see https://vercel.com/docs/og-image-generation */ export {}; declare module "react" { interface HTMLAttributes { tw?: string; } } ================================================ FILE: apps/docs/package.json ================================================ { "name": "@jsrepo/docs", "version": "0.0.0", "private": true, "scripts": { "build": "next build", "dev": "next dev --turbo", "start": "next start", "postinstall": "fumadocs-mdx" }, "dependencies": { "@gsap/react": "^2.1.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-presence": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@shikijs/transformers": "3.14.0", "@tanstack/react-query": "^5.90.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "fumadocs-core": "16.0.7", "fumadocs-mdx": "13.0.5", "fumadocs-ui": "16.0.7", "gsap": "^3.13.0", "lucide-react": "^0.575.0", "next": "16.1.6", "next-themes": "^0.4.6", "ogl": "^1.0.11", "react": "19.2.4", "react-dom": "19.2.4", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0" }, "devDependencies": { "@jsrepo/shadcn": "workspace:*", "@tailwindcss/postcss": "^4.2.0", "@types/mdx": "^2.0.13", "@types/node": "25.3.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "fuzzysort": "^3.1.0", "jsrepo": "workspace:*", "motion": "^12.34.3", "postcss": "^8.5.6", "posthog-js": "^1.352.0", "tailwindcss": "^4.2.0", "tw-animate-css": "^1.4.0", "typescript": "^5.9.3", "zod": "catalog:" } } ================================================ FILE: apps/docs/postcss.config.mjs ================================================ export default { plugins: { '@tailwindcss/postcss': {}, }, }; ================================================ FILE: apps/docs/source.config.ts ================================================ import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config"; import { remarkMdxFiles } from "fumadocs-core/mdx-plugins"; // You can customize Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections export const docs = defineDocs({ docs: { schema: frontmatterSchema, postprocess: { includeProcessedMarkdown: true, }, }, meta: { schema: metaSchema, }, }); export default defineConfig({ mdxOptions: { remarkPlugins: [remarkMdxFiles], }, }); ================================================ FILE: apps/docs/src/app/(home)/code-block.tsx ================================================ import * as Base from "fumadocs-ui/components/codeblock"; import { highlight } from "fumadocs-core/highlight"; import { type HTMLAttributes } from "react"; import { transformerNotationDiff, transformerNotationHighlight } from "@shikijs/transformers"; import { cn } from "@/lib/utils"; export async function CodeBlock({ code, lang, className, ...rest }: HTMLAttributes & { code: string; lang: string; }) { const rendered = await highlight(code, { lang, components: { pre: (props) => , }, transformers: [transformerNotationDiff(), transformerNotationHighlight()], }); return ( {rendered} ); } ================================================ FILE: apps/docs/src/app/(home)/layout.tsx ================================================ import { HomeLayout } from "@/components/layout/home"; import { baseOptions } from "@/lib/layout.shared"; export default function Layout({ children }: LayoutProps<"/">) { return ( Docs, url: "/docs", }, ]} > {children} ); } ================================================ FILE: apps/docs/src/app/(home)/page.tsx ================================================ import Link from "next/link"; import type { Metadata } from "next"; import { Button } from "@/components/ui/button"; import { FeatureTabs, FeatureTabsList, FeatureTabsTrigger, FeatureTabsContent } from "@/components/feature-tabs"; import { AnimatedSpan, Terminal, TypingAnimation } from "@/components/ui/terminal"; import { CodeBlock } from "./code-block"; import { ProvidersSection } from "./providers-section"; import { cn } from "@/lib/utils"; import PrismaticBurst from "@/components/PrismaticBurst"; import { ExternalLinkIcon } from "lucide-react"; export const metadata: Metadata = { title: "jsrepo.dev - The modern registry toolchain", description: "jsrepo - The modern registry toolchain", openGraph: { title: "jsrepo.dev", description: "jsrepo - The modern registry toolchain", type: "website", siteName: "jsrepo.dev", images: [ { url: "/og", width: 1200, height: 630, }, ], }, twitter: { card: "summary_large_image", title: "jsrepo.dev", description: "jsrepo - The modern registry toolchain", images: [ { url: "/og", width: 1200, height: 630, }, ], }, metadataBase: new URL("https://jsrepo.dev"), }; export default function HomePage() { return ( <>

Ready to level up your registry?

© {new Date().getFullYear()} jsrepo, All rights reserved.
GitHub jsrepo.com
); } function HeroSection() { return (

The modern registry toolchain

jsrepo handles the hard parts of registries so you can focus on building.

); } function PluginsSection() { return (

Customize your experience with plugins

With a js based config the sky is the limit.

); } function RestEasySection() { return (

Rest easy

If your registry builds with jsrepo, it will work for your users.

); } function LLMsSection() { return (

Built for LLMs

jsrepo is optimized for LLMs by giving them demos and documentation alongside registry items.

); } function ShadcnCompatibilitySection() { return (

Shadcn compatible

Add and update items seamlessly from shadcn registries.

> jsrepo add shadcn:@react-bits/AnimatedContent-TS-TW ✔ Fetched manifest from shadcn:@react-bits/AnimatedContent-TS-TW ✔ Fetched AnimatedContent-TS-TW. Added AnimatedContent-TS-TW to your project. Updated 1 file.
); } function FeatureAccordionSection() { return (
Configure Build Add Update > jsrepo init ✔ Wrote config to jsrepo.config.ts ✔ Installed dependencies. ✔ Initialization complete. > jsrepo build --watch ✔ Finished in 10.49ms @ieedan/std: Created 1 output in 10.12ms with 3 items and 4 files. Watching for changes... > jsrepo add button ✔ Fetched manifest from @ieedan/shadcn-svelte-extras ✔ Fetched button. Added button, utils to your project. Updated 2 files. > jsrepo update button ✔ Fetched manifest from @ieedan/shadcn-svelte-extras ✔ Fetched button.
@ieedan/shadcn-svelte-extras/button {"->"} src/components/button.svelte
+ 20 more unchanged (-E to expand)
21 {" "}link: 'text-primary underline-offset
22 {" "} {"},"}
23 {" "}size: {"{"}
24 {" default: 'h-10 px-"} 3 4 {" py-2',"}
25 {" "}sm: 'h-9 rounded-md px-3',
26 {" "}lg: 'h-11 rounded-md px-8',
27 {" "}icon: 'h-10 w-10'
+ 60 more unchanged (-E to expand)
Updated button, utils in your project. Updated 2 files.
); } ================================================ FILE: apps/docs/src/app/(home)/providers-section.tsx ================================================ "use client"; import { GitHubLogo, JsrepoLogo, RegistryKitLogo } from "@/components/logos"; import { AnimatedBeam } from "@/components/ui/animated-beam"; import { cn } from "@/lib/utils"; import { Folder } from "lucide-react"; import { useRef } from "react"; function Circle({ className, ...props }: React.ComponentProps<"div">) { return (
); } export function ProvidersSection() { const containerRef = useRef(null); const githubRef = useRef(null); const httpRef = useRef(null); const jsrepoRef = useRef(null); const cliRef = useRef(null); const projectRef = useRef(null); return (

Host your registry anywhere

Host your registry publicly or privately wherever you want.

github/jsrepojs/registry-kit
@jsrepo/registry-kit
https://registry-kit.dev/r
); } ================================================ FILE: apps/docs/src/app/api/search/route.ts ================================================ import { source } from '@/lib/source'; import { createFromSource } from 'fumadocs-core/search/server'; export const { GET } = createFromSource(source, { // https://docs.orama.com/docs/orama-js/supported-languages language: 'english', }); ================================================ FILE: apps/docs/src/app/app-client.tsx ================================================ "use client"; import { RootProvider } from "fumadocs-ui/provider/next"; import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useScrollToTop } from "@/hooks/use-scroll-to-top"; export function App({ children }: { children: React.ReactNode }) { useScrollToTop(); const [queryClient] = useState(() => new QueryClient()); return ( {children} ); } ================================================ FILE: apps/docs/src/app/docs/[[...slug]]/page.tsx ================================================ import { getPageImage, source } from "@/lib/source"; import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page"; import { notFound } from "next/navigation"; import { getMDXComponents } from "@/mdx-components"; import type { Metadata } from "next"; import { createRelativeLink } from "fumadocs-ui/mdx"; import { LLMCopyButton, ViewOptions } from "@/components/page-actions"; export default async function Page(props: PageProps<"/docs/[[...slug]]">) { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); const MDX = page.data.body; return (
{page.data.title}
{page.data.description}
); } export async function generateStaticParams() { return source.generateParams(); } export async function generateMetadata(props: PageProps<"/docs/[[...slug]]">): Promise { const params = await props.params; const page = source.getPage(params.slug); if (!page) notFound(); return { title: page.data.title, description: page.data.description, openGraph: { images: getPageImage(page).url, }, }; } ================================================ FILE: apps/docs/src/app/docs/layout.tsx ================================================ import { DocsLayout } from 'fumadocs-ui/layouts/docs'; import { baseOptions } from '@/lib/layout.shared'; import { source } from '@/lib/source'; export default function Layout({ children }: LayoutProps<'/docs'>) { const base = baseOptions(); return ( {children} ); } ================================================ FILE: apps/docs/src/app/global.css ================================================ @import "tailwindcss"; @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @theme inline { --font-sans: "Manrope", "Manrope Fallback"; --font-mono: "IBM Plex Mono", "IBM Plex Mono Fallback"; --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); } :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.21 0.006 285.885); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); --ring: oklch(0.705 0.015 286.067); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.21 0.006 285.885); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.015 286.067); } .dark { --background: oklch(0.141 0.005 285.823); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.92 0.004 286.32); --primary-foreground: oklch(0.21 0.006 285.885); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); --muted-foreground: oklch(0.705 0.015 286.067); --accent: oklch(0.274 0.006 286.033); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.552 0.016 285.938); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.552 0.016 285.938); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } /* Apply monospace font to all code blocks */ pre, code, [data-codeblock], .fd-codeblock pre, .fd-codeblock code { font-family: var(--font-mono), ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; } } /* For components that need horizontal scrolling */ .scrollbar-hide { -ms-overflow-style: none; /* Internet Explorer and Edge */ scrollbar-width: none; /* Firefox */ } .scrollbar-hide::-webkit-scrollbar { display: none; /* Chrome, Safari, and Opera */ } ================================================ FILE: apps/docs/src/app/layout.tsx ================================================ import "@/app/global.css"; import { Manrope, IBM_Plex_Mono } from "next/font/google"; import { App } from "./app-client"; const fontSans = Manrope({ subsets: ["latin"], variable: "--font-sans", }); const fontMono = IBM_Plex_Mono({ subsets: ["latin"], variable: "--font-mono", weight: ["400", "500", "600", "700"], }); export default function Layout({ children }: LayoutProps<"/">) { return ( {children} ); } ================================================ FILE: apps/docs/src/app/llms-full.txt/route.ts ================================================ import { getLLMText, source } from '@/lib/source'; export const revalidate = false; export async function GET() { const scan = source.getPages().map(getLLMText); const scanned = await Promise.all(scan); return new Response(scanned.join('\n\n')); } ================================================ FILE: apps/docs/src/app/llms.mdx/[[...slug]]/route.ts ================================================ import { getLLMText } from "@/lib/source"; import { source } from "@/lib/source"; import { notFound } from "next/navigation"; export const revalidate = false; export async function GET(_req: Request, { params }: RouteContext<"/llms.mdx/[[...slug]]">) { const { slug } = await params; const page = source.getPage(slug); if (!page) notFound(); return new Response(await getLLMText(page), { headers: { "Content-Type": "text/markdown", }, }); } export function generateStaticParams() { return source.generateParams(); } ================================================ FILE: apps/docs/src/app/og/docs/[...slug]/route.tsx ================================================ import { getPageImage, source } from "@/lib/source"; import { notFound } from "next/navigation"; import { ImageResponse } from "next/og"; import { loadGoogleFont } from "@/lib/og"; export const revalidate = false; export async function GET(_req: Request, { params }: RouteContext<"/og/docs/[...slug]">) { const { slug } = await params; const page = source.getPage(slug.slice(0, -1)); if (!page) notFound(); return new ImageResponse( (
jsrepo.dev

$ {page.data.title}

{page.data.description}

), { width: 1200, height: 630, fonts: [ { name: "IBM Plex Mono", data: await loadGoogleFont("IBM Plex Mono", "$ jsrepo.dev" + page.data.title + page.data.description), style: "normal", }, ], } ); } export function generateStaticParams() { return source.getPages().map((page) => ({ lang: page.locale, slug: getPageImage(page).segments, })); } ================================================ FILE: apps/docs/src/app/og/route.tsx ================================================ import { loadGoogleFont } from "@/lib/og"; import { ImageResponse } from "next/og"; export const revalidate = false; export async function GET() { const title = "jsrepo"; const description = "The modern registry toolchain."; return new ImageResponse( (
jsrepo.dev

$ {title}

{description}

), { width: 1200, height: 630, fonts: [ { name: "IBM Plex Mono", data: await loadGoogleFont("IBM Plex Mono", "$ jsrepo.dev" + title + description), style: "normal", }, ], } ); } ================================================ FILE: apps/docs/src/app/robots.txt ================================================ # allow crawling everything by default User-agent: * Disallow: ================================================ FILE: apps/docs/src/components/PrismaticBurst.tsx ================================================ 'use client'; import React, { useEffect, useRef } from 'react'; import { Renderer, Program, Mesh, Triangle, Texture } from 'ogl'; type Offset = { x?: number | string; y?: number | string }; type AnimationType = 'rotate' | 'rotate3d' | 'hover'; export type PrismaticBurstProps = { intensity?: number; speed?: number; animationType?: AnimationType; colors?: string[]; distort?: number; paused?: boolean; offset?: Offset; hoverDampness?: number; rayCount?: number; mixBlendMode?: React.CSSProperties['mixBlendMode'] | 'none'; }; const vertexShader = `#version 300 es in vec2 position; in vec2 uv; out vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 0.0, 1.0); } `; const fragmentShader = `#version 300 es precision highp float; precision highp int; out vec4 fragColor; uniform vec2 uResolution; uniform float uTime; uniform float uIntensity; uniform float uSpeed; uniform int uAnimType; uniform vec2 uMouse; uniform int uColorCount; uniform float uDistort; uniform vec2 uOffset; uniform sampler2D uGradient; uniform float uNoiseAmount; uniform int uRayCount; float hash21(vec2 p){ p = floor(p); float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005))); return fract(f); } mat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); } float layeredNoise(vec2 fragPx){ vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0); vec2 q = rot30() * p; float n = 0.0; n += 0.40 * hash21(q); n += 0.25 * hash21(q * 2.0 + 17.0); n += 0.20 * hash21(q * 4.0 + 47.0); n += 0.10 * hash21(q * 8.0 + 113.0); n += 0.05 * hash21(q * 16.0 + 191.0); return n; } vec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){ float focal = res.y * max(dist, 1e-3); return normalize(vec3(2.0 * (frag - offset) - res, focal)); } float edgeFade(vec2 frag, vec2 res, vec2 offset){ vec2 toC = frag - 0.5 * res - offset; float r = length(toC) / (0.5 * min(res.x, res.y)); float x = clamp(r, 0.0, 1.0); float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0); float s = q * 0.5; s = pow(s, 1.5); float tail = 1.0 - pow(1.0 - s, 2.0); s = mix(s, tail, 0.2); float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s; return clamp(s + dn, 0.0, 1.0); } mat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); } mat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); } mat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); } vec3 sampleGradient(float t){ t = clamp(t, 0.0, 1.0); return texture(uGradient, vec2(t, 0.5)).rgb; } vec2 rot2(vec2 v, float a){ float s = sin(a), c = cos(a); return mat2(c, -s, s, c) * v; } float bendAngle(vec3 q, float t){ float a = 0.8 * sin(q.x * 0.55 + t * 0.6) + 0.7 * sin(q.y * 0.50 - t * 0.5) + 0.6 * sin(q.z * 0.60 + t * 0.7); return a; } void main(){ vec2 frag = gl_FragCoord.xy; float t = uTime * uSpeed; float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0); vec3 dir = rayDir(frag, uResolution, uOffset, 1.0); float marchT = 0.0; vec3 col = vec3(0.0); float n = layeredNoise(frag); vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0)); mat2 M2 = mat2(c.x, c.y, c.z, c.w); float amp = clamp(uDistort, 0.0, 50.0) * 0.15; mat3 rot3dMat = mat3(1.0); if(uAnimType == 1){ vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17); rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x); } mat3 hoverMat = mat3(1.0); if(uAnimType == 2){ vec2 m = uMouse * 2.0 - 1.0; vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0); hoverMat = rotY(ang.y) * rotX(ang.x); } for (int i = 0; i < 44; ++i) { vec3 P = marchT * dir; P.z -= 2.0; float rad = length(P); vec3 Pl = P * (10.0 / max(rad, 1e-6)); if(uAnimType == 0){ Pl.xz *= M2; } else if(uAnimType == 1){ Pl = rot3dMat * Pl; } else { Pl = hoverMat * Pl; } float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1; float grow = smoothstep(0.35, 3.0, marchT); float a1 = amp * grow * bendAngle(Pl * 0.6, t); float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9); vec3 Pb = Pl; Pb.xz = rot2(Pb.xz, a1); Pb.xy = rot2(Pb.xy, a2); float rayPattern = smoothstep( 0.5, 0.7, sin(Pb.x + cos(Pb.y) * cos(Pb.z)) * sin(Pb.z + sin(Pb.y) * cos(Pb.x + t)) ); if (uRayCount > 0) { float ang = atan(Pb.y, Pb.x); float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang); comb = pow(comb, 3.0); rayPattern *= smoothstep(0.15, 0.95, comb); } vec3 spectralDefault = 1.0 + vec3( cos(marchT * 3.0 + 0.0), cos(marchT * 3.0 + 1.0), cos(marchT * 3.0 + 2.0) ); float saw = fract(marchT * 0.25); float tRay = saw * saw * (3.0 - 2.0 * saw); vec3 userGradient = 2.0 * sampleGradient(tRay); vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault; vec3 base = (0.05 / (0.4 + stepLen)) * smoothstep(5.0, 0.0, rad) * spectral; col += base * rayPattern; marchT += stepLen; } col *= edgeFade(frag, uResolution, uOffset); col *= uIntensity; fragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }`; const hexToRgb01 = (hex: string): [number, number, number] => { let h = hex.trim(); if (h.startsWith('#')) h = h.slice(1); if (h.length === 3) { const r = h[0], g = h[1], b = h[2]; h = r + r + g + g + b + b; } const intVal = parseInt(h, 16); if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1]; const r = ((intVal >> 16) & 255) / 255; const g = ((intVal >> 8) & 255) / 255; const b = (intVal & 255) / 255; return [r, g, b]; }; const toPx = (v: number | string | undefined): number => { if (v == null) return 0; if (typeof v === 'number') return v; const s = String(v).trim(); const num = parseFloat(s.replace('px', '')); return isNaN(num) ? 0 : num; }; const PrismaticBurst = ({ intensity = 2, speed = 0.5, animationType = 'rotate3d', colors, distort = 0, paused = false, offset = { x: 0, y: 0 }, hoverDampness = 0, rayCount, mixBlendMode = 'lighten' }: PrismaticBurstProps) => { const containerRef = useRef(null); const programRef = useRef(null); const rendererRef = useRef(null); const mouseTargetRef = useRef<[number, number]>([0.5, 0.5]); const mouseSmoothRef = useRef<[number, number]>([0.5, 0.5]); const pausedRef = useRef(paused); const gradTexRef = useRef(null); const hoverDampRef = useRef(hoverDampness); const isVisibleRef = useRef(true); const meshRef = useRef(null); const triRef = useRef(null); useEffect(() => { pausedRef.current = paused; }, [paused]); useEffect(() => { hoverDampRef.current = hoverDampness; }, [hoverDampness]); useEffect(() => { const container = containerRef.current; if (!container) return; const dpr = Math.min(window.devicePixelRatio || 1, 2); const renderer = new Renderer({ dpr, alpha: false, antialias: false }); rendererRef.current = renderer; const gl = renderer.gl; gl.canvas.style.position = 'absolute'; gl.canvas.style.inset = '0'; gl.canvas.style.width = '100%'; gl.canvas.style.height = '100%'; gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : ''; container.appendChild(gl.canvas); const white = new Uint8Array([255, 255, 255, 255]); const gradientTex = new Texture(gl, { image: white, width: 1, height: 1, generateMipmaps: false, flipY: false }); gradientTex.minFilter = gl.LINEAR; gradientTex.magFilter = gl.LINEAR; gradientTex.wrapS = gl.CLAMP_TO_EDGE; gradientTex.wrapT = gl.CLAMP_TO_EDGE; gradTexRef.current = gradientTex; const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms: { uResolution: { value: [1, 1] as [number, number] }, uTime: { value: 0 }, uIntensity: { value: 1 }, uSpeed: { value: 1 }, uAnimType: { value: 0 }, uMouse: { value: [0.5, 0.5] as [number, number] }, uColorCount: { value: 0 }, uDistort: { value: 0 }, uOffset: { value: [0, 0] as [number, number] }, uGradient: { value: gradientTex }, uNoiseAmount: { value: 0.8 }, uRayCount: { value: 0 } } }); programRef.current = program; const triangle = new Triangle(gl); const mesh = new Mesh(gl, { geometry: triangle, program }); triRef.current = triangle; meshRef.current = mesh; const resize = () => { const w = container.clientWidth || 1; const h = container.clientHeight || 1; renderer.setSize(w, h); program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight]; }; let ro: ResizeObserver | null = null; if ('ResizeObserver' in window) { ro = new ResizeObserver(resize); ro.observe(container); } else { (window as Window).addEventListener('resize', resize); } resize(); const onPointer = (e: PointerEvent) => { const rect = container.getBoundingClientRect(); const x = (e.clientX - rect.left) / Math.max(rect.width, 1); const y = (e.clientY - rect.top) / Math.max(rect.height, 1); mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)]; }; container.addEventListener('pointermove', onPointer, { passive: true }); let io: IntersectionObserver | null = null; if ('IntersectionObserver' in window) { io = new IntersectionObserver( entries => { if (entries[0]) isVisibleRef.current = entries[0].isIntersecting; }, { root: null, threshold: 0.01 } ); io.observe(container); } const onVis = () => {}; document.addEventListener('visibilitychange', onVis); let raf = 0; let last = performance.now(); let accumTime = 0; const update = (now: number) => { const dt = Math.max(0, now - last) * 0.001; last = now; const visible = isVisibleRef.current && !document.hidden; if (!pausedRef.current) accumTime += dt; if (!visible) { raf = requestAnimationFrame(update); return; } const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5; const alpha = 1 - Math.exp(-dt / tau); const tgt = mouseTargetRef.current; const sm = mouseSmoothRef.current; sm[0] += (tgt[0] - sm[0]) * alpha; sm[1] += (tgt[1] - sm[1]) * alpha; program.uniforms.uMouse.value = sm as any; program.uniforms.uTime.value = accumTime; renderer.render({ scene: meshRef.current! }); raf = requestAnimationFrame(update); }; raf = requestAnimationFrame(update); return () => { cancelAnimationFrame(raf); container.removeEventListener('pointermove', onPointer); ro?.disconnect(); if (!ro) window.removeEventListener('resize', resize); io?.disconnect(); document.removeEventListener('visibilitychange', onVis); try { container.removeChild(gl.canvas); } catch (e) { void e; } meshRef.current = null; triRef.current = null; programRef.current = null; try { const glCtx = rendererRef.current?.gl; if (glCtx && gradTexRef.current?.texture) glCtx.deleteTexture(gradTexRef.current.texture); } catch (e) { void e; } rendererRef.current = null; gradTexRef.current = null; }; }, []); useEffect(() => { const canvas = rendererRef.current?.gl?.canvas as HTMLCanvasElement | undefined; if (canvas) { canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : ''; } }, [mixBlendMode]); useEffect(() => { const program = programRef.current; const renderer = rendererRef.current; const gradTex = gradTexRef.current; if (!program || !renderer || !gradTex) return; program.uniforms.uIntensity.value = intensity ?? 1; program.uniforms.uSpeed.value = speed ?? 1; const animTypeMap: Record = { rotate: 0, rotate3d: 1, hover: 2 }; program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate']; program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0; const ox = toPx(offset?.x); const oy = toPx(offset?.y); program.uniforms.uOffset.value = [ox, oy]; program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0)); let count = 0; if (Array.isArray(colors) && colors.length > 0) { const gl = renderer.gl; const capped = colors.slice(0, 64); count = capped.length; const data = new Uint8Array(count * 4); for (let i = 0; i < count; i++) { const [r, g, b] = hexToRgb01(capped[i]); data[i * 4 + 0] = Math.round(r * 255); data[i * 4 + 1] = Math.round(g * 255); data[i * 4 + 2] = Math.round(b * 255); data[i * 4 + 3] = 255; } gradTex.image = data; gradTex.width = count; gradTex.height = 1; gradTex.minFilter = gl.LINEAR; gradTex.magFilter = gl.LINEAR; gradTex.wrapS = gl.CLAMP_TO_EDGE; gradTex.wrapT = gl.CLAMP_TO_EDGE; gradTex.flipY = false; gradTex.generateMipmaps = false; gradTex.format = gl.RGBA; gradTex.type = gl.UNSIGNED_BYTE; gradTex.needsUpdate = true; } else { count = 0; } program.uniforms.uColorCount.value = count; }, [intensity, speed, animationType, colors, distort, offset, rayCount]); return
; }; export default PrismaticBurst; ================================================ FILE: apps/docs/src/components/badges-table.tsx ================================================ "use client"; import React from "react"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CheckIcon, CopyIcon, EllipsisIcon } from "lucide-react"; import { Button, buttonVariants } from "./ui/button"; import { Input } from "./ui/input"; import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; import { Label } from "@/components/ui/label"; type Props = { badges: { alt: string; hrefTemplate: string }[]; defaultRegistry: string; }; function buildBadgeUrl(template: string, registry: string) { return template.replace("{{registry}}", registry); } type RegistryContextType = { registry: string; setRegistry: (registry: string) => void; } export const RegistryContext = React.createContext({ registry: "", setRegistry: () => {}, }); export function useRegistry() { const ctx = React.useContext(RegistryContext); if (!ctx) throw new Error("RegistryContext not found"); return ctx; } export function BadgesTable({ badges, defaultRegistry }: Props) { const [registry, setRegistry] = React.useState(defaultRegistry); const ctx = React.useMemo(() => ({ registry, setRegistry }), [registry, setRegistry]); return ( Badge {badges.map((badge) => ( {badge.alt} ))}
); } export function BadgeActions({ badge }: { badge: { alt: string; hrefTemplate: string } }) { const [copy, isCopied] = useCopyToClipboard(); const [copyUrl, isUrlCopied] = useCopyToClipboard(); const { registry, setRegistry } = useRegistry(); return (
setRegistry(e.target.value)} />
{registry === "" ? ( <> ) : ( {badge.alt} )}
); } ================================================ FILE: apps/docs/src/components/feature-tabs.tsx ================================================ "use client"; import { cn } from "@/lib/utils"; import React, { createContext, useContext, useEffect, useMemo, useState } from "react"; type FeatureTabsContext = { activeTab: string; setActiveTab: (tab: string, fromUser?: boolean) => void; tabs: { value: string; duration: number }[]; registerTab: (tab: { value: string; duration: number }) => void; mode: "auto" | "manual"; }; const FeatureTabsContext = createContext({ activeTab: "", setActiveTab: () => {}, tabs: [], registerTab: () => {}, mode: "auto", }); function useFeatureTabs() { const ctx = useContext(FeatureTabsContext); if (!ctx) throw new Error("FeatureTabsContext not found"); return ctx; } function FeatureTabs({ defaultValue, className, children, subClassName, ...props }: React.HTMLAttributes & { defaultValue: string; subClassName?: string }) { const [activeTab, setActiveTab] = useState(defaultValue); const [mode, setMode] = useState<"auto" | "manual">("auto"); const [tabs, setTabs] = useState<{ value: string; duration: number }[]>([]); function registerTab(tab: { value: string; duration: number }) { setTabs((prev) => [...prev, tab]); } function _setActiveTab(tab: string, fromUser: boolean = false) { if (fromUser) { setMode("manual"); } setActiveTab(tab); } const ctx = useMemo( () => ({ activeTab, setActiveTab: _setActiveTab, tabs, registerTab, mode }), [activeTab, tabs, mode] ); useEffect(() => { if (mode === "manual" || tabs.length === 0) return; const t = tabs.map((tab, i) => ({ tab, index: i })).find(({ tab }) => tab.value === activeTab); if (!t) return; const { index, tab } = t; const timeoutId = setTimeout(() => { if (index + 1 === tabs.length) { _setActiveTab(tabs[0].value, false); } else { _setActiveTab(tabs[index + 1].value, false); } }, tab.duration); return () => clearTimeout(timeoutId); }, [activeTab, mode, tabs]); return (
{children}
); } function FeatureTabsList({ className, children, ...props }: React.HTMLAttributes) { return (
{children}
); } function FeatureTabsTrigger({ value, duration, description, className, children, ...props }: React.HTMLAttributes & { value: string; duration: number; description: string }) { const { registerTab, setActiveTab, activeTab } = useFeatureTabs(); // we don't want to re-register the tab if the value changes useEffect(() => { registerTab({ value, duration }); }, []); return (

{description}

); } function FeatureTabsContent({ value, className, children, ...props }: React.HTMLAttributes & { value: string }) { const { activeTab } = useFeatureTabs(); return activeTab !== value ? ( <> ) : (
{children}
); } export { FeatureTabs, FeatureTabsList, FeatureTabsTrigger, FeatureTabsContent }; ================================================ FILE: apps/docs/src/components/files.tsx ================================================ 'use client'; import { cva } from 'class-variance-authority'; import { FileIcon as LucideFileIcon, FolderIcon, FolderOpenIcon, } from 'lucide-react'; import { type HTMLAttributes, type ReactNode, useState } from 'react'; import { cn } from '@/lib/utils'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from './ui/collapsible'; import { JavaScriptLogo, SvelteLogo, TypeScriptLogo, VueLogo } from './logos'; const itemVariants = cva( 'flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4', ); export function Files({ className, ...props }: HTMLAttributes): React.ReactElement { return (
{props.children}
); } export interface FileProps extends HTMLAttributes { name: string; icon?: ReactNode; } export interface FolderProps extends HTMLAttributes { name: string; disabled?: boolean; /** * Open folder by default * * @defaultValue false */ defaultOpen?: boolean; } export function File({ name, className, ...rest }: FileProps): React.ReactElement { return (
{name}
); } export function FileIcon({ name }: { name: string }) { const extension = name.includes('.') ? name.split('.').pop() : null; switch (extension) { case 'svelte': return ; case 'js': return ; case 'ts': return ; case 'vue': return ; default: return ; } } export function Folder({ name, defaultOpen = false, ...props }: FolderProps): React.ReactElement { const [open, setOpen] = useState(defaultOpen); return ( {open ? : } {name}
{props.children}
); } ================================================ FILE: apps/docs/src/components/language-toggle.tsx ================================================ 'use client'; import { type ButtonHTMLAttributes, type HTMLAttributes } from 'react'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; import { Popover, PopoverContent, PopoverTrigger, } from './ui/popover'; import { cn } from '../lib/cn'; import { buttonVariants } from './ui/button'; export type LanguageSelectProps = ButtonHTMLAttributes; export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { const context = useI18n(); if (!context.locales) throw new Error('Missing ``'); return ( {props.children}

{context.text.chooseLanguage}

{context.locales.map((item) => ( ))}
); } export function LanguageToggleText( props: HTMLAttributes, ): React.ReactElement { const context = useI18n(); const text = context.locales?.find( (item) => item.locale === context.locale, )?.name; return {text}; } ================================================ FILE: apps/docs/src/components/layout/home/client.tsx ================================================ 'use client'; import { type ComponentProps, Fragment, useState } from 'react'; import { cva } from 'class-variance-authority'; import Link from 'fumadocs-core/link'; import { cn } from '../../../lib/cn'; import { BaseLinkItem, type LinkItemType } from '../shared/index'; import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, } from '../../navigation-menu'; import { useNav } from 'fumadocs-ui/contexts/layout'; import { buttonVariants } from '../../ui/button'; export const navItemVariants = cva('[&_svg]:size-4', { variants: { variant: { main: 'inline-flex items-center gap-1 p-2 text-fd-muted-foreground transition-colors hover:text-fd-accent-foreground data-[active=true]:text-fd-primary', button: buttonVariants({ variant: 'secondary', className: 'gap-1.5', }), icon: buttonVariants({ variant: 'ghost', size: 'icon', }), }, }, defaultVariants: { variant: 'main', }, }); export function Navbar(props: ComponentProps<'div'>) { const [value, setValue] = useState(''); const { isTransparent } = useNav(); return (
0 && 'max-lg:shadow-lg max-lg:rounded-b-2xl', (!isTransparent || value.length > 0) && 'bg-fd-background/80', props.className, )} >
); } export { NavigationMenuItem }; export function NavigationMenuLinkItem({ item, ...props }: { item: LinkItemType; className?: string; }) { if (item.type === 'custom') return
{item.children}
; if (item.type === 'menu') { const children = item.items.map((child, j) => { if (child.type === 'custom') { return {child.children}; } const { banner = child.icon ? (
{child.icon}
) : null, ...rest } = child.menu ?? {}; return ( {rest.children ?? ( <> {banner}

{child.text}

{child.description}

)}
); }); return ( {item.url ? ( {item.text} ) : ( item.text )} {children} ); } return ( {item.type === 'icon' ? item.icon : item.text} ); } export function MobileNavigationMenuLinkItem({ item, ...props }: { item: LinkItemType; className?: string; }) { if (item.type === 'custom') return
{item.children}
; if (item.type === 'menu') { const header = ( <> {item.icon} {item.text} ); return (

{item.url ? ( {header} ) : ( header )}

{item.items.map((child, i) => ( ))}
); } return ( {item.icon} {item.type === 'icon' ? undefined : item.text} ); } export function MobileNavigationMenuTrigger({ enableHover = false, ...props }: ComponentProps & { /** * Enable hover to trigger */ enableHover?: boolean; }) { return ( e.preventDefault()} > {props.children} ); } export function MobileNavigationMenuContent( props: ComponentProps, ) { return ( {props.children} ); } ================================================ FILE: apps/docs/src/components/layout/home/index.tsx ================================================ import { type HTMLAttributes, useMemo } from 'react'; import { cn } from '../../../lib/cn'; import { type BaseLayoutProps, getLinks, type LinkItemType, type NavOptions, } from '../shared/index'; import { NavProvider } from 'fumadocs-ui/contexts/layout'; import { LargeSearchToggle, SearchToggle, } from '../../search-toggle'; import { ThemeToggle } from '../../theme-toggle'; import { LanguageToggle, LanguageToggleText, } from '../../language-toggle'; import { ChevronDown, Languages } from 'lucide-react'; import Link from 'fumadocs-core/link'; import { Navbar, NavigationMenuLinkItem, MobileNavigationMenuContent, MobileNavigationMenuLinkItem, MobileNavigationMenuTrigger, NavigationMenuItem, } from './client'; import { buttonVariants } from '../../ui/button'; import { GitHubButton } from '@/components/ui/github-button'; export interface HomeLayoutProps extends BaseLayoutProps { nav?: Partial< NavOptions & { /** * Open mobile menu when hovering the trigger */ enableHoverToOpen?: boolean; } >; } export function HomeLayout( props: HomeLayoutProps & HTMLAttributes, ) { const { nav = {}, links, githubUrl, i18n, themeSwitch = {}, searchToggle, ...rest } = props; return (
{nav.enabled !== false && (nav.component ?? (
))} {props.children}
); } export function Header({ nav = {}, i18n = false, links, githubUrl, themeSwitch = {}, searchToggle = {}, }: HomeLayoutProps) { const finalLinks = useMemo( () => getLinks(links), [links], ); const navItems = finalLinks.filter((item) => ['nav', 'all'].includes(item.on ?? 'all'), ); const menuItems = finalLinks.filter((item) => ['menu', 'all'].includes(item.on ?? 'all'), ); return ( {nav.title} {nav.children}
    {navItems .filter((item) => !isSecondary(item)) .map((item, i) => ( ))}
{searchToggle.enabled !== false && (searchToggle.components?.lg ?? ( ))} {themeSwitch.enabled !== false && (themeSwitch.component ?? )} {i18n && ( )}
    {navItems.filter(isSecondary).map((item, i) => ( ))}
    {searchToggle.enabled !== false && (searchToggle.components?.sm ?? ( ))} {menuItems .filter((item) => !isSecondary(item)) .map((item, i) => ( ))}
    {menuItems.filter(isSecondary).map((item, i) => ( ))}
    {i18n && ( )} {themeSwitch.enabled !== false && (themeSwitch.component ?? ( ))}
); } function isSecondary(item: LinkItemType): boolean { if ('secondary' in item && item.secondary != null) return item.secondary; return item.type === 'icon'; } ================================================ FILE: apps/docs/src/components/layout/shared/client.tsx ================================================ 'use client'; import type { ComponentProps } from 'react'; import { usePathname } from 'fumadocs-core/framework'; import { isActive } from '../../../lib/is-active'; import Link from 'fumadocs-core/link'; import type { BaseLinkType } from './index'; export function BaseLinkItem({ ref, item, ...props }: Omit, 'href'> & { item: BaseLinkType }) { const pathname = usePathname(); const activeType = item.active ?? 'url'; const active = activeType !== 'none' && isActive(item.url, pathname, activeType === 'nested-url'); return ( {props.children} ); } ================================================ FILE: apps/docs/src/components/layout/shared/index.tsx ================================================ import type { HTMLAttributes, ReactNode } from 'react'; import type { NavProviderProps } from 'fumadocs-ui/contexts/layout'; import type { I18nConfig } from 'fumadocs-core/i18n'; export interface NavOptions extends NavProviderProps { enabled: boolean; component: ReactNode; title?: ReactNode; /** * Redirect url of title * @defaultValue '/' */ url?: string; children?: ReactNode; } export interface BaseLayoutProps { themeSwitch?: { enabled?: boolean; component?: ReactNode; mode?: 'light-dark' | 'light-dark-system'; }; searchToggle?: Partial<{ enabled: boolean; components: Partial<{ sm: ReactNode; lg: ReactNode; }>; }>; /** * I18n options * * @defaultValue false */ i18n?: boolean | I18nConfig; /** * GitHub url */ githubUrl?: string; links?: LinkItemType[]; /** * Replace or disable navbar */ nav?: Partial; children?: ReactNode; } interface BaseItem { /** * Restrict where the item is displayed * * @defaultValue 'all' */ on?: 'menu' | 'nav' | 'all'; } export interface BaseLinkType extends BaseItem { url: string; /** * When the item is marked as active * * @defaultValue 'url' */ active?: 'url' | 'nested-url' | 'none'; external?: boolean; } export interface MainItemType extends BaseLinkType { type?: 'main'; icon?: ReactNode; text: ReactNode; description?: ReactNode; } export interface IconItemType extends BaseLinkType { type: 'icon'; /** * `aria-label` of icon button */ label?: string; icon: ReactNode; text: ReactNode; /** * @defaultValue true */ secondary?: boolean; } export interface ButtonItemType extends BaseLinkType { type: 'button'; icon?: ReactNode; text: ReactNode; /** * @defaultValue false */ secondary?: boolean; } export interface MenuItemType extends Partial { type: 'menu'; icon?: ReactNode; text: ReactNode; items: ( | (MainItemType & { /** * Options when displayed on navigation menu */ menu?: HTMLAttributes & { banner?: ReactNode; }; }) | CustomItemType )[]; /** * @defaultValue false */ secondary?: boolean; } export interface CustomItemType extends BaseItem { type: 'custom'; /** * @defaultValue false */ secondary?: boolean; children: ReactNode; } export type LinkItemType = | MainItemType | IconItemType | ButtonItemType | MenuItemType | CustomItemType; /** * Get Links Items with shortcuts */ export function getLinks( links: LinkItemType[] = [], githubUrl?: string, ): LinkItemType[] { let result = links ?? []; if (githubUrl) result = [ ...result, { type: 'icon', url: githubUrl, text: 'Github', label: 'GitHub', icon: ( ), external: true, }, ]; return result; } export { BaseLinkItem } from './client'; ================================================ FILE: apps/docs/src/components/logos/antigravity.tsx ================================================ import type { SVGProps } from "react"; function Antigravity(props: SVGProps) { return ( ); } export { Antigravity }; ================================================ FILE: apps/docs/src/components/logos/azure-devops.tsx ================================================ import type { SVGProps } from "react"; const AzureDevops = (props: SVGProps) => ( ); export { AzureDevops }; ================================================ FILE: apps/docs/src/components/logos/biome.tsx ================================================ import type { SVGProps } from "react"; const Biomejs = (props: SVGProps) => ( ); export { Biomejs }; ================================================ FILE: apps/docs/src/components/logos/bitbucket.tsx ================================================ import type { SVGProps } from "react"; const Bitbucket = (props: SVGProps) => ( ); export { Bitbucket }; ================================================ FILE: apps/docs/src/components/logos/claude.tsx ================================================ import type { SVGProps } from "react"; const Claude = (props: SVGProps) => ( ); export { Claude }; ================================================ FILE: apps/docs/src/components/logos/css.tsx ================================================ import type { SVGProps } from "react"; const CSSNew = (props: SVGProps) => ( ); export { CSSNew }; ================================================ FILE: apps/docs/src/components/logos/cursor.tsx ================================================ import { cn } from "@/lib/utils"; import type { SVGProps } from "react"; const Cursor = ({ className, ...props }: SVGProps) => ( ); export { Cursor }; ================================================ FILE: apps/docs/src/components/logos/github.tsx ================================================ import { cn } from "@/lib/utils"; import type { SVGProps } from "react"; const GitHub = (props: SVGProps) => ( ); export { GitHub }; ================================================ FILE: apps/docs/src/components/logos/gitlab.tsx ================================================ import type { SVGProps } from "react"; const GitLab = (props: SVGProps) => ( ); export { GitLab }; ================================================ FILE: apps/docs/src/components/logos/html.tsx ================================================ import type { SVGProps } from "react"; const HTML5 = (props: SVGProps) => ( ); export { HTML5 }; ================================================ FILE: apps/docs/src/components/logos/index.ts ================================================ import { GitHub } from "./github"; import { GitLab } from "./gitlab"; import { AzureDevops } from "./azure-devops"; import { Bitbucket } from "./bitbucket"; import { Jsrepo } from "./jsrepo-com"; import { JavaScript } from "./javascript"; import { Svelte } from "./svelte"; import { Vue } from "./vue"; import { CSSNew } from "./css"; import { HTML5 } from "./html"; import { Claude } from "./claude"; import { OpenAI } from "./openai"; import { VSCode } from "./vscode"; import { Cursor } from "./cursor"; import { Shadcn } from "./shadcn"; import { Biomejs } from "./biome"; import { Prettier } from "./prettier"; import { Oxfmt } from "./oxfmt"; import { NPM } from "./npm"; import { TypeScript } from "./typescript"; import { RegistryKit } from "./registry-kit"; import { Antigravity } from "./antigravity"; export { GitHub as GitHubLogo, GitLab as GitLabLogo, AzureDevops as AzureDevopsLogo, Bitbucket as BitbucketLogo, Jsrepo as JsrepoLogo, JavaScript as JavaScriptLogo, Svelte as SvelteLogo, Vue as VueLogo, CSSNew as CSSLogo, HTML5 as HTML5Logo, Claude as ClaudeLogo, OpenAI as OpenAILogo, VSCode as VSCodeLogo, Cursor as CursorLogo, Shadcn as ShadcnLogo, Biomejs as BiomeLogo, Prettier as PrettierLogo, Oxfmt as OxfmtLogo, NPM as NpmLogo, TypeScript as TypeScriptLogo, RegistryKit as RegistryKitLogo, Antigravity as AntigravityLogo, }; ================================================ FILE: apps/docs/src/components/logos/javascript.tsx ================================================ import type { SVGProps } from "react"; const JavaScript = (props: SVGProps) => ( ); export { JavaScript }; ================================================ FILE: apps/docs/src/components/logos/jsrepo-com.tsx ================================================ import type { SVGProps } from "react"; const Jsrepo = (props: SVGProps) => ( ); const JsrepoWordmark = (props: SVGProps) => ( ); export { Jsrepo, JsrepoWordmark }; ================================================ FILE: apps/docs/src/components/logos/npm.tsx ================================================ import type { SVGProps } from "react"; const NPM = (props: SVGProps) => ( ); export { NPM }; ================================================ FILE: apps/docs/src/components/logos/openai.tsx ================================================ import { cn } from "@/lib/utils"; import type { SVGProps } from "react"; const OpenAI = ({ className, ...props }: SVGProps) => ( ); export { OpenAI }; ================================================ FILE: apps/docs/src/components/logos/oxfmt.tsx ================================================ import type { SVGProps } from "react"; const Oxfmt = (props: SVGProps) => ( ); export { Oxfmt }; ================================================ FILE: apps/docs/src/components/logos/prettier.tsx ================================================ import type { SVGProps } from "react"; const Prettier = (props: SVGProps) => ( ); export { Prettier }; ================================================ FILE: apps/docs/src/components/logos/registry-kit.tsx ================================================ import { cn } from "@/lib/utils"; import type { SVGProps } from "react"; const RegistryKit = ({ className, ...props }: SVGProps) => ( ); export { RegistryKit }; ================================================ FILE: apps/docs/src/components/logos/shadcn.tsx ================================================ import type { SVGProps } from "react"; const Shadcn = (props: SVGProps) => ( ); export { Shadcn }; ================================================ FILE: apps/docs/src/components/logos/svelte.tsx ================================================ import type { SVGProps } from "react"; const Svelte = (props: SVGProps) => ( ); export { Svelte }; ================================================ FILE: apps/docs/src/components/logos/typescript.tsx ================================================ import type { SVGProps } from "react"; const TypeScript = (props: SVGProps) => ( ); export { TypeScript }; ================================================ FILE: apps/docs/src/components/logos/vscode.tsx ================================================ import type { SVGProps } from "react"; const VSCode = (props: SVGProps) => ( ); export { VSCode }; ================================================ FILE: apps/docs/src/components/logos/vue.tsx ================================================ import type { SVGProps } from "react"; const Vue = (props: SVGProps) => ( ); export { Vue }; ================================================ FILE: apps/docs/src/components/navigation-menu.tsx ================================================ 'use client'; import * as React from 'react'; import * as Primitive from '@radix-ui/react-navigation-menu'; import { cn } from '../lib/cn'; const NavigationMenu = Primitive.Root; const NavigationMenuList = Primitive.List; const NavigationMenuItem = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )); NavigationMenuItem.displayName = Primitive.NavigationMenuItem.displayName; const NavigationMenuTrigger = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )); NavigationMenuTrigger.displayName = Primitive.Trigger.displayName; const NavigationMenuContent = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); NavigationMenuContent.displayName = Primitive.Content.displayName; const NavigationMenuLink = Primitive.Link; const NavigationMenuViewport = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => (
)); NavigationMenuViewport.displayName = Primitive.Viewport.displayName; export { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuContent, NavigationMenuTrigger, NavigationMenuLink, NavigationMenuViewport, }; ================================================ FILE: apps/docs/src/components/page-actions.tsx ================================================ 'use client'; import { useMemo, useState } from 'react'; import { Check, ChevronDown, Copy, ExternalLinkIcon, Loader2, MessageCircleIcon, } from 'lucide-react'; import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'; import { buttonVariants } from './ui/button'; import { Popover, PopoverContent, PopoverTrigger, } from 'fumadocs-ui/components/ui/popover'; import { cva } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const cache = new Map(); export function LLMCopyButton({ /** * A URL to fetch the raw Markdown/MDX content of page */ markdownUrl, }: { markdownUrl: string; }) { const [isLoading, setLoading] = useState(false); const [checked, onClick] = useCopyButton(async () => { const cached = cache.get(markdownUrl); if (cached) return navigator.clipboard.writeText(cached); setLoading(true); try { await navigator.clipboard.write([ new ClipboardItem({ 'text/plain': fetch(markdownUrl).then(async (res) => { const content = await res.text(); cache.set(markdownUrl, content); return content; }), }), ]); } finally { setLoading(false); } }); return ( ); } const optionVariants = cva( 'text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4', ); export function ViewOptions({ markdownUrl, githubUrl, }: { /** * A URL to the raw Markdown/MDX content of page */ markdownUrl: string; /** * Source file URL on GitHub */ githubUrl: string; }) { const items = useMemo(() => { const fullMarkdownUrl = typeof window !== 'undefined' ? new URL(markdownUrl, window.location.origin) : 'loading'; const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`; return [ { title: 'Open in GitHub', href: githubUrl, icon: ( GitHub ), }, { title: 'Open in Scira AI', href: `https://scira.ai/?${new URLSearchParams({ q, })}`, icon: ( Scira AI ), }, { title: 'Open in ChatGPT', href: `https://chatgpt.com/?${new URLSearchParams({ hints: 'search', q, })}`, icon: ( OpenAI ), }, { title: 'Open in Claude', href: `https://claude.ai/new?${new URLSearchParams({ q, })}`, icon: ( Anthropic ), }, { title: 'Open in T3 Chat', href: `https://t3.chat/new?${new URLSearchParams({ q, })}`, icon: , }, { title: 'Open in Finalchat', href: `https://finalchat.app/chat?${new URLSearchParams({ q, })}`, icon: ( Finalchat ), } ]; }, [githubUrl, markdownUrl]); return ( Open {items.map((item) => ( {item.icon} {item.title} ))} ); } ================================================ FILE: apps/docs/src/components/registry-index.tsx ================================================ "use client"; import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; import { InputGroup, InputGroupAddon, InputGroupInput } from "./ui/input-group"; import { CheckIcon, CopyIcon, SearchIcon } from "lucide-react"; import { useState } from "react"; import fuzzysort from "fuzzysort"; import { Item, ItemActions, ItemContent } from "./ui/item"; import { Button } from "./ui"; import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; const RegistryIndexEntrySchema = z.object({ name: z.string(), homepage: z.string(), url: z.string(), description: z.string(), logo: z.string(), }); type RegistryIndexEntry = z.infer; export function RegistryDirectory() { const [search, setSearch] = useState(""); const query = useQuery({ queryKey: ["registry-index"], queryFn: async () => { try { const response = await fetch( "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/directory.json" ); if (!response.ok) { return []; } return (await response.json()) as RegistryIndexEntry[]; } catch { return []; } }, staleTime: 1000 * 60 * 60 * 6, // 6 hours refetchOnMount: false, refetchOnWindowFocus: false, }); const filteredItems = search.trim().length > 0 ? fuzzysort .go(search, query.data ?? [], { keys: ["name", "description"], }) .map((res) => res.obj) : query.data ?? []; return (
setSearch(e.target.value)} /> {search.trim().length > 0 && ( {filteredItems.length} {filteredItems.length === 1 ? "result" : "results"} )}
{filteredItems.map((registry) => ( ))}
); } function Registry({ registry }: { registry: RegistryIndexEntry }) { const [copyInit, initCopied] = useCopyToClipboard(1500); return (
{registry.logo && ( )}

{registry.name}

{registry.description}

); } ================================================ FILE: apps/docs/src/components/registry-kit/demo-example.tsx ================================================ import { Demo, DemoCode, DemoPreview } from "./demo"; export function DemoExample() { return (
Hello
Hello
); } ================================================ FILE: apps/docs/src/components/registry-kit/demo.tsx ================================================ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { ComponentProps, useState } from "react"; // a new comment export function Demo({ className, ...props }: ComponentProps): React.ReactElement { const [tab, setTab] = useState("preview"); return ( Preview Code {props.children} ); } export function DemoPreview({ className, ...props }: Omit, 'value'>): React.ReactElement { return ( {props.children} ); } export function DemoCode({ className, ...props }: Omit, 'value'>): React.ReactElement { return ( {props.children} ); } ================================================ FILE: apps/docs/src/components/search-toggle.tsx ================================================ 'use client'; import type { ComponentProps } from 'react'; import { Search } from 'lucide-react'; import { useSearchContext } from 'fumadocs-ui/contexts/search'; import { useI18n } from 'fumadocs-ui/contexts/i18n'; import { cn } from '../lib/cn'; import { type ButtonProps, buttonVariants } from './ui/button'; interface SearchToggleProps extends Omit, 'color'>, ButtonProps { hideIfDisabled?: boolean; } export function SearchToggle({ hideIfDisabled, size = 'icon-sm', variant = 'ghost', ...props }: SearchToggleProps) { const { setOpenSearch, enabled } = useSearchContext(); if (hideIfDisabled && !enabled) return null; return ( ); } export function LargeSearchToggle({ hideIfDisabled, ...props }: ComponentProps<'button'> & { hideIfDisabled?: boolean; }) { const { enabled, hotKey, setOpenSearch } = useSearchContext(); const { text } = useI18n(); if (hideIfDisabled && !enabled) return null; return ( ); } ================================================ FILE: apps/docs/src/components/sidebar.tsx ================================================ 'use client'; import { ChevronDown, ExternalLink } from 'lucide-react'; import { usePathname } from 'fumadocs-core/framework'; import { type ComponentProps, createContext, type FC, Fragment, type ReactNode, useContext, useMemo, useRef, useState, } from 'react'; import Link, { type LinkProps } from 'fumadocs-core/link'; import { useOnChange } from 'fumadocs-core/utils/use-on-change'; import { cn } from '../lib/cn'; import { ScrollArea, ScrollViewport } from './ui/scroll-area'; import { isActive } from '../lib/is-active'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from './ui/collapsible'; import { type ScrollAreaProps } from '@radix-ui/react-scroll-area'; import { useSidebar } from 'fumadocs-ui/contexts/sidebar'; import { cva } from 'class-variance-authority'; import type { CollapsibleContentProps, CollapsibleTriggerProps, } from '@radix-ui/react-collapsible'; import type * as PageTree from 'fumadocs-core/page-tree'; import { useTreeContext, useTreePath } from 'fumadocs-ui/contexts/tree'; import { useMediaQuery } from 'fumadocs-core/utils/use-media-query'; import { Presence } from '@radix-ui/react-presence'; export interface SidebarProps { /** * Open folders by default if their level is lower or equal to a specific level * (Starting from 1) * * @defaultValue 0 */ defaultOpenLevel?: number; /** * Prefetch links * * @defaultValue true */ prefetch?: boolean; /** * Children to render */ Content: ReactNode; /** * Alternative children for mobile */ Mobile?: ReactNode; } interface InternalContext { defaultOpenLevel: number; prefetch: boolean; level: number; } const itemVariants = cva( 'relative flex flex-row items-center gap-2 rounded-lg p-2 ps-(--sidebar-item-offset) text-start text-fd-muted-foreground [overflow-wrap:anywhere] [&_svg]:size-4 [&_svg]:shrink-0', { variants: { active: { true: 'bg-fd-primary/10 text-fd-primary', false: 'transition-colors hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none', }, }, }, ); const Context = createContext(null); const FolderContext = createContext<{ open: boolean; setOpen: React.Dispatch>; } | null>(null); export function Sidebar({ defaultOpenLevel = 0, prefetch = true, Mobile, Content, }: SidebarProps) { const isMobile = useMediaQuery('(width < 768px)') ?? false; const context = useMemo(() => { return { defaultOpenLevel, prefetch, level: 1, }; }, [defaultOpenLevel, prefetch]); return ( {isMobile && Mobile != null ? Mobile : Content} ); } export function SidebarContent(props: ComponentProps<'aside'>) { const { collapsed } = useSidebar(); const [hover, setHover] = useState(false); const timerRef = useRef(0); const closeTimeRef = useRef(0); useOnChange(collapsed, () => { setHover(false); closeTimeRef.current = Date.now() + 150; }); return ( ); } export function SidebarContentMobile({ className, children, ...props }: ComponentProps<'aside'>) { const { open, setOpen } = useSidebar(); const state = open ? 'open' : 'closed'; return ( <>
setOpen(false)} /> {({ present }) => ( )} ); } export function SidebarHeader(props: ComponentProps<'div'>) { return (
{props.children}
); } export function SidebarFooter(props: ComponentProps<'div'>) { return (
{props.children}
); } export function SidebarViewport(props: ScrollAreaProps) { return ( {props.children} ); } export function SidebarSeparator(props: ComponentProps<'p'>) { return (

{props.children}

); } export function SidebarItem({ icon, ...props }: LinkProps & { icon?: ReactNode; }) { const pathname = usePathname(); const active = props.href !== undefined && isActive(props.href, pathname, false); const { prefetch } = useInternalContext(); return ( {icon ?? (props.external ? : null)} {props.children} ); } export function SidebarFolder({ defaultOpen = false, ...props }: ComponentProps<'div'> & { defaultOpen?: boolean; }) { const [open, setOpen] = useState(defaultOpen); useOnChange(defaultOpen, (v) => { if (v) setOpen(v); }); return ( ({ open, setOpen }), [open])} > {props.children} ); } export function SidebarFolderTrigger({ className, ...props }: CollapsibleTriggerProps) { const { open } = useFolderContext(); return ( {props.children} ); } export function SidebarFolderLink(props: LinkProps) { const { open, setOpen } = useFolderContext(); const { prefetch } = useInternalContext(); const pathname = usePathname(); const active = props.href !== undefined && isActive(props.href, pathname, false); return ( { if ( e.target instanceof Element && e.target.matches('[data-icon], [data-icon] *') ) { setOpen(!open); e.preventDefault(); } else { setOpen(active ? !open : true); } }} prefetch={prefetch} > {props.children} ); } export function SidebarFolderContent(props: CollapsibleContentProps) { const { level, ...ctx } = useInternalContext(); return ( ({ ...ctx, level: level + 1, }), [ctx, level], )} > {props.children} ); } export function SidebarTrigger({ children, ...props }: ComponentProps<'button'>) { const { setOpen } = useSidebar(); return ( ); } export function SidebarCollapseTrigger(props: ComponentProps<'button'>) { const { collapsed, setCollapsed } = useSidebar(); return ( ); } function useFolderContext() { const ctx = useContext(FolderContext); if (!ctx) throw new Error('Missing sidebar folder'); return ctx; } function useInternalContext() { const ctx = useContext(Context); if (!ctx) throw new Error(' component required.'); return ctx; } export interface SidebarComponents { Item: FC<{ item: PageTree.Item }>; Folder: FC<{ item: PageTree.Folder; level: number; children: ReactNode }>; Separator: FC<{ item: PageTree.Separator }>; } /** * Render sidebar items from page tree */ export function SidebarPageTree(props: { components?: Partial; }) { const { root } = useTreeContext(); return useMemo(() => { const { Separator, Item, Folder } = props.components ?? {}; function renderSidebarList( items: PageTree.Node[], level: number, ): ReactNode[] { return items.map((item, i) => { if (item.type === 'separator') { if (Separator) return ; return ( {item.icon} {item.name} ); } if (item.type === 'folder') { const children = renderSidebarList(item.children, level + 1); if (Folder) return ( {children} ); return ( {children} ); } if (Item) return ; return ( {item.name} ); }); } return ( {renderSidebarList(root.children, 1)} ); }, [props.components, root]); } function PageTreeFolder({ item, ...props }: { item: PageTree.Folder; children: ReactNode; }) { const { defaultOpenLevel, level } = useInternalContext(); const path = useTreePath(); return ( = level) || path.includes(item) } > {item.index ? ( {item.icon} {item.name} ) : ( {item.icon} {item.name} )} {props.children} ); } ================================================ FILE: apps/docs/src/components/theme-toggle.tsx ================================================ 'use client'; import { cva } from 'class-variance-authority'; import { Moon, Sun, Airplay } from 'lucide-react'; import { useTheme } from 'next-themes'; import { type HTMLAttributes, useLayoutEffect, useState } from 'react'; import { cn } from '../lib/cn'; const itemVariants = cva( 'size-6.5 rounded-full p-1.5 text-fd-muted-foreground', { variants: { active: { true: 'bg-fd-accent text-fd-accent-foreground', false: 'text-fd-muted-foreground', }, }, }, ); const full = [ ['light', Sun] as const, ['dark', Moon] as const, ['system', Airplay] as const, ]; export function ThemeToggle({ className, mode = 'light-dark', ...props }: HTMLAttributes & { mode?: 'light-dark' | 'light-dark-system'; }) { const { setTheme, theme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); useLayoutEffect(() => { setMounted(true); }, []); const container = cn( 'inline-flex items-center rounded-full border p-1', className, ); if (mode === 'light-dark') { const value = mounted ? resolvedTheme : null; return ( ); } const value = mounted ? theme : null; return (
{full.map(([key, Icon]) => ( ))}
); } ================================================ FILE: apps/docs/src/components/ui/accordion.tsx ================================================ "use client" import * as React from "react" import * as AccordionPrimitive from "@radix-ui/react-accordion" import { ChevronDownIcon } from "lucide-react" import { cn } from "@/lib/utils" function Accordion({ className, ...props }: React.ComponentProps) { return } function AccordionItem({ className, ...props }: React.ComponentProps) { return ( ) } function AccordionTrigger({ className, children, ...props }: React.ComponentProps) { return ( svg]:rotate-180", className )} {...props} > {children} ) } function AccordionContent({ className, children, ...props }: React.ComponentProps) { return (
{children}
) } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } ================================================ FILE: apps/docs/src/components/ui/animated-beam.tsx ================================================ "use client" import { RefObject, useEffect, useId, useState } from "react" import { motion } from "motion/react" import { cn } from "@/lib/utils" export interface AnimatedBeamProps { className?: string containerRef: RefObject // Container ref fromRef: RefObject toRef: RefObject curvature?: number reverse?: boolean pathColor?: string pathWidth?: number pathOpacity?: number gradientStartColor?: string gradientStopColor?: string delay?: number duration?: number startXOffset?: number startYOffset?: number endXOffset?: number endYOffset?: number } export const AnimatedBeam: React.FC = ({ className, containerRef, fromRef, toRef, curvature = 0, reverse = false, // Include the reverse prop duration = Math.random() * 3 + 4, delay = 0, pathColor = "gray", pathWidth = 2, pathOpacity = 0.2, gradientStartColor = "#ffaa40", gradientStopColor = "#9c40ff", startXOffset = 0, startYOffset = 0, endXOffset = 0, endYOffset = 0, }) => { const id = useId() const [pathD, setPathD] = useState("") const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 }) // Calculate the gradient coordinates based on the reverse prop const gradientCoordinates = reverse ? { x1: ["90%", "-10%"], x2: ["100%", "0%"], y1: ["0%", "0%"], y2: ["0%", "0%"], } : { x1: ["10%", "110%"], x2: ["0%", "100%"], y1: ["0%", "0%"], y2: ["0%", "0%"], } useEffect(() => { const updatePath = () => { if (containerRef.current && fromRef.current && toRef.current) { const containerRect = containerRef.current.getBoundingClientRect() const rectA = fromRef.current.getBoundingClientRect() const rectB = toRef.current.getBoundingClientRect() const svgWidth = containerRect.width const svgHeight = containerRect.height setSvgDimensions({ width: svgWidth, height: svgHeight }) const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset const controlY = startY - curvature const d = `M ${startX},${startY} Q ${ (startX + endX) / 2 },${controlY} ${endX},${endY}` setPathD(d) } } // Initialize ResizeObserver const resizeObserver = new ResizeObserver(() => { updatePath() }) // Observe the container element if (containerRef.current) { resizeObserver.observe(containerRef.current) } // Call the updatePath initially to set the initial path updatePath() // Clean up the observer on component unmount return () => { resizeObserver.disconnect() } }, [ containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset, ]) return ( ) } ================================================ FILE: apps/docs/src/components/ui/badge.tsx ================================================ "use client"; import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { BadgeCheckIcon, CodeIcon, Download, SettingsIcon } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"; import { cn } from "@/lib/utils"; import { useQuery } from "@tanstack/react-query"; const badgeVariants = cva( "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", }, }, defaultVariants: { variant: "default", }, } ); function Badge({ className, variant, asChild = false, ...props }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { const Comp = asChild ? Slot : "span"; return ; } function SourceBadge({ path, className, ...props }: Omit, "variant"> & { /** * The path to the source file. Relative to the root of the project. */ path: string; }) { return ( Source ); } function OfficialBadge({ className, ...props }: Omit, "variant">) { return ( Official

Officially supported by the jsrepo team.

); } function DefaultBadge({ className, ...props }: Omit, "variant">) { return ( Default

Works with zero config.

); } type DownloadsResponse = { downloads: number; start: string; end: string; package: string; }; function NpmBadge({ packageName, className, ...props }: Omit, "variant"> & { packageName: string }) { const query = useQuery({ queryKey: ["npm", packageName], queryFn: async () => { try { const response = await fetch(`https://api.npmjs.org/downloads/point/last-month/${packageName}`); if (!response.ok) { return 0; } const data = (await response.json()) as DownloadsResponse; return data.downloads; } catch { return 0; } }, staleTime: 1000 * 60 * 60 * 24, // 24 hours refetchOnMount: false, refetchOnWindowFocus: false, }); return ( {query.data ?? 0}/month

Available on npm.

); } function BadgeGroup({ className, ...props }: React.ComponentProps<"div">) { return (
); } export { Badge, badgeVariants, SourceBadge, OfficialBadge, DefaultBadge, BadgeGroup, NpmBadge }; ================================================ FILE: apps/docs/src/components/ui/button.tsx ================================================ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", "icon-sm": "size-8", "icon-lg": "size-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export type ButtonProps = React.ComponentProps<"button"> & VariantProps & { asChild?: boolean; }; function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { const Comp = asChild ? Slot : "button"; return ; } export { Button, buttonVariants }; ================================================ FILE: apps/docs/src/components/ui/collapsible.tsx ================================================ 'use client'; import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; import { forwardRef, useEffect, useState } from 'react'; import { cn } from '@/lib/utils'; const Collapsible = CollapsiblePrimitive.Root; const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; const CollapsibleContent = forwardRef< HTMLDivElement, React.ComponentPropsWithoutRef >(({ children, ...props }, ref) => { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); return ( {children} ); }); CollapsibleContent.displayName = CollapsiblePrimitive.CollapsibleContent.displayName; export { Collapsible, CollapsibleTrigger, CollapsibleContent }; ================================================ FILE: apps/docs/src/components/ui/field.tsx ================================================ "use client" import { useMemo } from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { return (
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", className )} {...props} /> ) } function FieldLegend({ className, variant = "legend", ...props }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { return ( ) } function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { return (
[data-slot=field-group]]:gap-4", className )} {...props} /> ) } const fieldVariants = cva( "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", { variants: { orientation: { vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], horizontal: [ "flex-row items-center", "[&>[data-slot=field-label]]:flex-auto", "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", ], responsive: [ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", "@md/field-group:[&>[data-slot=field-label]]:flex-auto", "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", ], }, }, defaultVariants: { orientation: "vertical", }, } ) function Field({ className, orientation = "vertical", ...props }: React.ComponentProps<"div"> & VariantProps) { return (
) } function FieldContent({ className, ...props }: React.ComponentProps<"div">) { return (
) } function FieldLabel({ className, ...props }: React.ComponentProps) { return (