Skip to content

Commit

Permalink
Implement GitDirectoryResource
Browse files Browse the repository at this point in the history
Related to #1787, Follows up on #1793

Implements GitDirectoryResource to enable loading files directly from
git repositories as follows:

```ts
{
	"landingPage": "/guides/for-plugin-developers.md",
	"steps": [
		{
			"step": "writeFiles",
			"writeToPath": "/wordpress/guides",
			"filesTree": {
				"resource": "git:directory",
				"url": "https://github.com/WordPress/wordpress-playground.git",
				"ref": "trunk",
				"path": "packages/docs/site/docs/main/guides"
			}
		}
	]
}
```

 ## Implementation details

Uses git client functions merged in
#1764 to sparse
checkout the requested files. It also leans on the PHP CORS proxy which
is now started as a part of the `npm run dev` command.

The CORS proxy URL is configurable per `compileBlueprint()` call so that each
Playground runtime may choose to either use it or not. For example, it
wouldn't be very useful in the CLI version of Playground.

 ## Testing plan

Go to
`http://localhost:5400/website-server/#{%20%22landingPage%22:%20%22/guides/for-plugin-developers.md%22,%20%22steps%22:%20[%20{%20%22step%22:%20%22writeFiles%22,%20%22writeToPath%22:%20%22/wordpress/guides%22,%20%22filesTree%22:%20{%20%22resource%22:%20%22git:directory%22,%20%22url%22:%20%22https://github.com/WordPress/wordpress-playground.git%22,%20%22ref%22:%20%22trunk%22,%20%22path%22:%20%22packages/docs/site/docs/main/guides%22%20}%20}%20]%20}`
and confirm Playground loads a markdown file.
  • Loading branch information
adamziel committed Oct 7, 2024
1 parent 8639616 commit 845bcc4
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 21 deletions.
18 changes: 18 additions & 0 deletions packages/playground/blueprints/src/lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ export interface CompileBlueprintOptions {
semaphore?: Semaphore;
/** Optional callback with step output */
onStepCompleted?: OnStepCompleted;
/**
* Proxy URL to use for cross-origin requests.
*
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
* then the CORS requests to https://github.com/WordPress/gutenberg.git would actually
* be made to https://cors.wordpress.net/proxy.php/https://github.com/WordPress/gutenberg.git.
*/
corsProxy?: string;
}

/**
Expand All @@ -83,6 +91,7 @@ export function compileBlueprint(
progress = new ProgressTracker(),
semaphore = new Semaphore({ concurrency: 3 }),
onStepCompleted = () => {},
corsProxy,
}: CompileBlueprintOptions = {}
): CompiledBlueprint {
// Deep clone the blueprint to avoid mutating the input
Expand Down Expand Up @@ -293,6 +302,7 @@ export function compileBlueprint(
semaphore,
rootProgressTracker: progress,
totalProgressWeight,
corsProxy,
})
);

Expand Down Expand Up @@ -480,6 +490,12 @@ interface CompileStepArgsOptions {
rootProgressTracker: ProgressTracker;
/** The total progress weight of all the steps in the blueprint */
totalProgressWeight: number;
/**
* Proxy URL to use for cross-origin requests.
*
* @see CompileBlueprintOptions.corsProxy
*/
corsProxy?: string;
}

/**
Expand All @@ -496,6 +512,7 @@ function compileStep<S extends StepDefinition>(
semaphore,
rootProgressTracker,
totalProgressWeight,
corsProxy,
}: CompileStepArgsOptions
): { run: CompiledStep; step: S; resources: Array<Resource<any>> } {
const stepProgress = rootProgressTracker.stage(
Expand All @@ -508,6 +525,7 @@ function compileStep<S extends StepDefinition>(
if (isResourceReference(value)) {
value = Resource.create(value, {
semaphore,
corsProxy,
});
}
args[key] = value;
Expand Down
47 changes: 32 additions & 15 deletions packages/playground/blueprints/src/lib/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import {
} from '@php-wasm/progress';
import { FileTree, UniversalPHP } from '@php-wasm/universal';
import { Semaphore } from '@php-wasm/util';
import {
listDescendantFiles,
listGitFiles,
sparseCheckout,
} from '@wp-playground/storage';
import { zipNameToHumanName } from './utils/zip-name-to-human-name';

export type { FileTree };
Expand Down Expand Up @@ -129,10 +134,12 @@ export abstract class Resource<T extends File | Directory> {
{
semaphore,
progress,
corsProxy,
}: {
/** Optional semaphore to limit concurrent downloads */
semaphore?: Semaphore;
progress?: ProgressTracker;
corsProxy?: string;
}
): Resource<File | Directory> {
let resource: Resource<File | Directory>;
Expand All @@ -153,7 +160,9 @@ export abstract class Resource<T extends File | Directory> {
resource = new UrlResource(ref, progress);
break;
case 'git:directory':
resource = new GitDirectoryResource(ref, progress);
resource = new GitDirectoryResource(ref, progress, {
corsProxy,
});
break;
case 'literal:directory':
resource = new LiteralDirectoryResource(ref, progress);
Expand Down Expand Up @@ -442,26 +451,34 @@ export class UrlResource extends FetchResource {
export class GitDirectoryResource extends Resource<Directory> {
constructor(
private reference: GitDirectoryReference,
public override _progress?: ProgressTracker
public override _progress?: ProgressTracker,
private options?: { corsProxy?: string }
) {
super();
}

async resolve() {
// @TODO: Use the actual sparse checkout logic here once
// https://github.com/WordPress/wordpress-playground/pull/1764 lands.
throw new Error('Not implemented yet');
const repoUrl = this.options?.corsProxy
? `${this.options.corsProxy}/${this.reference.url}`
: this.reference.url;
const ref = `refs/heads/${this.reference.ref}`;
const allFiles = await listGitFiles(repoUrl, ref);
const filesToClone = listDescendantFiles(allFiles, this.reference.path);
let files = await sparseCheckout(repoUrl, ref, filesToClone);
// Remove the path prefix from the cloned file names.
files = Object.fromEntries(
Object.entries(files).map(([name, contents]) => {
name = name.substring(this.reference.path.length);
name = name.replace(/^\/+/, '');
return [name, contents];
})
);
return {
name: 'hello-world',
files: {
'README.md': 'Hello, World!',
'index.php': `<?php
/**
* Plugin Name: Hello World
* Description: A simple plugin that says hello world.
*/
`,
},
name: `${this.reference.ref} (${this.reference.path})`.replaceAll(
'/',
'-'
),
files,
};
}

Expand Down
14 changes: 14 additions & 0 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ export interface StartPlaygroundOptions {
* for more details.
*/
scope?: string;
/**
* Proxy URL to use for cross-origin requests.
*
* For example, if corsProxy is set to "https://cors.wordpress.net/proxy.php",
* then the CORS requests to https://github.com/WordPress/wordpress-playground.git would actually
* be made to https://cors.wordpress.net/proxy.php/https://github.com/WordPress/wordpress-playground.git.
*
* The Blueprints library will arbitrarily choose which requests to proxy. If you need
* to proxy every single request, do not use this option. Instead, you should preprocess
* your Blueprint to replace all cross-origin URLs with the proxy URL.
*/
corsProxy?: string;
}

/**
Expand All @@ -96,6 +108,7 @@ export async function startPlaygroundWeb({
onBeforeBlueprint,
mounts,
scope,
corsProxy,
shouldInstallWordPress,
}: StartPlaygroundOptions): Promise<PlaygroundClient> {
assertValidRemote(remoteUrl);
Expand All @@ -116,6 +129,7 @@ export async function startPlaygroundWeb({
const compiled = compileBlueprint(blueprint, {
progress: progressTracker.stage(0.5),
onStepCompleted: onBlueprintStepCompleted,
corsProxy,
});

await new Promise((resolve) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/playground/storage/src/lib/git-sparse-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function sparseCheckout(
fullyQualifiedBranchName: string,
filesPaths: string[]
) {
const refs = await listRefs(repoUrl, fullyQualifiedBranchName);
const refs = await listGitRefs(repoUrl, fullyQualifiedBranchName);
const commitHash = refs[fullyQualifiedBranchName];
const treesIdx = await fetchWithoutBlobs(repoUrl, commitHash);
const objects = await resolveObjects(treesIdx, commitHash, filesPaths);
Expand Down Expand Up @@ -84,11 +84,11 @@ export type FileTree = FileTreeFile | FileTreeFolder;
* @param fullyQualifiedBranchName The full name of the branch to fetch from (e.g., 'refs/heads/main').
* @returns A list of all files in the repository.
*/
export async function listFiles(
export async function listGitFiles(
repoUrl: string,
fullyQualifiedBranchName: string
): Promise<FileTree[]> {
const refs = await listRefs(repoUrl, fullyQualifiedBranchName);
const refs = await listGitRefs(repoUrl, fullyQualifiedBranchName);
if (!(fullyQualifiedBranchName in refs)) {
throw new Error(`Branch ${fullyQualifiedBranchName} not found`);
}
Expand Down Expand Up @@ -131,7 +131,7 @@ function gitTreeToFileTree(tree: GitTree): FileTree[] {
* @param fullyQualifiedBranchPrefix The prefix of the refs to fetch. For example: refs/heads/my-feature-branch
* @returns A map of refs to their corresponding commit hashes.
*/
export async function listRefs(
export async function listGitRefs(
repoUrl: string,
fullyQualifiedBranchPrefix: string
) {
Expand Down
1 change: 1 addition & 0 deletions packages/playground/website/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"nx run playground-php-cors-proxy:start",
"nx dev playground-remote --configuration=development-for-website",
"sleep 1; nx dev:standalone playground-website --hmr --output-style=stream-without-prefixes"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { getRemoteUrl } from '../../config';
import { setActiveModal, setActiveSiteError } from './slice-ui';
import { PlaygroundDispatch, PlaygroundReduxState } from './store';
import { selectSiteBySlug } from './slice-sites';
// @ts-ignore
import { corsProxyUrl } from 'virtual:cors-proxy-url';

export function bootSiteClient(
siteSlug: string,
Expand Down Expand Up @@ -131,6 +133,7 @@ export function bootSiteClient(
]
: [],
shouldInstallWordPress: !isWordPressInstalled,
corsProxy: corsProxyUrl,
});

// @TODO: Remove backcompat code after 2024-12-01.
Expand Down
14 changes: 12 additions & 2 deletions packages/playground/website/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import type { Plugin, ViteDevServer } from 'vite';
import type { CommonServerOptions, Plugin, ViteDevServer } from 'vite';
import react from '@vitejs/plugin-react';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { viteTsConfigPaths } from '../../vite-extensions/vite-ts-config-paths';
Expand All @@ -21,8 +21,9 @@ import { join } from 'node:path';
import { buildVersionPlugin } from '../../vite-extensions/vite-build-version';
import { listAssetsRequiredForOfflineMode } from '../../vite-extensions/vite-list-assets-required-for-offline-mode';
import { addManifestJson } from '../../vite-extensions/vite-manifest';
import virtualModule from '../../vite-extensions/vite-virtual-module';

const proxy = {
const proxy: CommonServerOptions['proxy'] = {
'^/plugin-proxy': {
target: 'https://playground.wordpress.net',
changeOrigin: true,
Expand Down Expand Up @@ -77,6 +78,15 @@ export default defineConfig(({ command, mode }) => {
}),
ignoreWasmImports(),
buildVersionPlugin('website-config'),
virtualModule({
name: 'cors-proxy-url',
content: `
export const corsProxyUrl = '${
mode === 'production'
? '/cors-proxy.php'
: 'http://127.0.0.1:5263/cors-proxy.php'
}';`,
}),
// GitHub OAuth flow
{
name: 'configure-server',
Expand Down

0 comments on commit 845bcc4

Please sign in to comment.