diff --git a/packages/create/src/cli/migrate/tryLoadMigrationPreset.ts b/packages/create/src/cli/migrate/tryLoadMigrationPreset.ts index 1a6a7fcc..6ef90b9d 100644 --- a/packages/create/src/cli/migrate/tryLoadMigrationPreset.ts +++ b/packages/create/src/cli/migrate/tryLoadMigrationPreset.ts @@ -1,7 +1,7 @@ import path from "node:path"; -import { CreateConfig } from "../../config/createConfig.js"; import { tryImportConfig } from "../../config/tryImportConfig.js"; +import { CreateConfig } from "../../config/types.js"; import { tryImportTemplatePreset } from "../importers/tryImportTemplatePreset.js"; export interface MigrationLoadSettings { diff --git a/packages/create/src/config/createConfig.ts b/packages/create/src/config/createConfig.ts index fc733e50..e207275b 100644 --- a/packages/create/src/config/createConfig.ts +++ b/packages/create/src/config/createConfig.ts @@ -1,21 +1,5 @@ -import { Block } from "../types/blocks.js"; -import { CreatedBlockAddons } from "../types/creations.js"; import { Preset } from "../types/presets.js"; - -export interface CreateConfig { - preset: Preset; - settings?: CreateConfigSettings; -} - -export interface CreateConfigBlockSettings { - add?: Block[]; - remove?: Block[]; -} - -export interface CreateConfigSettings { - addons?: CreatedBlockAddons[]; - blocks?: CreateConfigBlockSettings; -} +import { CreateConfigSettings } from "./types.js"; export function createConfig(preset: Preset, settings?: CreateConfigSettings) { return { preset, settings }; diff --git a/packages/create/src/config/types.ts b/packages/create/src/config/types.ts new file mode 100644 index 00000000..1f944940 --- /dev/null +++ b/packages/create/src/config/types.ts @@ -0,0 +1,18 @@ +import { Block } from "../types/blocks.js"; +import { CreatedBlockAddons } from "../types/creations.js"; +import { Preset } from "../types/presets.js"; + +export interface BlockModifications { + add?: Block[]; + remove?: Block[]; +} + +export interface CreateConfig { + preset: Preset; + settings?: CreateConfigSettings; +} + +export interface CreateConfigSettings { + addons?: CreatedBlockAddons[]; + blocks?: BlockModifications; +} diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts index 0a973956..9b964ac8 100644 --- a/packages/create/src/index.ts +++ b/packages/create/src/index.ts @@ -3,6 +3,7 @@ export * from "./cli/runCli.js"; // Config export * from "./config/createConfig.js"; +export type * from "./config/types.js"; // Creators export * from "./creators/createBase.js"; diff --git a/packages/create/src/predicates/isCreateConfig.ts b/packages/create/src/predicates/isCreateConfig.ts index a9c6cdb7..c28adb1b 100644 --- a/packages/create/src/predicates/isCreateConfig.ts +++ b/packages/create/src/predicates/isCreateConfig.ts @@ -1,4 +1,4 @@ -import { CreateConfig } from "../config/createConfig.js"; +import { CreateConfig } from "../config/types.js"; import { isPreset } from "./isPreset.js"; export function isCreateConfig(value: unknown): value is CreateConfig { diff --git a/packages/create/src/producers/applyBlockModifications.test.ts b/packages/create/src/producers/applyBlockModifications.test.ts new file mode 100644 index 00000000..168b9dc1 --- /dev/null +++ b/packages/create/src/producers/applyBlockModifications.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; +import { z } from "zod"; + +import { createBase } from "../creators/createBase.js"; +import { applyBlockModifications } from "./applyBlockModifications.js"; + +const base = createBase({ + options: { + value: z.string(), + }, +}); + +const blockA = base.createBlock({ + about: { name: "A" }, + produce: vi.fn(), +}); + +const blockB = base.createBlock({ + about: { name: "B" }, + produce: vi.fn(), +}); + +const blockC = base.createBlock({ + about: { name: "C" }, + produce: vi.fn(), +}); + +describe("runPreset", () => { + it("returns the initial blocks when no modifications are provided", () => { + const initial = [blockA, blockB]; + + const actual = applyBlockModifications(initial); + + expect(actual).toBe(initial); + }); + + it("returns the initial blocks when modifications are empty", () => { + const initial = [blockA, blockB]; + + const actual = applyBlockModifications(initial, { add: [], remove: [] }); + + expect(actual).toBe(initial); + }); + + it("applies modifications when they exist", () => { + const initial = [blockA, blockB]; + + const actual = applyBlockModifications(initial, { + add: [blockC], + remove: [blockB], + }); + + expect(actual).toEqual([blockA, blockC]); + }); +}); diff --git a/packages/create/src/producers/applyBlockModifications.ts b/packages/create/src/producers/applyBlockModifications.ts new file mode 100644 index 00000000..b8910a74 --- /dev/null +++ b/packages/create/src/producers/applyBlockModifications.ts @@ -0,0 +1,23 @@ +import { BlockModifications } from "../config/types.js"; +import { Block } from "../types/blocks.js"; + +export function applyBlockModifications( + initial: Block[], + { add = [], remove = [] }: BlockModifications = {}, +) { + if (!add.length && !remove.length) { + return initial; + } + + const blocks = new Set(initial); + + for (const added of add) { + blocks.add(added); + } + + for (const removed of remove) { + blocks.delete(removed); + } + + return Array.from(blocks); +} diff --git a/packages/create/src/producers/executePresetBlocks.test.ts b/packages/create/src/producers/executePresetBlocks.test.ts index 8f7850ab..f72fa040 100644 --- a/packages/create/src/producers/executePresetBlocks.test.ts +++ b/packages/create/src/producers/executePresetBlocks.test.ts @@ -36,16 +36,9 @@ describe("runPreset", () => { }, }); - const preset = base.createPreset({ - about: { - name: "Example Preset", - }, - blocks: [block], - }); - const result = executePresetBlocks({ + blocks: [block], options: { value: "Hello, world!" }, - preset, presetContext, }); @@ -62,7 +55,7 @@ describe("runPreset", () => { name: "Example Block", }, addons: { - extra: z.string().default(""), + extra: z.string().optional(), }, produce({ addons, options }) { return { @@ -71,17 +64,10 @@ describe("runPreset", () => { }, }); - const preset = base.createPreset({ - about: { - name: "Example Preset", - }, - blocks: [block], - }); - const result = executePresetBlocks({ addons: [block({ extra: "line" })], + blocks: [block], options: { value: "Hello, world!" }, - preset, presetContext, }); @@ -116,17 +102,10 @@ describe("runPreset", () => { }, }); - const preset = base.createPreset({ - about: { - name: "Example Preset", - }, - blocks: [block], - }); - it("does not augment creations with a Block's initialize() or migrate() when mode is undefined", () => { const result = executePresetBlocks({ + blocks: [block], options: { value: "Hello, world!" }, - preset, presetContext, }); @@ -139,9 +118,9 @@ describe("runPreset", () => { it("augments creations with a Block's initialize() when mode is 'initialize'", () => { const result = executePresetBlocks({ + blocks: [block], mode: "initialize", options: { value: "Hello, world!" }, - preset, presetContext, }); @@ -155,9 +134,9 @@ describe("runPreset", () => { it("augments creations with a Block's migrate() when mode is 'migrate'", () => { const result = executePresetBlocks({ + blocks: [block], mode: "migrate", options: { value: "Hello, world!" }, - preset, presetContext, }); diff --git a/packages/create/src/producers/executePresetBlocks.ts b/packages/create/src/producers/executePresetBlocks.ts index f874b597..ec80c0cd 100644 --- a/packages/create/src/producers/executePresetBlocks.ts +++ b/packages/create/src/producers/executePresetBlocks.ts @@ -3,33 +3,29 @@ import { getUpdatedBlockAddons, } from "../mergers/getUpdatedBlockAddons.js"; import { mergeCreations } from "../mergers/mergeCreations.js"; -import { AnyShape, InferredObject } from "../options.js"; import { Block, BlockWithAddons } from "../types/blocks.js"; import { CreatedBlockAddons, Creation } from "../types/creations.js"; import { ProductionMode } from "../types/modes.js"; -import { Preset } from "../types/presets.js"; import { SystemContext } from "../types/system.js"; import { produceBlock } from "./produceBlock.js"; -export interface ExecutePresetBlocksSettings { +export interface ExecutePresetBlocksSettings { // TODO: I don't know what to put here instead of object... // eslint-disable-next-line @typescript-eslint/no-explicit-any - addons?: CreatedBlockAddons>[]; + addons?: CreatedBlockAddons[]; + blocks: Block[]; mode?: ProductionMode; - options: InferredObject; - preset: Preset; + options: Options; presetContext: SystemContext; } -export function executePresetBlocks({ +export function executePresetBlocks({ addons, + blocks, mode, options, - preset, presetContext, -}: ExecutePresetBlocksSettings) { - type Options = InferredObject; - +}: ExecutePresetBlocksSettings) { // From engine/runtime/execution.md: // This engine continuously re-runs Blocks until no new Args are provided. @@ -39,7 +35,7 @@ export function executePresetBlocks({ >(addons?.map((addon) => [addon.block, { addons: addon.addons as object }])); // 1. Create a queue of Blocks to be run, starting with all defined in the Preset - const blocksToBeRun = new Set(preset.blocks); + const blocksToBeRun = new Set(blocks); // 2. For each Block in the queue: while (blocksToBeRun.size) { diff --git a/packages/create/src/producers/producePreset.ts b/packages/create/src/producers/producePreset.ts index f73f11bf..76a3c54f 100644 --- a/packages/create/src/producers/producePreset.ts +++ b/packages/create/src/producers/producePreset.ts @@ -1,15 +1,18 @@ +import { BlockModifications } from "../config/types.js"; import { AnyShape, InferredObject } from "../options.js"; import { createSystemContextWithAuth } from "../system/createSystemContextWithAuth.js"; import { CreatedBlockAddons, Creation } from "../types/creations.js"; import { ProductionMode } from "../types/modes.js"; import { Preset } from "../types/presets.js"; import { NativeSystem } from "../types/system.js"; +import { applyBlockModifications } from "./applyBlockModifications.js"; import { executePresetBlocks } from "./executePresetBlocks.js"; export interface PresetProductionSettings extends Partial, ProductionSettingsBase { addons?: CreatedBlockAddons>[]; + blocks?: BlockModifications>; options: InferredObject; } @@ -22,6 +25,7 @@ export async function producePreset( preset: Preset, { addons, + blocks: blockModifications, directory = ".", mode, options, @@ -35,9 +39,9 @@ export async function producePreset( const creation = executePresetBlocks({ addons, + blocks: applyBlockModifications(preset.blocks, blockModifications), mode, options, - preset, presetContext: { ...system, directory }, }); diff --git a/packages/create/src/runners/runPreset.ts b/packages/create/src/runners/runPreset.ts index 59864c70..8d65c964 100644 --- a/packages/create/src/runners/runPreset.ts +++ b/packages/create/src/runners/runPreset.ts @@ -1,5 +1,6 @@ import fs from "node:fs/promises"; +import { BlockModifications } from "../config/types.js"; import { AnyShape, InferredObject } from "../options.js"; import { producePreset } from "../producers/producePreset.js"; import { createSystemContextWithAuth } from "../system/createSystemContextWithAuth.js"; @@ -12,6 +13,7 @@ import { applyCreation } from "./applyCreation.js"; export interface PresetRunSettings extends RunSettingsBase { addons?: CreatedBlockAddons>[]; + blocks?: BlockModifications>; options: InferredObject; } diff --git a/packages/site/src/content/docs/configuration.md b/packages/site/src/content/docs/configuration.md index 5a835fc2..df9a87d5 100644 --- a/packages/site/src/content/docs/configuration.md +++ b/packages/site/src/content/docs/configuration.md @@ -66,10 +66,6 @@ Running `npx create` in a repository with that configuration file would merge in ### `blocks` -:::danger -Blocks in config files have not yet been implemented. -::: - Any customizations to the [Blocks](./engines/concepts/blocks) provided as part of the Preset. #### `add` diff --git a/packages/site/src/content/docs/engine/apis/producers.md b/packages/site/src/content/docs/engine/apis/producers.md index 70d0d526..bf7aac12 100644 --- a/packages/site/src/content/docs/engine/apis/producers.md +++ b/packages/site/src/content/docs/engine/apis/producers.md @@ -215,6 +215,8 @@ Given a [Preset](../concepts/presets), creates a [Creation](../runtime/creations 1. `preset` _(required)_: a Preset 2. `settings` _(required)_: + - `addons` _(optional)_: any additional [Addons](../concepts/blocks#addons) to provide to Blocks + - `blocks` _(optional)_: any additions and/or removals of Blocks - `options` _(required)_: [Base](../concepts/bases) options to run with - _(optional)_ any other properties from a [Block Context](../runtime/contexts#block-contexts) @@ -239,6 +241,58 @@ const preset = base.createPreset({ await producePreset(preset, { options: { title: "My App" } }); ``` +### `addons` {#preset-addons} + +Any additional [Addons](../concepts/blocks#addons) to provide to Blocks. + +For example, this production adds a `"generated"` Addon to a Prettier Block: + +```ts +import { Preset, producePreset } from "create"; +import { z } from "zod"; + +import { blockPrettier } from "./blockPrettier"; + +declare const preset: Preset<{ name: z.ZodString }>; + +await producePreset(preset, { + addons: [blockPrettier({ ignores: ["generated"] })], + options: { + name: "My Production", + }, +}); +``` + +See [Configuration > `addons`](../../configuration#addons) for how this is used. + +### `blocks` {#preset-blocks} + +Any Blocks to `add` and/or `remove`. + +For example, this production adds a Jest Block and removes a Vitest Block: + +```ts +import { Preset, producePreset } from "create"; +import { z } from "zod"; + +import { blockJest } from "./blockJest"; +import { blockVitest } from "./blockVitest"; + +declare const preset: Preset<{ name: z.ZodString }>; + +await producePreset(preset, { + blocks: { + add: [blockJest], + remove: [blockVitest], + }, + options: { + name: "My Production", + }, +}); +``` + +See [Configuration > `blocks`](../../configuration#blocks) for how this is used. + ### `options` {#preset-options} Any options defined by the Preset's [Base](../concepts/bases).