WordPress / blueprints-library

32 stars 7 forks source link

Blueprints: Merging Blueprints #49

Open adamziel opened 1 year ago

adamziel commented 1 year ago

I want to be able to merge these two Blueprints:

{
    "landingPage": "/wp-admin/",
    "steps": [
        // .. Set up a blog ...
    ]
}
{
    "steps": [
        // .. Set up a woo store ...
    ]
}

And get a single Blueprint which sets up a site that's both a blog and a store

Known plugins with a Blueprint.json file

Let's use these Blueprints to explore the merging process

Related

seanmorris commented 11 months ago

@adamziel For semver combinations, (ie "php": ">=7.4 <8.0") would it be appropriate to use logical-and space notation here? https://devhints.io/semver (see: Combining ranges)

It looks to be supported by the major semver libs in both JS & PHP:

https://www.npmjs.com/package/semver#advanced-range-syntax

https://getcomposer.org/doc/articles/versions.md#version-range https://packagist.org/packages/composer/semver

adamziel commented 11 months ago

@seanmorris that could be an interesting direction! Probably nothing to worry about for now, though – the constraints field isn't supported at the moment. I was envisioning it while writing up this issue, but noone ever asked to actually implement it so maybe the demand isn't there.

adamziel commented 11 months ago

This Blueprint.json for the interactive-code-block plugin could come handy when exploring this issue: https://github.com/WordPress/playground-tools/blob/trunk/packages/interactive-code-block/public/blueprint.json @seanmorris

dmsnell commented 11 months ago

Consider each step and each property of the Blueprint as its own datatype. If we do that and take inspiration from the Monoid type class, we can think of each type having an append method (as well as an empty value empty that provides a no-op when appended to another).

For a PHP version the empty value is going to be >= 1 or perhaps for our sake >= 5.6.4. The append needs to find the intersection of intervals.

For steps we probably need to separate out those which demand sequence and those which don't. For plugins and theme installations then, the empty value is just an empty list, and the append merges two lists with uniqueness.

For inherently irreconcilable properties like landingPage we can form a deterministic semantic: first-in-wins or last-in-wins. The empty value is the default / and the append is replacement.

And so on.

adamziel commented 11 months ago

Oooh Monoid-ization is an interesting idea @dmsnell, thank you for that – I love it. In other words, the mental model is that each Blueprint is already a function composition like below:

blueprint_a = (phpVersion 7.0) . (installPlugin "gutenberg") . ( landingPage "/" )
blueprint_b = (installTheme "pendant" ) . (phpVersion 8.2) . (installPlugin "classic-editor")

But if so, merging them both would be just composing all the functions together:

blueprint_c =  blueprint_a . blueprint_b

Or perhaps even:

blueprint_a = (phpVersion 7.0) >>= (installPlugin "gutenberg") >>= ( landingPage "/" )
blueprint_b = (installTheme "pendant" ) >>= (phpVersion 8.2) >>= (installPlugin "classic-editor")
blueprint_c = blueprint_a >>= blueprint_b
dmsnell commented 11 months ago

the mental model is that each Blueprint is already a function composition like below:

more specifically, each kind of property in the Blueprint is, and any properties/steps that aren't explicitly defined are implicitly there with their empty value.

this is based on the idea that the properties are probably independent and most things are append-only. e.g. there's no removePlugin step.

const finalBlueprints = allBlueprints.reduce( mergeBlueprints, emptyBlueprints );
seanmorris commented 11 months ago

@adamziel not sure why this wasn't on the board, pulling it in.

adamziel commented 2 months ago

Surfacing this relevant comment from Running Multiple Blueprints:

@swissspidy so if I understand correctly, you would like to:

  1. Set up a WordPress site with a Blueprint
  2. Run another Blueprint against that same WordPress site

...like in multi-stage Docker builds?

If so, you should be able to do it with:

npx @wp-playground/cli@latest run-blueprint --blueprint=my-blueprint.json --mount-before-install="./my-wp-dir:/wordpress"
npx @wp-playground/cli@latest run-blueprint --blueprint=remote-blueprint.json --mount-before-install="./my-wp-dir:/wordpress"

This will use your local directory as the base site without forcefully installing WordPress in VFS and using that.

I would love to support that natively in the Playground webapp. It should be fairly easy with the upcoming first-class support for stored sites. We could accept multiple Blueprints via the Query API or, alternatively, consider a run-blueprint step.

It related to Merging Blueprints, except merging would run two Blueprints at once and with sequential runs we could run the first Blueprint, wait a month, and run a second one. This seems similar to Docker base images.

Perhaps the "multistage build" strategy could become the first official way of "merging Blueprints". It's not quite merging, sure, but perhaps merging was the wrong idea all along. Multistage seems clean, doesn't require any special resolution rules, and conveniently it gives us multistage builds for free. And if it's ever not enough, then we can discuss where it falls short and how to address that. Ha! Thank you @swissspidy! This is why I really like letting some of these architectural problems simmer for a long time.