Skip to content

Commit

Permalink
Blueprints: Directory Resources (#1793)
Browse files Browse the repository at this point in the history
## Description

Adds a Directory resource type to enable loading file trees from git
repositories, local hard drive, deeply nested zip archives etc:

```json
{
	"steps": [
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "git:directory",
				"repositoryUrl": "https://github.com/WordPress/wordpress-playground.git",
				"ref": "HEAD",
				"path": "packages/docs"
			}
		},
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "literal:directory",
				"name": "hello-world",
				"files": {
					"README.md": "Hello, World!",
					"index.php": "<?php\n/**\n* Plugin Name: Hello World\n* Description: A simple plugin that says hello world.\n*/",
				}
			}
		}
	]
}
```

## Motivation

This PR opens the door to:

* Seamless Git integration. Import path mapping from git to Playground
is now just a few steps referencing specific git directories. No more
custom logic required!
* Blueprint-based site imports and exports without any Playground
webapp-specific logic.
* Runtime-specific "resource overrides", e.g.
`--resource-override=GUTENBERG:./gutenberg.zip` in CLI to test a
Blueprint with a my local version of Gutenberg. The same logic would be
used by the Blueprints builder to use files selected via `<input
type="file">` controls.

### Schema 

Every step can declare which kinds of resources it accepts (file-based
resources vs directory-based resources). Using a single `pluginData`
property in the `installPlugin` step means less choices for the
developer. It also makes local resource overrides easy, e.g. we could
tell Playground CLI to load a local Gutenberg directory instead of a
remote Gutenberg zip. This wouldn't be as easy had we used separate
options for passing ZIP-based and directory-based resources.

On one hand, `pluginData` is less informative than `pluginZipFile`. On
the other, the name accommodates for non-zip resources such as
directories.

## Developer notes about specific API changes introduced in this PR

This PR changes introduces a new `literal:directory` resource that can
be used in Blueprints as follows:

```json
{
	"steps": [
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "literal:directory",
				"name": "hello-world",
				"files": {
					"README.md": "Hello, World!",
					"index.php": "<?php\n/**\n* Plugin Name: Hello World\n* Description: A simple plugin that says hello world.\n*/",
				}
			}
		}
	]
}
```

Or via the JS API:

```ts
await installTheme(php, {
	themeData: {
		name: 'test-theme',
		files: {
			'index.php': `/**\n * Theme Name: Test Theme`,
		},
	},
	ifAlreadyInstalled: 'overwrite',
	options: {
		activate: false,
	},
});
```

It also introduces a new `writeFiles` step:

```ts
{
	"steps": [
		{
			"step": "writeFiles",
			"writeToPath": "/wordpress/wp-content/plugins/my-plugin",
			"filesTree": {
				"name": "my-plugin",
				"files": {
					"index.php": "<?php echo '<a>Hello World!</a>'; ?>",
					"public": {
						"style.css": "a { color: red; }"
					}
				}
			}
		}
	]
}
```

Specific changes:

* Adds a `Resource<Directory>` resource type that provides a `name:
string` and `files: FileTree`.
* Renames `pluginZipFile` to `pluginData` in the `installPlugin` step
* Renames `themeZipFile` to `themeData` in the `installPlugin` step
* Adds a new `writeFiles` step for writing entire directory trees
* Adds a new `literal:directory` resource type where an entire file tree
can be specified inline
* Adds a new `git:directory` resource type that throws an error for now,
but will load arbitrary directories from git repositories once
#1764 lands

## Remaining work

- [x] Discuss the scope and the ideas
- [x] Add unit tests
- [x] Update the documentation
- [x] Adjust the `installPlugin` and `installTheme` step for
compatibility with it's former signature. Ensure the existing packages
consuming those functions from the `@wp-playground/blueprints` package
will continue to work.
- [x] Confirm we can safely omit streaming from the system design at
this point without setting ourselves up for a grand refactor a few
months down the road.
* I think we can! Streaming support could be an addition to the system,
not a change in how the system works. For example, there could be a new
`DirectoryStream` resource type producing an `AsyncDirectoryIterator`
with streamable `File` or `Blob` objects as its leafs. It would work
nicely with remote APIs or the ZIP streaming plumbing in
`@php-wasm/stream-compression`. Any existing code expecting a
`DirectoryResource` should be relatively easily adaptable to use these
async iterators instead.

## Follow-up work

- [ ] Include actual git support once the [Git sparse checkout
PR](#1764) lands
- [ ] Ship a Playground CORS proxy to enable using git checkout in the
webapp
- [ ] Once we have a use-able `git:directory` resource, expand the
developer notes from this PR and other related PRs and write a post on
https://make.wp.org/playground

## Tangent – Streaming and a shorthand URL notation

Without streaming, the entire directory must be loaded into memory. Our
git sparse checkout implementation buffers everything anyway, but we
will want to stream-read directory resources in the future. For example:

```js
{
	"steps": [
		// Stream plugin files directly from a doubly zipped Git artifact
		{
			"step": "installPlugin",
			"pluginData": {
				"resource": "zip:github-artifact",
				"zipFile": {
					"resource": "url",
					"url": "https://github.com/WordPress/guteneberg/pr/54713/artifacts/build.zip"
				}
			}
		}
	}
}
```

That's extremely verbose, I'd love to explore a shorthand notation. One
idea would be to make it a valid
[URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) shaped
after the [data URL
syntax](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data):

```js
const dataUri = `data:text/html;base64,%3Cscript%3Ealert%28%27hi%27%29%3B%3C%2Fscript%3E`;
const githubArtifactUri = `zip-github-artifact+url:https://github.com/WordPress/gutenberg/pr/54713/artifacts/build.zip`;
const gitResourceUri = `git:branch=HEAD;path=src,https://github.com/WordPress/hello-dolly.git`;
```

It wouldn't allow easy composition of the resources, e.g. a directory
inside a zip sourced from a GitHub repo. Maybe that's for the best,
though, since such a string would be extremely dense and difficult for
humans to read. The object-based syntax might still be the most
convenient way of to declare those.
  • Loading branch information
adamziel authored Oct 7, 2024
1 parent 24c8d29 commit 1035a25
Show file tree
Hide file tree
Showing 35 changed files with 3,501 additions and 1,746 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ You can connect to the Playground using the JavaScript client. Here's an example
},
{
step: 'installPlugin',
pluginZipFile: {
pluginData: {
resource: 'wordpress.org/plugins',
slug: 'friends',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/site/docs/blueprints/01-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Because Blueprints can be pasted in the URL, you can embed or link to a Playgrou
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "pendant"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/site/docs/blueprints/02-using-blueprints.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ You can also use Blueprints with the JavaScript API using the `startPlaygroundWe
},
{
step: 'installPlugin',
pluginZipFile: {
pluginData: {
resource: 'wordpress.org/plugins',
slug: 'friends',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/site/docs/blueprints/03-data-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Arguably the most powerful property, `steps` allows you to configure the Playgro
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "gutenberg"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/site/docs/blueprints/05-steps-shorthands.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ Or
[
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "hello-dolly"
}
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://raw.githubusercontent.com/adamziel/blueprints/trunk/docs/assets/hello-from-the-dashboard.zip"
}
Expand Down
10 changes: 5 additions & 5 deletions packages/docs/site/docs/blueprints/08-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ Let's see some cool things you can do with Blueprints.
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "coblocks"
}
},
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "pendant"
}
Expand Down Expand Up @@ -91,14 +91,14 @@ blueprint={{
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://your-site.com/your-plugin.zip"
}
},
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "url",
"url": "https://your-site.com/your-theme.zip"
}
Expand Down Expand Up @@ -173,7 +173,7 @@ Use the `writeFile` step to add code to a mu-plugin that runs on every request.
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "interactive-code-block"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Adventurer is an open-source theme [available in the WordPress theme directory](
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
Expand All @@ -117,7 +117,7 @@ The site should now look like the screenshot below:

### Resources

The `themeZipFile` defines a [resource](/blueprints/steps/resources) and referrences an external file required to complete the step. Playground supports different types of resources, including
The `themeData` defines a [resource](/blueprints/steps/resources) and referrences an external file required to complete the step. Playground supports different types of resources, including

- `url`,
- `wordpress.org/themes`,
Expand Down Expand Up @@ -145,14 +145,14 @@ A classic WordPress plugin that displays random lyrics from the song "Hello, Dol
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "hello-dolly"
}
Expand All @@ -165,7 +165,7 @@ A classic WordPress plugin that displays random lyrics from the song "Hello, Dol

The Hello Dolly plugin is now installed and activated.

Like the `themeZipFile`, the `pluginZipFile` defines a reference to an external file required for the step. The example uses the `wordpress.org/plugins` resource to install the plugin with the matching `slug` from the WordPress plugin directory.
Like the `themeData`, the `pluginData` defines a reference to an external file required for the step. The example uses the `wordpress.org/plugins` resource to install the plugin with the matching `slug` from the WordPress plugin directory.

## 5. Install a custom plugin

Expand Down Expand Up @@ -240,14 +240,14 @@ Here's the complete Blueprint:
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "hello-dolly"
}
Expand Down Expand Up @@ -289,21 +289,21 @@ Encoding PHP files as `JSON` can be useful for quick testing, but it's inconveni
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "hello-dolly"
}
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://raw.githubusercontent.com/wordpress/blueprints/trunk/docs/assets/hello-from-the-dashboard.zip"
}
Expand All @@ -325,7 +325,7 @@ You can shorten that Blueprint even more using the shorthand syntax:
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
Expand Down Expand Up @@ -392,7 +392,7 @@ Here's what the final Blueprint looks like:
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "adventurer"
}
Expand Down
10 changes: 5 additions & 5 deletions packages/docs/site/docs/developers/03-build-an-app/01-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,21 @@ You can still showcase it on Playground by using [JSON Blueprints](/blueprints).
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://your-site.com/your-plugin.zip"
}
},
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "url",
"url": "https://your-site.com/your-theme.zip"
}
},
{
"step": "importWxr",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://your-site.com/starter-content.wxr"
}
Expand Down Expand Up @@ -100,7 +100,7 @@ Those zip bundles aren't any different from regular WordPress Plugins, which mea
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://your-site.com/pull-request-1234.zip"
}
Expand Down Expand Up @@ -146,7 +146,7 @@ blueprint={{
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "vfs",
"path": "/wordpress/pr/gutenberg.zip"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/site/docs/developers/06-apis/01-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ display={`{
},
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "friends"
}
Expand All @@ -65,7 +65,7 @@ blueprint={{
},
{
step: 'installPlugin',
pluginZipFile: {
pluginData: {
resource: 'wordpress.org/plugins',
slug: 'friends',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const client = await startPlaygroundWeb({
{ step: 'login' },
{
step: 'installPlugin',
pluginZipFile: {
pluginData: {
resource: 'wordpress.org/plugins',
slug: 'gutenberg',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ await installPlugin(client, {
// Resources can only be used with JSON Blueprints.
// If you use functions, you must provide the resolved
// file.
pluginZipFile: await fetch(pluginUrl),
pluginData: await fetch(pluginUrl),
});
```

Expand Down
10 changes: 5 additions & 5 deletions packages/docs/site/docs/main/guides/for-plugin-developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ You can also load any plugin from the WordPress plugins directory by setting the
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "gutenberg"
}
Expand All @@ -42,15 +42,15 @@ You can also load any plugin from the WordPress plugins directory by setting the
}
```

[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22landingPage%22:%22/wp-admin/plugins.php%22,%22login%22:true,%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginZipFile%22:{%22resource%22:%22wordpress.org/plugins%22,%22slug%22:%22gutenberg%22}}]})
[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22landingPage%22:%22/wp-admin/plugins.php%22,%22login%22:true,%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginData%22:{%22resource%22:%22wordpress.org/plugins%22,%22slug%22:%22gutenberg%22}}]})

Blueprints can be passed to a Playground instance [in several ways](/blueprints/using-blueprints).

### Plugin in a GitHub repository

A plugin stored in a GitHub repository can also be loaded in a Playground instance via Blueprints.

With the `pluginZipFile` property of the [`installPlugin` blueprint step](/blueprints/steps#installPlugin), you can define a [`url` resource](/blueprints/steps/resources#urlreference) that points to the location of the `.zip` file containing the plugin you want to load in the Playground instance.
With the `pluginData` property of the [`installPlugin` blueprint step](/blueprints/steps#installPlugin), you can define a [`url` resource](/blueprints/steps/resources#urlreference) that points to the location of the `.zip` file containing the plugin you want to load in the Playground instance.

To avoid CORS issues, the Playground project provides a [GitHub proxy](https://playground.wordpress.net/proxy) that allows you to generate a `.zip` from a repository (or even a folder inside a repo) containing your plugin.

Expand All @@ -67,7 +67,7 @@ For example, the following `blueprint.json` installs a plugin from a GitHub repo
"steps": [
{
"step": "installPlugin",
"pluginZipFile": {
"pluginData": {
"resource": "url",
"url": "https://github-proxy.com/proxy/?repo=wptrainingteam/devblog-dataviews-plugin"
}
Expand All @@ -76,7 +76,7 @@ For example, the following `blueprint.json` installs a plugin from a GitHub repo
}
```

[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22landingPage%22:%22/wp-admin/admin.php?page=add-media-from-third-party-service%22,%22login%22:true,%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginZipFile%22:{%22resource%22:%22url%22,%22url%22:%22https://github-proxy.com/proxy/?repo=wptrainingteam/devblog-dataviews-plugin%22}}]})
[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22landingPage%22:%22/wp-admin/admin.php?page=add-media-from-third-party-service%22,%22login%22:true,%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginData%22:{%22resource%22:%22url%22,%22url%22:%22https://github-proxy.com/proxy/?repo=wptrainingteam/devblog-dataviews-plugin%22}}]})

### Plugin from code in a file or gist in GitHub

Expand Down
10 changes: 5 additions & 5 deletions packages/docs/site/docs/main/guides/for-theme-developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ You can also load any theme from the WordPress themes directory by setting the [
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "wordpress.org/themes",
"slug": "twentytwenty"
},
Expand All @@ -40,13 +40,13 @@ You can also load any theme from the WordPress themes directory by setting the [
}
```

[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22steps%22:[{%22step%22:%22installTheme%22,%22themeZipFile%22:{%22resource%22:%22wordpress.org/themes%22,%22slug%22:%22twentytwenty%22},%22options%22:{%22activate%22:true,%22importStarterContent%22:true}}]})
[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22steps%22:[{%22step%22:%22installTheme%22,%22themeData%22:{%22resource%22:%22wordpress.org/themes%22,%22slug%22:%22twentytwenty%22},%22options%22:{%22activate%22:true,%22importStarterContent%22:true}}]})

### Themes in a GitHub repository

A theme stored in a GitHub repository can also be loaded in a Playground instance with Blueprints.

In the `themeZipFile` property of the [`installTheme` blueprint step](/blueprints/steps#InstallThemeStep), you can define a [`url` resource](/blueprints/steps/resources#urlreference) that points to the location of the `.zip` file containing the theme you want to load in the Playground instance.
In the `themeData` property of the [`installTheme` blueprint step](/blueprints/steps#InstallThemeStep), you can define a [`url` resource](/blueprints/steps/resources#urlreference) that points to the location of the `.zip` file containing the theme you want to load in the Playground instance.

To avoid CORS issues, the Playground project provides a [GitHub proxy](https://playground.wordpress.net/proxy) that allows you to generate a `.zip` from a repository (or even a folder inside a repo) containing your or theme.

Expand All @@ -61,7 +61,7 @@ For example the following `blueprint.json` installs a theme from a GitHub reposi
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"themeData": {
"resource": "url",
"url": "https://github-proxy.com/proxy/?repo=Automattic/themes&branch=trunk&directory=assembler"
},
Expand All @@ -73,7 +73,7 @@ For example the following `blueprint.json` installs a theme from a GitHub reposi
}
```

[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22steps%22:[{%22step%22:%22installTheme%22,%22themeZipFile%22:{%22resource%22:%22url%22,%22url%22:%22https://github-proxy.com/proxy/?repo=Automattic/themes&branch=trunk&directory=assembler%22},%22options%22:{%22activate%22:true}}]})
[<kbd> &nbsp; Run Blueprint &nbsp; </kbd>](https://playground.wordpress.net/builder/builder.html#{%22steps%22:[{%22step%22:%22installTheme%22,%22themeData%22:{%22resource%22:%22url%22,%22url%22:%22https://github-proxy.com/proxy/?repo=Automattic/themes&branch=trunk&directory=assembler%22},%22options%22:{%22activate%22:true}}]})

A blueprint can be passed to a Playground instance [in several ways](/blueprints/using-blueprints).

Expand Down
Loading

0 comments on commit 1035a25

Please sign in to comment.