Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

đŸ–Œī¸ Sketch

Templating, made portable

Templating is awesome. While it is mostly used for websites, it can be a powerful tool for developers as it can allow us to automate some repetitive tasks like setting up projects which share the same basic structure.

There are very powerful templating engines, which however require a library to work with them, which makes them not exactly portable.

And there are pieces of software like ansible, which provide better portability and great templating capabilities but still require a significant amount of setup, which makes them slightly overkill for simpler tasks like generating a few files and directories.

This is why I made sketch.

sketch is portable enough so that the most basic setups require zero configuration whatsoever, but can also sustain more complex setups like extensible, multi-format configuration files with overrides available via cli flags in most cases.

It makes it very easy to define a template for a file or for an entire project, and set it all up with just a single command.

And there's more: by leveraging the Tera rendering engine, you can take advantage of its built-in filters and functions which can allow you to populate your templates not only with static values but with dynamically calculated ones, such as the current time of the day, a manipulated string, an env variable, or the result of an arithmetic calculation.

Every setup that used to require several repetitive steps can now be executed in just a single command.

And speaking of commands, you can use sketch to render the output to stdout (so that it can be piped to other commands), or even to execute it as a shell command directly, which expands its utility for even more creative and flexible use cases.

Typescript Project Generation

sketch was initially conceived as a typescript-centric tool, because I needed a way to manage the chaotic nature of typescript projects in a structured, orderly and easily reproducible way, that would also give me enough flexibility to be able to customize how each project piece is defined and generated.

It contains special commands and tools to generate new typescript projects and packages, such as the ability to define reusable, extensible package.json and tsconfig presets, as well as commonly used configuration files such as those belonging to oxlint or vitest.

Documentation

You can find the full documentation in the dedicated website.

Command-Line Help for sketch

This document contains the help content for the sketch command-line program.

Command Overview:

sketch

đŸ–Œī¸ Templating made portable. A tool to generate files, project structures or shell commands via custom templates

Usage: sketch [OPTIONS] <COMMAND>

Subcommands:
  • ts — Launches typescript-specific commands
  • init — Creates a new git repo with a gitignore file. Optionally, it sets up the git remote and the pre-commit config
  • new — Generates a new config file with some optional initial values defined via the cli flags
  • render — Renders a single template to a file or to stdout
  • render-preset — Renders a templating preset defined in the configuration file
  • exec — Renders a template and launches it as a command
Options:
  • -c, --config <FILE> — Sets a custom config file
  • --ignore-config-file — Ignores any config files, uses cli instructions only
  • --shell <SHELL> — The shell to use for commands [default: cmd.exe on windows and sh elsewhere]
  • --debug — Activates debugging mode
  • --root-dir <DIR> — The base path for the generated files [default: "."]
  • --templates-dir <DIR> — The path to the templates directory, starting from the cwd (when set via cli) or from the config file (when defined in one of them)
  • --no-overwrite — Does not overwrite existing files
  • --dry-run — Aborts before writing any content to disk
  • -s, --set <KEY=VALUE> — Set a variable (as key=value) to use in templates. Overrides global and local variables

sketch ts

Launches typescript-specific commands

Usage: sketch ts [OPTIONS] <COMMAND>

Subcommands:
  • monorepo — Generates a new typescript monorepo
  • package — Generates a new typescript package
Options:
  • --package-manager <NAME> — The package manager being used. [default: pnpm]

    Possible values: pnpm, npm, deno, bun, yarn

  • --no-default-deps — Does not add default dependencies to new package.json files (typescript and oxlint, plus vitest if enabled)

  • --version-range <KIND> — The kind of version ranges to use for dependencies that are fetched automatically. [default: minor]

    Possible values: patch, minor, exact

  • --catalog — Uses the pnpm catalog for default dependencies

  • --no-convert-latest — Does not convert dependencies marked as latest to a version range

sketch ts monorepo

Generates a new typescript monorepo

Usage: sketch ts monorepo [OPTIONS]

Options:
  • -n, --name <NAME> — The name of the root package [default: "root"]
  • -t, --ts-config <output=PATH,id=ID> — One or many tsconfig files for the root package. If unset, defaults are used
  • -p, --package-json <ID> — The id of the package.json preset to use for the root package
  • --no-oxlint — Does not generate an oxlint config at the root
  • -i, --install — Install the dependencies at the root after creation

sketch ts package

Generates a new typescript package

Usage: sketch ts package [OPTIONS] [DIR]

Arguments:
  • <DIR> — The new package's directory, starting from the root_dir. Defaults to the name of the package
Options:
  • -p, --preset <PRESET> — The package preset to use
  • --update-root-tsconfig — Whether the tsconfig file at the workspace root should receive a reference to the new package
  • --no-vitest — Does not set up vitest for this package
  • --oxlint — Sets up an oxlint config file for this package
  • -i, --install — Installs the dependencies with the chosen package manager
  • --app — Marks the package as an application (only relevant for default tsconfigs)
  • --library — Marks the package as a library (only relevant for default tsconfigs)
  • -n, --name <NAME> — The name of the new package. If dir is set, it defaults to the last segment of it
  • -t, --ts-config <output=PATH,id=ID> — One or many tsconfig files for this package. If unset, defaults are used
  • --package-json <ID> — The id of the package.json preset to use for this package

sketch init

Creates a new git repo with a gitignore file. Optionally, it sets up the git remote and the pre-commit config

Usage: sketch init [OPTIONS]

Options:
  • --no-pre-commit — Does not generate a pre-commit config
  • --remote <REMOTE> — The link to the git remote to use

sketch new

Generates a new config file with some optional initial values defined via the cli flags

Usage: sketch new [OUTPUT]

Arguments:
  • <OUTPUT> — The output file [default: sketch.yaml]

sketch render

Renders a single template to a file or to stdout

Usage: sketch render [OPTIONS] <OUTPUT_PATH|--stdout>

Arguments:
  • <OUTPUT_PATH> — The output file (relative from the cwd)
Options:
  • --stdout — Output the result to stdout
  • -f, --file <FILE> — The path to the template file, from the cwd
  • -i, --id <ID> — The id of the template to use
  • -c, --content <CONTENT> — The literal definition for the template

sketch render-preset

Renders a templating preset defined in the configuration file

Usage: sketch render-preset <ID>

Arguments:
  • <ID> — The id of the preset

sketch exec

Renders a template and launches it as a command

Usage: sketch exec [OPTIONS] [CMD]

Arguments:
  • <CMD> — The literal definition for the command's template
Options:
  • -f, --file <FILE> — The path to the command's template file
  • -t, --template <TEMPLATE> — The id of the template to use

This document was generated automatically by clap-markdown.

Configuration

Sketch supports yaml, json and toml formats for its configuration file.

The configuration file can be manually specified with the -c or --config flag. If none is provided, the cli will automatically look for a file named sketch.{yaml, json, toml} in the cwd. If none is found, then it will look in the XDG_CONFIG_HOME directory. If no config is found there, it will use default values.

You can also use the --ignore-config-file flag to temporarily ignore configuration files and only use cli-set values.

Top Level Configuration

These are the defaults for the top level configuration values:

# yaml-language-server: $schema=../schemas/latest.json

# The root path for the generated files.
# All output paths will start from this directory.
root_dir: .

# Enables debugging mode
debug: false

# Extends other configuration files
extends: []

# Settings for the .gitignore file generated via the `init` command.
# This can be:
# - A list of directives (to add to the defaults)
# - A literal string (to replace defaults)
gitignore: []

# Variables for templates
vars: {}

# Do not overwrite existing files
no_overwrite: false

# The pre-commit settings, used for the .pre-commit-config file
# generated with the `init` command.
# Can be:
# - A boolean, to use defaults or disable it altogether
# - A list of repos to populate the config with
# The default setting includes just the gitleaks repo.
pre_commit: true

# The shell to use for the `exec` command. Defaults to `cmd.exe` on windows and `sh` elsewhere.
shell: null

# A map of template definitions.
templates: {}

# A map of templating presets.
templating_presets: {}

# The relative path from the config file (or from the cwd, if set via the cli flag)
# to the directory containing templates.
templates_dir: null

# The configuration for typescript-related commands.
typescript: null

Generating Config Files

You can use the sketch new command to generate a new configuration file in the desired output file and format. The default is sketch.yaml, but any name is supported as long as the format is either json, toml or yaml, and the configuration file is specified with the -c or --config flag.

If extra arguments are provided, the generated config will populated with the values of these arguments. So if we run sketch --templates-dir "/path/to/templates", templates_dir will be set to that path in the generated config.

âš ī¸ Since the default sketch.yaml file is always automatically detected, if there is such a file in the cwd and you want to create a new config with certain values, you need to use --ignore-config-file to ignore the config file's values.

Command:

sketch --templates-dir tests/templates --shell zsh --set hello="there" --set general="kenobi" new tests/output/generated_configs/with_extras.yaml

Output:

shell: zsh
root_dir: tests/output
templates_dir: tests/templates
no_overwrite: false
pre_commit: true
templates: {}
templating_presets: {}
vars:
  hello: there
  general: kenobi

Repo Setup

You can also use the init command to create a new git repo. This command will:

  1. Create a new git repo in the specified root_dir.
  2. If a --remote is provided, it will also add that remote as the origin/main for the repo.
  3. Unless pre-commit is disabled, it will generate a new .pre-commit-config.yaml file in the root, with the repos specified in the config file (if there are any, otherwise it will just add the gitleaks repo). It will then run pre-commit install to install the given hooks.

Lsp Integration

In order to ensure the best development experience, it is highly recommended to set up your IDE to load the json schema for Sketch's configuration file.

This will provide type-safety, autocompletion and additional documentation when writing config files.

Every time a new version is released, the json schema for the configuration file will be updated in the github repo. Each version will have its own distinct schema file, and the latest.json file will track the schema for the latest version.

You can then use this schema with the yaml, toml or json language servers to get autocompletion, type information and documentation for each element of the config file.

This can be done by configuring the specific LSP in your editor, or simply by using a special comment at the top of your file that links to the config's json schema.

Examples

Yaml:

# yaml-language-server: $schema=https://github.com/Rick-Phoenix/sketch/blob/main/schemas/latest.json

Json:

{
  "$schema": "https://github.com/Rick-Phoenix/sketch/blob/main/schemas/latest.json"
}

Toml:

#:schema https://github.com/Rick-Phoenix/sketch/blob/main/schemas/latest.json

Extensible Configurations

Configuration files can extend one another by using the extends field:

extends: ["other_config.yaml"]

Where the path being used can be either an absolute path or a relative path starting from the original config file.

The merging strategy works as follows:

  • For conflicting values, such as opposite booleans, the previous value will be overridden.
  • For non-conflicting values such as maps (for example, the global template vars map), the values will be merged.

Templating

Sketch uses the Tera templating engine to render custom templates. Every template, whether it's defined in a file, in the config file, or inlined, can take advantage of all of Tera's functionalities, like using filters, functions, defining and using macros, performing operations and importing other templates.

Special variables

Sketch provides some special variables prefixed with to get access to some commonly used information. All of the following variables are available in templates, prefixed with sketch_ (i.e. sketch_os and so on).

  • cwd
  • tmp_dir (obtained with env::temp_dir)
  • home (obtained with env::home_dir)
  • os (env OS)
  • user (env USER)
  • hostname (env HOSTNAME)
  • arch (env HOSTTYPE)
  • xdg_config (env XDG_CONFIG_HOME)
  • xdg_data (env XDG_DATA_HOME)
  • xdg_state (env XDG_STATE_HOME)
  • xdg_cache (env XDG_CACHE_HOME)

Filters and functions

All of the builtin filters and functions for Tera are available.

On top of that, Sketch adds a uuid function that can be used to generate a v4 UUID.

Global and Local Context

Variables are evaluated based on the locality of their context. Variables set via cli with the --set flag have the highest priority, followed by variables defined in a local context (the context field in a template preset) and by global variables defined in the config file.

âš ī¸ Variables defined with the --set flag must be formatted in valid json. This means that, for example, strings must be wrapped in escaped quotes.

Rendering Templates

Templates can be defined in files or directly within a command. The variables can also be defined in the config file or as part of a command.

# This will be the starting path for the templates' output paths
root_dir = "../output/custom_templates"

vars = { my_var = 15 }

# We can define templates directly in the config file
templates = { lit_template = "my_var: {{ my_var }}" }

# Or as files inside a directory
templates_dir = "../templates"

We can then render them singularly, or in groups.

â„šī¸ Single templates can be rendered to stdout with the --stdout flag.

Rendering A Single Template

Using A File

If templates_dir is set, all the files in templates_dir can be referenced with their ID, which is the relative path to them from templates_dir.

So if we have this directory structure in our templates_dir:

.
├── abc.j2
└── subdir
    └── nested.j2

We can render the contents of nested.j2 by running

sketch render --id subdir/nested.j2 from_template_file.yaml

However, we can also render content from any file, by using the -f flag. In this case, the relative paths will be resolved from the cwd.

Example:

sketch render -f tests/custom_templates/single_file.j2 from_single_file.yaml

From A Config Definition

Templates can be defined inside the configuration file:

templates = { lit_template = "my_var: {{ my_var }}" }

sketch render --id lit_template from_config_template.yaml

From Literal Definition

...or directly as part of the command:

sketch --set location="Isengard" render --content "they're taking the hobbits to {{ location }}!" from_literal.txt

Rendering Presets

Templates can also be defined in groups, which can be rendered all at once starting from the same root_dir.

This is how groups are defined:

# A group of templates that will be rendered together
templating_presets.test = [
  # Full path to the template file from `templates_dir`
  { template = "subdir/nested.j2", output = "new_dir/nested.yaml" },

  # Only using the global variables
  { template = "lit_template", output = "with_global_var.yaml" },

  # Using a local override
  { template = "lit_template", output = "with_override.yaml", context = { my_var = 20 } },

  # Defining a literal template
  { template = { name = "literal_inlined", content = "my_var: {{ my_var }}" }, output = "literal_inlined.yaml" },
]

When we run the command

sketch render-preset test

These templates will be rendered together, so that the output of the specified root_dir will look like this:

.
├── literal_inlined.yaml
├── new_dir
│   └── nested.yaml
├── with_global_var.yaml
└── with_override.yaml

Executing Templates

With the sketch exec command, you can render a template and then execute it as a shell command.

To do this, you can use a regular file, a template with an id (either defined in the config file or inside the templates_dir), or even one defined within the command itself.

From Template

Let's say that we have a file named cmd_template.j2 inside our templates_dir, and it looks like this:

echo "{{ category }} engine... {{ category }}... argh!" > output_from_templates_dir.txt

â„šī¸ You do not have to use .j2 as an extension for the template files. Any extension can be used.

We can refer to it by its id:

sketch --set category="gp2" exec -t cmd_template.j2

This will create a file containing

gp2 engine... gp2... argh!

From File

We can also use a file that is not inside templates_dir by using the -f flag and providing the path to said file. Relative paths will be resolved starting from the cwd.

So let's say that we have this template file:

echo "all the time you have to leave the {{ something }}!" > output_from_file.txt

We launch the command like this:

sketch --set something="space" exec -f tests/commands_tests/cmd_from_file.j2

To create a file containing:

all the time you have to leave the space!

From Literal Template

Templates can also be defined directly as part of the command.

sketch --set general="kenobi" exec 'echo "hello there!\ngeneral {{ general }}." > command_output.txt'

This creates a file containing:

hello there!
general kenobi.

Typescript Projects

Sketch can be used to quickly generate Typescript monorepos and packages, and uses modular and extensible presets to define some reusable elements that can be shared among different kinds of typical TS projects.

These are the top level configuration settings for Typescript projects, with their default values:

# yaml-language-server: $schema=../../schemas/latest.json

typescript:
  # Convert `latest` to a version range for dependencies
  no_convert_latest_to_range: false

  # Use this kind of version range when converting from `latest`
  version_range: patch

  # Do not add default dependencies to new `package.json` files (typescript and oxlint, plus vitest if enabled)
  no_default_deps: false

  # Use the pnpm catalog for default dependencies
  catalog: false

  package_manager: pnpm

  # Configuration for the generated pnpm-workspace.yaml file
  pnpm_config:
    # All dirs listed here will be created automatically
    packages: ["packages/*"]

  # A map containing extensible package.json presets
  package_json_presets: {}

  # A map containing extensible package presets
  package_presets: {}

  # A map containing extensible tsconfig presets
  tsconfig_presets: {}

  # A map of contributors and maintainers, which can be referred to
  # in package.json presets with their ID
  people: {}

  # The configuration for the root package
  # (only relevant when creating a monorepo)
  root_package: null

Monorepo Generation

Once we have our settings (or not, if we want to use defaults), we can run

sketch ts monorepo

to create our new Typescript monorepo.

For example, if we use this config:

typescript:
  root_package:
    name: workspace

    package_json:
      license: Apache-2.0
      devDependencies:
        husky: latest

    # The tsconfig files to generate at the root.
    # If left empty, defaults will be used.
    ts_config:
      - output: tsconfig.options.json
        config:
          compilerOptions:
            verbatimModuleSyntax: true
            strict: true

      - output: tsconfig.json
        config:
          files: []

    # Literal oxlint configuration to generate at the root
    # of the workspace.
    # Can be set to `true` to use defaults.
    oxlint: |
      {
        "plugins": ["unicorn", "typescript", "oxc"]
      }

And the package.json file of the root package will be like this:

{
  "name": "workspace",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "license": "Apache-2.0",
  "packageManager": "pnpm",
  "scripts": {},
  "devDependencies": {
    "husky": "^9.1.7", 
    "oxlint": "^1.16.0", 
    "typescript": "^5.9.2"
  },
  "dependencies": {}
}

You can also use the generate_templates field to automatically generate a certain file structure when the monorepo is generated.

Let's say, for example, that every time that you create a new monorepo, you always want to create a docker directory with a basic dev.dockerfile inside of it, so that you can quickly create a dev environment using docker.

For the root package, you would do so like this:

    generate_templates:
      - output: docker/dev.dockerfile
        template:
          name: dev_dockerfile
          content: |
            FROM node:23-alpine

            COPY . .
            EXPOSE {{ docker_dev_port }}
            CMD ["npm", "run", "dev"]

vars:
  docker_dev_port: 5173

So the final tree structure of the output directory will look like this:

.
├── .oxlintrc.json
├── docker
│   └── dev.dockerfile
├── package.json
├── packages
├── pnpm-workspace.yaml
├── tsconfig.json
└── tsconfig.options.json

â„šī¸ You can use the -i flag to install dependencies for the root package after creating the new monorepo.

Generating A Package

You can use the command sketch ts package to generate a new Typescript package.

For setting up the root package in a monorepo, go look in the dedicated section.

This is an example of a package-related configuration:

typescript:
  package_presets:
    frontend:
      # The name of the package. If this is unset and `dir` is set,
      # it will use the last segment from it.
      name: "@myproject/frontend"

      # The package.json config for this package. Can be a preset id
      # or a literal configuration.
      package_json:
        devDependencies:
          tailwindcss: "^4.0.0"

      # The directory for this package, starting from the `root_dir`
      dir: packages/frontend

      # The kind of package to generate. Only relevant if you use
      # default tsconfigs.
      kind: library

      # Can be set to true (for a default config), a literal config
      # or to false (which is the default setting) for no oxlint config.
      oxlint: false

      # Create a vitest setup.
      # Can be set to true to use defaults (which are as below).
      vitest:
        # Where the config file should be generated, starting from
        # the root of the package.
        # It defaults to the value of `tests_dir`
        out_dir: null

        # The directory to use for tests
        tests_dir: tests

        # The directory containing the setup files for tests.
        # (starting from tests_dir)
        setup_dir: setup

        # Can use a list of plugins which will be set up in the config file
        plugins: []

After setting things up, you can run

sketch ts package --preset frontend

To generate the new package.

Template rendering

You can use the generate_templates field to specify a list of templates that should also be generated whenever a package preset is being generated.

Let's say for example that in a type of package, you always need a schemas directory, where you import some common schemas from a shared package and define new ones.

You can use this feature to generate a file inside src/schemas/index.ts automatically like this:

      # Inside the package preset configuration
      generate_templates:
        - output: src/schemas/index.ts
          # This can be a template defined in the config file
          # or file path inside templates_dir
          template: schemas

templates:
  schemas: |
    import z from "zod";
    import { sharedSchemas } from "{{ schemas_package }}";

vars:
  schemas_package: "@myproject/schemas"

So the final tree structure of the output directory will look like this:

.
├── .oxlintrc.json
├── package.json
├── src
│   ├── index.ts
│   └── schemas
│       └── index.ts
├── tests
│   ├── setup
│   │   └── tests_setup.ts
│   └── vitest.config.ts
├── tsconfig.dev.json
├── tsconfig.json
└── tsconfig.src.json

â„šī¸ You can also use the -i flag to automatically install the dependencies with your selected package manager whenever a new package is created.

TsConfig Presets

The configuration file can contain a variety of presets that can be used for generating Tsconfig files.

These presets contain all of the fields of a normal tsconfig.json file, with the addition of an extra field called extend-presets, which allows you to extend a basic preset from another one.

â„šī¸ You can omit the ts_config field entirely, and the defaults will be used.

Example

Let's start with some basic starting point here:

Here we define two typical tsconfig settings to use for the packages in our workspace.

The app preset will not emit any .d.ts or .js files, whereas the library preset, which can be used for internal packages, will have emitDeclarationOnly set to true.

typescript:
  tsconfig_presets:
    app:
      compilerOptions:
        noEmit: true
      include:
        - src

    lib:
      compilerOptions:
        emitDeclarationOnly: true

Now let's say that we want to create another preset which starts from the app preset but adds the tests directory to include:

    testing:
      extend_presets: ["app"]
      # The two lists will be merged and deduped
      include: ["tests"]

Now we will set up a package that will generate 2 tsconfig files.

  • A basic tsconfig.json file, which we will define literally, and it will have just the files field set to an empty array.
  • One which will use the lib preset and extend it with an extra feature of our choice, and that will apply only to the files inside src. We will generate this in tsconfig.src.json.
  • A separate config that will take care of typechecking the files inside the tests directory, without emitting files. We will create this inside tsconfig.dev.json.
  package_presets:
    tsconfig-example:
      dir: packages/tsconfig-example
      name: "@examples/tsconfig"

      # Tsconfig can also be not defined at all,
      # and the default presets will be used.
      ts_config:
        - output: tsconfig.json
          # Literal definition
          config:
            files: []

        - output: tsconfig.dev.json
          # Only specifying the preset id here
          config: testing

        - output: tsconfig.src.json
          config:
            # Literal definition that extends a preset
            extend_presets: ["lib"]
            compilerOptions:
              verbatimModuleSyntax: true

After running the command

sketch ts package --preset tsconfig-example

We get the following files:

tsconfig.json

{
  "files": []
}

tsconfig.src.json

{
  "compilerOptions": {
    "emitDeclarationOnly": true,
    "verbatimModuleSyntax": true
  }
}

âœī¸ Note how the two configs have been merged!

tsconfig.dev.json

{
  "include": [
    "src",
    "tests"
  ],
  "compilerOptions": {
    "noEmit": true
  }
}

Package.json Presets

You can use the config file to store some package.json presets that you can easily reuse among different projects or packages with similar characteristics, like scripts or dependencies.

Just like tsconfigs and global configurations, package.json presets can also extend one another.

Example

typescript:
  package_json_presets:
    # The base from which all the packages from a specific project will start
    base:
      author:
        name: Bruce Wayne
        email: i-may-or-may-not-be-batman@gotham.com
      license: Apache-2.0
      bugs:
        url: joker.com

    # Another preset defining some common dependencies and scripts
    frontend:
      extends: ["base"]
      scripts:
        build: vite build
        dev: vite dev
      devDependencies:
        # By default, these will be converted into version ranges
        # starting from the actual latest version for them
        tailwindcss: latest
        svelte: latest
        vite: latest

    # This will inherit the settings from `base` and `frontend`
    svelte_frontend:
      extends: ["frontend"]
      # But it can also override values selectively
      license: MIT

  package_presets:
    svelte_frontend:
      package_json: svelte_frontend
      dir: packages/svelte_frontend

After we run

sketch ts package --preset svelte_frontend

We get this output in the package.json file:

â„šī¸ Typescript and Oxlint are always added by default (along with Vitest for non-root packages), unless no_default_deps is set to true.

{
  "name": "svelte_frontend",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "license": "MIT",
  "packageManager": "pnpm",
  "scripts": {
    "build": "vite build", 
    "dev": "vite dev"
  },
  "bugs": {
    "url": "joker.com"
  },
  "author": {
    "name": "Bruce Wayne",
    "email": "i-may-or-may-not-be-batman@gotham.com"
  },
  "devDependencies": {
    "oxlint": "^1.16.0", 
    "svelte": "^5.39.2", 
    "tailwindcss": "^4.1.13", 
    "typescript": "^5.9.2", 
    "vite": "^7.1.6", 
    "vitest": "^3.2.4"
  },
  "dependencies": {}
}

Handling Dependencies

Converting latest to a range

By default, all latest versions will be converted to a version range (which can be specified in the config) that starts from the actual latest version for that package.

This means that you can easily use your presets and ensure that they're always going to be updated, without using latest, which is not very good practice.

âš ī¸ Sketch uses the npm api to fetch the latest version for any given package. Depending on how many requests are made in a given timeframe, you might be rate limited by the api.

typescript:
  # Default
  no_convert_latest_to_range: false

  # Default
  version_range: patch

Adding dependencies to the catalog

If you set up your package manager to be pnpm (which is the default) and set catalog to true, whenever you try to generate a new package that has dependencies marked with catalog: (either the default catalog or a named one), each package that is not present in the target catalog inside pnpm-workspace.yaml will be added automatically to it.

Example

Let's say that we are starting with a basic pnpm-workspace.yaml file, and we generate a package that has catalog dependencies, which are currently absent from their target catalogs:

typescript:
  catalog: true

  package_presets:
    with_catalog:
      name: with_catalog
      dir: packages/with_catalog
      package_json:
        devDependencies:
          # Named catalog
          svelte: catalog:svelte
          # Default catalog
          hono: "catalog:"

After running

sketch ts package --preset with_catalog

We can see that the pnpm-workspace.yaml file has been updated with the new named catalog and dependencies:

packages:
  - 'packages/*'

catalogs:
  "svelte":
    "svelte": ^5.39.2

catalog:
  "hono": ^4.9.8
  "oxlint": ^1.16.0
  "typescript": ^5.9.2
  "vitest": ^3.2.4

Maintainers Information

When working with a team on a monorepo that is made of several individual packages, it's very common that the same people have to be added to the author, contributors and maintainers fields in the package.json files for these packages.

To make this job easier and faster, you can use the people field in the typescript config to store information about the contributors that are referred in multiple places, and then you can simply refer them by their ID.

Example

typescript:
  people:
    bruce:
      name: Bruce Wayne
      email: bruce@gotham.com
      url: brucewayne.com

  package_json_presets:
    people-example:
      author: bruce # Using the id from the store
      maintainers:
        # Literal definition
        - name: Clark Kent
          url: clarkkent.com
          email: clark-kent@dailyplanet.com
        - bruce
      contributors:
        - bruce

    # Since package.json presets are extensible,
    # you can use the above as a base for other package.json files,
    # if the author or contributors are always the same
    some-other-project:
      extends:
        - people-example
      scripts:
        say-hi: echo hi!

  package_presets:
    people-example:
      dir: packages/people-example
      package_json: people-example

After we run

sketch ts package --preset people-example

We get this package.json file in the root of the new package:

{
  "name": "people-example",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "packageManager": "pnpm",
  "scripts": {},
  "contributors": [
    {
      "name": "Bruce Wayne",
      "email": "bruce@gotham.com",
      "url": "brucewayne.com" 
    }
  ],
  "maintainers": [
    {
      "name": "Bruce Wayne",
      "email": "bruce@gotham.com",
      "url": "brucewayne.com" 
    },
    {
      "name": "Clark Kent",
      "email": "clark-kent@dailyplanet.com",
      "url": "clarkkent.com" 
    }
  ],
  "devDependencies": {
    "oxlint": "^1.16.0", 
    "typescript": "^5.9.2", 
    "vitest": "^3.2.4"
  },
  "dependencies": {}
}