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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@acrontum/fst

v0.1.3

Published

Filesystem templating engine and project scaffolding tool

Downloads

3

Readme

filesystem-template

NOTE fst is in early beta, docs are subject to change and may not be accurate.


Usage

Quickstart:
npm install --save-dev @acrontum/fst

Basic usage:
npx fst [options] <path_to_recipe_file>

See npx fst --help for more options, or see API.


Recipe file

Schema

interface RecipeSchema {
  /**
   * unique name of the recipe (for use with depends and passing data between files)
   */
  name?: string;
  /**
   * file, recipe, path to file(s), url, or repository to fetch templates from
   */
  from?: string;
  /**
   * output folder
   */
  to?: string;
  /**
   * arbitrary data store
   */
  data?: any;
  /**
   * array of names of other recipes which must be built first
   */
  depends?: string[];
  /**
   * lifecycle cli / shell commands
   */
  scripts?: { before?: string; after?: string };
  /**
   * path to render handler js file or handler function
   */
  fileHandler?: string | RenderFunction;
  /**
   * dependent / sub recipes
   */
  recipes?: RecipeSchema[];
  /**
   * list of folders to skip when copying or generating
   */
  excludeDirs?: string[];
  /**
   * when cloning, only clone certain folders
   */
  includeDirs?: string[];
}

Example

{
  "recipes": [
    {
      "name": "openapi-spec",
      "to": "spec",
      "scripts": {
        "after": "npm run build"
      }
    },
    {
      "name": "server",
      "from": "https://mytemplates.test/server-template",
      "to": "backend",
      "fileHandler": "server-builder.js",
      "recipes": [
        {
          "name": "client",
          "from": "./client",
          "to": "./client",
          "scripts": {
            "after": "node link-client.js"
          }
        }
      ]
    },
    {
      "name": "docker-compose",
      "from": "https://fstr-std.com/compose",
      "to": "docker",
      "depends": ["openapi-spec", "server", "client"],
      "excludeDirs": ["volumes"]
    }
  ]
}

Understanding relative paths

All recipe block paths are relative to the parent recipe's "to" section (defaulting to './' when not present. In the case of the root, it's relative to where the recipe was called from (cwd).
Eg:

For this file setup:

.
├── demo.fstr.json
├── scripts
│   ├── child-script.js
│   └── parent-script.js
└── sub-template
    └── readme.txt

With demo.fstr.json:

{
  "name": "demo",
  "to": "./demo-output",
  "fileHandler": "scripts/parent-script.js",
  "recipes": [
    {
      "name": "demo-sub-recipe",
      "from": "../sub-template",
      "to": "sub-folder",
      "fileHandler": "scripts/child-script.js",
      "scripts": {
        "before": "touch readme.txt"
      }
    }
  ]
}

Note that for demo-sub-recipe, both "from" and "scripts" are relative to the parent "to", wheras the top-level script path ('scripts/parent-script.js') is relative to the recipe file (in this case, './').

For the sub-recipe, since both "from" and "scripts" read from folders in the root, their paths are parsed as ./demo-output/../sub-template and ./demo-output/../scripts/child-script.js (respectively).

Running the recipe file would then generate this folder structure:

.
├── demo.fstr.json
├── demo-output          <-- generated
│   └── sub-folder
│       └── readme.txt
├── scripts
│   ├── child-script.js
│   └── parent-script.js
└── sub-template
    └── readme.txt

Lifecycle scripts

Import and run scripts at different points in the runtime.

scripts.before:

Runs after source material exists on disk (after pulling from remote, or on finding local path), but before the render.

{
  // ...
  "scripts": {
    "before": "rm -rf old-files"
  }
}

scripts.after:

Runs after render and all files copied. Useful for cleanup or logging.

{
  // ...
  "scripts": {
    "after": "./build.sh && cp output ../release"
  }
}

fileHandler (rendering callback):

Used when registering callbacks for during render.

fileHandler?: (recipe: Recipe, renderer: Renderer) => void | Promise<void>;

FST will search in the current directory of the recipe for the file handler script and, if that does not exist, in the temp template source folder.

import * as nunjucks from 'nunjucks';
import { promises } from 'fs';
import { RenderFunction, Recipe, Renderer, VirtualFile } from '@acrontum/filesystem-template';

const render: RenderFunction = (recipe: Recipe, renderer: Renderer) => {
  // recipe contains a name-indexed map of other recipes that have run
  const parentRecipeData = recipe.map['parent-name'].data;

  renderer.onFile(async (node: VirtualFile) => {
    if (node.name === 'ignore-me.txt') {
      node.skip = true; // skip generating this file

      return;
    }

    if (!node.name.endsWith('.njk')) {
      // if node is not skipped, and has no outputs, the default rendering will be to copy the file or folder
      return;
    }

    const njkTemplate = await promises.readFile(node.fullSourcePath, 'utf8');

    // the parent node may be a folder which generated in multiple places, meaning the source file will have multple outputs
    for (const output of node.getGenerationTargets()) {
      const content = await nunjunks.renderFile(njkTemplate, { ...parentRecipeData, ...recipe.data });
      await promises.writeFile(output, content);
      node.outputs.push(output);
    }
  });
};

API

fst <options | RECIPE>

options:
  -c, --cache             Do not delete fetched files between runs
  -o, --output PATH       Folder to output files
  -p, --parallel COUNT    Max number of concurrent recipe parsing (default 10)
  -r, --recipe RECIPE     Add recipe to builder (file or URL)
  -s, --silent            Log less verbosely
  -v, --verbose           Log more verbosely
  -e, --exclude PATHS     Skip PATHS in template generation (default node_modules,.git)
  -b, --no-buffer         Disable log buffering (default true unless not tty, CI=true, or TERM=dumb)

Try:
npx fst --help

Roadmap

  • [ ] write more tests
  • [ ] update docs
  • [ ] re-implement cli params (include / exclude files, cache, parallel, etc)
  • [ ] re-implement recipe params (include / exclude files, cache, parallel, etc)