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

tscw-config

v1.0.1

Published

Run tsc on files with tsconfig respected

Downloads

611

Readme

Motivation

Running tsc locally will compile the closest project defined by a tsconfig.json, or you can compile a set of TypeScript files by passing in a glob of files you want. When input files are specified on the command line, tsconfig.json files are ignored. - tsc CLI Options

tscw-config lets you run tsc on files while keeping tsconfig.json respected.

[!NOTE]
tscw-config stands for tsc with config.

Use cases

A common use case for running tsc on certain files is when used in a pre-commit hook. e.g. lint-staged.

For example, you may want to type-check staged files by running tsc --noEmit foo.ts bar.ts. In this case tsc will ignore the tsconfig.json, using -p tsconfig.json with files will result in an error.

You can explicitly pass the CLI options in. e.g. --strict --allowSyntheticDefaultImports ... to tsc, but that can be tedious.

Using tscw is much easier: tscw --noEmit foo.ts bar.ts -p tsconfig.json.

Getting Started

tscw seamlessly integrates with most popular package managers, including:

  • npm
  • pnpm
  • Yarn
  • Yarn (Plug’n’Play)

npm:

npm i -D tscw-config

pnpm:

pnpm add -D tscw-config

yarn:

yarn add -D tscw-config

Usage

After installing tscw-config, you can use tscw the same way you use tsc, but tscw will not ignore your tsconfig.json when files are specified.

By default, tscw uses the root tsconfig.json if no one is specified.

# root tsconfig.json is used
npx tscw foo.ts

# specify a tsconfig
npx tscw --noEmit foo.ts -p ./config/tsconfig.json
# or
npx tscw --noEmit foo.ts --project ./config/tsconfig.json

# match ./foo.ts, ./bar.ts ...
npx tscw *.ts

# match ./foo/baz.ts, ./bar/foo.ts ...
npx tscw **/*.ts

# you can even use it without any files specified
npx tscw --noEmit # it is the same as npx tsc --noEmit

Here's an example of using it in a .lintstagedrc.js file. You can also check out the .lintstagedrc.mjs in this project.

/**
 * Passing absolute path is fine, but relative path is cleaner in console.
 * @param {string[]} files
 */
const typeCheck = files => {
  const cwd = process.cwd();
  const relativePaths = files.map(file => path.relative(cwd, file)).join(" ");
  return `npx tscw --noEmit ${relativePaths}`;
};

export default {
  "**/*.{ts,mts,cts,tsx}": [prettier, typeCheck, eslint],
};

if your're using yarn PnP, instead of using npx tscw, use yarn tscw:

yarn tscw foo.ts

[!NOTE]
tscw supports all CLI options supported by tsc.

API

tscw-config also exposes a function to run tsc programmatically, but in most cases you should use the CLI tscw:

import tscw from 'tscw-config';

const result = await tscw`foo.ts --noEmit -p tsconfig.json`
// or
const result = await tscw("foo.ts", "--noEmit", "-p", "tsconfig.json");

Return type

type Result = Promise<SpawnResult | SpawnError>;

interface SpawnResult {
  pid: number;
  exitCode: number;
  stdout: string;
  stderr: string;
}

interface SpawnError {
  pid: null;
  exitCode: number;
  stderr: string;
  stdout: null;
}

In the following scenarios, the function returns Promise<SpawnError>:

  • No package.json is found in the root of your project.
  • No tsconfig.json is found in the root of your project if no tsconfig is passed to the function.
  • Specified files not found.
  • Missing argument for -p or --project.
import tscw from "tscw-config";

const result = await tscw`foo.ts --noEmit -p noSuchFile.json`;

/* 
result: {
      pid: null,
      exitCode: 1,
      stderr: "Can't find noSuchFile.json",
      stdout: null,
   };
*/

Otherwise the function returns Promise<SpawnResult>, which means that the args are successfully passed to tsc.

Under the hood, tscw uses spawn to run tsc, the result from tsc is stored in result.stdout even when exitCode is not 0.

// containTypeError.ts

type A = number;

const _a: A = "";
import tscw from "tscw-config";

const result1 = await tscw`containTypeError.ts --noEmit -p tsconfig.json --pretty false`;

console.log(result1.pid); // number
console.log(result1.exitCode); // 1
console.log(result1.stdout); // "containTypeError.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'.\r\n"
console.log(result1.stderr); // ""

const result2 = await tscw`noTypeError.ts --noEmit -p tsconfig.json`;

console.log(result2.pid); // number
console.log(result2.exitCode); // 0
console.log(result2.stdout); // ""
console.log(result2.stderr); // ""

[!NOTE]
By default, stdout contains ANSI escape code, if you want stdout to be plain text, pass --pretty false to the function.

/* 
"\x1B[96mcontainTypeError.ts\x1B[0m:\x1B[93m3\x1B[0m:\x1B[93m7\x1B[0m - \x1B[91merror\x1B[0m\x1B[90m TS2322: \x1B[0mType 'string' is not assignable to type 'number'.\r\n" +
    '\r\n' +
    '\x1B[7m3\x1B[0m const _a: A = "";\r\n' +
    '\x1B[7m \x1B[0m \x1B[91m      ~~\x1B[0m\r\n' +
    '\r\n' +
    '\r\n' +
    'Found 1 error in containTypeError.ts\x1B[90m:3\x1B[0m\r\n' +
    '\r\n'
 */

Notice that when you pass a file to the function using a relative path, it is relative to the current working directory (cwd) when you run the script, not relative to the file where this function is used.

[!IMPORTANT]
In most cases, you should use the CLI tscw when you want the process to fail if the compilation fails. For example in CI pipeline, lint-staged, etc. Executing the function will not cause the process to fail even if the returned exitCode is not 0, unless you explicitly exit the process with the returned exitCode, like tscw does.

How it works

  1. Argument Parsing:
    • The script processes user-specified arguments to handle flags and file paths.
  2. Finding tsconfig.json:
    • If no tsconfig.json file is specified via the -p or --project flag, the nearest tsconfig.json file will be used for the current workspace.
    • The script first looks for the current working directory, if not found, it goes all the way up until the level where package.json is located.
  3. Temporary File:
    • A temporary file is created to store the content of the tsconfig.json file being used.
    • It adds/replaces the "files" field with the files specified.
    • It empties the "include" field.
  4. Running tsc:
    • It runs tsc with the temp file and any specified flags.
  5. Cleanup:
    • The script removes the temporary file when the script exits or receives certain signals(SIGINT, SIGHUP, SIGTERM).

[!NOTE]
Windows has limited support for process signals compared to Unix-like systems, especially when process.kill is used to terminate a process, signal will not be caught by the process, therefore cleaning up the temp file is a problem. See Signal events.

Technically, to fix the cleanup problem, using options.detached for a child process would be enough, but lint-staged takes the approach of terminating all the child processes by calling process.kill on the tasks that are KILLED(When multiple tasks are running concurrently, if one task FAILED, other tasks will be KILLED).

In order to properly fix this problem, tscw-config creates a daemon to handle the cleanup task if it is running on Windows. The daemon will exit gracefully after the temporary file is deleted or, at most, after 1 minute.