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

content-factory

v1.1.0

Published

A content transformation engine with visibility filtering and file includes

Readme

content-factory

A build tool for generating multiple output files from a single source of truth. Define your content once, then filter and transform it for different targets.

The Problem

You have content that needs to exist in multiple places with slight variations. For example:

  • AI coding assistant rules for Claude, Copilot, and Roo (each with different capabilities)
  • Documentation for different platforms
  • Config files for different environments

Maintaining these separately leads to drift and duplication. content-factory lets you write once and generate targeted outputs.

Quick Start

1. Create a config file (content-factory.config.js):

const { defineConfig } = require("content-factory");

module.exports = defineConfig({
  useLogs: true,
  tools: {
    claude: {
      strategies: [
        {
          name: "rules",
          matches: ["src/rules/**/*.md"],
          transform: ({ files }) => ({
            files: files.map((f) => ({
              path: `dist/claude/${f.name}`,
              content: f.content,
            })),
          }),
        },
      ],
    },
    copilot: {
      strategies: [
        {
          name: "rules",
          matches: ["src/rules/**/*.md"],
          transform: ({ files }) => ({
            files: files.map((f) => ({
              path: `dist/copilot/${f.name}`,
              content: f.content,
            })),
          }),
        },
      ],
    },
  },
});

2. Write your source content (src/rules/coding.md):

# Coding Rules

Always write clean, readable code.

<content-factory-filter include="claude">
Use XML tags for structured output.
</content-factory-filter>

<content-factory-filter include="copilot">
Use markdown code blocks for examples.
</content-factory-filter>

<content-factory-filter exclude="copilot">
You can use multi-file editing.
</content-factory-filter>

3. Run:

npx content-factory

This generates dist/claude/coding.md and dist/copilot/coding.md with tool-specific content.

Features

Conditional Content

Use <content-factory-filter> to include/exclude content per tool:

<content-factory-filter include="claude,roo">
Only in Claude and Roo outputs.
</content-factory-filter>

<content-factory-filter exclude="copilot">
In all outputs except Copilot.
</content-factory-filter>

Filters can be nested.

File Includes

Use <content-factory-include-file> to compose content from multiple files:

# Main Document

<content-factory-include-file path="./intro.md" />
<content-factory-include-file path="@/shared/footer.md" />

Path resolution:

  • ./relative.md — relative to current file
  • @/from-root.md — relative to project root
  • /absolute/path.md — absolute path

Included files are recursively processed (filters and includes apply). Circular dependencies are detected and throw an error.

Pipelines

Pipelines allow you to transform the content of an included file before it's inserted. This is useful for things like adjusting heading levels, removing specific lines, or wrapping content.

  1. Define pipelines in your config:
module.exports = defineConfig({
  // ...
  pipelines: {
    "adjust-headings": ({ params, content }) => {
      const level = parseInt(params[0] || "1", 10);
      const prefix = "#".repeat(level);
      // Note: use engine.readFile() if you need to pull in additional content
      return {
        content: content.replace(/^(#+)/gm, (match) => match + prefix),
      };
    },
    "inject-header": async ({ content, engine, toolName, strategyName }) => {
      const header = await engine.readFile("@/shared/header.md", {
        toolName,
        strategyName,
      });
      return {
        content: header.content + "\n" + content,
      };
    },
  },
});
  1. Use pipelines in your include tags:
<content-factory-include-file path="./section.md" pipelines="adjust-headings(2), wrap-example" />

Pipelines are executed in order (left to right). Arguments are passed as strings. Each pipeline receives an engine instance for reading additional files.

Programmatic File Reading

The engine instance is available in transform, onFinish, and pipelines. It provides two methods for reading files with full preprocessing (visibility filters, includes, and optional pipelines):

engine.readFile(path, options) — Read a Single File

Returns a SourceFile with preprocessed content.

transform: async ({ files, engine }) => {
  // Relative to project root
  const header = await engine.readFile("src/shared/header.md", {
    toolName: "claude",
    strategyName: "rules",
  });

  // Root-relative (@/ syntax)
  const footer = await engine.readFile("@/shared/footer.md", {
    toolName: "claude",
    strategyName: "rules",
  });

  // With pipelines applied after preprocessing
  const section = await engine.readFile("src/section.md", {
    toolName: "claude",
    strategyName: "rules",
    pipelines: "adjust-headings(2), wrap-example",
  });

  return {
    files: [
      {
        path: "dist/output.md",
        content:
          header.content + "\n" + section.content + "\n" + footer.content,
      },
    ],
  };
};

Path resolution for readFile:

  • src/file.md — relative to project root
  • @/shared/file.md — relative to project root (explicit)
  • /absolute/path.md — absolute path

engine.readFiles(patterns, options) — Read Multiple Files via Glob

Returns SourceFile[] with preprocessed content for all matched files.

transform: async ({ engine }) => {
  const extras = await engine.readFiles(["src/extras/**/*.md"], {
    toolName: "claude",
    strategyName: "rules",
  });

  // With pipelines applied to every matched file
  const sections = await engine.readFiles(["src/sections/**/*.md"], {
    toolName: "claude",
    strategyName: "rules",
    pipelines: "adjust-headings(1)",
  });

  return {
    files: extras.map((f) => ({
      path: `dist/claude/${f.name}`,
      content: f.content,
    })),
  };
};

SourceFile Shape

Both methods return objects with this shape:

interface SourceFile {
  name: string; // e.g. "coding.md"
  content: string; // Preprocessed content
  path: string; // Absolute path
  relativePath: string; // Path relative to project root
  extension: string; // e.g. ".md"
}

Standalone Usage (Outside engine.run)

You can also use readFile and readFiles without running the full build:

const { Engine, defineConfig } = require("content-factory");

const config = defineConfig({
  tools: {
    /* ... */
  },
  pipelines: {
    /* ... */
  },
});
const engine = new Engine(config, process.cwd());

const file = await engine.readFile("src/rules/coding.md", {
  toolName: "claude",
  strategyName: "rules",
  pipelines: "uppercase",
});
console.log(file.content);

Transform Functions

Each strategy's transform function receives processed files and an engine instance:

transform: ({ files, dir, root, config, engine }) => {
  return {
    files: [{ path: "output.md", content: "..." }],
    metadata: {
      /* passed to onFinish */
    },
    // Optional: Glob patterns of files to delete
    deleteFiles: ["dist/**/*.tmp"],
  };
};

onFinish Hook

Run logic after all strategies complete for a tool. Also receives the engine instance:

{
  strategies: [...],
  onFinish: async ({ metadata, engine }) => {
    const index = await engine.readFile("@/templates/index.md", {
      toolName: "claude",
      strategyName: "finalize",
    });

    return {
      files: [{ path: "dist/index.md", content: index.content }],
      deleteFiles: ["dist/temp/**/*"]
    };
  }
}

CLI

# Use default config (content-factory.config.js)
npx content-factory

# Use custom config
npx content-factory --config my-config.js

API

const { Engine, defineConfig, defineTool } = require("content-factory");

const config = defineConfig({
  tools: {
    /* ... */
  },
});
const engine = new Engine(config, process.cwd());
await engine.run();

License

MIT