Skip to content

Merging

Blocks act as synchronous functions with optional metadata in the form of Addons. Blocks don’t know what order they’re run in or what other Blocks exist. They only know to map Addons and Options inputs to output Creations.

Addons

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"] },
];

Creations

Each of the four other Creations from Blocks has their own merging logic:

Files

File objects are recursively merged:

  • false and undefined values are ignored
  • Files are deduplicated if they have the same value, and an error is thrown if they do not

For example, given the following two files Creations to be merged:

{
"LICENSE.txt": "# MIT",
src: {
"index.ts": `export * from "./types.ts"`,
},
}
{
"LICENSE.txt": "# MIT",
src: {
"types.ts": `export type Example = true;`,
},
}

The merged result would be:

{
"LICENSE.txt": "# MIT",
src: {
"index.ts": `export * from "./types.ts"`,
"types.ts": `export type Example = true;`,
},
}

Requests

Requests are deduplicated based on their id. Later-created requests will override any previously created requests with the same id.

For example, given the following two requests Creations to be merged:

[
{
id: "branch-protection",
async send({ octokit }) {
console.log("TODO: Set up branch protection");
},
},
];
[
{
id: "branch-protection",
async send({ octokit }) {
await octokit.request(
`PUT /repos/{owner}/{repository}/branches/{branch}/protection`,
{
branch: "main",
// ...
},
);
},
},
];

The merged result would be just the latter Creation:

[
{
id: "branch-protection",
async send({ octokit }) {
await octokit.request(
`PUT /repos/{owner}/{repository}/branches/{branch}/protection`,
{
branch: "main",
// ...
},
);
},
},
];

Scripts

Scripts are deduplicated based on whether they include a phase:

  • “Phase” scripts are deduplicated if any’s arrays of commands are the same as any other
  • “Standalone” scripts provided as string are deduplicated and run in parallel after scripts with phases

For example, given the following two scripts Creations to be merged:

[
{
commands: ["pnpm install", "pnpm dedupe"],
phase: 0,
},
];
[
`npx set-github-repository-labels --labels "$(cat labels.json)"`,
{
commands: ["pnpm install"],
phase: 0,
},
];

The merged result would be:

[
{
commands: ["pnpm install", "pnpm dedupe"],
phase: 0,
},
`npx set-github-repository-labels --labels "$(cat labels.json)"`,
];

Suggestions

Suggestions are deduplicated by identity.

For example, given the following two suggestions Creations to be merged:

[
"- set a CODECOV secret to an codecov repository token",
"- set a NPM_TOKEN secret to an npm automation token",
];
[
"- set an ACCESS_TOKEN secret to a GitHub PAT with repo and workflow permissions",
"- set a NPM_TOKEN secret to an npm access token with automation permissions",
];

The merged result would be:

[
"- set a CODECOV secret to an codecov repository token",
"- set a NPM_TOKEN secret to an npm access token with automation permissions",
"- set an ACCESS_TOKEN secret to a GitHub PAT with repo and workflow permissions",
];
Made with 💝 in Boston by Josh Goldberg.