Blocks
A Block defines the logic to create a portion of a repository. Each Block is associated with a parent Base. Blocks can then be listed in Presets associated with the same Base.
Blocks can be individually added and removed by users when customizing their repository.
Production
Blocks define their logic for created repository portions in a produce() function.
produce() returns a Creation describing any produced output.
When create scaffolds a repository from a Preset, it merges together the produced outputs from its listed Blocks.
For example, this Block describes creating a .nvmrc file:
import { base } from "./base";
export const blockNvmrc = base.createBlock({ produce() { return { files: { ".nvmrc": "20.12.2", }, }; },});That blockNvmrc can then be listed in a Preset’s blocks array:
import { base } from "./base";import { blockNvmrc } from "./blockNvmrc";
export const presetVersioned = base.createPreset({ blocks: [ blockNvmrc, // ... ],});That presetVersioned would then produce an .nvmrc file with text content 20.12.2 when run.
20.12.2Options
Each Block runs with the options defined by its parent Base.
For example, a Base with a name option could create a Block that generates part of a README.md file:
import { base } from "./base";
export const blockREADME = base.createBlock({ produce({ options }) { return { files: { "README.md": `# ${options.name}`, }, }; },});If create is run with --name My Repository, a README.md would be generated with that as its heading:
# My RepositoryAddons
Blocks can define additional optional data called “Addons” that they can be receive from other Blocks. Blocks define Addons as the properties for a Zod object schema and then receive them in their context.
For example, this Block takes in a string array under a names Addon, to be printed in a names.txt file:
import { z } from "zod";
import { base } from "./base";
export const blockNames = base.createBlock({ addons: { names: z.array(z.string()).default([]), }, async produce({ addons }) { return { files: { "names.txt": addons.names.join("\n"), }, }; },});Other Blocks may produce Addons to be given to any other Blocks. These Addons will be merged together when a Preset containing the Blocks is run.
For example, the following Names Block receives composed Addons from the FruitNames Block:
import { z } from "zod";
import { base } from "./base";
export const blockNames = base.createBlock({ addons: { names: z.array(z.string()).default([]), }, async produce({ addons }) { return { files: { "names.txt": addons.names.join("\n"), }, }; },});
export const blockFruitNames = base.createBlock({ async produce() { return { addons: [ blockNames({ names: ["apple", "banana", "cherry"], }), ], }; },});A Preset containing both Blocks would then produce a names.txt file with those three names as lines in its text:
applebananacherryMerging
At runtime, the create engine will often need to re-run Blocks continuously as they receive Addons from other Blocks.
Blocks will be re-run whenever other Blocks signal new Addon data to them that they haven’t yet seen.
This allows Blocks to not need any explicit indication of what order to run in.
Addons are merged by concatenating arrays and removing duplicate elements.
Duplicates are detected by hash-object object hashing.
For example, given the following two Addons to be merged:
[ { name: "First", steps: ["a", "b"] }, { name: "Second", steps: ["c", "d"] },];[ { name: "Second", steps: ["c", "d"] }, { name: "Third", steps: ["e", "f"] },],The merged result would be:
[ { name: "First", steps: ["a", "b"] }, { name: "Second", steps: ["c", "d"] }, { name: "Third", steps: ["e", "f"] },];