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.2
Options
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 Repository
Addons
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:
applebananacherry
Merging
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"] },];