0.33.2 • Published 2 months ago

skott v0.33.2

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

How to use skott

Install

You can install skott either locally or globally

npm install skott 
// or
npm install skott -g

JavaScript API

import skott from "skott";

const { getStructure, getWorkspace, useGraph, findUnusedDependencies } = await skott({
  /**
   * (Optional) Entrypoint of the project. If not provided, `skott` will search for all
   * supported files starting from the current working directory.
   * Defaults to `""`
   */ 
  entrypoint: "src/index.ts",
  /**
   * (Optional) Ignore pattern that applies during file traversal and module 
   * resolution. Each module matching the pattern will be discarded from the 
   * graph.
   * Defaults to `none`;
   */ 
  ignorePattern: "src/examples/**/*",
  /**
   * (Optional) Whether to run Skott using the incremental pattern. By setting "true",
   * Skott will create a `.skott/cache.json` file to only detect and re-process what
   * changed since the last analysis.
   * Defaults to `true`;
   */ 
  incremental: true,
  /**
   * (Optional) restricts file discovering when building the graph. 
   * Defaults to `[".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"]`
   */ 
  fileExtensions: [".ts", ".tsx"],
  /**
   * (Optional) Max depth search for circular dependencies. This can be useful for 
   * performance purposes. 
   * Defaults to `POSITIVE_INFINITY`.
   */
  circularMaxDepth: 20,
  /**
   * (Optional) Sets the base directory to start the analysis from. It's useful
   * when you want to run skott from a parent directory targetting a specific sub
   * directory (in the context of monorepo for instance). Using `cwd=some-path` 
   * is equivalent to `cd some-path && skott`.
   * Defaults to `process.cwd()`.
   */
  cwd: "./",
  /**
   * (Optional) Whether the base directory of the entrypoint should be included in relative 
   * file paths. For the specified `src/index.ts` above, it would consider the 
   * root path to be `./` consequently `src/` would never appear in any file paths.
   * Note: `includeBaseDir` can only be set to "true" when there is an `entrypoint`
   * provided.
   * Defaults to `false`.
   */
  includeBaseDir: false,
  /**
   * (Optional) Whether third-party dependencies (npm) and/or builtin (Node.js core modules)
   * should be added in the graph and/or TypeScript type-only import should be followed. 
   * Defaults to `thirdParty=false`, `builtin=false`, and `typeOnly=true`.
   */
  dependencyTracking: {
    thirdParty: true,
    builtin: true,
    typeOnly: true
  },
  /**
   * (Optional) Provide a custom tsconfig file to help skott resolve path aliases.
   * When extending some other tsconfig files, skott will be able to parse
   * all the way up all the path aliases referenced. 
   * Defaults to `tsconfig.json`.
   */
  tsConfigPath: "./tsconfig.json",
  /**
   * (Optional) Provide a path to the package.json that should be used to detect
   * unused third-party dependencies.
   * Defaults to `package.json`.
   */
  manifestPath: "./package.json",
  /**
   * (Optional) Provide custom dependency resolvers to take full control over the
   * content that will be added to the graph nodes.
   * Defaults to `EcmaScriptModuleResolver` which is used a standard dependency
   * resolver for ECMAScript projects.
   */
  dependencyResolvers: [new RushResolver()],
  /**
   * (Optional) Enable verbose internal logging.
   * Defaults to `false`
   */
  verbose: true,
  /**
   *
   * (Optional) If this function is provided, Skott will build a separate graph of links
   * between entire groups of modules, which can be later accessed as `groupedGraph` in the
   * result of `getStructure` call.
   *
   */
  groupBy: (path) => {
    if (path.includes("core")) return "core";
    if (path.includes("feature-a")) return "feature-a";

    return undefined;
  };
});

More API examples can be found there.

Command line interface

skott exposes a CLI directly using features from the core library. All the options shown from the API can be used from the CLI, please use skott --help to see how to express them via the command line.

When the library installed locally you can run:

Providing an entrypoint:

$ ./node_modules/.bin/skott src/index.js

When the library is installed globally:

$ skott src/index.js

Note: The CLI output might be massive, so don't hesitate to pipe the stdout into a file:

$ skott --displayMode=file-tree > skott.txt

Run a global analysis from the current working directory:

Using this command, skott will deeply search for all ".ts" and ".tsx" files starting from cwd

$ skott --fileExtensions=.ts,.tsx

skott offers many ways to visualize the generated graph.

Embedded Web Application

skott embeds a display mode "skott --displayMode=webapp" allowing you to visualize more precisely dependencies and the links between them. Here is an overview of what you can do:

As shown above Third-party and Built-in dependencies can be toggled when they are tracked (by providing --trackThirdPartyDependencies and --trackBuiltinDependencies to the CLI).

When Circular dependencies are found in the graph, they can also be toggled via the Node visualization options as shown below:

skott also offers other visualization modes, for instance static files (.svg, .png, .md, .json).

Note: this static file generator is provided via a skott plugin @skottorg/static-file-plugin that needs to be installed. So before using svg/png/md/json be sure to install the appropriate @skottorg/static-file-plugin plugin. Using the Node resolution algorithm, skott will be able to find the plugin on its own, no need to define it anywhere.

$ npm install @skottorg/static-file-plugin
$ skott src/index.js --displayMode=svg

For skott itself, the following .svg file is generated:

On the CLI side, here are some examples of output generated:

Example targetting Fastify using "graph" display mode:

Using "file-tree" display mode:

When asking for circular dependencies to be found (using the --showCircularDependencies option):

See all the options of the CLI running:

$ skott --help

More about the JavaScript API

To initialize the dependency graph, the default exported function must be used first.

Once executed, the default function returns a set of functions to retrieve some information about the graph just built.

import skott from "skott";

const { getStructure } = await skott({
  entrypoint: "index.js",
  // ...rest of the config
});

const { graph, files } = getStructure();
console.log(graph); // logs { "index.js": { id: "index.js", adjacentTo: [], body: {...} } };
console.log(files); // logs [ "index.js" ]

Graph API

To easily consume the graph that was emitted while exploring the project, skott exposes a graph API including various methods to traverse all the nodes, collect parent and children dependencies, find circular dependencies, and more.

import skott from "skott";

const { useGraph } = await skott();

const { 
  getFileNode,
  traverseFiles, 
  collectFilesDependencies, 
  collectFilesDependingOn, 
  findLeaves, 
  findCircularDependencies, 
  hasCircularDependencies 
} = useGraph();

Graph walking

const { useGraph } = await skott();
const { traverseFiles } = useGraph();

// Starting from any node, walking the whole graph
for(const file of traverseFiles()) {
  // SkottNode { }
}

// Starting from a specifc node, walking the graph from it
for(const file of traverseFiles({ rootFile: "index.js" })) {
  // SkottNode { }
}

// By default, skott will collect "shallow first" files in a Breadth-First fashion
// meaning the iterator will first emit direct module imports for each visited node.
// If the traversal needs to be "deep first" instead i.e. you first want to go deep
// down through the graph until meeting a leaf you might want to use "deepFirst" option
// to turn the traversal into Depth-First search.

for(const file of traverseFiles({ rootFile: "index.js", traversal: "deepFirst" })) {
  // SkottNode { }
}

Search for circular dependencies

import skott from "skott";

const { useGraph } = await skott({
  entrypoint: "index.js",
});
const { findCircularDependencies, hasCircularDependencies} = useGraph();

// Imagine that starting from "index.js" skott detects a circular dependency
// between "core.js" and "utils.js" files

console.log(findCircularDependencies()); // logs [ [ "core.js", "utils.js" ] ]
console.log(hasCircularDependencies()); // logs "true"

Search for leaves (nodes with no children)

leaf.js

console.log("I'm a leaf because I have no dependency");

index.js

import skott from "skott";

const { useGraph } = await skott({
  entrypoint: "leaf.js",
});
const { findLeaves } = useGraph();

console.log(findLeaves()); // logs [ "leaf.js" ]

Deeply or Shallowly search for parent or children dependencies of a given node

children.js

export const childrenFunction = () => {};

parent.js

import { childrenFunction } from "./children.js";

childrenFunction();

index.js

import skott from "skott";
import { CollectLevel } from "skott/graph/traversal";

const { useGraph } = await skott({
  entrypoint: "parent.js",
});
const { collectFilesDependingOn, collectFilesDependencies } = useGraph();

// CollectLevel.Deep or CollectLevel.Shallow. In that case just one level so we can use Shallow

console.log(collectFilesDependingOn("children.js", CollectLevel.Shallow)); 
// logs [ SkottNode { id: "parent.js" } ]

console.log(collectFilesDependencies("parent.js", CollectLevel.Shallow)); 
// logs [ SkottNode { id: "children.js" } ]

Find unused dependencies

skott provides a way to walk through dependencies listed in the current working directory manifest (package.json) and compare them to what it founds and marked as "used" during the analysis. The "use" marking will be done when a third-party module appears to be imported in the source code that was walked. All the third-party dependencies that are not used in the traversed files will be returned as "unused".

Additionnally to the source code analysis, skott integrates with depcheck allowing it to take a peak at "implicit" dependencies and emit hypothesis about whether some devDependencies are unused or not, by walking through most common config files.

Note: finding precisely implicit dependencies is hard so please double check dependencies part of the devDependencies that are marked as "unused" by the analysis. If some dependencies (production deps) appear to be unused but are indeed used somewhere in the codebase, it could mean two things:

  • the input files pattern you provided to skott don't cover the parts of the graph where the dependency is used
  • the dependency is used nowhere through the source code files walked, meaning that it should probably be moved to devDependencies or just get removed.

In any case, unused dependencies just raise an alert so I would advise to double check before getting rid of a dependency.

import skott from "skott";

const { findUnusedDependencies } = await skott();

const { thirdParty } = await findUnusedDependencies();
// [ lodash, rxjs, typescript ]

Explore file node metadata

Take for instance lib.js with the following content:

lib.js

import * as fs from "node:fs";
import { parseScript } from "meriyah";

And given the entrypoint main.js module below:

main.js

import skott from "skott";

const { getStructure } = await skott({
  entrypoint: "lib.js",
  dependencyTracking: {
    builtin: true,
    thirdParty: true,
    typeOnly: true
  }
});

const { graph } = getStructure();
console.log(graph["lib.js"].body);

// Prints
{ 
  size: 70, 
  thirdPartyDependencies: ["meriyah"], 
  builtinDependencies: ["node:fs"] 
}

Explore your architecture

Skott allows you to explore the relationships between parts of your architecture, not just between specific modules.

To do that you need to tell Skott, how exactly modules in your project are combined into a architecture blocks - use groupBy API for that:

const instance = await skott({
  groupBy: (path) => {
    if (path.includes("src/core")) return "core";
    if (path.includes("src/feature-a")) return "feature-a";

    // ... other conditions

    // if no match
    return undefined;
  }
});

const { groupedGraph } = instance.getStructure();

groupedGraph["core"];
// { id: "core", adjacentTo: [], body: { size, files, ... } }

groupedGraph["feature-a"];
// { id: "feature-a", adjacentTo: ["core", ...], body: { size, files, ... } }

Explore workspace content

Let's suppose we're currently using pnpm workspaces and we have the following workspace:

/apps/my-app/package.json
/libs/my-lib/package.json

Skott allows you to traverse the workspace and collect all manifest files with their own dependencies.

main.js

import skott from "skott";

const { getWorkspace } = await skott();

console.log(getWorkspace());
// Prints
{ 
  "my-app": {
    dependencies: {
      // 
    },
    devDependencies: {
      //
    },
    peerDependencies: {
      //
    }
  },
  "my-lib": {
    dependencies: {
      // 
    },
    devDependencies: {
      //
    },
    peerDependencies: {
      //
    }
  }
}

This feature could help creating a dependency graph only using manifests instead of parsing and traversing the whole source code graph using static analysis.

Contributors

0.33.2

2 months ago

0.33.1

2 months ago

0.33.0

2 months ago

0.32.1

3 months ago

0.32.0

3 months ago

0.31.4

5 months ago

0.30.3

7 months ago

0.30.2

7 months ago

0.31.3

7 months ago

0.31.2

7 months ago

0.31.1

7 months ago

0.31.0

7 months ago

0.30.1

7 months ago

0.30.0

8 months ago

0.29.0

8 months ago

0.27.0

10 months ago

0.25.1

10 months ago

0.25.0

10 months ago

0.23.0

11 months ago

0.29.1

8 months ago

0.28.1

10 months ago

0.28.0

10 months ago

0.26.0

10 months ago

0.24.0

11 months ago

0.22.1

11 months ago

0.28.4

8 months ago

0.28.3

8 months ago

0.28.2

8 months ago

0.20.1

1 year ago

0.20.0

1 year ago

0.19.0

1 year ago

0.17.2

1 year ago

0.19.1

1 year ago

0.17.3

1 year ago

0.15.0

1 year ago

0.15.1

1 year ago

0.17.0

1 year ago

0.15.2

1 year ago

0.17.1

1 year ago

0.15.3

1 year ago

0.21.1

12 months ago

0.16.3

1 year ago

0.16.0

1 year ago

0.16.1

1 year ago

0.18.0

1 year ago

0.16.2

1 year ago

0.22.0

12 months ago

0.20.2

1 year ago

0.11.0

1 year ago

0.12.0

1 year ago

0.11.1

1 year ago

0.13.0

1 year ago

0.12.1

1 year ago

0.14.0

1 year ago

0.12.2

1 year ago

0.11.3

1 year ago

0.14.1

1 year ago

0.12.3

1 year ago

0.11.4

1 year ago

0.12.4

1 year ago

0.11.5

1 year ago

0.12.5

1 year ago

0.12.6

1 year ago

0.10.0

2 years ago

0.3.0

2 years ago

0.9.0

2 years ago

0.8.0

2 years ago

0.9.1

2 years ago

0.5.0

2 years ago

0.4.1

2 years ago

0.4.0

2 years ago

0.7.0

2 years ago

0.6.0

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago

0.0.22

2 years ago

0.0.21

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago