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

@ockilson/schematics-utils

v1.0.0-alpha.7

Published

Schematic utilities for writing custom schematics

Readme

@ockilson/schematics-utils

Schematics Utilities and Helpers

This is a collection of helper functions for working with Schematics

While offering a lot of powerful functionality the Schematics toolkit from angular-devkit/{core,schematics} are fairly low level so this wraps some high level re-usable functionality.

Dependencies

Handling dependencies when setting up a new tool is fairly common so there are a few functions to make it a touch easier.

  • addPackageJsonDependency(tree: Tree, dependency: NodeDependency): void - adds a dependency (either; dev, default, peer or optional) at the set version to the package.json at the root of the tree.
  • getPackageJsonDependency(tree: Tree, name: string): NodeDependency | null - returns a dependencies information from the package.json at the root of the tree.
  • removePackageJsonDependency(tree: Tree, dependency: DeleteNodeDependency): void - remove a dependency from the package.json at the root of the tree.

These utilities can be used in your own custom schematics by passing the tree and the dependency to modify.

There are also two wrapper functions that take the schematics options and an array of dependencies; these return a Rule so can be chained with other schematics easily

  • addDependenciesToPackageJson(options: any, deps: NodeDependency[] = []): Rule - loops through the dependencies, adds them to the package.json file at the root of the tree and then runs npm install.
  • removeDependenciesFromPackageJson(options: any, deps: DeleteNodeDependency[] = []): Rule - loops through the dependencies, removes them from the package.json file at the root of the tree and then runs npm install (has the same effect as running npm uninstall on each of the packages individually).
  • addScriptToPackageJson(options: any, key: string, value: any): Rule - adds a script to the scripts object at property key with value value in the package.json at the root of the tree.

Usage Example

Inside a custom schematic factory file...

const dependencies: NodeDependency[] = [
    {
        type: NodeDependencyType.Dev,
        version: '^23.6.0',
        name: 'jest'
    }
];

const removeDeps: DeleteNodeDependency[] = [
    {
        name: "karma",
        type: NodeDependencyType.Dev
    }
];

export function myCustomSchematic(_options: any): Rule {
    return (tree: Tree, context: SchematicContext) => {
        return chain([
            removeDependenciesFromPackageJson(_options, removeDeps),
            addDependenciesToPackageJson(_options, dependencies),
            customDependencyRule(_options)
        ]);
    }
}

export function customDependencyRule(options: any) {
    return (tree: Tree, context: SchematicContext) => {
        const jestDep = getPackageJsonDependency(tree, 'jest');

        if(!jestDep) {
            throw new SchematicsException('Jest is not currently installed, you should fix that');
        }
        console.log(`Currently installed jest version is: ${jestDep.version}`);
        return tree;
    }
}

Files

Since this is primarily targetted at people using the @angular/cli two things that are fairly common are copying files from your custom schematic into the project directory and reading from json, so...

  • readJsonFile(tree: Tree, filePath: string): JsonAstObject - reads a json file at filePath in the project tree and converts to an AST representation.
  • addSchematicsFilesToProject(options: any, dest: string = '', src: string = './files', modifiers: object = {}): Rule - copies all files from the src directory to the project tree at dest. This allows you to use a few modifiers from @angular-devkit/core by default
    • camelize - Convert a string into camelCase (my-thing-is-cool => myThingIsCool)
    • dasherize - Convert a string into kebab-case (my thing is cool => my-thing-is-cool)
    • classify - Convert a string into PascalCase, handy for class names (my-component => MyComponent)
    • if-flat - Checks if options.flat is set, handy for working out paths You can use the last parameter to add additional modifiers as required.

Usage Example

import { strings } from "@angular-devkit/core";

export function myCustomSchematic(_options: any): Rule {
    return (tree: Tree, context: SchematicContext) => {
        return chain([
            addSchematicsFilesToProject(_options) // adds everything in `./files` to the root of the current tree
            addSchematicsFilesToProject(_options, 'scripts', './projectFiles', {
                strings.decamelize
            }) // adds everything in `./projectFiles` to `./scripts/` in the current tree and adds the additional `decamelize` modifier
        ]);
    }
}

Ast (abstract syntax tree)

Since Schematics is centered around modifying Ast objects which can be pretty tricky to work with there are a few utilities to make common tasks a little easier.

  • appendPropertyInAstObject(recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, indent: number) - add a new property to an existing ast object, this could be used to add a new property to the package.json, tsconfig.json or angular.json (after reading the file with readJsonFile). It does no checking for existing properties.
  • insertPropertyInAstObjectInOrder(recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue, indent: number) - adds a new property to an existing ast object (as above) but in alphabetical order by property key.
  • appendValueInAstArray(recorder: UpdateRecorder, node: JsonAstArray, value: JsonValue, indent = 4) - add a new item to an existing ast array object. It does no checking for existing properties.
  • findPropertyInAstObject(node: JsonAstObject, propertyName: string): JsonAstNode | null - finds and returns a property from an ast object (or null if not found).

These are a little more low level but should help you modify basic objects a little easier.

Usage Example

export function customSchematic(_options: any) {
    return (tree: Tree, _context: SchematicContext) => {
        const configAst = readJsonFile(tree, 'tsconfig.json');
        const compilerOptionsAst = findPropertyInAstObject(tsSpecConfigAst, 'compilerOptions') as JsonAstObject;
        const recorder = tree.beginUpdate('tsconfig.json');

        if(!compilerOptionsAst) {
            // if compiler options don't exist add them
            appendPropertyInAstObject(
                recorder,
                configAst,
                "compilerOptions",
                {
                    types: ['jest']
                },
                2
            );
        }
        tree.commitUpdate(recorder);
    }
}

Workspace

The angular cli uses the concept of a workspace, configured via angular.json at your project root, for a lot of schematics you may want to modify this config or get information out of it, the following are provided to make these easier.

  • getWorkspacePath(host: Tree): string - return the path to the workspace configuration file (either angular.json or .angular.json are valid).
  • getWorkspace(host: Tree): WorkspaceSchema - load the workspace configuration.
  • addProjectToWorkspace(workspace: WorkspaceSchema, name: string, project: WorkspaceProject): Rule - add a new project to a given workspace, will fail if project with that name already exists.
  • getProjectFromWorkspace(workspace: WorkspaceSchema, name: string): WorkspaceProject - returns a specific project from the given workspace.
  • updateProjectInWorkspace(workspace: WorkspaceSchema, name: string, project: WorkspaceProject): Rule - update an existing project in a given workspace.
  • getProject(host: Tree, name: string): WorkspaceProject - short cut that calls getWorkspace then getProjectFromWorkspace on the result.
  • getProjectRootPath(host: Tree, name: string): string - returns the path to the root of the given project, this will be the workspace root when only one project exists.
  • getProjectPath(host: Tree, name: string): string - returns the path to the project code root, for applications this will be <root>/src/app and for libraries it will be <root>/src/lib.
  • getDefaultProject(host: Tree): string - returns the name of the default project in the current workspace.

A lot of this functionality is better covered by extending angulars existing schematics (for example for adding a new project).

Usage Example

export function customSchematic(_options: any) {
    return chain([
        (tree: Tree, _context: SchematicContext) => {
            const workspace = getWorkspace(tree);

            // update the default collection the workspace should use for schematics
            workspace.cli = {
                ...workspace.cli,
                defaultCollection: "."
            };

            tree.overwrite(getWorkspacePath(tree), JSON.stringify(workspace, null, 2));
            return tree;
        },
        (tree: Tree, _context: SchematicContext) => {
            const projectName = getDefaultProject(tree);
            const workspace = getWorkspace(tree);
            const project = getProjectFromWorkspace(workspace, projectName);

            if(!project.architect || !project.architect.test) {
                throw new SchematicsException(`No project architect configuration available for project ${projectName}`);
            }

            project.architect.test.builder = "@angular-builders/jest:run";

            return branchAndMerge(updateProjectInWorkspace(workspace, projectName, project));
        }
    ]);
}