ποΈ Sketch
Sketch is a tool designed to reduce boilerplate, increase productivity, and facilitate the creation and management of structured and easily reproducible workspaces.
Table of contents
Goals And Design Philosophy
Templating is mostly used to render webpages, but it can also be used to generate files used in day-to-day development, and even more, it can be used to define structures for entire projects in a way that can be standardized enough to provide structure and clarity, but also flexible enough as to make use of customized or even dynamically generated parameters.
While templating is a powerful technology on its own, it needs a frontend, a framework of sort, to make use of its flexible toolset, enhance it with with extra features, and to wrap it with an ergonomic API explicitely designed with the intent of simplifying most if not all activities related to creating and managing files and projects.
This is that Sketch aims to be, and it all starts with the concept of preset.
Presets
The basic concept about presets is that you should be able to define specific configurations that you find yourself using very often, and then use them to generate the files that you need or as the base for a more detailed preset.
Presets come in different forms and shapes and with different features.
Some presets are used as a way to aggregate other presets or templates into a single structure. For example, a git repo preset can use a gitignore or a pre-commit preset, it can aggregate customized templates templates to render inside the new repo, and it can also contain a list of commands (which also benefit from templating features) to execute before or after generation.
Other presets, such as those that belong to some of the most widely used configuration files such as Rust's Cargo.toml, or Typescript's tsconfig.json, are fully typed and documented (as part of the JSON schema for Sketch's configuration file), so that defining them comes with all of the benefits of IDE integration such as type safety and autocompletion.
Other presets are extensible, which means that you can define a base preset containing some common data (such as a list of networks or volumes in a compose.yaml file or a list of dependencies in a package.json file), and then create a preset that extends that base preset, so that you do not need to repeat inputs for all presets that share common settings.
Why Use Presets
Using presets helps in dealing with some of the most common pain points in software development:
1. Boilerplate Generation
The Problem
Writing boilerplate is an unrewarding and time consuming process. It causes unwanted cognitive load and kills productive momentum.
The Solution
When using presets, a single command can be used to generate a file or an entire project structure. It's orders of magnitude faster than doing it manually, while still being less complex than creating an ad-hoc script and more flexible than mere copy-pasting.
2. Structure And Reproducibility
The Problem
There are many cases in which you need to use a configuration file in multiple projects, such as a compose.yaml file, a github workflow, a setup file for a formatter or action runner, and so on. And chances are, you want to use this exact configuration in many if not all of your projects.
When you need to update this configuration file, you then need to update it in every other place where you are using it. If this is done manually, each copy may look slightly different than the other even for something as simple as ordering fields in a different way from one file to another.
While this may seem trivial, it causes mental friction and it can compound to outright confusion because each time your brain has to process a slightly different structure.
The Solution
If you use a preset, then every file generated with that preset will look the exact same. It removes the cognitive penalty caused by ever-slightly-different configuration files, and lets you focus on more important areas of your work.
And with extensible presets, you can keep a core preset where you define all of your shared configurations, and then extend it with more customized settings based on your project's needs.
3. Maintainability
The Problem
When you need to use a specific configuration in many different projects, that means that every change must be applied to every instance of the same configuration. This is a tedious and error-prone process, that eventually leads to projects being misconfigured and requiring manual review to bring them up to date with the new settings.
The Solution
With a preset, you can modify the preset itself and then update each instance programmatically (either by reusing or extending another preset). Even if the new changes are very specific and need manual review rather than being immediately applicable everywhere, it's still easier to maintain 1 preset (or even 2 or 10, for that matter) than maintaining 100s of individual files.
Supported Presets
The list of supported presets includes:
-
Git
.gitignore(extensible).pre-commit-config.yaml(extensible)- Github workflow (extensible)
- Github workflow job (extensible)
-
Docker
- Docker Compose file (extensible)
- Docker Compose service (extensible)
-
Rust
Cargo.toml(extensible)
-
Typescript
pnpm-workspace.yaml(extensible)package.json(extensible, with extra features)tsconfig.json(extensible, with merging of values for thereferences,include,excludeandfilesfields).oxlintrc.json(extensible)vitest(not a full configuration forvitest.config.ts, but a basic testing setup)
Enhanced Templating Toolset
While presets are designed to cover some of the most common use cases, custom templates can also be used to cover all sorts of scenarios. Custom templates can then be aggregated and used by other presets.
Sketch uses Tera as the templating engine to render custom templates, enhanced with a variety of extra features, such as:
- Special variables that extract commonly used values such as the user directory or the host's operating system
- Functions that perform actions such as:
- Making a glob search in a directory
- Generating a uuid
- Transforming a relative path into an absolute path
- Extracting capture groups from a regex
...and many other more, which you can find out about in more detail in the dedicated website.
Installation
Sketch can be installed in two ways:
- By downloading a pre-built binary from the github repository
- Via
cargo(cargo install sketch-it)
Documentation
You can find out more about Sketch in the dedicated website.
The homepage will always show the docs for the latest version. Each minor version will have its own dedicated subroute under /versions.
Command-Line Help for sketch
This document contains the help content for the sketch command-line program.
Command Overview:
sketchβ΄sketch newβ΄sketch repoβ΄sketch renderβ΄sketch render-presetβ΄sketch execβ΄sketch gitignoreβ΄sketch gh-workflowβ΄sketch docker-composeβ΄sketch pre-commitβ΄sketch cargo-tomlβ΄sketch tsβ΄sketch ts monorepoβ΄sketch ts packageβ΄sketch ts barrelβ΄sketch package-jsonβ΄sketch ts-configβ΄sketch oxlintβ΄sketch pnpm-workspaceβ΄sketch licenseβ΄
sketch
ποΈ Templating made simple. A tool to define and generate files and reusable project structures
Usage: sketch [OPTIONS] <COMMAND>
Subcommands:
newβ Generates a new config filerepoβ Creates a new git repo from a presetrenderβ Renders a single template to a file or to stdoutrender-presetβ Renders a templating presetexecβ Renders a template and executes it as a shell commandgitignoreβ Generates a.gitignorefile from a presetgh-workflowβ Generates a Github workflowdocker-composeβ Generates a Docker Compose file from a presetpre-commitβ Generates apre-commitconfig file from a presetcargo-tomlβ Generates aCargo.tomlfile from a presettsβ Executes typescript-specific commandspackage-jsonβ Generates apackage.jsonfile from a presetts-configβ Generates atsconfig.jsonfile from a presetoxlintβ Generates a.oxlintrc.jsonfile from a presetpnpm-workspaceβ Generates apnpm-workspace.yamlfile from a presetlicenseβ Generates a license file
Options:
--print-configβ Prints the full parsed config--templates-dir <DIR>β The path to the templates directory--no-overwriteβ Do not overwrite existing files-c,--config <FILE>β Sets a custom config file. Any file namedsketch.{yaml,json,toml}in the cwd or inXDG_CONFIG_HOME/sketchwill be detected automatically. If no file is found, the default settings are used--ignore-configβ Ignores any automatically detected config files, uses cli instructions and config file defined with --config-s,--set <KEY=VALUE>β Sets a variable (as key=value) to use in templates. Overrides global and local variables. Values must be in valid JSON--vars-file <VARS_FILES>β One or more paths to json, yaml or toml files to extract template variables from, in the given order
sketch new
Generates a new config file
Usage: sketch new [OUTPUT]
Arguments:
<OUTPUT>β The output file [default: sketch.yaml]
sketch repo
Creates a new git repo from a preset
Usage: sketch repo [OPTIONS] [DIR]
Arguments:
<DIR>β The directory where the new repo should be generated. [default:.]
Options:
-
-p,--preset <PRESET>β Selects a git preset from a configuration file -
--no-pre-commitβ Do not generate a pre-commit config -
--pre-commit <PRE_COMMIT>β Selects a pre-commit preset -
-g,--gitignore <GITIGNORE>β Selects a gitignore preset -
-l,--license <LICENSE>β A license file to generate for the new repoPossible values:
apache2: Apache 2.0 licensegpl3: GNU GPL 3.0 licenseagpl3: GNU AGPL 3.0 licensemit: MIT license
-
-w,--with-template <PRESET_ID|id=TEMPLATE_ID,output=PATH>β One or many individual templates or templating presets to render in the new repo -
--workflow <id=PRESET_ID,file=PATH>β One or many workflow presets to use for the new repo. The file path will be joined to.github/workflows -
-r,--remote <REMOTE>β The link of the git remote to use for the new repo
sketch render
Renders a single template to a file or to stdout
Usage: sketch render [OPTIONS] <OUTPUT_PATH|--stdout>
Arguments:
<OUTPUT_PATH>β The output path for the generated file
Options:
--stdoutβ Prints the result to stdout-f,--file <FILE>β The path to the template file-i,--id <ID>β The id of the template to use-t,--template <TEMPLATE>β The path to a template file, starting fromtemplates_dir-c,--content <CONTENT>β The literal definition for the template
sketch render-preset
Renders a templating preset
Usage: sketch render-preset <ID> [OUT_DIR]
Arguments:
<ID>β The id of the preset<OUT_DIR>β The base path to join to relative output paths. [default:.]
sketch exec
Renders a template and executes it as a shell command
Usage: sketch exec [OPTIONS] [CMD]
Arguments:
<CMD>β The literal definition for the template (incompatible with--fileor--template)
Options:
--print-cmdβ Prints the rendered command to stdout before executing it-s,--shell <SHELL>β The shell to use for commands [default:cmd.exeon windows andshelsewhere]--cwd <CWD>β The cwd for the command to execute [default:.]-f,--file <FILE>β The path to the command's template file, as an absolute path or relative to the cwd-t,--template <TEMPLATE>β The id of the template to use (a name for config-defined templates, or a relative path to a file fromtemplates_dir)
sketch gitignore
Generates a .gitignore file from a preset
Usage: sketch gitignore <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the new file [default:.gitignore]
sketch gh-workflow
Generates a Github workflow
Usage: sketch gh-workflow <PRESET> <OUTPUT>
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the new file
sketch docker-compose
Generates a Docker Compose file from a preset
Usage: sketch docker-compose [OPTIONS] <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the new file [default:compose.yaml]
Options:
-S,--service <SERVICES>β id=PRESET,name=NAME|ID
sketch pre-commit
Generates a pre-commit config file from a preset
Usage: sketch pre-commit <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the new file [default:.pre-commit-config.yaml]
sketch cargo-toml
Generates a Cargo.toml file from a preset
Usage: sketch cargo-toml [OPTIONS] <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the new file [default:Cargo.toml]
Options:
-n,--name <NAME>β Sets the name of the package in the new file. Overrides the value in the preset
sketch ts
Executes typescript-specific commands
Usage: sketch ts [OPTIONS] <COMMAND>
Subcommands:
monorepoβ Generates a new typescript monorepopackageβ Generates a new typescript packagebarrelβ Creates a barrel file
Options:
-
--package-manager <NAME>β The package manager being used. [default: pnpm]Possible values:
pnpm,npm,deno,bun,yarn -
--no-default-deps <NO_DEFAULT_DEPS>β Do not add default dependencies to newpackage.jsonfiles (typescript and oxlint, plus vitest if enabled)Possible values:
true,false -
--version-range <KIND>β The kind of version range to use for dependencies that are fetched automatically. [default: minor]Possible values:
minor,patch,exact -
--catalog <CATALOG>β Uses the default catalog (supported by pnpm and bun) for default dependencies, and automatically adds dependencies marked withcatalog:to the catalog, if they are missingPossible values:
true,false -
--no-convert-latest <NO_CONVERT_LATEST_TO_RANGE>β Do not convert dependencies marked aslatestto a version rangePossible values:
true,false
sketch ts monorepo
Generates a new typescript monorepo
Usage: sketch ts monorepo [OPTIONS] [DIR]
Arguments:
<DIR>β The root directory for the new monorepo. [default:ts_root]
Options:
-
-p,--pnpm <PRESET_ID>β Thepnpm-workspace.yamlpreset to use for the new monorepo. If it's unset andpnpmis the chosen package manager, the default preset will be used -
-r,--root-package <PRESET_ID>β The id of the package preset to use for the root package. If unset, the default preset is used, along with the values set via cli flags -
-n,--name <NAME>β The name of the new package. It defaults to the name of its directory -
--ts-config <id=ID,output=PATH>β One or many tsconfig presets (with their output path) to use for this package (uses defaults if not provided) -
--package-json <ID>β The package.json preset ID to use (uses defaults if not provided) -
--license <LICENSE>β A license file to generate for the new packagePossible values:
apache2: Apache 2.0 licensegpl3: GNU GPL 3.0 licenseagpl3: GNU AGPL 3.0 licensemit: MIT license
-
--hook-pre <ID>β One or many IDs of templates to render and execute as commands before the package's creation -
--hook-post <ID>β One or many IDs of templates to render and execute as commands after the package's creation -
--oxlint <ID>β The oxlint preset to use. It can be set todefaultto use the default preset -
-i,--installβ Installs the dependencies with the chosen package manager -
-w,--with-template <PRESET_ID|id=TEMPLATE_ID,output=PATH>β One or many templates or templating presets to generate in the new package's root
sketch ts package
Generates a new typescript package
Usage: sketch ts package [OPTIONS] [DIR]
Arguments:
<DIR>β The root directory for the new package. Defaults to the package name
Options:
-
-p,--preset <ID>β The package preset to use. If unset, the default preset is used, along with the values set via cli flags -
-u,--update-tsconfig <UPDATE_TSCONFIG>β An optional list of tsconfig files where the new tsconfig file will be added as a reference -
--oxlint <ID>β The oxlint preset to use. It can be set todefaultto use the default preset -
-i,--installβ Installs the dependencies with the chosen package manager -
-w,--with-template <PRESET_ID|id=TEMPLATE_ID,output=PATH>β One or many templates or templating presets to generate in the new package's root -
--vitest <ID>β The vitest preset to use. It can be set todefaultto use the default preset -
-n,--name <NAME>β The name of the new package. It defaults to the name of its directory -
--ts-config <id=ID,output=PATH>β One or many tsconfig presets (with their output path) to use for this package (uses defaults if not provided) -
--package-json <ID>β The package.json preset ID to use (uses defaults if not provided) -
--license <LICENSE>β A license file to generate for the new packagePossible values:
apache2: Apache 2.0 licensegpl3: GNU GPL 3.0 licenseagpl3: GNU AGPL 3.0 licensemit: MIT license
-
--hook-pre <ID>β One or many IDs of templates to render and execute as commands before the package's creation -
--hook-post <ID>β One or many IDs of templates to render and execute as commands after the package's creation
sketch ts barrel
Creates a barrel file
Usage: sketch ts barrel [OPTIONS] [DIR]
Arguments:
<DIR>β The directory where to search recursively for the files and generate the barrel file [default:.]
Options:
-o,--output <OUTPUT>β The output path for the barrel file. It defaults to{dir}/index.ts--keep-ext <EXT>β The file extensions that should be kept in export statements--js-extβ Exports.tsfiles as.js. It assumes thatjsis among the file extensions to keep--exclude <EXCLUDE>β One or more glob patterns to exclude from the imported modules
sketch package-json
Generates a package.json file from a preset
Usage: sketch package-json <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the generated file [default:package.json]
sketch ts-config
Generates a tsconfig.json file from a preset
Usage: sketch ts-config <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the generated file [default:tsconfig.json]
sketch oxlint
Generates a .oxlintrc.json file from a preset
Usage: sketch oxlint <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the generated file [default:.oxlintrc.json]
sketch pnpm-workspace
Generates a pnpm-workspace.yaml file from a preset
Usage: sketch pnpm-workspace <PRESET> [OUTPUT]
Arguments:
<PRESET>β The preset id<OUTPUT>β The output path of the generated file [default:pnpm-workspace.yaml]
sketch license
Generates a license file
Usage: sketch license [OPTIONS] <LICENSE>
Arguments:
-
<LICENSE>Possible values:
apache2: Apache 2.0 licensegpl3: GNU GPL 3.0 licenseagpl3: GNU AGPL 3.0 licensemit: MIT license
Options:
-o,--output <OUTPUT>β The path of the output file [default:LICENSE]
This document was generated automatically by
clap-markdown.
Configuration
Sketch supports yaml, json and toml formats for its configuration file.
The strategy for detecting the configuration file works like this:
- If a file is manually specified with
--config, that is used. - Any file named
sketch.{yaml, json, toml}in the cwd is used. - Any file named
sketch.{yaml, json, toml}inXDG_CONFIG_HOMEor$HOME/.configis used. - No file detected, using default settings.
Some of the values from configuration files can also be set via cli flags. When a value is set in a config file but also in a command, the value from the command has the higher priority.
You can also use the --ignore-config flag to temporarily ignore configuration files and only use cli-set values.
Generating Config Files
You can use the sketch new <OUTPUT> command to generate a new configuration file in the desired output file and format (the default output is sketch.yaml).
Extending 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 for config files is the same as for all the other presets.
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
External Configs
As of now, these external configurations defined with sketch are all fully typed and documented, so they benefit from the same lsp integration as the rest of the other settings:
.pre-commit-config.yaml.oxlintrc.jsonpnpm-workspace.yamlpackage.jsontsconfig.jsoncompose.yaml(Docker)- github workflows
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.
Context And Variables
Each template can be populated with context variables. These variables can be set at many different stages, with different degrees of priority (from lowest to highest):
- Global variables
- Cli-set variable files
- Preset context
- Single template context
- Cli-set variables
--set flag must be formatted in valid json. This means that, for example, strings must be wrapped in escaped quotes.
Special Variables
Sketch provides some special variables to get access to commonly used values such as the home directory.
All of the following variables are available in templates, prefixed with sketch_ (i.e. sketch_os and so on).
cwdtmp_dir(fromenv::temp_dir)home(fromenv::home_dir)os(fromCARGO_CFG_TARGET_OSor envOS)os_family(fromCARGO_CFG_TARGET_FAMILY)arch(fromCARGO_CFG_TARGET_ARCHor envHOSTTYPE)user(envUSER)hostname(envHOSTNAME)xdg_config(envXDG_CONFIG_HOME)xdg_data(envXDG_DATA_HOME)xdg_state(envXDG_STATE_HOME)xdg_cache(envXDG_CACHE_HOME)is_unix(fromcfg!(unix))is_linux(fromcfg!(target_os = "linux"))is_macos(fromcfg!(target_os = "macos"))is_wsl(checks/proc/sys/kernel/osrelease)is_windows(fromcfg!(windows))
Filters And Functions
All of the builtin functionalities for Tera are available.
On top of that, Sketch adds some extra filters and functions.
Functions
uuid(generates a v4 UUID)
Filters
Strings
capture(regex=REGEX)(matches a regex once and returns the named capture groups)capture_many(regex=REGEX)(matches a regex repetitively and returns the list of named capture groups)semver(parses a cargo-style semver and returns the segments)matches_semver(target=TARGET)(checks if a cargo-style semver matches a target)strip_prefix(prefix=PREFIX)(strips a prefix from a string, if present)strip_suffix(suffix=SUFFIX)(strips a suffix from a string, if present)
Filesystem
basename(gets the basename of a directory/file)parent_dir(gets the parent directory of a directory/file)is_file(checks if a path is a file)is_dir(checks if a path is a directory)is_absolute(checks if a path is absolute)is_relative(checks if a path is relative)absolute(makes a path absolute)relative(from=PATH)(returns the relative path between two paths)read_dir(returns the list of the files contained in a directory and its subdirectories)glob(pattern=GLOB)(returns the glob matching entries in a directory and its subdirectories)matches_glob(pattern=GLOB)(checks if a path matches a glob pattern)
Serialization
to_yaml(serializes input into yaml)to_toml(serializes input into prettified toml)
Examples
Template:
{# Using special variables -#}
Current arch is: {{ sketch_arch }}
Current os is: {{ sketch_os }}
Current os family is: {{ sketch_os_family }}
Is unix: {{ sketch_is_unix }}
{# Functions and filters -#}
A random uuid is: {{ uuid() }}
{# Generating a random alphanumeric string -#}
A random string is: {{ uuid() | truncate(length=6, end="") }}
{# Generating a random number -#}
A random number is: {{ get_random(start=100000, end=9999999) }}
Now is {{ now(utc=true) }}
{# Assigning an env to a variable -#}
{%- set greeting = get_env(name="GREETING") -%}
{#- Manipulating data -#}
{%- set segments = greeting | split(pat=",") -%}
First segment is: {{ segments[0] }}
Second segment is: {{ segments[1] }}
{% set some_path = "mydir/myfile" -%}
Basename is: {{ some_path | basename }}
Parent dir is: {{ some_path | parent_dir }}
{% set dir = "../sketch" -%}
{% if dir | is_dir -%}
It's a dir!
{% endif -%}
{% set file = "Cargo.toml" -%}
{% if file | is_file -%}
It's a file!
{%- endif %}
Matches glob: {{ file | matches_glob(pattern="**/*.toml") }}
{%- set captures = file | capture(regex="(?<path>.*)\.(?<extension>\w+)") %}
Path is: {{ captures['path'] }}
Extension is: {{ captures['extension'] }}
In yaml form:
{{ captures | to_yaml }}
In toml form:
{{ captures | to_toml }}
{% set words = "They're taking the hobbits to Isengard" | capture_many(regex="(?<word>[\w']+)") -%}
{% for capture in words -%}
{{ capture['word'] -}} {% if not loop.last %}{{ " " }}{% endif -%}
{%- endfor -%}!
{% set version = "v0.1.0" -%}
{% set segments = version | semver -%}
Major is: {{ segments['major'] }}
Minor is: {{ segments['minor'] }}
Patch is: {{ segments['patch'] }}
Version matches >=0.1.0: {{ version | matches_semver(target=">=0.1.0") }}
Version matches >=0.2.0: {{ version | matches_semver(target=">=0.2.0") }}
To camelCase: {{ "my_var" | camel }}
To snake_case: {{ "myVar" | snake }}
To SCREAMING_CASE: {{ "myVar" | upper_snake }}
To PascalCase: {{ "myVar" | pascal }}
Luke, I am your {{ "grandfather" | strip_prefix(prefix="grand") }}!
{% set dir = "../examples/templating/templates" -%}
{% for entry in dir | glob(pattern="example.j2") -%}
Entry: {{ entry }}
{% endfor %}
Cmd:
GREETING="hello,world" sketch render --template example.j2 --stdout
Output:
Current arch is: x86_64
Current os is: linux
Current os family is: unix
Is unix: true
A random uuid is: fd61daf5-83bf-46c6-afe3-5fcac0d6212c
A random string is: 0de4ed
A random number is: 3574820
Now is 2025-10-27T19:13:47.625250487+00:00
First segment is: hello
Second segment is: world
Basename is: myfile
Parent dir is: mydir
It's a dir!
It's a file!
Matches glob: true
Path is: Cargo
Extension is: toml
In yaml form:
path: Cargo
extension: toml
In toml form:
path = "Cargo"
extension = "toml"
They're taking the hobbits to Isengard!
Major is: 0
Minor is: 1
Patch is: 0
Version matches >=0.1.0: true
Version matches >=0.2.0: false
To camelCase: myVar
To snake_case: my_var
To SCREAMING_CASE: MY_VAR
To PascalCase: MyVar
Luke, I am your father!
Entry: example.j2
Rendering A Template
We can render a template to a file or to stdout with the command sketch render.
From A Configuration File
A template can be defined as simple text in a config file, inside the templates map. In this case, you can refer to this template by using its map key. This is the easiest method, but it also means slightly worse IDE integration for things like snippets and syntax highlighting.
templates_dir: templates
vars:
location: Isengard
greeting: hello
general: kenobi
meal: breakfast
templates:
hobbits: "they're taking the hobbits to {{ location }}!"
breakfast: "what about second {{ meal }}?"
Command:
sketch render --id hobbits tests/output/custom_templates/from_template_id.txt
From templates_dir
The best method is to use a file inside templates_dir. With this method, the template's ID becomes the relative path from templates_dir. This method provides better IDE integration if you have set up snippets or syntax highlighting for jinja files (Tera, the templating engine used by sketch is based on jinja).
Example
Tree structure of templates_dir:
βββ example.j2
βββ subdir
βββ nested
βΒ Β βββ more_nested_file.j2
βββ nested_file.j2
Command:
sketch render --template subdir/nested_file.j2 tests/output/custom_templates/from_template_file.txt
βΉοΈ You can also use the
-fflag to render a template from any file, even outsidetemplates_dir.
βΉοΈ
--idand--templateare aliases for one another, and they can be used to refer to a file insidetemplates_dir, or to a literal template in the config file.
From Literal Definition
Alternatively, a template can also be defined directly within a command:
sketch render --content "they're taking the hobbits to {{ location }}!" tests/output/custom_templates/from_literal.txt
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.
Examples
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
.j2as an extension for the template files. Any extension can be used.
We can refer to it by its id:
sketch --set category="gp2" tests/output/commands_tests -t cmd_template.j2
This will trigger the command and 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
Command:
sketch --set something="space" tests/output/commands_tests -f tests/commands_tests/cmd_from_file.j2
Output:
all the time you have to leave the space!
From Literal Template
Templates can also be defined directly as part of the command.
Command:
sketch --set condition="slower" tests/output/commands_tests 'echo "engine feels good... much {{ condition }} than before... amazing" > command_output.txt'
Output:
engine feels good... much slower than before... amazing
Hooks
Templates can be executed as commands as pre or post hooks in a git repo, typecript monorepo or typescript package presets:
# Commands to run before generation, from the root of the new project
hooks_pre:
- command:
# Inlining a new template definition here,
# but as always, we can use stored templates too
name: pre_hook
content: "echo '{{ greeting }}' > pre.txt"
context:
greeting: hi
# Commands to run after generation, from the root of the new project
hooks_post:
- command:
name: post_hook
content: "echo '{{ greeting }}' > post.txt"
context:
greeting: hi
Presets
Sketch supports various kinds of presets, which are designed to serve different functionalities. Some presets serve as aggregators for other presets, others provide extensibility, while others provide type safety and lsp integration.
As of now, these presets are available:
-
Templating
- Templating presets (extensible)
-
Docker
- Docker Compose file (extensible)
- Docker Compose service (extensible)
-
Git
- Git repo
.gitignore(extensible).pre-commit-config.yaml(extensible)- Github workflow (extensible)
- Github workflow job (extensible)
- Github workflow step
-
Rust
Cargo.toml(extensible, with merging of features for dependencies)
-
Typescript
- Typescript package
pnpm-workspace.yaml(extensible)package.json(extensible, with extra features)tsconfig.json(extensible, with merging of values for thereferences,include,excludeandfilesfields).oxlintrc.json(extensible)vitest(not a full configuration forvitest.config.ts, but a basic testing setup)
These can be generated individually, or as part of another preset. You can find more information about each command in the cli reference.
Extending Presets
Some presets can extend other presets. When a preset is extended, the merging strategy for their fields works like this:
- Collections are merged and, in almost all cases, also deduped and sorted (except for cases where order matters such as a list of command arguments). If the collection is a map and the values in it are also maps (as is the case for the
catalogsfield inpackage.jsonorpnpm-workspace.yaml), the inner maps will be merged. - Values that are also extensible (such as
compilerOptionsin atsconfigpreset) will be merged with the same rules as above - All other values are overwritten, except if the previous value was present and the new value is
null. This is to avoid merging values that come from partially-defined presets, where the missing fields are all unset. Generally speaking, the correct strategy to extend presets is to define a base and thenaddelements to it, rather than replacing other values.
Examples
This is a detailed example of the various kinds of presets that are available:
# `Cargo.toml` presets
cargo_toml_presets:
base:
package:
version: "0.1.0"
edition: "2024"
cli-custom:
extends_presets:
- base
- cli-tools
- serde-ordered
package:
name: cli-custom
dependencies:
clap:
features:
- derive
owo-colors:
features:
- supports-colors
cli-tools:
dependencies:
ratatui: "0.29"
clap: "4.5"
serde: "1"
owo-colors: "4"
serde-ordered:
dependencies:
serde:
version: "1"
features:
- preserve_order
indexmap:
version: "2.11"
features:
- serde
# Docker Compose presets
docker:
service_presets:
# A base service preset with common configurations
base_service:
environment:
TZ: Europe/Berlin
networks:
- my_network
# A preset for a db service
database:
extends_presets:
- base_service
image: postgres
caddy:
extends_presets:
- base_service
image: lucaslorentz/caddy-docker-proxy:ci-alpine
networks:
- my_network
ports:
- 80:80
- 443:443
compose_presets:
# Base preset for a compose file
base:
volumes:
my_volume:
external: true
networks:
my_network:
external: true
extended:
extends_presets:
- base
services:
# Using the service preset by ID
db: database
my_service:
networks:
- my_network
volumes:
- my_volume:/target
volumes:
my_other_volume:
external: true
# Gitignore presets
gitignore_presets:
# Can be a string
base:
content: |
*.env
dist
# Or a list of strings
ts:
extends_presets:
# When being merged, the new entries are placed at the top
- base
content:
- "*.tsBuildInfo"
- node_modules
# Pre-commit presets
pre_commit_presets:
# Commonly used repo
base:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0
hooks:
- id: gitleaks
# Hooks specific to typescript projects
typescript:
# Extending the base preset
extends_presets:
- base
repos:
- repo: local
hooks:
- id: oxlint
name: oxlint
entry: oxlint
language: system
files: '\.svelte$|\.js$|\.ts$'
types: [file]
# Git presets
git_presets:
ts_package:
workflows:
- file_name: my_workflow.yaml
# Preset ID
id: extended
# License file to generate
license: Apache-2.0
# Selecting a preset
gitignore: ts
pre_commit: typescript
# Templates that will be generated with this preset
# starting from the new repo's root
with_templates:
- id: dockerfile
# Commands to run before generation, from the root of the new project
hooks_pre:
- command:
# Inlining a new template definition here,
# but as always, we can use stored templates too
name: pre_hook
content: "echo '{{ greeting }}' > pre.txt"
context:
greeting: hi
# Commands to run after generation, from the root of the new project
hooks_post:
- command:
name: post_hook
content: "echo '{{ greeting }}' > post.txt"
context:
greeting: hi
typescript:
# By default, typescript and oxlint are added to
# the dependencies
no_default_deps: true
# By default, all versions marked with `latest` are converted to
# a version range starting from the current version
no_convert_latest_to_range: true
# Version range to use when converting from `latest`
version_range: patch
# `pnpm-workspace.yaml` presets
pnpm_presets:
base:
# All the dirs listed here will be created automatically
# when new monorepos are created
packages:
- packages/*
- apps/*
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440
# `package.json` presets
package_json_presets:
# Defining some reusable fields such as some common scripts and dependencies
frontend:
description: I am the base preset
scripts:
build: vite build
dev: vite dev
devDependencies:
tailwindcss: "*"
vite: "*"
svelte_frontend:
extends_presets: ["frontend"]
# Adding new fields
license: MIT
# Overriding others
description: I am the frontend preset
# And merging dependencies
devDependencies:
svelte: "*"
# Tsconfig presets
ts_config_presets:
base:
references:
- path: /some/path
include:
- src
compilerOptions:
noEmit: true
verbatimModuleSyntax: true
extended:
extends_presets:
- base
# Unlike in real tsconfigs with `extends`, references are merged here,
# and so are all other collections such as `files` or `include`.
references:
- path: /other/path
include:
- tests
compilerOptions:
# Whereas other fields are overwritten
noEmit: false
# Oxlint presets
oxlint_presets:
base:
ignorePatterns:
- "**/node_modules/**"
extended:
extends_presets:
- base
ignorePatterns:
- .cache
# Vitest presets
vitest_presets:
base:
# 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, from the root of the package
tests_dir: tests
# The directory, inside `tests_dir`, that contains the setup files for tests.
# A file named `tests_setup.ts`, containing some basic testing boilerplate,
# will be generated inside of it.
setup_dir: setup
# A list of setup files (the paths will be joined to the `setup_dir`)
# `tests_setup.ts` will be added automatically.
setup_files:
- somefile.ts
- anotherfile.ts
environment: jsdom
silent: passed-only
# Package presets
package_presets:
example:
name: example
# For every preset, we can either refer to them
# by their id...
package_json: svelte_frontend
# ...or we can inline a preset definition
oxlint:
extends_presets:
- extended
ignorePatterns:
- .output
# We can define one or many tsconfig files
ts_config:
# Can be omitted, would default to `tsconfig.json`
- output: tsconfig.json
config:
# Once again creating a new config here with some extras
extends_presets:
- extended
include:
- scripts
vitest: base
Templating Presets
Rendering a single template is for cases when we need a simpler or more flexible setup. But things become more interesting with templating presets, where different kinds of templates can be aggregated and used in order to define easily reproducible project structures.
Templating presets can be rendered with the render-preset command, or as part of another preset, such as a git repo preset.
A templating preset contains an optional context (which overrides the global context), and one or many of these items:
1. Individual Templates
- Individual templates, which provide a manually controlled output path and their own local context
Example
templating_presets:
lotr:
# Group context, lower priority
context: {}
templates:
# Selecting individual templates
- template: hobbits
output: hobbits.txt
# Individual context, higher priority
context: {}
- template: breakfast
output: subdir/breakfast.txt
Command:
sketch render-preset lotr
Tree output:
βββ hobbits.txt
βββ subdir
βββ breakfast.txt
2. Template Directory
- A path to a directory inside
templates_dir, where all templates will be recursively rendered in the output directory, with the same file tree structure
βΉοΈ Any template files that end with the
.j2,.jinjaor.jinja2extensions will have them automatically removed. Somyfile.json.j2will just becomemyfile.json.
Example
Let's say that this is our templates_dir:
βββ example.j2
βββ subdir
βββ nested
βΒ Β βββ more_nested_file.j2
βββ nested_file.j2
And we define this preset, which is meant to reproduce the entire file structure of subdir in the target directory.
templating_presets:
structured:
# Selecting all the files inside this directory
# and its descendants
templates:
- dir: subdir
# Glob patterns to exclude certain templates
exclude:
- "some/glob/pattern/*"
Command:
sketch render-preset structured
Tree output:
βββ nested
βΒ Β βββ more_nested_file
βββ nested_file
3. Remote Template
- A special kind of template which points to a git repository. Every file inside of it will be rendered in the output directory.
templating_presets:
remote:
templates:
- repo: https://github.com/Rick-Phoenix/sketch-remote-preset-example
# Glob patterns to exclude
exclude: [.gitignore]
Example
We start from this basic example
Command:
sketch --set 'continuation="gp2 engine... gp2!"' render-preset remote
Tree output:
βββ some_file
βββ subdir
βββ nested
βββ nested_file
File output for some_file:
Roses are red, violets are blue, gp2 engine... gp2!
Extending Templating Presets
Templating presets are extensible. When a preset is being extended, its templates will be added to the receiving preset, and the two context maps will be merged, with the new context overwriting the previous context in case of conflicting variables.
extended:
extends_presets:
- lotr
Generating A Git Repo
The sketch repo command allows you to generate a new git repository, starting from a preset stored in one of your configuration files.
A git preset uses (or defines) a preset for its gitignore file and, optionally, for pre-commit, as well as a list of templates that will be generated inside the root of the new repo when the command is triggered.
# Gitignore presets
gitignore_presets:
# Can be a string
base:
content: |
*.env
dist
# Or a list of strings
ts:
extends_presets:
# When being merged, the new entries are placed at the top
- base
content:
- "*.tsBuildInfo"
- node_modules
# Pre-commit presets
pre_commit_presets:
# Commonly used repo
base:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0
hooks:
- id: gitleaks
# Hooks specific to typescript projects
typescript:
# Extending the base preset
extends_presets:
- base
repos:
- repo: local
hooks:
- id: oxlint
name: oxlint
entry: oxlint
language: system
files: '\.svelte$|\.js$|\.ts$'
types: [file]
# Git presets
git_presets:
ts_package:
workflows:
- file_name: my_workflow.yaml
# Preset ID
id: extended
# License file to generate
license: Apache-2.0
# Selecting a preset
gitignore: ts
pre_commit: typescript
Adding Workflows
We can also include some Github workflows by using their presets:
workflows:
- file_name: my_workflow.yaml
# Preset ID
id: extended
Adding Templates
We can use the with_templates setting to add a group of templates to a git preset. Let's say that we want to automatically generate a basic docker setup whenever we use this preset:
# We define a template in a file or in a config file...
templates:
dockerfile: |
FROM node:23-alpine
COPY . .
EXPOSE {{ docker_dev_port | default(value=5173) }}
CMD ["npm", "run", "dev"]
# Templating presets
templating_presets:
dockerfile:
context:
# Group context, lower priority
docker_dev_port: 9530
# List of templates
templates:
# Single template
- output: Dockerfile
template: dockerfile
# Local context with higher priority
context:
docker_dev_port: 5173
# ...and then we add it to a preset
# Git presets
git_presets:
ts_package:
workflows:
- file_name: my_workflow.yaml
# Preset ID
id: extended
# License file to generate
license: Apache-2.0
# Selecting a preset
gitignore: ts
pre_commit: typescript
# Templates that will be generated with this preset
# starting from the new repo's root
with_templates:
- id: dockerfile
Hooks
We can define some commands (which can also be templates) to execute before and/or after generating the new repo:
# Commands to run before generation, from the root of the new project
hooks_pre:
- command:
# Inlining a new template definition here,
# but as always, we can use stored templates too
name: pre_hook
content: "echo '{{ greeting }}' > pre.txt"
context:
greeting: hi
# Commands to run after generation, from the root of the new project
hooks_post:
- command:
name: post_hook
content: "echo '{{ greeting }}' > post.txt"
context:
greeting: hi
Example
Starting from this config, we can run this command:
sketch repo --preset ts_package
To get this tree output:
βββ .github
βΒ Β βββ workflows
βΒ Β βββ my_workflow.yaml
βββ .gitignore
βββ .pre-commit-config.yaml
βββ Dockerfile
βββ LICENSE
βββ post.txt
βββ pre.txt
βΉοΈ With cli flags, we can override the
gitignoreandpre-commitpresets, as well as adding new templates or hooks to run or generate when the preset is being used.
pre-commit-config.yaml output
repos:
- repo: local
hooks:
- id: oxlint
entry: oxlint
files: \.svelte$|\.js$|\.ts$
language: system
name: oxlint
types:
- file
- repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0
hooks:
- id: gitleaks
gitignore output
*.tsBuildInfo
node_modules
*.env
dist
Github Workflow Presets
Sketch supports presets for Github workflows, as well as individual components in a workflow, such as a job or a step.
Example
We use this configuration:
github:
workflow_presets:
# Common settings for all workflows
base:
defaults:
run:
shell: bash
env:
my_env: somevalue
another_env: anothervalue
extended:
extends_presets:
- base
on:
push:
branches:
- main
jobs:
# Adding a job by using its preset ID
check_main_branch: check_main_branch
do_something:
extends_presets:
- base
if: needs.check_branch.outputs.is_on_main == 'true'
name: Do something while on main branch
steps:
- run: echo "Done something from main branch!"
workflow_job_presets:
# Common settings for all jobs
base:
runs-on: ubuntu-latest
timeout-minutes: 25
continue-on-error: false
env:
my_env: somevalue
another_env: anothervalue
check_main_branch:
extends_presets:
- base
env:
another_other_value: yetanothervalue
outputs:
is_on_main: ${{ steps.branch_check.outputs.is_on_main }}
steps:
# Using a step preset
- check_main
steps_presets:
# Reusable step preset
check_main:
name: Check if a tag is on the main branch
id: branch_check
run: |
if git branch -r --contains ${{ github.ref }} | grep -q 'origin/main'; then
echo "On main branch. Proceeding with the workflow..."
echo "is_on_main=true" >> "$GITHUB_OUTPUT"
else
echo "Not on main branch. Skipping workflow..."
fi
Run the command
sketch gh-workflow extended
And get this output:
on:
push:
branches:
- main
env:
another_env: anothervalue
my_env: somevalue
defaults:
run:
shell: bash
jobs:
check_main_branch:
runs-on: ubuntu-latest
outputs:
is_on_main: ${{ steps.branch_check.outputs.is_on_main }}
env:
another_env: anothervalue
another_other_value: yetanothervalue
my_env: somevalue
timeout-minutes: 25
continue-on-error: false
steps:
- name: Check if a tag is on the main branch
id: branch_check
run: |
if git branch -r --contains ${{ github.ref }} | grep -q 'origin/main'; then
echo "On main branch. Proceeding with the workflow..."
echo "is_on_main=true" >> "$GITHUB_OUTPUT"
else
echo "Not on main branch. Skipping workflow..."
fi
do_something:
runs-on: ubuntu-latest
name: Do something while on main branch
if: needs.check_branch.outputs.is_on_main == 'true'
env:
another_env: anothervalue
my_env: somevalue
timeout-minutes: 25
continue-on-error: false
steps:
- run: echo "Done something from main branch!"
Cargo.toml Presets
You can use sketch to define and generate extensible Cargo.toml presets.
When presets are extended, each dependency will also get merged according to the merging rules.
Example
Config:
cargo_toml_presets:
base:
package:
version: "0.1.0"
edition: "2024"
cli-custom:
extends_presets:
- base
- cli-tools
- serde-ordered
package:
name: cli-custom
dependencies:
clap:
features:
- derive
owo-colors:
features:
- supports-colors
cli-tools:
dependencies:
ratatui: "0.29"
clap: "4.5"
serde: "1"
owo-colors: "4"
serde-ordered:
dependencies:
serde:
version: "1"
features:
- preserve_order
indexmap:
version: "2.11"
features:
- serde
Command:
sketch cargo-toml cli-custom
Output:
[package]
name = "cli-custom"
version = "0.1.0"
edition = "2024"
[dependencies]
ratatui = "0.29"
[dependencies.clap]
features = ["derive"]
[dependencies.indexmap]
features = ["serde"]
[dependencies.owo-colors]
features = ["supports-colors"]
[dependencies.serde]
features = ["preserve_order"]
Docker Compose Presets
Sketch can be used to create extensible presets for entire compose files as well as individual services.
Example
Config:
docker:
service_presets:
# A base service preset with common configurations
base_service:
environment:
TZ: Europe/Berlin
networks:
- my_network
# A preset for a db service
database:
extends_presets:
- base_service
image: postgres
caddy:
extends_presets:
- base_service
image: lucaslorentz/caddy-docker-proxy:ci-alpine
networks:
- my_network
ports:
- 80:80
- 443:443
compose_presets:
# Base preset for a compose file
base:
volumes:
my_volume:
external: true
networks:
my_network:
external: true
extended:
extends_presets:
- base
services:
# Using the service preset by ID
db: database
my_service:
networks:
- my_network
volumes:
- my_volume:/target
volumes:
my_other_volume:
external: true
Command:
sketch docker-compose --service caddy extended
βΉοΈ With the
--serviceflag, extra service presets can be added to the output file.
Output:
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
environment:
TZ: Europe/Berlin
networks:
- my_network
ports:
- 443:443
- 80:80
db:
image: postgres
environment:
TZ: Europe/Berlin
networks:
- my_network
my_service:
networks:
- my_network
volumes:
- my_volume:/target
networks:
my_network:
external: true
volumes:
my_other_volume:
external: true
my_volume:
external: true
Typescript Presets
Many typical components of a typescript project have their own dedicated preset.
Package.json Presets
package_json_presets:
# Defining some reusable fields such as some common scripts and dependencies
frontend:
description: I am the base preset
scripts:
build: vite build
dev: vite dev
devDependencies:
tailwindcss: "*"
vite: "*"
svelte_frontend:
extends_presets: ["frontend"]
# Adding new fields
license: MIT
# Overriding others
description: I am the frontend preset
# And merging dependencies
devDependencies:
svelte: "*"
Just like in actual package.json files, custom fields are allowed.
βΉοΈ
package.jsonpresets come with extra features. Dedicated section
Tsconfig Presets
ts_config_presets:
base:
references:
- path: /some/path
include:
- src
compilerOptions:
noEmit: true
verbatimModuleSyntax: true
extended:
extends_presets:
- base
# Unlike in real tsconfigs with `extends`, references are merged here,
# and so are all other collections such as `files` or `include`.
references:
- path: /other/path
include:
- tests
compilerOptions:
# Whereas other fields are overwritten
noEmit: false
Unlike what happens when you merge two tsconfig files by using the extends field, extending presets will merge all collections, including files, include, exclude and references, which would normally be overwritten, rather than merged.
Oxlint Presets
oxlint_presets:
base:
ignorePatterns:
- "**/node_modules/**"
extended:
extends_presets:
- base
ignorePatterns:
- .cache
Pnpm-workspace Presets
pnpm_presets:
base:
# All the dirs listed here will be created automatically
# when new monorepos are created
packages:
- packages/*
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440
Vitest Presets
vitest_presets:
base:
# 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, from the root of the package
tests_dir: tests
# The directory, inside `tests_dir`, that contains the setup files for tests.
# A file named `tests_setup.ts`, containing some basic testing boilerplate,
# will be generated inside of it.
setup_dir: setup
# A list of setup files (the paths will be joined to the `setup_dir`)
# `tests_setup.ts` will be added automatically.
setup_files:
- somefile.ts
- anotherfile.ts
environment: jsdom
silent: passed-only
Package Presets
A package preset can be used to collect other presets, such as in this example:
package_presets:
example:
name: example
# For every preset, we can either refer to them
# by their id...
package_json: svelte_frontend
# ...or we can inline a preset definition
oxlint:
extends_presets:
- extended
ignorePatterns:
- .output
# We can define one or many tsconfig files
ts_config:
# Can be omitted, would default to `tsconfig.json`
- output: tsconfig.json
config:
# Once again creating a new config here with some extras
extends_presets:
- extended
include:
- scripts
vitest: base
Tsconfig output
{
"include": [
"scripts",
"src",
"tests"
],
"references": [
{
"path": "/other/path"
},
{
"path": "/some/path"
}
],
"compilerOptions": {
"noEmit": false,
"verbatimModuleSyntax": true
}
}
Package.json output
{
"name": "example",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite dev"
},
"description": "I am the frontend preset",
"license": "MIT",
"packageManager": "pnpm",
"dependencies": {},
"devDependencies": {
"svelte": "*",
"tailwindcss": "*",
"vite": "*"
}
}
Oxlintrc.json output
{
"plugins": [
"oxc",
"typescript",
"unicorn"
],
"ignorePatterns": [
"**/node_modules/**",
".cache",
".output"
]
}
vitest.config.ts output
import { defineConfig } from "vitest/config";
import { resolve, join } from "node:path";
const j = join;
const r = resolve;
const dir = import.meta.dirname;
const setupDir = r(dir, "setup");
export default defineConfig({
plugins: [],
resolve: {
alias: {
'@': r(dir, "../src"),
},
},
test: {
setupFiles: [
j(setupDir, "tests_setup.ts"),
j(setupDir, "somefile.ts"),
j(setupDir, "anotherfile.ts")
],
globals: true,
environment: "jsdom",
silent: "passed-only",
sequence: {
setupFiles: "list",
},
},
});
Smart Features
Sketch provides some additional features that may come in handy when dealing with a typescript project.
Converting latest to a range
By default, all versions marked as latest will be converted to a version range (minor by default, but can be customized) that starts from the actual latest version for that package.
This means that you can easily reuse your presets over time to start new projects, without needing to manually bump all versions, and while also avoiding latest, which is not suitable for stability.
Sketch uses the npm api to fetch the latest version for any given package. It sends a maximum of 10 parallel requests, but depending on how many requests are made in a given timeframe, you might still be rate limited by the api, causing an error.
typescript:
# Default
no_convert_latest_to_range: false
# Default
version_range: minor
Adding dependencies to the catalog
If you set up your package manager to be pnpm (which is the default) or bun and set catalog to true, whenever you try to generate a new package that has dependencies marked with catalog:, each package that is not present in the target catalog (inside pnpm-workspace.yaml for pnpm, and inside package.json for bun) will be added automatically to it.
Example
Let's say that we are starting with a basic pnpm-workspace.yaml config like this one:
pnpm_presets:
base:
# All the dirs listed here will be created automatically
# when new monorepos are created
packages:
- packages/*
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440
and we generate a package that has catalog dependencies, which are currently absent from their target catalogs:
typescript:
no_default_deps: true
catalog: true
# Default
package_manager: pnpm
package_presets:
with_catalog:
name: 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/*
catalog:
hono: ^4.10.3
catalogs:
svelte:
svelte: ^5.42.2
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440
Storing And Reusing Contributors' Data
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 a bit easier, you can use the people field in the typescript config to store information about contributors which you can then simply refer to by their ID.
Example
typescript:
no_default_deps: true
people:
# Person ID
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_presets:
- people-example
scripts:
say-hi: echo hi!
package_presets:
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",
"scripts": {},
"author": {
"name": "Bruce Wayne",
"email": "bruce@gotham.com",
"url": "brucewayne.com"
},
"packageManager": "pnpm",
"dependencies": {},
"devDependencies": {},
"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"
}
]
}
Typescript Projects
Sketch was initially conceived as a typescript-centric tool, as I needed a way to deal with the chaotic typescript ecosystem in a structured and easily reproducible way.
It supports a rich feature set dedicated to typescript, with dedicated preset categories (such as tsconfig.json and package.json presets) and commands (such as generating individual config files from presets, or generating barrel files).
These are the top level configuration settings for Typescript projects, with their default values:
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: minor
# 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
# and add missing dependencies to it automatically
catalog: false
package_manager: pnpm
# Configuration for the generated pnpm-workspace.yaml file
pnpm:
# 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
ts_config_presets: {}
# A map containing extensible oxlint presets
oxlint_presets: {}
# A map of contributors and maintainers, which can be referenced
# in `package.json` presets with their ID
people: {}
Monorepo Generation
To set up a basic monorepo with default settings, we can just run the command sketch ts monorepo. But we can also create a package preset and use that for the root package of the monorepo.
This is a more customized configuration for the root package:
typescript:
# By default, typescript and oxlint would be added to
# the root package's dependencies
no_default_deps: true
# Pnpm is the default, and if selected, it will generate
# a pnpm-workspace.yaml file
package_manager: pnpm
pnpm_presets:
base:
# All the dirs listed here will be created automatically
# when new monorepos are created
packages:
- packages/*
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440
package_presets:
# Defining a preset for the root package
root:
name: workspace
package_json:
workspaces:
# All dirs listed here are also created automatically
# unless they contain '{', '[', '?' or '!'
- apps/**/test
license: Apache-2.0
devDependencies:
husky: ^9.0.0
# 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"]
Adding Templates
You can also use the with_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.
To do that, we add this to the root package's definition:
with_templates:
- templates:
# Relative path from the root of the package
- 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
βΉοΈ You can also use
--with-template <PRESET_ID|id=TEMPLATE_ID,output=PATH>as a flag to add more templates or templating presets when generating a new package.
Hooks
We can define some commands (which can also be templates) to execute before and/or after generating the new monorepo:
# Commands to run before generation, from the root of the new project
hooks_pre:
- command:
# Inlining a new template definition here,
# but as always, we can use stored templates too
name: pre_hook
content: "echo '{{ greeting }}' > pre.txt"
context:
greeting: hi
# Commands to run after generation, from the root of the new project
hooks_post:
- command:
name: post_hook
content: "echo '{{ greeting }}' > post.txt"
context:
greeting: hi
Example
So after setting everything up, we run the command
sketch ts monorepo --root-package root --pnpm base
And get this tree output:
βββ .oxlintrc.json
βββ apps
βΒ Β βββ test
βββ docker
βΒ Β βββ dev.dockerfile
βββ package.json
βββ packages
βββ pnpm-workspace.yaml
βββ tsconfig.json
βββ tsconfig.options.json
βΉοΈ You can use the
-iflag 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.
Hooks
We can define some commands (which can also be templates) to execute before and/or after generating the new package:
# Commands to run before generation, from the root of the new project
hooks_pre:
- command:
# Inlining a new template definition here,
# but as always, we can use stored templates too
name: pre_hook
content: "echo '{{ greeting }}' > pre.txt"
context:
greeting: hi
# Commands to run after generation, from the root of the new project
hooks_post:
- command:
name: post_hook
content: "echo '{{ greeting }}' > post.txt"
context:
greeting: hi
Adding Templates
You can also use the with_templates field (or the --with-template cli flag) to specify a list of templates or templating presets that should be generated whenever a package preset is being used.
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
with_templates:
# We can refer to a preset by id or create a new one locally
- 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"
Example
We start from this configuration:
typescript:
# By default, typescript and oxlint are added to
# the dependencies
no_default_deps: true
package_presets:
frontend:
# The name of the package. If this is unset,
# the name of the output directory will be used.
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"
# Generates an oxlint config file in the root.
# Can be a boolean, a preset id, or
# a literal configuration.
oxlint: false
# Create a vitest setup.
# Can be a boolean, a preset id, or
# a literal configuration.
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
And then run
sketch ts package --preset frontend
To obtain this output in the designated directory:
βββ package.json
βββ src
βΒ Β βββ index.ts
βΒ Β βββ schemas
βΒ Β βββ index.ts
βββ tests
βΒ Β βββ setup
βΒ Β βΒ Β βββ tests_setup.ts
βΒ Β βββ vitest.config.ts
βββ tsconfig.json
βΉοΈ You can also use the
-iflag to automatically install the dependencies with your selected package manager whenever a new package is created.
Generating A Barrel File
Sometimes it is useful to keep one or many barrel files in a typescript project, to make it easier to import/export elements from a group of modules.
Sketch has a dedicated command to make this process easier.
Examples
Target directory:
tests/ts_barrel
βββ nested
βββ file1.ts
βββ nested2
βββ file2.ts
Command:
sketch ts barrel
Output:
export * from "nested/file1";
export * from "nested/nested2/file2";