Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 86 additions & 3 deletions packages/playground/blueprints/src/lib/v1/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,93 @@ function compileBlueprintJson(

const { valid, errors } = validateBlueprint(blueprint);
if (!valid) {
// Format all validation errors with context
const errorMessages = errors!
.map((err, index) => {
const path = err.instancePath || '/';
let message = err.message || 'validation failed';

// For "additional properties" errors, highlight the actual problematic key
let highlightedSnippet = '';
if (message.includes('must NOT have additional properties')) {
// Extract the property name from the error params
const additionalProperty = (err.params as any)
?.additionalProperty;
if (additionalProperty) {
message = `has unexpected property "${additionalProperty}"`;

// Try to show the offending key highlighted
try {
const pathParts = path.split('/').filter(Boolean);
let currentValue: any = blueprint;
for (const part of pathParts) {
if (
currentValue &&
typeof currentValue === 'object'
) {
currentValue = currentValue[part];
}
}

if (
currentValue &&
typeof currentValue === 'object'
) {
const offendingValue =
currentValue[additionalProperty];
const valueStr = JSON.stringify(offendingValue);
highlightedSnippet = `\n "${additionalProperty}": ${valueStr}\n ${'^'.repeat(
additionalProperty.length + 2
)} This property is not recognized`;
}
} catch {
// If we can't extract context, that's okay
}
}
} else {
// For other errors, try to extract the offending value
try {
const pathParts = path.split('/').filter(Boolean);
let currentValue: any = blueprint;
for (const part of pathParts) {
if (
currentValue &&
typeof currentValue === 'object'
) {
currentValue = currentValue[part];
}
}
if (currentValue !== undefined) {
const valueStr = JSON.stringify(
currentValue,
null,
2
);
// Limit snippet length
const snippet =
valueStr.length > 200
? valueStr.substring(0, 200) + '...'
: valueStr;
highlightedSnippet = `\n Value: ${snippet}`;
}
} catch {
// If we can't extract context, that's okay
}
}

return `${
index + 1
}. At path "${path}": ${message}${highlightedSnippet}`;
})
.join('\n\n');

const e = new Error(
`Invalid blueprint: ${errors![0].message} at ${
errors![0].instancePath
}`
`Invalid Blueprint: The Blueprint does not conform to the schema.\n\n` +
`Found ${
errors!.length
} validation error(s):\n\n${errorMessages}\n\n` +
`Please review your Blueprint and fix these issues. ` +
`Learn more about the Blueprint format: https://wordpress.github.io/wordpress-playground/blueprints/data-format`
);
// Attach Ajv output to the thrown object for easier debugging
(e as any).errors = errors;
Expand Down
11 changes: 10 additions & 1 deletion packages/playground/blueprints/src/lib/v1/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,16 @@ export abstract class Resource<T extends File | Directory> {
case 'bundled':
if (!streamBundledFile) {
throw new Error(
'Filesystem is required for blueprint resources'
'Blueprint resource of type "bundled" requires a filesystem.\n\n' +
'This Blueprint refers to files that should be bundled with it (like images, plugins, or themes), ' +
'but the filesystem needed to access these files is not available. This usually happens when:\n\n' +
"1. You're trying to load a Blueprint as a standalone JSON file that was meant to be part of a bundle\n" +
'2. The Blueprint was not packaged correctly as a blueprint.zip file\n\n' +
'To fix this:\n' +
"• If you're loading from a URL, make sure all referenced files are accessible relative to the Blueprint file\n" +
"• If you're using a blueprint.zip file, ensure it contains all the files referenced in the Blueprint\n" +
'• Check that the "resource": "bundled" references in your Blueprint match actual files in your bundle\n\n' +
'Learn more about Blueprint resources: https://wordpress.github.io/wordpress-playground/blueprints/data-format#resources'
);
}
resource = new BundledResource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,202 @@ function SiteErrorMessage({
);
}

if (error === 'blueprint-fetch-failed') {
const errorDetails = (window as any).__playgroundBlueprintError;
const errorMessage =
errorDetails instanceof Error
? errorDetails.message
: String(errorDetails || 'Unknown error');

return (
<>
<h1>Failed to load Blueprint</h1>
<p>
The Blueprint could not be downloaded or loaded. This
usually happens when:
</p>
<ul style={{ textAlign: 'left', margin: '1rem 0' }}>
<li>
The Blueprint URL is incorrect or the file doesn't exist
</li>
<li>The server hosting the Blueprint is not reachable</li>
<li>The Blueprint file is not a valid JSON or ZIP file</li>
<li>
CORS (Cross-Origin Resource Sharing) is blocking the
request
</li>
</ul>
<details style={{ textAlign: 'left', margin: '1rem 0' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Error details
</summary>
<pre
style={{
background: '#f5f5f5',
padding: '1rem',
overflow: 'auto',
fontSize: '0.9em',
marginTop: '0.5rem',
}}
>
{errorMessage}
</pre>
</details>
<p>
<a
target="_blank"
rel="noopener noreferrer"
href="https://wordpress.github.io/wordpress-playground/blueprints/troubleshoot-and-debug"
>
Learn more about troubleshooting Blueprints
</a>
</p>
<Button
className={css.actionButton}
variant="primary"
onClick={() => {
const url = new URL(window.location.href);
// Remove blueprint-related parameters
url.searchParams.delete('blueprint-url');
url.searchParams.delete('blueprint');
window.location.href = url.toString();
}}
>
Start without a Blueprint
</Button>
</>
);
}

if (error === 'blueprint-filesystem-required') {
const errorDetails = (window as any).__playgroundBlueprintError;
const errorMessage =
errorDetails instanceof Error
? errorDetails.message
: String(errorDetails || 'Unknown error');

return (
<>
<h1>Blueprint Resource Error</h1>
<p>
This Blueprint refers to files that should be bundled with
it (like images, plugins, or themes), but the filesystem
needed to access these files is not available.
</p>
<p>
<strong>Common causes:</strong>
</p>
<ul style={{ textAlign: 'left', margin: '1rem 0' }}>
<li>
Loading a standalone JSON file that was meant to be part
of a bundle
</li>
<li>
The Blueprint was not packaged correctly as a
blueprint.zip file
</li>
<li>
Referenced files are not accessible relative to the
Blueprint file
</li>
</ul>
<details style={{ textAlign: 'left', margin: '1rem 0' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Error details
</summary>
<pre
style={{
background: '#f5f5f5',
padding: '1rem',
overflow: 'auto',
fontSize: '0.9em',
marginTop: '0.5rem',
}}
>
{errorMessage}
</pre>
</details>
<p>
<a
target="_blank"
rel="noopener noreferrer"
href="https://wordpress.github.io/wordpress-playground/blueprints/data-format#resources"
>
Learn more about Blueprint resources
</a>
</p>
<Button
className={css.actionButton}
variant="primary"
onClick={() => {
window.location.reload();
}}
>
Try again
</Button>
</>
);
}

if (error === 'blueprint-validation-failed') {
const errorDetails = (window as any).__playgroundBlueprintError;
const errorMessage =
errorDetails instanceof Error
? errorDetails.message
: String(errorDetails || 'Unknown error');

return (
<>
<h1>Invalid Blueprint</h1>
<p>
The Blueprint does not conform to the required schema.
Please review the validation errors below and fix your
Blueprint.
</p>
<details open style={{ textAlign: 'left', margin: '1rem 0' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Validation errors
</summary>
<pre
style={{
background: '#f5f5f5',
padding: '1rem',
overflow: 'auto',
fontSize: '0.85em',
marginTop: '0.5rem',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{errorMessage}
</pre>
</details>
<p>
<a
target="_blank"
rel="noopener noreferrer"
href="https://wordpress.github.io/wordpress-playground/blueprints/data-format"
>
Learn more about the Blueprint format
</a>
</p>
<Button
className={css.actionButton}
variant="primary"
onClick={() => {
const url = new URL(window.location.href);
// Remove blueprint-related parameters
url.searchParams.delete('blueprint-url');
url.searchParams.delete('blueprint');
window.location.href = url.toString();
}}
>
Start without a Blueprint
</Button>
</>
);
}

return (
<>
<h1>Something went wrong</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,27 @@ export function bootSiteClient(
}
} catch (e) {
logger.error(e);

// Store the error details for display
(window as any).__playgroundBlueprintError = e;

if (
(e as any).name === 'ArtifactExpiredError' ||
(e as any).originalErrorClassName === 'ArtifactExpiredError'
) {
dispatch(setActiveSiteError('github-artifact-expired'));
} else if (
e instanceof Error &&
e.message.includes(
'Blueprint resource of type "bundled" requires a filesystem'
)
) {
dispatch(setActiveSiteError('blueprint-filesystem-required'));
} else if (
e instanceof Error &&
e.message.startsWith('Invalid Blueprint:')
) {
dispatch(setActiveSiteError('blueprint-validation-failed'));
} else {
dispatch(setActiveSiteError('site-boot-failed'));
dispatch(setActiveModal(modalSlugs.ERROR_REPORT));
Expand Down
Loading
Loading