Skip to content

Creator APIs

The main driver of create is a set of APIs that set up the generators for repositories:

createBase

Given a Base Definition, creates a Base.

A Base Definition is an object containing:

options

The Zod values for options that will be made available to the Base and all its Blocks.

For example, this Base defines a required name string and optional value number:

import { createBase } from "create";
export const base = createBase({
options: {
name: z.string(),
value: z.number().optional(),
},
});

produce

A Base may define a produce method to fill in any values that aren’t inferred by the system at runtime.

produce() methods receive a Base Context parameter. They must return an object whose properties fill in any options that can be inferred from the system. Each property may either be a value or an asynchronous function to retrieve that value.

For example, this Base allows defaulting a required name option to that property of its package.json using an Input:

import { createBase } from "create";
import { z } from "zod";
import { inputJsonFile } from "./inputJsonFile";
export const base = createBase({
options: {
name: z.string(),
},
produce({ take }) {
return {
name: async () =>
(await take(inputJsonFile, { fileName: "package.json" })).name,
};
},
});

Lazy Production

Note that produce() is itself not an async function. This is to encourage options to be lazy: they should only be evaluated if needed. The lazy-value package can be used to create chained lazy properties.

For example, this Base retrieves both a description and a name from a package.json on disk lazily:

import { createBase } from "create";
import lazyValue from "lazy-value";
import { z } from "zod";
import { inputJsonFile } from "./inputJsonFile";
export const base = createBase({
options: {
description: z.string(),
name: z.string(),
},
produce({ take }) {
const packageData = lazyValue(async () =>
take(inputJsonFile({ fileName: "package.json" })),
);
return {
description: async () => (await packageData()).description,
name: async () => (await packageData()).name,
};
},
});

That produce() will only read and parse the package.json file if either of description and/or name are not provided by the user.

Production Options

produce()’s Context parameter contains an options property with any options explicitly provided by the user. This may be useful if the logic to produce some options should set defaults based on other options.

For example, this Base defaults an optional author option to its required owner option:

import { createBase } from "create";
import { z } from "zod";
export const base = createBase({
options: {
author: z.string().optional(),
owner: z.string(),
},
produce({ options }) {
return {
author: options.owner,
};
},
});

When the Base’s Presets are run, if the user provides an author, then they’ll use that. Otherwise, they’ll default the author to whatever owner the user provided.

createBlock

Blocks can be created by the createBlock() method of a Base. createBlock() takes in a Block Definition and returns a Block.

A Block Definition is an object containing:

  • about (optional): tooling metadata for the Block
  • args (optional): a Block args object containing Zod values
  • produce (required): a Block production method

about

Metadata about the Block that can be used by tooling to describe it.

This is an object containing any of:

  • description: a sentence describing what the Block does
  • name: what to refer to the Block as

For example, this Block describes itself as setting up monorepo TypeScript building:

import { base } from "./base";
base.createBlock({
about: {
description: "TSConfigs and build tasks for a monorepo.",
name: "TypeScript Builds",
},
produce() {
// ...
},
});

args

Block Definitions may include an args object with defining Zod values as its properties. Whenever a new instance of a Block with args is constructed, those args must be provided to it.

Block args are typically provided when a Block is being constructed in a Preset’s blocks array. Those args are used to describe how the Block behaves inside that Preset.

For example, this Prettier block optionally allows adding in any plugins with a plugins arg:

import { z } from "zod";
import { base } from "./base";
export const blockPrettier = base.createBlock({
args: {
plugins: z.array(z.string()).optional(),
},
async produce({ args }) {
return {
files: {
".prettierrc.json":
args.plugins &&
JSON.stringify({
$schema: "http://json.schemastore.org/prettierrc",
plugins: args.plugins,
}),
},
};
},
});

Presets can then specify the blockPrettier Block with plugins arg set to an array:

import { base } from "./base";
import { blockPrettier } from "./blockPrettier";
export const presetFormatted = base.createPreset({
blocks: [
blockPrettier({
plugins: [
"prettier-plugin-curly",
"prettier-plugin-sh",
"prettier-plugin-packagejson",
],
}),
// ...
],
});

Creating with that presetFormatted Preset would then produce a .prettierrc.json file with those three plugins listed in its JSON contents.

produce

Block Definitions must include a produce() method for their core logic.

  • It receives one parameter: a Context object containing options as well as other utilities.
  • It returns a Creation object describing the generated pieces of tooling.

For example, this Block defines a files Creation for a knip.json:

import { base } from "./base";
export const blockKnip = base.createBlock({
produce() {
return {
files: {
"knip.json": JSON.stringify({
$schema: "https://unpkg.com/knip@latest/schema.json",
}),
},
};
},
});

createPreset

Presets can be created by the createPreset() method of a Base. createPreset() takes in a Preset Definition and returns a Preset.

A Preset Definition is an object containing:

  • about (optional): tooling metadata for the Preset
  • blocks (required): any number of Blocks run by the Preset

about

Metadata about the Preset that can be used by tooling to describe it.

This is an object containing any of:

  • description: a sentence describing what the Preset does
  • name: what to refer to the Preset as

For example, this Preset describes itself as setting up a bare-bones TypeScript monorepo:

import { base } from "./base";
base.createPreset({
about: {
description: "The barest of bones tooling for a type-safe monorepo.",
name: "Minimal",
},
blocks: [
// ...
],
});

blocks

The Blocks that will be run to generate the Preset’s Creations during production.

For example, this Preset includes blocks for building and testing:

import { base } from "./base";
import { blockBuilds } from "./blockBuilds";
import { blockTests } from "./blockTests";
base.createPreset({
blocks: [blockBuilds, blockTests],
});

The Blocks provided to a Preset must be created from the same root Base.

blocks Functions

A Preset’s blocks can be defined as an array of Blocks or a function that takes in Options and returns an array of Blocks. This allows Blocks to be given Args based on Options.

For example, this Preset forwards a name option as an configuration in an ESLint Block’s Args:

import { base } from "./base";
import { blockESLint } from "./blockESLint";
base.createPreset({
blocks: (options) => [
blockESLint({
rules: {
files: ["**/*.md/*.ts"],
rules: {
"n/no-missing-import": [
"error",
{ allowModules: [options.repository] },
],
},
},
}),
],
});

createTemplate

Given a Template Definition, creates a Template.

A Template Definition is an object containing:

  • about (optional): tooling metadata for the Template
  • default (optional): Which Preset should be selected by default in CLIs
  • presets (required): an array of objects for the Presets available with the Template

about

Metadata about the Template that can be used by tooling to describe it.

This is an object containing any of:

  • description: a sentence describing what the Block does
  • name: what to refer to the Block as

For example, this Template describes itself as a solution for TypeScript repositories:

import { createTemplate } from "create";
createTemplate({
about: {
description:
"One-stop shop for the latest and greatest TypeScript tooling.",
name: "Create TypeScript App",
},
presets: [
// ...
],
});

default

The default Preset to select for users, if not the first in the array.

This should be the same string as one of the labels under presets.

For example, this Template defaults to the "Common" Preset:

import { createTemplate } from "create";
import { presetCommon } from "./presetCommon";
import { presetEverything } from "./presetEverything";
export const templateTypeScriptApp = createTemplate({
default: "Common",
presets: [
{ label: "Common", preset: presetCommon },
{ label: "Everything", preset: presetEverything },
],
});

presets

The Presets users can choose from with the Template, in order of how they should be listed.

Each element in the array is an object containing:

  • label (required): a brief name for the Preset for text displays
  • preset (required): the Preset itself

For example, this Template allows choosing between two Presets for TypeScript apps:

import { createTemplate } from "create";
import { presetCommon } from "./presetCommon";
import { presetEverything } from "./presetEverything";
export const templateTypeScriptApp = createTemplate({
about: {
name: "TypeScript App",
},
presets: [
{ label: "Common", preset: presetCommon },
{ label: "Everything", preset: presetEverything },
],
});

createInput

Given an Input Definition, creates an Input.

An Input Definition is an object containing:

args

Input Definitions may include an args object with defining Zod values as its properties. Whenever an Input with args is passed to take, those args must be provided to it.

For example, this Input defines a required path string:

import { createInput } from "create";
import { z } from "zod";
export const inputFromFile = createInput({
args: {
path: z.string(),
},
async produce({ args, fs }) {
return await fs.readFile(args.path);
},
});

produce

Input Definitions must include a produce() method for their core logic.

  • It receives one parameter: a Context object
  • It can return any kind of data.

For example, this Input fetches text from a URL:

import { createInput } from "create";
export const inputNow = createInput({
async produce({ fetcher }) {
return await (await fetcher("https://example.com")).text();
},
});