ποΈ 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:
-
Templating
- Templating presets (extensible)
-
Docker
- Docker Compose file (extensible)
- Docker Compose service (extensible)
-
Git
- Git repo
.gitignore(extensible)
-
Pre-commit
.pre-commit-config.yaml(extensible)
-
Github Workflows
- Github workflow (extensible)
- Github workflow job (extensible)
- Github workflow step
-
Rust
- Rust crate (uses gitignore preset + detects workspace like
cargo newdoes) Cargo.toml(extensible, with merging of features for dependencies)
- Rust crate (uses gitignore preset + detects workspace like
-
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)
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 execβ΄sketch gitignoreβ΄sketch gh-workflowβ΄sketch docker-composeβ΄sketch pre-commitβ΄sketch rustβ΄sketch rust crateβ΄sketch rust manifestβ΄sketch tsβ΄sketch ts monorepoβ΄sketch ts packageβ΄sketch ts barrelβ΄sketch ts configβ΄sketch package-jsonβ΄sketch oxlintβ΄sketch pnpm-workspaceβ΄sketch licenseβ΄sketch json-schemaβ΄
sketch
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 stdoutexecβ 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 presetrustβ The subcommands to generate files used in Rust workspacestsβ Executes typescript-specific commandspackage-jsonβ Generates apackage.jsonfile from a presetoxlintβ Generates a.oxlintrc.jsonfile from a presetpnpm-workspaceβ Generates apnpm-workspace.yamlfile from a presetlicenseβ Generates a license filejson-schemaβ Generates the json schema for the configuration 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 -
-g,--gitignore <GITIGNORE>β Settings for the gitignore file -
--pre-commit <PRE_COMMIT>β Configuration settings forpre-commit -
-t,--template <WITH_TEMPLATES>β A set of templates to generate when this preset is used -
-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 licensempl2
-
--workflow <id=PRESET_ID,file=PATH>β One or many workflows to generate in the new repo -
-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]
Arguments:
<OUTPUT>β The output path for the template/preset. Impliesstdoutif absent for single templates. Required when a preset is selected
Options:
-p,--preset <PRESET>β The id of a templating preset-f,--file <FILE>β The path to the template file-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)-c,--content <CONTENT>β The literal definition for the template
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. Not required if services are added manually with the--serviceflag<OUTPUT>β The output path of the new file [default:compose.yaml]
Options:
-s,--service <SERVICES>β PRESET_ID|id=PRESET,name=NAME
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 rust
The subcommands to generate files used in Rust workspaces
Usage: sketch rust <COMMAND>
Subcommands:
crateβmanifestβ Generates a newCargo.tomlfile from a preset
sketch rust crate
Usage: sketch rust crate [OPTIONS] <DIR>
Arguments:
<DIR>β The output directory for the new crate. Also the name of the generated crate by default
Options:
-
-p,--preset <PRESET>β The crate preset to use -
-m,--manifest <MANIFEST>β TheCargo.tomlmanifest preset to use (overrides the one in the preset if one was selected) -
-n,--name <NAME>β The name of the generated crate (by default, it uses the name of the output dir) -
--gitignore <GITIGNORE>β Settings for the gitignore file -
--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 licensempl2
-
-t,--template <PRESET_ID>
sketch rust manifest
Generates a new Cargo.toml file from a preset
Usage: sketch rust manifest <PRESET> [OUTPUT]
Arguments:
<PRESET>β The id of the preset<OUTPUT>β The output path [default:Cargo.toml]
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 fileconfigβ Generates atsconfig.jsonfile from a preset
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: major]Possible values:
major,minor,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
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 licensempl2
-
-t,--template <PRESET_ID>β One or many templates to generate along with this package. Relative output paths will resolve from the root of the package -
--oxlint <ID>β The configuration for this package's oxlint setup. It can be set totrue(to use defaults), to a preset id, or to a literal configuration -
-i,--installβ Installs the dependencies with the chosen package manager
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 -
-i,--installβ Installs the dependencies with the chosen package manager -
--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 licensempl2
-
-t,--template <PRESET_ID>β One or many templates to generate along with this package. Relative output paths will resolve from the root of the package -
--oxlint <ID>β The configuration for this package's oxlint setup. It can be set totrue(to use defaults), to a preset id, or to a literal configuration
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 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 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 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 licensempl2
Options:
-o,--output <OUTPUT>β The path of the output file [default:LICENSE]
sketch json-schema
Generates the json schema for the configuration file
Usage: sketch json-schema <OUTPUT>
Arguments:
<OUTPUT>β The output path for the json schema
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.
Alternatively, if you build the binary with the schemars feature, you can generate the schema by yourself with the command sketch json-schema.
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)Cargo.toml- 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
Output:
Current arch is: x86_64
Current os is: linux
Current os family is: unix
Is unix: true
A random uuid is: 7bf3ebd4-2ec0-4e02-9535-11ddae8fe81a
A random string is: 33baa8
A random number is: 2576097
Now is 2026-02-16T16:22:24.614830966+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 --template hobbits 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 nested_file.j2 from_template_file.txt
βΉοΈ You can also use the
-fflag to render a template from any file, even outsidetemplates_dir.
From Literal Definition
Alternatively, a template can also be defined directly within a command:
sketch render --content "they're taking the hobbits to {{ location }}!" 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" 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" commands_tests -f 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" commands_tests 'echo "engine feels good... much {{ condition }} than before... amazing" > command_output.txt'
Output:
engine feels good... much slower than before... amazing
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
.pre-commit-config.yaml(extensible)
-
Github Workflows
- Github workflow (extensible)
- Github workflow job (extensible)
- Github workflow step
-
Rust
- Rust crate (uses gitignore preset + detects workspace like
cargo newdoes) Cargo.toml(extensible, with merging of features for dependencies)
- Rust crate (uses gitignore preset + detects workspace like
-
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
rust:
manifest_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.30.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
repo_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:
- preset_id: dockerfile
# Adding context
context:
docker_dev_port: 9530
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: minor
# `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 command by using the --preset flag, 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
context: {}
templates:
# Selecting individual templates
- template: hobbits
output: hobbits.txt
- template: breakfast
output: subdir/breakfast.txt
Command:
sketch render --preset lotr simple
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 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 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.30.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
repo_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
docker_dev_port: 9530
# List of templates
templates:
# Single template
- output: Dockerfile
template: dockerfile
# ...and then we add it to a preset
# Git presets
repo_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:
- preset_id: dockerfile
# Adding context
context:
docker_dev_port: 9530
Example
Starting from the config from above, we can run this command:
sketch repo --preset ts_package repo
To get this tree output:
βββ Dockerfile
βββ .github
βΒ Β βββ workflows
βΒ Β βββ my_workflow.yaml
βββ .gitignore
βββ LICENSE
βββ .pre-commit-config.yaml
βΉοΈ With cli flags, we can override the
gitignoreandpre-commitpresets, as well as adding new templates to 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.30.0
hooks:
- id: gitleaks
gitignore output
*.env
dist
*.tsBuildInfo
node_modules
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 workflow.yaml
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!"
Rust Presets
Sketch can be used to define and generate presets for rust crates (with the sketch rust crate command) and for individual Cargo.toml manifests (with the sketch rust manifest command).
The sketch rust crate command mimics the cargo new command in the sense that it detects if these is a workspace manifest in the parent directory of the target output, and if one is detected, the new crate will be added to its members. Also, if certain fields such as lints, keywords, license and so on are set in the workspace manifest but not in the manifest that is generated, they will be added as entries with workspace = true.
Cargo.toml presets are extensible. Dependencies are deeply merged, so for example the features will be joined.
Example
Config:
rust:
manifest_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 rust manifest cli-custom cargo-example.toml
Output:
[package]
name = "cli-custom"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
indexmap = { version = "2.11", features = ["serde"] }
owo-colors = { version = "4", features = ["supports-colors"] }
ratatui = "0.29"
serde = { version = "1", 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
One of the goals of Sketch is to bring more structure and order when dealing with the somewhat chaotic typescript ecosystem.
Many typical components of a typescript project have their own dedicated preset, and there are also dedicated commands for common operations such as generating barrel files.
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
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.
Example
So after setting everything up with the examples from above, we run the command
sketch ts monorepo --root-package root --pnpm base ts_monorepo
And get this tree output:
βββ apps
βΒ Β βββ test
βββ docker
βΒ Β βββ dev.dockerfile
βββ .oxlintrc.json
βββ 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.
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 frontend
To obtain this output in the designated directory:
βββ package.json
βββ src
βΒ Β βββ 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.
With the following package.json file
{
"name": "@myproject/frontend",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {},
"packageManager": "pnpm",
"dependencies": {},
"devDependencies": {
"tailwindcss": "^4.0.0"
}
}
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";
Smart Features
Sketch provides some additional features that may come in handy when dealing with a typescript project.
Converting latest to a range
When the npm-versions feature is enabled, 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 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.11.9
catalogs:
svelte:
svelte: ^5.51.2
onlyBuiltDependencies:
- esbuild
minimumReleaseAge: 1440