npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

phage-cms

v0.1.10

Published

Astro integration: Phage CMS (Cloudflare KV/R2, block editor, API routes)

Readme

phage-cms

Astro integration for Phage: block CMS editor, Cloudflare KV + R2 APIs, /phage admin UI, and published-content helpers.

  • Runtime: Workers (SSR) via @astrojs/cloudflare
  • UI: React islands (client:only="react" editor)
  • Host app: You provide src/blocks, src/collections, src/site-options, src/taxonomies (minimal stubs are enough to boot).

Install from npm: npm install phage-cms


Requirements

| Requirement | Notes | |-------------|--------| | Node.js | >= 22.12 | | Astro | v6.1+ recommended, output: 'server' | | @astrojs/cloudflare | With KV + R2 bindings in wrangler.toml | | @astrojs/react + react + react-dom | v19 | | vite + wrangler | v6 / v4 (peer ranges in package.json) |


Quick start (blank Astro + Phage)

1) Create the project and add adapters

npm create astro@latest my-phage-site
cd my-phage-site

Choose TypeScript, and a template that is not “static only” (or start empty and configure below).

Add Cloudflare + React:

npx astro add cloudflare react

2) Install Phage and peers

npm install phage-cms

If npm does not already install peers, add explicitly:

npm install @astrojs/cloudflare @astrojs/react astro react react-dom vite wrangler

3) Root wrangler.toml

Bindings must be named exactly KV and R2_BUCKET. Create wrangler.toml at the project root, for example:

name = "my-phage-site"
compatibility_date = "2024-05-01"

[[kv_namespaces]]
binding = "KV"
id = "<your-kv-namespace-id>"

[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "<your-r2-bucket-name>"
remote = true

Create namespaces with wrangler kv namespace create / wrangler r2 bucket create, then paste IDs / names.

4) astro.config.mjs

Use server output, Cloudflare adapter, and phageCms(). Remote R2 in dev, TipTap / use-sync-external-store shims, picomatch prebundle hints, and server.fs.allow for linked installs are applied inside phageCms()—you should not need extra vite boilerplate in the host.

// @ts-check
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import cloudflare from '@astrojs/cloudflare';
import phageCms, {
  phageCloudflareRemoteBindingsForAstro,
  viteReactPluginExcludeAllowPhageCms
} from 'phage-cms';

export default defineConfig({
  output: 'server',
  adapter: cloudflare({ remoteBindings: phageCloudflareRemoteBindingsForAstro(import.meta) }),
  integrations: [react({ exclude: viteReactPluginExcludeAllowPhageCms }), phageCms()],
  devToolbar: { enabled: false }
});

viteReactPluginExcludeAllowPhageCms keeps the React plugin from skipping node_modules/phage-cms. If you add custom vite.resolve.alias, merge additively (do not replace the whole alias array or you can wipe Phage’s entries).

Advanced: use phageCloudflareRemoteBindings(projectRoot) if you already have an absolute project root string instead of import.meta.

Leave PHAGE_REMOTE_R2 unset for local Miniflare R2 during astro dev.

5) team.json (project root)

Editable allowlist for auth (not inside node_modules):

{
  "members": [{ "email": "[email protected]", "role": "admin" }],
  "linkExpiryHours": 24,
  "sessionDays": 30
}

6) .env.local (project root, do not commit)

Minimum for local editor:

PHAGE_ENV=local
PHAGE_JWT_SECRET=<long-random-string>

Add PHAGE_REMOTE_R2=1 only if you intentionally want dev to hit real R2.

7) Minimal src/ layout Phage expects

Under src/ (or set phageCms({ srcDir: 'app' }) if your tree lives elsewhere):

| Path | Purpose | |------|---------| | src/blocks/index.ts | Re-export everything in PhageHostBlocksIndex (see phage-cms/host-blocks): builtinBlocks, blockPresets, phageBlockChromeSiteDefaults, registeredBlocks, blockRenderEntries, plus create page helpers below | | src/blocks/config.ts | registeredBlocks (each with a preset), derived builtinBlocks / blockPresets / blockRenderEntries, and the Pages → Create page triplet |

Presets and new-page starters — Default field values for the editor and the “Create page” dialog are not defined inside phage-cms. Put them on each registeredBlocks entry as preset, expose a blockPresets map (type Record<string, Record<string, unknown>>), and wire the Pages list modal:

| Export | Purpose | |--------|---------| | pageCreateStartingOptions | { value, label }[] for the create-page segmented control | | buildInitialPageBlocks(start) | Returns the initial blocks array for the new-page POST (usually { type, _id, ...clonedPreset }[]; 'blank'[]) | | PageCreateStartId (type export) | String union of the values above; import it from @blocks/index in your app only if you want the same union in host code |

Import PhageHostBlocksIndex from phage-cms/host-blocks if you want a structural check that your config module exports everything Phage expects. | src/collections/index.ts | Wire registeredCollections with createCollectionBindings from phage-cms/collections/content | | src/collections/config.ts | registeredCollections array (can be [] for an empty CMS) | | src/site-options/index.ts | registeredOptionPages, cloneOptionDefaults, getOptionsPageLabel, getOptionsPageDef | | src/taxonomies/index.ts | registeredVocabularies, isRegisteredVocabularyId, re-export taxonomy types from phage-cms/taxonomies as needed |

The integration registers Vite aliases (@blocks, @collections, …) so imports from inside node_modules/phage-cms resolve to your src/.

8) tsconfig.json paths (recommended)

So astro check / the editor agree with Vite, extend compilerOptions:

{
  "extends": "astro/tsconfigs/strict",
  "include": [".astro/types.d.ts", "**/*", "node_modules/phage-cms/types/user-team.d.ts"],
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "baseUrl": ".",
    "paths": {
      "@blocks": ["src/blocks/index.ts"],
      "@blocks/*": ["src/blocks/*"],
      "@collections": ["src/collections/index.ts"],
      "@collections/*": ["src/collections/*"],
      "@site-options": ["src/site-options/index.ts"],
      "@site-options/*": ["src/site-options/*"],
      "@taxonomies": ["src/taxonomies/index.ts"],
      "@taxonomies/*": ["src/taxonomies/*"],
      "@templates/*": ["src/collections/*"],
      "@phage/schema": ["node_modules/phage-cms/schema.ts"],
      "@phage/blockChrome": ["node_modules/phage-cms/blockChrome.ts"],
      "@phage/linkField": ["node_modules/phage-cms/linkField.ts"],
      "@phage/mediaUrl": ["node_modules/phage-cms/mediaUrl.ts"],
      "@phage/pagePath": ["node_modules/phage-cms/pagePath.ts"],
      "@phage/content": ["node_modules/phage-cms/collections/content.ts"],
      "@phage/taxonomies": ["node_modules/phage-cms/taxonomies/index.ts"],
      "@phage/forms": ["node_modules/phage-cms/forms/index.ts"],
      "@phage/user-team": ["./team.json"],
      "@phage/utils/markdown": ["node_modules/phage-cms/utils/markdown.ts"],
      "@phage/utils/richTextLinks": ["node_modules/phage-cms/utils/richTextLinks.ts"],
      "@phage/*": ["node_modules/phage-cms/*"]
    }
  }
}

If you use pnpm, dependency paths can differ; point node_modules/phage-cms/... at the real install path or use workspace: / file: while developing the package.

9) Run

npm run dev

Open /phage. With PHAGE_ENV=local, the middleware uses the local auth bypass.


Integration options

phageCms({
  /** Folder under project root containing blocks, collections, site-options, taxonomies. Default `"src"`. */
  srcDir: 'src',
  /** Allowlist JSON path relative to project root. Default `team.json`. */
  teamJsonFile: 'team.json'
})

Publishing (maintainers)

From the monorepo root:

npm publish -w phage-cms --access public

package.json "files" controls the tarball; do not commit stray *.tgz inside phage-cms/.


Support

For a full reference site (pages, blocks, collections, options), use this repo’s src/ tree as a template alongside this README.