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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@timeax/scaffold

v0.0.12

Published

A CLI tool that scaffolds project file structures based on a user-defined, type-safe configuration file.

Downloads

1,261

Readme

Scaffold – @timeax/scaffold

A tiny, opinionated scaffolding tool that keeps your project structure in sync with a declarative tree (like structure.txt) – Prisma‑style.

  • Define your desired folders/files in plain text.
  • Group structures by area (e.g. app, routes, resources/js).
  • Generate those files safely, with caching + hooks + stubs.
  • Reverse‑engineer existing projects into *.txt structures.
  • Watch for changes and re‑apply automatically.

Supported structure files: .tss, .stx, structure.txt (and any .txt inside .scaffold/).


Features

  • Prisma‑style scaffold directory: all config and structures live under a hidden root, .scaffold/, by default.

  • Config‑driven groups: declare multiple roots (e.g. app, frontend) with their own structure files.

  • Plain‑text structure files: strict, easy‑to‑read tree syntax with indentation and annotations.

  • Safe apply:

    • Creates missing files/directories.
    • Tracks what it created in a cache.
    • Only auto‑deletes files it previously created.
    • Interactive delete for “large” files.
  • Hooks:

    • Regular hooks around file create/delete.
    • Stub hooks around content generation.
  • Stubs: programmatic content generators for files (e.g. React pages, controllers, etc.).

  • Watch mode: watch .scaffold/ for changes and re‑run automatically.

  • Scanner: generate *.tss/structure.txt from an existing codebase.

  • AST + formatter: loose/strict parser with diagnostics, plus a smart formatter that can fix simple indentation mistakes.

  • VS Code extension: syntax highlighting, formatting, diagnostics, folding, hover info, “go to file”, and code actions.


Installation

npm install @timeax/scaffold --save-dev
# or
pnpm add -D @timeax/scaffold
yarn add -D @timeax/scaffold

The package exposes both a CLI (scaffold) and a programmatic API.


Quick start

1. Initialize scaffold folder

npx scaffold init
# or if installed locally
pnpm scaffold init

This will create:

.scaffold/
  config.ts       # main ScaffoldConfig
  structure.txt   # example structure (single-root mode)

If you want a different directory name:

scaffold init --dir tools/.scaffold

Use --force to overwrite existing config/structure files.


2. Define your structure

By default, .scaffold/structure.txt is used in single‑root mode.

Example:

src/
  index.ts

  schema/
    index.ts
    adapter.ts
    field.ts
    field-map.ts
    form.ts
    input-field.ts
    presets.ts
    variant.ts

index.ts
README.md

Rules:

  • Indent with 2 spaces per level by default (configurable via indentStep).
  • Directories must end with /.
  • Files must not end with /.
  • You cannot indent under a file (files cannot have children) – in strict mode this is an error, in loose mode you get a diagnostic.
  • You can’t “skip” levels (no jumping from depth 0 straight to depth 2 in one go).
  • Lines starting with # or // are treated as comments.
  • Inline comments are supported: index.ts # comment, index.ts // comment.

Annotations

You can attach metadata per line:

src/
  pages/ @stub:page
    home.tsx @include:pages/**
    about.tsx @exclude:pages/legacy/**

Supported inline annotations:

  • @stub:name – attach a stub name for content generation.
  • @include:pattern,pattern2 – extra include filters for this entry.
  • @exclude:pattern,pattern2 – extra exclude filters for this entry.

These map onto the StructureEntry fields in TypeScript.


Config: groups, base, and indent

In .scaffold/config.ts you can enable grouped mode and control the base root + indent step:

import type { ScaffoldConfig } from '@timeax/scaffold';

const config: ScaffoldConfig = {
  // Project root (defaults to cwd if omitted)
  base: '.',

  // Indent step in spaces (must match your `.tss`/`structure.txt`)
  indentStep: 2,

  // Optional: grouped mode
  groups: [
    { name: 'app',      root: 'app',          structureFile: 'app.txt' },
    { name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
  ],

  hooks: {},
  stubs: {},
};

export default config;

Then create per‑group structure files in .scaffold/:

# .scaffold/app.txt
App/Services/
  UserService.php

# .scaffold/frontend.txt
src/
  index.tsx
  pages/
    home.tsx

When groups is defined and non‑empty, single‑root structure/structureFile is ignored.


Running scaffold

# single run
scaffold

# or with explicit scaffold dir / config
scaffold --dir .scaffold --config .scaffold/config.ts

What happens:

  • Config is loaded from .scaffold/config.* (Prisma‑style resolution).

  • Structure(s) are resolved (grouped or single‑root).

  • Files/directories missing on disk are created.

  • New files are registered in a cache file (default: .scaffold-cache.json under project root).

  • Any previously created files that are no longer in the structure are candidates for deletion:

    • Small files are deleted automatically.
    • Large files (configurable threshold) trigger an interactive prompt.

Watch mode

scaffold --watch
  • Watches:

    • .scaffold/config.*
    • .scaffold/*.txt / .scaffold/*.tss / .scaffold/*.stx
  • Debounces rapid edits.

  • Prevents overlapping runs.


CLI commands

scaffold (default)

scaffold [options]

Options:

  • -c, --config <path> – override config file path.
  • -d, --dir <path> – override scaffold directory (default: ./.scaffold).
  • -w, --watch – watch scaffold directory for changes.
  • --quiet – silence logs.
  • --debug – verbose debug logs.

scaffold init

Initialize the scaffold directory + config + structure.

scaffold init [options]

Options:

  • -d, --dir <path> – scaffold directory (default: ./.scaffold, inherited from root options).
  • --force – overwrite existing config.ts / structure.txt.

scaffold scan

Generate *.tss/structure.txt‑style definitions from an existing project.

Two modes:

1. Config‑aware mode (default if no --root / --out)

scaffold scan
scaffold scan --from-config
scaffold scan --from-config --groups app frontend
  • Loads .scaffold/config.ts.

  • For each group in config:

    • Scans group.root on disk.
    • Writes to .scaffold/<group.structureFile || group.name + '.txt'>.
  • --groups filters which groups to scan.

2. Manual mode (single root)

scaffold scan -r src
scaffold scan -r src -o .scaffold/src.txt

Options:

  • -r, --root <path> – directory to scan.
  • -o, --out <path> – output file (otherwise prints to stdout).
  • --ignore <patterns...> – extra globs to ignore (in addition to defaults like node_modules/**, .git/**, etc.).

scaffold structures

Ensure that all structure files declared in your config exist.

scaffold structures

What it does:

  • Loads .scaffold/config.*.

  • Determines which structure files are expected:

    • Grouped mode (config.groups defined): each group gets group.structureFile || \${group.name}.txt``.
    • Single-root mode (no groups): uses config.structureFile || 'structure.txt'.
  • For each expected structure file:

    • If it already exists → it is left untouched.
    • If it is missing → it is created with a small header comment.

Examples:

# With grouped config:
# groups: [
#   { name: 'app', root: 'app', structureFile: 'app.txt' },
#   { name: 'frontend', root: 'resources/js', structureFile: 'frontend.txt' },
# ]
scaffold structures
# => ensures .scaffold/app.txt and .scaffold/frontend.txt exist

# With single-root config:
# structureFile: 'structure.txt'
scaffold structures
# => ensures .scaffold/structure.txt exists

This is useful right after setting up or editing .scaffold/config.ts so that all declared structure files are present and ready to edit.


TypeScript API

You can also use the core functions programmatically.

import { runOnce } from '@timeax/scaffold';

await runOnce(process.cwd(), {
  // optional overrides
  configPath: '.scaffold/config.ts',
  scaffoldDir: '.scaffold',
});

Scanner:

import {
  scanDirectoryToStructureText,
  scanProjectFromConfig,
  writeScannedStructuresFromConfig,
} from '@timeax/scaffold';

// low-level
const text = scanDirectoryToStructureText('src');

// config-aware (groups)
const results = await scanProjectFromConfig(process.cwd(), {
  groups: ['app', 'frontend'],
});

// write group structure files to .scaffold/
await writeScannedStructuresFromConfig(process.cwd(), {
  groups: ['app'],
});

AST & Formatter

@timeax/scaffold exposes an AST parser and formatter from a dedicated subpath:

import { parseStructureAst, formatStructureText } from '@timeax/scaffold/ast';

parseStructureAst(text, options)

Parses a structure file into a loose or strict AST with diagnostics:

const ast = parseStructureAst(text, {
  indentStep: 2,     // must match your structure files
  mode: 'loose',     // 'loose' | 'strict'
});

Return shape (simplified):

interface StructureAstNode {
  type: 'dir' | 'file';
  name: string;
  path?: string;         // normalized POSIX path (no trailing slash for files)
  line?: number;         // 1-based source line
  indentLevel?: number;  // 0,1,2,...
  stub?: string;
  include?: string[];
  exclude?: string[];
  children?: StructureAstNode[];
}

interface StructureDiagnostic {
  code:
    | 'indent-misaligned'
    | 'indent-skip-level'
    | 'child-of-file-loose'
    | 'path-colon'
    | 'unknown';
  message: string;
  line: number;      // 1-based
  severity: 'info' | 'warning' | 'error';
}

interface StructureAst {
  rootNodes: StructureAstNode[];
  indentStep: number;
  mode: 'loose' | 'strict';
  diagnostics: StructureDiagnostic[];
}

Loose mode tries to recover from small mistakes (over‑indent, under‑indent) and reports them as diagnostics instead of throwing.

Strict mode is closer to the CLI parser and may reject invalid indentation entirely.

Typical diagnostics:

  • indent-misaligned – indent is not a multiple of indentStep.
  • indent-skip-level – you jumped more than one level at once.
  • child-of-file-loose – a line is indented under a file.
  • path-colon – path token contains a colon (:), which is reserved for annotations.

formatStructureText(text, options)

Smart formatter that:

  • Normalizes line endings and trailing whitespace.

  • Re‑indents entries to canonical multiples of indentStep.

  • Fixes common over‑indent/under‑indent issues in loose mode.

  • Preserves:

    • Blank lines.
    • Full‑line comments (#, //).
    • Inline comments and annotations (keeps them attached to their entries).
const { text: formatted, ast, diagnostics } = formatStructureText(input, {
  indentStep: 2,
  mode: 'loose',      // 'loose' is recommended for editor integrations
});

formatStructureText reuses the same AST model and diagnostics, so you can:

  • Run it in an editor (e.g. VS Code) for formatting.
  • Show the diagnostics in a side panel or gutter.
  • Still feed the formatted text back into the strict CLI parser later.

Hooks & Stubs (high‑level overview)

Regular hooks

Regular hooks run around file lifecycle events:

import type { ScaffoldConfig } from '@timeax/scaffold';

const config: ScaffoldConfig = {
  // ...
  hooks: {
    preCreateFile: [
      {
        include: ['**/*.tsx'],
        async fn(ctx) {
          console.log('About to create', ctx.targetPath);
        },
      },
    ],
    postCreateFile: [],
    preDeleteFile: [],
    postDeleteFile: [],
  },
};

Hook kinds:

  • preCreateFile
  • postCreateFile
  • preDeleteFile
  • postDeleteFile

Each receives a HookContext with fields like:

  • projectRoot
  • targetPath (project‑relative, POSIX)
  • absolutePath
  • isDirectory
  • stubName?
  • group?

Stubs

Stubs generate file contents and can have their own pre/post hooks:

import type { ScaffoldConfig } from '@timeax/scaffold';

const config: ScaffoldConfig = {
  // ...
  stubs: {
    page: {
      name: 'page',
      async getContent(ctx) {
        const name = ctx.targetPath.split('/').pop();
        return `export default function ${name}() {
  return <div>${name}</div>;
}`;
      },
      hooks: {
        preStub: [
          {
            include: ['**/*.tsx'],
            fn(ctx) {
              console.log('Rendering page stub for', ctx.targetPath);
            },
          },
        ],
        postStub: [],
      },
    },
  },
};

In a structure file:

src/
  pages/ @stub:page
    home.tsx
    about.tsx

Any file in pages/ without an explicit stub inherits @stub:page from the parent directory.


Cache & Safety

  • Cache file (default): .scaffold-cache.json under project root (configurable via cacheFile).

  • Every file created by scaffold is recorded with:

    • project‑relative path
    • created time
    • size at creation
    • stub name (if any)
    • group metadata
  • On each run, scaffold compares the desired structure vs. cached entries:

    • If a cached file is no longer in the structure and still exists → deletion candidate.
    • If its size exceeds sizePromptThreshold (configurable) and the CLI is interactive → prompt the user.
    • If the user chooses “keep”, the file is left on disk and removed from the cache (user now owns it).

This keeps scaffolding idempotent and avoids reckless deletes.


VS Code Extension

There is a companion VS Code extension that makes editing scaffold files much nicer.

Language & files

  • Registers a scaffold-structure language.

  • Treats as scaffold structure files:

    • .tss
    • .stx
    • structure.txt
    • Any .txt inside .scaffold/ (configurable in the extension’s package.json).

Syntax highlighting

  • Tree‑like syntax with clear highlighting for:

    • Directories vs files.
    • Annotations (@stub:, @include:, @exclude:).
    • Comments and inline comments.

Formatting

  • Command: “Scaffold: Format structure” (timeax-scaffold.formatStructure).

  • Uses formatStructureText from @timeax/scaffold/ast:

    • Normalizes indentation to indentStep.
    • Fixes simple over/under‑indents in loose mode.
    • Preserves blank lines and comments.

You can wire this as the default formatter for scaffold-structure files via your VS Code settings.

Sorting

  • Command: “Scaffold: Sort entries” (timeax-scaffold.sortEntries).
  • Sorts non‑comment lines lexicographically while preserving comment/blank line positions.

Diagnostics

  • Live diagnostics (squiggles) using parseStructureAst:

    • indent-misaligned, indent-skip-level, child-of-file-loose, path-colon, etc.
  • Diagnostics update on open and on change.

Folding

  • Folding regions for directories based on AST:

    • Collapse an entire subtree under a dir.

Hover

  • Hovering an entry shows:

    • Kind (dir/file).
    • Effective path.
    • Stub / include / exclude.
    • Resolved absolute path based on base + group root.

“Go to file”

  • Command: “Scaffold: Go to file” (timeax-scaffold.openTargetFile).

  • On a file line:

    • Resolves the project base (from .scaffold/config.*, base, and group root).
    • Opens the target file if it exists.
    • If it doesn’t exist, you can create it on the spot.

Code actions

Source actions exposed in the light‑bulb menu for structure files:

  • “Scaffold: Ensure structure files exist (scaffold structures)”

    • Runs npx scaffold structures in a workspace terminal.
  • “Scaffold: Apply structure to project (scaffold)”

    • Runs npx scaffold in a workspace terminal.

Status bar integration

  • Status bar item (left side) shows current scaffold context:

    • Scaffold: frontend (resources/js) when editing .scaffold/frontend.txt.
    • Scaffold: single root when in single‑root mode.
  • Tooltip shows the resolved base root path.


Roadmap / Ideas

Some things this package is intentionally designed to grow into:

  • Richer annotations in *.tss (e.g. per‑entry hooks, metadata aliases).
  • Stub groups (one logical stub creating multiple files at once).
  • Built‑in templates for common stacks (Laravel + Inertia, Next.js, etc.).
  • Better diff/dry‑run UX (show what will change without touching disk).
  • Deeper editor integrations (per‑group commands, quick‑fixes for diagnostics, etc.).

PRs and ideas are welcome ✨