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

@bernierllc/content-management-suite

v0.4.2

Published

Comprehensive content management suite with editorial workflows, content types, and admin UI

Readme

/* Copyright (c) 2025 Bernier LLC

This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */

Content Management Suite

A comprehensive content management suite with editorial workflows, content types, and admin UI built with TypeScript and Tamagui.

Features

  • 🎯 Editorial Workflows: Configurable content workflows with stages, transitions, and permissions
  • 📝 Content Types: Pluggable content type system supporting text, images, audio, and video
  • ⚡ Auto-save: Intelligent auto-save with backoff retry and debounce
  • 🗑️ Soft Delete: Configurable soft delete with user visibility options
  • 🎨 Modern UI: Tamagui-based components with responsive design
  • 🔧 Configuration: File + database override + environment variable configuration
  • 🔌 Plugin System: Extensible plugin architecture for custom functionality
  • 🛡️ Security: JWT authentication, role-based permissions, and rate limiting
  • 📊 Analytics: Built-in analytics and metrics collection
  • 🌐 API: RESTful API with comprehensive endpoints
  • 📱 Admin UI: NeverAdmin integration for workflow configuration
  • 🔍 Search: Full-text search across all content types
  • 📈 Performance: Caching, compression, and optimization

Installation

npm install @bernierllc/content-management-suite

Quick Start

import { createContentManagementSuite } from '@bernierllc/content-management-suite';

const suite = createContentManagementSuite({
  config: {
    server: {
      port: 3000,
      host: 'localhost'
    }
  }
});

await suite.start();
console.log('Content Management Suite started on http://localhost:3000');

Configuration

The suite supports comprehensive configuration through multiple sources:

1. File Configuration

Create a content-management.config.json file:

{
  "server": {
    "port": 3000,
    "host": "localhost",
    "cors": {
      "origin": "*",
      "credentials": true
    }
  },
  "database": {
    "type": "sqlite",
    "name": "content_management"
  },
  "content": {
    "defaultWorkflow": {
      "id": "standard",
      "name": "Standard Workflow",
      "stages": [
        {
          "id": "write",
          "name": "Write",
          "order": 1,
          "isPublishStage": false,
          "allowsScheduling": false,
          "permissions": ["content.edit"]
        },
        {
          "id": "publish",
          "name": "Publish",
          "order": 2,
          "isPublishStage": true,
          "allowsScheduling": true,
          "permissions": ["content.publish"]
        }
      ]
    }
  }
}

2. Environment Variables

# Server
CONTENT_MANAGEMENT_PORT=3000
CONTENT_MANAGEMENT_HOST=localhost

# Database
CONTENT_MANAGEMENT_DATABASE_TYPE=postgresql
CONTENT_MANAGEMENT_DATABASE_URL=postgresql://user:password@localhost:5432/content_management

# Security
CONTENT_MANAGEMENT_JWT_SECRET=your-secret-key
CONTENT_MANAGEMENT_JWT_EXPIRES_IN=24h

# Integrations
CONTENT_MANAGEMENT_NEVERADMIN_ENABLED=true
CONTENT_MANAGEMENT_NEVERADMIN_URL=https://admin.myapp.com
CONTENT_MANAGEMENT_NEVERADMIN_API_KEY=your-api-key

3. Database Override

Configuration can be overridden through the database for runtime changes:

await suite.updateConfig({
  ui: {
    theme: {
      mode: 'dark'
    }
  }
});

Content Types

The suite comes with four built-in content types:

Text Content

import { TextContentType } from '@bernierllc/content-management-suite';

// Register text content type
suite.registerContentType(TextContentType);

// Create text content
const textContent = {
  title: 'My Blog Post',
  slug: 'my-blog-post',
  body: '<p>Hello world!</p>',
  excerpt: 'A brief description',
  tags: ['blog', 'example'],
  categories: ['general'],
  author: {
    id: 'user-1',
    name: 'John Doe',
    email: '[email protected]'
  }
};

Image Content

import { ImageContentType } from '@bernierllc/content-management-suite';

// Register image content type
suite.registerContentType(ImageContentType);

// Create image content
const imageContent = {
  title: 'Beautiful Landscape',
  slug: 'beautiful-landscape',
  description: 'A stunning mountain landscape',
  altText: 'Mountain landscape with sunset',
  imageUrl: 'https://example.com/images/landscape.jpg',
  tags: ['landscape', 'nature'],
  categories: ['photography'],
  author: {
    id: 'user-1',
    name: 'John Doe',
    email: '[email protected]'
  }
};

Audio Content

import { AudioContentType } from '@bernierllc/content-management-suite';

// Register audio content type
suite.registerContentType(AudioContentType);

// Create audio content
const audioContent = {
  title: 'My Podcast Episode',
  slug: 'my-podcast-episode',
  description: 'A great podcast episode',
  audioUrl: 'https://example.com/audio/episode.mp3',
  duration: 1800, // 30 minutes
  metadata: {
    artist: 'John Doe',
    album: 'Tech Talk Podcast',
    year: 2023
  },
  tags: ['podcast', 'technology'],
  categories: ['podcast'],
  author: {
    id: 'user-1',
    name: 'John Doe',
    email: '[email protected]'
  }
};

Video Content

import { VideoContentType } from '@bernierllc/content-management-suite';

// Register video content type
suite.registerContentType(VideoContentType);

// Create video content
const videoContent = {
  title: 'My Video Tutorial',
  slug: 'my-video-tutorial',
  description: 'A comprehensive tutorial',
  videoUrl: 'https://example.com/videos/tutorial.mp4',
  duration: 1800, // 30 minutes
  dimensions: {
    width: 1920,
    height: 1080
  },
  metadata: {
    director: 'John Doe',
    genre: 'Educational',
    year: 2023
  },
  tags: ['tutorial', 'video'],
  categories: ['tutorial'],
  author: {
    id: 'user-1',
    name: 'John Doe',
    email: '[email protected]'
  }
};

Editorial Workflows

Configure custom editorial workflows:

const customWorkflow = {
  id: 'editorial',
  name: 'Editorial Workflow',
  description: 'Multi-stage editorial workflow',
  stages: [
    {
      id: 'write',
      name: 'Write',
      order: 1,
      isPublishStage: false,
      allowsScheduling: false,
      permissions: ['content.edit']
    },
    {
      id: 'edit',
      name: 'Edit',
      order: 2,
      isPublishStage: false,
      allowsScheduling: false,
      permissions: ['content.edit']
    },
    {
      id: 'review',
      name: 'Review',
      order: 3,
      isPublishStage: false,
      allowsScheduling: false,
      permissions: ['content.review']
    },
    {
      id: 'publish',
      name: 'Publish',
      order: 4,
      isPublishStage: true,
      allowsScheduling: true,
      permissions: ['content.publish']
    }
  ],
  transitions: [
    {
      id: 'write-to-edit',
      from: 'write',
      to: 'edit',
      permissions: ['content.edit']
    },
    {
      id: 'edit-to-review',
      from: 'edit',
      to: 'review',
      permissions: ['content.review']
    },
    {
      id: 'review-to-publish',
      from: 'review',
      to: 'publish',
      permissions: ['content.publish']
    }
  ]
};

await suite.workflowService.createWorkflow(customWorkflow);

API Endpoints

Content Management

  • GET /api/content - List content
  • GET /api/content/:id - Get content by ID
  • POST /api/content - Create content
  • PUT /api/content/:id - Update content
  • DELETE /api/content/:id - Delete content
  • POST /api/content/:id/publish - Publish content
  • POST /api/content/:id/schedule - Schedule content
  • POST /api/content/:id/unpublish - Unpublish content

Workflow Management

  • GET /api/workflows - List workflows
  • GET /api/workflows/:id - Get workflow by ID
  • POST /api/workflows - Create workflow
  • PUT /api/workflows/:id - Update workflow
  • DELETE /api/workflows/:id - Delete workflow

Content Type Management

  • GET /api/content-types - List content types
  • GET /api/content-types/:id - Get content type by ID
  • POST /api/content-types - Create content type
  • PUT /api/content-types/:id - Update content type
  • DELETE /api/content-types/:id - Delete content type

Configuration Management

  • GET /api/config - Get configuration
  • PUT /api/config - Update configuration

User Management

  • GET /api/users - List users
  • GET /api/users/:id - Get user by ID
  • POST /api/users - Create user
  • PUT /api/users/:id - Update user
  • DELETE /api/users/:id - Delete user

UI Components

Content Editor

import { ContentEditor } from '@bernierllc/content-management-suite';

<ContentEditor
  content={content}
  onChange={(content) => console.log('Content changed:', content)}
  onSave={(content) => console.log('Save:', content)}
  onPublish={(content) => console.log('Publish:', content)}
  onSchedule={(content, date) => console.log('Schedule:', content, date)}
  showToolbar={true}
  showStatusBar={true}
  showWordCount={true}
  showCharacterCount={true}
  placeholder="Start writing your content..."
  maxCharacters={10000}
  targetWordCount={1000}
/>

Workflow Components

import { 
  WorkflowStepper, 
  StageActionButtons, 
  WorkflowTimeline,
  WorkflowAdminConfig 
} from '@bernierllc/content-management-suite';

// Workflow stepper
<WorkflowStepper
  workflow={workflow}
  currentStage={currentStage}
  onStageChange={(stage) => console.log('Stage changed:', stage)}
/>

// Stage action buttons
<StageActionButtons
  stage={currentStage}
  content={content}
  onAction={(action) => console.log('Action:', action)}
/>

// Workflow timeline
<WorkflowTimeline
  workflow={workflow}
  content={content}
  onStageClick={(stage) => console.log('Stage clicked:', stage)}
/>

// Workflow admin configuration
<WorkflowAdminConfig
  workflow={workflow}
  onChange={(workflow) => console.log('Workflow changed:', workflow)}
  onSave={(workflow) => console.log('Workflow saved:', workflow)}
/>

Content List

import { ContentList } from '@bernierllc/content-management-suite';

<ContentList
  content={contentList}
  onContentSelect={(content) => console.log('Content selected:', content)}
  onContentEdit={(content) => console.log('Edit content:', content)}
  onContentDelete={(content) => console.log('Delete content:', content)}
  onContentPublish={(content) => console.log('Publish content:', content)}
  view="table" // table, list, grid, kanban
  showSearch={true}
  showFilters={true}
  showSorting={true}
  showPagination={true}
  pageSize={20}
/>

Plugin System

Create custom plugins:

const analyticsPlugin = {
  name: 'analytics',
  version: '1.0.0',
  description: 'Analytics plugin for content management',
  dependencies: ['@bernierllc/analytics'],
  install: async (suite) => {
    console.log('Installing analytics plugin');
    // Initialize analytics
  },
  uninstall: async (suite) => {
    console.log('Uninstalling analytics plugin');
    // Cleanup analytics
  },
  enable: async (suite) => {
    console.log('Enabling analytics plugin');
    // Enable analytics tracking
  },
  disable: async (suite) => {
    console.log('Disabling analytics plugin');
    // Disable analytics tracking
  }
};

suite.registerPlugin(analyticsPlugin);

Middleware System

Add custom middleware:

suite.addMiddleware({
  name: 'request-logger',
  handler: (req, res, next) => {
    console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
    next();
  },
  order: 1
});

Hook System

Add custom hooks:

suite.addHook({
  name: 'content:created',
  handler: async (content) => {
    console.log('New content created:', content.id);
    // Send notification, update analytics, etc.
  },
  priority: 1
});

Security

JWT Authentication

const suite = createContentManagementSuite({
  config: {
    security: {
      jwt: {
        secret: 'your-super-secret-jwt-key',
        expiresIn: '24h',
        issuer: 'content-management-suite'
      }
    }
  }
});

Role-Based Permissions

const suite = createContentManagementSuite({
  config: {
    security: {
      permissions: {
        enabled: true,
        defaultRole: 'user',
        roles: [
          {
            name: 'admin',
            permissions: ['*'],
            description: 'Full administrative access'
          },
          {
            name: 'editor',
            permissions: ['content.edit', 'content.publish', 'content.schedule'],
            description: 'Content editing and publishing'
          },
          {
            name: 'author',
            permissions: ['content.edit'],
            description: 'Content creation and editing'
          },
          {
            name: 'user',
            permissions: ['content.view'],
            description: 'Content viewing only'
          }
        ]
      }
    }
  }
});

Performance

Caching

const suite = createContentManagementSuite({
  config: {
    performance: {
      cache: {
        enabled: true,
        ttl: 300, // 5 minutes
        maxSize: 1000
      }
    }
  }
});

Compression

const suite = createContentManagementSuite({
  config: {
    performance: {
      compression: {
        enabled: true,
        level: 6
      }
    }
  }
});

Rate Limiting

const suite = createContentManagementSuite({
  config: {
    performance: {
      rateLimit: {
        enabled: true,
        windowMs: 15 * 60 * 1000, // 15 minutes
        max: 100
      }
    }
  }
});

Logging

const suite = createContentManagementSuite({
  config: {
    logging: {
      level: 'info',
      format: 'json',
      file: {
        enabled: true,
        path: './logs',
        maxSize: '10MB',
        maxFiles: 5
      },
      console: {
        enabled: true,
        colorize: true
      }
    }
  }
});

Integrations

NeverAdmin Integration

const suite = createContentManagementSuite({
  config: {
    integrations: {
      neverAdmin: {
        enabled: true,
        url: 'https://admin.myapp.com',
        apiKey: 'your-api-key',
        syncInterval: 300000 // 5 minutes
      }
    }
  }
});

NeverHub Integration

const suite = createContentManagementSuite({
  config: {
    integrations: {
      neverHub: {
        enabled: true,
        url: 'https://hub.myapp.com',
        apiKey: 'your-api-key',
        packageDiscovery: true
      }
    }
  }
});

Error Handling

suite.addHook({
  name: 'error',
  handler: async (error) => {
    console.error('Suite error:', error);
    // Send to error tracking service
  }
});

Graceful Shutdown

process.on('SIGINT', async () => {
  console.log('Received SIGINT, shutting down gracefully...');
  try {
    await suite.stop();
    console.log('Suite stopped successfully');
    process.exit(0);
  } catch (error) {
    console.error('Error during shutdown:', error);
    process.exit(1);
  }
});

Development vs Production

const isDevelopment = process.env.NODE_ENV === 'development';

const suite = createContentManagementSuite({
  config: {
    server: {
      port: isDevelopment ? 3000 : 80,
      host: isDevelopment ? 'localhost' : '0.0.0.0'
    },
    logging: {
      level: isDevelopment ? 'debug' : 'info',
      format: isDevelopment ? 'text' : 'json'
    },
    security: {
      jwt: {
        secret: isDevelopment ? 'dev-secret' : process.env.JWT_SECRET
      }
    }
  }
});

Testing

# Run unit tests
npm test

# Run Playwright tests
npm run test:playwright

# Run all tests
npm run test:all

Examples

See the examples.ts file for comprehensive usage examples:

  • Basic setup
  • Advanced configuration
  • Custom content types
  • Plugin system
  • Error handling
  • API usage
  • Graceful shutdown
  • Environment-specific setup

License

Copyright (c) 2025 Bernier LLC. Licensed under limited-use license.