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

@duffcloudservices/cms

v0.12.0

Published

Vue 3 composables and Vite plugins for DCS CMS integration

Readme

@duffcloudservices/cms

Vue 3 composables and Vite plugins for DCS (Duff Cloud Services) CMS integration.

Installation

# Using pnpm
pnpm add @duffcloudservices/cms

# Using npm
npm install @duffcloudservices/cms

# Using yarn
yarn add @duffcloudservices/cms

Peer Dependencies

This package requires:

  • vue ^3.4.0
  • @unhead/vue ^1.9.0

Quick Start

1. Configure Vite Plugins

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { dcsContentPlugin, dcsSeoPlugin } from '@duffcloudservices/cms/plugins'

export default defineConfig({
  plugins: [
    vue(),
    dcsContentPlugin(),
    dcsSeoPlugin()
  ]
})

For VitePress:

// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { dcsContentPlugin, dcsSeoPlugin } from '@duffcloudservices/cms/plugins'

export default defineConfig({
  vite: {
    plugins: [
      dcsContentPlugin(),
      dcsSeoPlugin()
    ]
  }
})

2. Set Environment Variables

# .env
# For runtime overrides (premium tier): the API base URL only.
VITE_API_BASE_URL=https://portal.duffcloudservices.com
VITE_TEXT_OVERRIDE_MODE=commit  # 'commit' (default) or 'runtime'

# Deprecated: the site is now resolved server-side from the request Host
# (or the dedicated Container App's DCS_SITE_SLUG). VITE_SITE_SLUG is no
# longer placed in request URLs; it is optional and used only as a local
# cache-key hint. Safe to omit.
# VITE_SITE_SLUG=your-site-slug

3. Use Composables

<script setup lang="ts">
import { useTextContent, useSEO } from '@duffcloudservices/cms'

// Text content with defaults
const { t } = useTextContent({
  pageSlug: 'home',
  defaults: {
    'hero.title': 'Welcome to Our Site',
    'hero.subtitle': 'Build amazing things with us',
    'cta.primary': 'Get Started'
  }
})

// SEO configuration
const { applyHead } = useSEO('home')
applyHead()
</script>

<template>
  <section class="hero">
    <h1>{{ t('hero.title') }}</h1>
    <p>{{ t('hero.subtitle') }}</p>
    <button>{{ t('cta.primary') }}</button>
  </section>
</template>

Composables

useTextContent

Provides text content management with build-time injection and optional runtime overrides.

import { useTextContent } from '@duffcloudservices/cms'

const {
  t,                    // (key: string, fallback?: string) => string
  texts,                // ComputedRef<Record<string, string>>
  overrides,            // Ref<Record<string, string>>
  isLoading,            // Ref<boolean>
  error,                // Ref<string | null>
  refresh,              // () => Promise<void>
  hasOverride,          // (key: string) => boolean
  hasBuildTimeContent,  // boolean
  mode                  // 'commit' | 'runtime'
} = useTextContent({
  pageSlug: 'home',
  defaults: {
    'hero.title': 'Default Title'
  },
  fetchOnMount: true,  // default: true (only matters in runtime mode)
  cacheTtl: 60000      // default: 60000ms
})

Content Resolution Order:

  1. Runtime API overrides (premium tier only)
  2. Build-time content from .dcs/content.yaml
  3. Hardcoded defaults passed to the composable

useSEO

Provides SEO configuration with meta tags, Open Graph, Twitter Cards, and JSON-LD.

import { useSEO } from '@duffcloudservices/cms'

const {
  config,         // ComputedRef<ResolvedPageSeo>
  applyHead,      // (overrides?: HeadOverrides) => void
  getSchema,      // () => object[]
  getCanonical,   // () => string
  hasBuildTimeSeo // boolean
} = useSEO('home', '/') // pageSlug, optional pagePath

// Apply all meta tags
applyHead()

// Or with overrides
applyHead({
  title: 'Custom Title',
  description: 'Custom description',
  schemas: [...getSchema(), customSchema]
})

useReleaseNotes

Fetches release notes from the DCS Portal API.

import { useReleaseNotes } from '@duffcloudservices/cms'

const {
  releaseNote,  // Ref<ReleaseNote | null>
  isLoading,    // Ref<boolean>
  error,        // Ref<string | null>
  refresh       // () => Promise<void>
} = useReleaseNotes('1.2.0')  // or 'latest'

useSiteVersion

Gets the current site version for footer badges.

import { useSiteVersion } from '@duffcloudservices/cms'

const {
  version,          // Ref<string | null>
  isLoading,        // Ref<boolean>
  releaseNotesUrl   // ComputedRef<string>
} = useSiteVersion()

Vite Plugins

dcsContentPlugin

Injects .dcs/content.yaml at build time.

import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'

dcsContentPlugin({
  contentPath: '.dcs/content.yaml',  // default
  debug: false                        // default
})

dcsSeoPlugin

Injects .dcs/seo.yaml at build time and, opt-in, emits per-route static SEO.

import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'

dcsSeoPlugin({
  seoPath: '.dcs/seo.yaml',  // default
  debug: false,               // default

  // Vue-SPA per-route static SEO (VitePress sites leave this off):
  emitStaticHtml: true,           // emit dist/<route>/index.html with baked
                                  // <head> meta + JSON-LD, plus sitemap/robots/llms
  noindex: ['account', 'projects'], // auth-gated routes → robots noindex,nofollow
})

Body prerender (crawler-visible body content)

When emitStaticHtml is on, the plugin also prerenders each indexable route's body so non-JS AI crawlers see the real page prose — not just <div id="app"></div>. After the per-route <head> is emitted, it drives the just-built SPA in headless Chromium (via the site's playwright), captures each route's rendered #app DOM, and splices it into the mount container. On the client, app.mount('#app') replaces that DOM (no hydration), so users are unaffected while crawlers get the body.

  • Default ON whenever emitStaticHtml is on — a cms bump enables it with no per-site edit. playwright is already a fleet devDependency; if it is absent the pass is a graceful no-op (head + JSON-LD still emitted).
  • noindex / auth-gated routes are never body-prerendered (they get a head-only file).
  • A route that crashes at render time fails the build loud rather than shipping a broken/empty body. Because it renders the PRODUCTION bundle, dev-only fallback defaults (e.g. sample reviews) stay off — no fabricated content is baked.
  • Per-site escape hatch (no code change, no cms republish): set prerenderBody: false at the root of .dcs/seo.yaml to disable body prerender for one site, or pass dcsSeoPlugin({ prerenderBody: false }). Preview builds skip it automatically.

Configuration Files

.dcs/content.yaml

version: 1
lastUpdated: "2025-01-01T00:00:00Z"
updatedBy: "portal"

global:
  nav.home: Home
  nav.about: About
  footer.copyright: © 2025 My Company

pages:
  home:
    hero.title: Welcome to Our Site
    hero.subtitle: Build amazing things
    cta.primary: Get Started
  about:
    hero.title: About Us
    hero.subtitle: Learn more about our mission

.dcs/seo.yaml

version: 1
lastUpdated: "2025-01-01T00:00:00Z"

global:
  siteName: My Site
  siteUrl: https://example.com
  locale: en_US
  defaultTitle: My Site
  defaultDescription: Build amazing things with us
  titleTemplate: "%s | My Site"
  
  social:
    twitter: mycompany
    linkedin: my-company
  
  images:
    logo: https://example.com/logo.png
    ogDefault: https://example.com/og-image.jpg

pages:
  home:
    title: Welcome
    description: Build amazing things with our platform
    noTitleTemplate: true
    openGraph:
      type: website
    twitter:
      card: summary_large_image
  
  about:
    title: About Us
    description: Learn more about our company and mission

TypeScript Support

All composables and plugins are fully typed. Import types as needed:

import type {
  TextContentConfig,
  TextContentReturn,
  SeoConfiguration,
  PageSeoConfig,
  ReleaseNote
} from '@duffcloudservices/cms'

Migration from Manual Setup

If you're migrating from manually copied composables:

// Before:
import { useTextContent } from '@/lib/use-text-content'

// After:
import { useTextContent } from '@duffcloudservices/cms'

The API is the same, so no other code changes are needed.

DCS Visual Editor Bridge

For DCS-managed sites, src/editor/editorBridge.ts is the shared discovery/runtime layer that turns site DOM markers into portal editing entry points.

Managed background images

For customer-site hero, CTA, footer, card, staff, and gallery images that need editor image replacement, prefer the exported ManagedImage component over CSS-only background-image or hardcoded image arrays.

<script setup lang="ts">
import ManagedImage from '@duffcloudservices/cms/managed-image'
</script>

<template>
  <ManagedImage
    page-slug="home"
    image-key="hero.image.url"
    alt-key="hero.image.alt"
    fallback-src="/images/hero.jpg"
    fallback-alt="Clinic hero"
    context="hero"
    class="h-full w-full object-cover"
  />
</template>

Add the URL and alt keys to .dcs/content.yaml using CDN URLs for committed content:

pages:
  home:
    hero.image.url: https://files.duffcloudservices.com/content/site-slug/assets/example.jpg
    hero.image.alt: Clinic hero

ManagedImage resolves build-time/runtime content with useTextContent, renders a real <picture>/<img> target, applies responsive CDN variants through useResponsiveImage, and emits data-dcs-image-key, data-dcs-image-url, and data-dcs-image-alt on the actual <img>. This lets the visual editor open image management for the asset and lets snapshot capture wait on an actual image before full-page.png.

For repeated galleries or cards, use indexed keys such as gallery.item-0.image.url and gallery.item-0.image.alt, then pass those keys through the rendered item. Do not make editable images CSS-only backgrounds; if a design needs background behavior, render a real managed image layer behind the content.

Click-to-call (NAP phone)

DcsCallButton renders a tel: call affordance driven by the portal-managed business.phone NAP key (the same global content key used for LocalBusiness identity). Use it for the above-the-fold mobile call path service sites need.

<script setup lang="ts">
import DcsCallButton from '@duffcloudservices/cms/call-button'
</script>

<template>
  <!-- compact, above-the-fold mobile header affordance -->
  <DcsCallButton variant="icon" />

  <!-- icon + label + number for a nav/menu row or hero -->
  <DcsCallButton variant="inline" />
</template>

business.phone lives in .dcs/content.yaml under global (create-dcs-site scaffolds it empty):

global:
  business.phone: "(248) 385-2926"
  business.name: Iron Oak Contractors

The number is read through useTextContent, so it is SSR-safe (no window/document at setup) and portal-editable. The component renders nothing until business.phone is set — it never fabricates a placeholder number into prod SSR. The tel: href strips formatting to digits (preserving a leading + for E.164), the visible number carries data-dcs-text="business.phone" for inline editing, and styling is themeable via --dcs-call-* custom properties (icon inherits currentColor and stays unobtrusive on desktop).

Current first-party surfaces:

  • Managed forms — discovers [data-form-key], reports hasManagedForms/managedFormIds, and emits dcs:managed-form-click
  • Reviews — discovers [data-dcs-reviews], reports hasReviews/reviewKeys, emits dcs:reviews-click, and accepts structured dcs:update-reviews data for live preview refresh

The bridge owns discovery and affordances; the portal owns the actual editing workflow. Keep every entry point converged on one sheet per component family instead of creating parallel editors.

See ../FIRST-PARTY-COMPONENTS.md for the shared contract across runtime markers, bridge events, portal workflows, publishing, rollout, and validation.

License

MIT