Skip to content

Commit

Permalink
feat: allow blocks in config files (#96)
Browse files Browse the repository at this point in the history
* feat: allow blocks in config files

* Oh also the files

* Tests

* it
  • Loading branch information
JoshuaKGoldberg authored Dec 31, 2024
1 parent 151c831 commit 05075db
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 63 deletions.
2 changes: 1 addition & 1 deletion packages/create/src/cli/migrate/tryLoadMigrationPreset.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
18 changes: 1 addition & 17 deletions packages/create/src/config/createConfig.ts
Original file line number Diff line number Diff line change
@@ -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 };
Expand Down
18 changes: 18 additions & 0 deletions packages/create/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -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<Options extends object = object> {
add?: Block<object | undefined, Options>[];
remove?: Block<object | undefined, Options>[];
}

export interface CreateConfig {
preset: Preset;
settings?: CreateConfigSettings;
}

export interface CreateConfigSettings<Options extends object = object> {
addons?: CreatedBlockAddons<object, Options>[];
blocks?: BlockModifications<Options>;
}
1 change: 1 addition & 0 deletions packages/create/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion packages/create/src/predicates/isCreateConfig.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
55 changes: 55 additions & 0 deletions packages/create/src/producers/applyBlockModifications.test.ts
Original file line number Diff line number Diff line change
@@ -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]);
});
});
23 changes: 23 additions & 0 deletions packages/create/src/producers/applyBlockModifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BlockModifications } from "../config/types.js";
import { Block } from "../types/blocks.js";

export function applyBlockModifications<Options extends object>(
initial: Block<object | undefined, Options>[],
{ add = [], remove = [] }: BlockModifications<Options> = {},
) {
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);
}
33 changes: 6 additions & 27 deletions packages/create/src/producers/executePresetBlocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand All @@ -62,7 +55,7 @@ describe("runPreset", () => {
name: "Example Block",
},
addons: {
extra: z.string().default(""),
extra: z.string().optional(),
},
produce({ addons, options }) {
return {
Expand All @@ -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,
});

Expand Down Expand Up @@ -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,
});

Expand All @@ -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,
});

Expand All @@ -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,
});

Expand Down
20 changes: 8 additions & 12 deletions packages/create/src/producers/executePresetBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OptionsShape extends AnyShape> {
export interface ExecutePresetBlocksSettings<Options extends object> {
// TODO: I don't know what to put here instead of object...
// eslint-disable-next-line @typescript-eslint/no-explicit-any
addons?: CreatedBlockAddons<any, InferredObject<OptionsShape>>[];
addons?: CreatedBlockAddons<any, Options>[];
blocks: Block<object | undefined, Options>[];
mode?: ProductionMode;
options: InferredObject<OptionsShape>;
preset: Preset<OptionsShape>;
options: Options;
presetContext: SystemContext;
}

export function executePresetBlocks<OptionsShape extends AnyShape>({
export function executePresetBlocks<Options extends object>({
addons,
blocks,
mode,
options,
preset,
presetContext,
}: ExecutePresetBlocksSettings<OptionsShape>) {
type Options = InferredObject<OptionsShape>;

}: ExecutePresetBlocksSettings<Options>) {
// From engine/runtime/execution.md:
// This engine continuously re-runs Blocks until no new Args are provided.

Expand All @@ -39,7 +35,7 @@ export function executePresetBlocks<OptionsShape extends AnyShape>({
>(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) {
Expand Down
6 changes: 5 additions & 1 deletion packages/create/src/producers/producePreset.ts
Original file line number Diff line number Diff line change
@@ -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<OptionsShape extends AnyShape>
extends Partial<NativeSystem>,
ProductionSettingsBase {
addons?: CreatedBlockAddons<object, InferredObject<OptionsShape>>[];
blocks?: BlockModifications<InferredObject<OptionsShape>>;
options: InferredObject<OptionsShape>;
}

Expand All @@ -22,6 +25,7 @@ export async function producePreset<OptionsShape extends AnyShape>(
preset: Preset<OptionsShape>,
{
addons,
blocks: blockModifications,
directory = ".",
mode,
options,
Expand All @@ -35,9 +39,9 @@ export async function producePreset<OptionsShape extends AnyShape>(

const creation = executePresetBlocks({
addons,
blocks: applyBlockModifications(preset.blocks, blockModifications),
mode,
options,
preset,
presetContext: { ...system, directory },
});

Expand Down
2 changes: 2 additions & 0 deletions packages/create/src/runners/runPreset.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,6 +13,7 @@ import { applyCreation } from "./applyCreation.js";
export interface PresetRunSettings<OptionsShape extends AnyShape>
extends RunSettingsBase {
addons?: CreatedBlockAddons<object, InferredObject<OptionsShape>>[];
blocks?: BlockModifications<InferredObject<OptionsShape>>;
options: InferredObject<OptionsShape>;
}

Expand Down
4 changes: 0 additions & 4 deletions packages/site/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Loading

0 comments on commit 05075db

Please sign in to comment.