Skip to content

Add JSON Schema validation for JSON files with a $schema property#2325

Merged
westonruter merged 9 commits intotrunkfrom
add/json-validation-and-fix-blueprint
Jan 7, 2026
Merged

Add JSON Schema validation for JSON files with a $schema property#2325
westonruter merged 9 commits intotrunkfrom
add/json-validation-and-fix-blueprint

Conversation

@westonruter
Copy link
Copy Markdown
Member

@westonruter westonruter commented Jan 6, 2026

Summary

This change introduces a new validation script to ensure that JSON files (such as Playground blueprints and .wp-env.json) are correctly formatted according to their defined schemas.

  • Implement bin/validate-json-schema.js using ajv.
  • Add lint-json script to package.json.
  • Integrate JSON schema validation into lint-staged and GitHub Actions.
  • Update blueprint.json to use pluginData instead of deprecated pluginZipFile.
  • Fix trailing comma in tsconfig.json to make it valid JSON.

Fixes #2279
Closes #2280

Use of AI Tools

I used Gemini CLI with Gemini 3 author these changes.

My prompts:

  1. There is this @plugins/performance-lab/.wordpress-org/blueprints/blueprint.json which is a JSON file with a schema defined. I want to incorporate JSON Schema validation as part of lint-staged and the static-analysis checks which run during @.github/workflows/js-lint.yml
  2. I want you to save exact versions
  3. In PhpStorm I'm getting TypeScript errors in @bin/validate-json-schema.js for Ajv and addFormats from ajv-formats. TS2351: This expression is not constructable... TS2349: This expression is not callable...
  4. One thing I'm confused about with @bin/validate-json-schema.js is why ajv-cli wasn't used instead. Is it not considered a good approach?
  5. OK, but there's a problem with the new lint-json command in that it is hard-coding the list of JSON files to validate. It should instead be like the lint-js command which will automatically supply all JSON files, as long as they have a $schema property.
  6. Draft a commit message for all of the changes staged
  7. OK, there seems to be an issue still with @bin/validate-json-schema.js in that @plugins/performance-lab/.wordpress-org/blueprints/blueprint.json has a password field which is apparently deprecated, but this is not getting picked up in the validation. Can the validation logic be made more strict to check for deprecated properties?

This change introduces a new validation script to ensure that JSON files
(such as Playground blueprints and .wp-env.json) are correctly formatted
according to their defined schemas.

- Implement `bin/validate-json-schema.js` using `ajv`.
- Add `lint-json` script to `package.json`.
- Integrate JSON schema validation into `lint-staged` and GitHub Actions.
- Update `blueprint.json` to use `pluginData` instead of deprecated `pluginZipFile`.
- Fix trailing comma in `tsconfig.json` to make it valid JSON.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@westonruter westonruter added the [Type] Enhancement A suggestion for improvement of an existing feature label Jan 6, 2026
@westonruter westonruter added the no milestone PRs that do not have a defined milestone for release label Jan 6, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 6, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: b1ink0 <b1ink0@git.wordpress.org>
Co-authored-by: iqbal-web <iqbal1hossain@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@@ -12,8 +12,8 @@
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

The password is no longer required, the playground works without the password field.

Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the JSON Schema validator increase the strictness to catch deprecations?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the AJV does not consider the deprecated as a error as deprecated is a annotation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my quick research, I found that if we want to handle this in CI, we’ll need to write a custom schema validator to flag deprecated annotations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a working example I created with the help of Claude that can flag deprecated values.

Show patch
diff --git a/bin/validate-json-schema.js b/bin/validate-json-schema.js
index 9a7084a4..11647213 100755
--- a/bin/validate-json-schema.js
+++ b/bin/validate-json-schema.js
@@ -30,6 +30,226 @@ const ajv = new Ajv( {
 } );
 addFormats( ajv );
 
+/**
+ * Fetches the full schema from URL.
+ *
+ * @param {string} schemaUrl The schema URL.
+ * @return {Promise<Object>} The schema object.
+ */
+async function fetchSchema( schemaUrl ) {
+	const response = await fetch( schemaUrl );
+	if ( ! response.ok ) {
+		throw new Error( `Failed to fetch schema from ${ schemaUrl }` );
+	}
+	return await response.json();
+}
+
+/**
+ * Resolves $ref in schema to get the actual definition.
+ *
+ * @param {string} ref        The $ref string.
+ * @param {Object} rootSchema The root schema object.
+ * @return {Object|null} The resolved schema definition.
+ */
+function resolveRef( ref, rootSchema ) {
+	if ( ! ref || ! ref.startsWith( '#/' ) ) {
+		return null;
+	}
+
+	const pathParts = ref.substring( 2 ).split( '/' );
+	let current = rootSchema;
+
+	for ( const part of pathParts ) {
+		if ( ! current || ! current[ part ] ) {
+			return null;
+		}
+		current = current[ part ];
+	}
+
+	return current;
+}
+
+/**
+ * Finds the matching oneOf/anyOf schema based on data.
+ *
+ * @param {Array}  schemas    Array of possible schemas.
+ * @param {*}      data       The data to match against.
+ * @param {Object} rootSchema Root schema for resolving refs.
+ * @return {Object|null} The matched schema.
+ */
+function findMatchingSchema( schemas, data, rootSchema ) {
+	if ( ! Array.isArray( schemas ) || ! data || typeof data !== 'object' ) {
+		return null;
+	}
+
+	// Try to find a match using discriminator (step property)
+	for ( const schema of schemas ) {
+		let resolvedSchema = schema;
+		if ( schema.$ref ) {
+			resolvedSchema = resolveRef( schema.$ref, rootSchema );
+		}
+
+		if ( ! resolvedSchema ) {
+			continue;
+		}
+
+		// Check if this schema matches based on discriminator
+		if ( resolvedSchema.properties ) {
+			// Look for const values that match data
+			let matches = true;
+			for ( const [ key, propSchema ] of Object.entries(
+				resolvedSchema.properties
+			) ) {
+				if (
+					propSchema.const !== undefined &&
+					data[ key ] !== undefined
+				) {
+					if ( propSchema.const !== data[ key ] ) {
+						matches = false;
+						break;
+					}
+				}
+			}
+			if ( matches ) {
+				return resolvedSchema;
+			}
+		}
+	}
+
+	return schemas[ 0 ] || null;
+}
+
+/**
+ * Fully resolves a schema, including nested oneOf/anyOf.
+ *
+ * @param {Object} schema     The schema to resolve.
+ * @param {Object} data       The data to match against (for oneOf/anyOf).
+ * @param {Object} rootSchema Root schema for $ref resolution.
+ * @return {Object|null} Fully resolved schema.
+ */
+function fullyResolveSchema( schema, data, rootSchema ) {
+	if ( ! schema ) {
+		return null;
+	}
+
+	let current = schema;
+
+	// Resolve $ref
+	if ( current.$ref ) {
+		current = resolveRef( current.$ref, rootSchema );
+		if ( ! current ) {
+			return null;
+		}
+	}
+
+	// If this schema has oneOf/anyOf, we need to match and resolve again
+	if ( current.oneOf && data ) {
+		const matched = findMatchingSchema( current.oneOf, data, rootSchema );
+		if ( matched ) {
+			// Recursively resolve the matched schema
+			return fullyResolveSchema( matched, data, rootSchema );
+		}
+	} else if ( current.anyOf && data ) {
+		const matched = findMatchingSchema( current.anyOf, data, rootSchema );
+		if ( matched ) {
+			// Recursively resolve the matched schema
+			return fullyResolveSchema( matched, data, rootSchema );
+		}
+	}
+
+	return current;
+}
+
+/**
+ * Recursively validates deprecated properties.
+ *
+ * @param {Object} schema     Current schema.
+ * @param {*}      data       Current data.
+ * @param {Object} rootSchema Root schema for $ref resolution.
+ * @param {string} path       Current path for error reporting.
+ * @return {Array} Array of deprecation warnings.
+ */
+function validateDeprecated( schema, data, rootSchema, path = '' ) {
+	const warnings = [];
+
+	if ( ! schema || typeof schema !== 'object' ) {
+		return warnings;
+	}
+
+	// Resolve $ref
+	let currentSchema = schema;
+	if ( schema.$ref ) {
+		currentSchema = resolveRef( schema.$ref, rootSchema );
+		if ( ! currentSchema ) {
+			return warnings;
+		}
+	}
+
+	// Handle arrays
+	if ( Array.isArray( data ) ) {
+		if ( currentSchema.items ) {
+			data.forEach( ( item, index ) => {
+				const itemPath = `${ path }[${ index }]`;
+
+				// Fully resolve the item schema
+				const itemSchema = fullyResolveSchema(
+					currentSchema.items,
+					item,
+					rootSchema
+				);
+
+				if ( itemSchema ) {
+					warnings.push(
+						...validateDeprecated(
+							itemSchema,
+							item,
+							rootSchema,
+							itemPath
+						)
+					);
+				}
+			} );
+		}
+		return warnings;
+	}
+
+	// Handle objects
+	if ( data && typeof data === 'object' ) {
+		// Check each property
+		if ( currentSchema.properties ) {
+			for ( const [ key, value ] of Object.entries( data ) ) {
+				const propSchema = currentSchema.properties[ key ];
+				if ( ! propSchema ) {
+					continue;
+				}
+
+				const propPath = path ? `${ path }.${ key }` : key;
+
+				// Check if this property itself is deprecated
+				if ( propSchema.deprecated ) {
+					const msg =
+						typeof propSchema.deprecated === 'string'
+							? propSchema.deprecated
+							: 'This property is deprecated';
+					warnings.push( { path: propPath, message: msg } );
+				}
+
+				// Recursively check the value
+				warnings.push(
+					...validateDeprecated(
+						propSchema,
+						value,
+						rootSchema,
+						propPath
+					)
+				);
+			}
+		}
+	}
+
+	return warnings;
+}
+
 /**
  * Validates a JSON file against its schema.
  *
@@ -61,6 +281,7 @@ async function validateFile( filePath ) {
 	console.log( `Validating ${ filePath } against schema: ${ data.$schema }` );
 
 	try {
+		// Standard validation
 		const validate = await ajv.compileAsync( { $ref: data.$schema } );
 		const valid = validate( data );
 
@@ -71,6 +292,30 @@ async function validateFile( filePath ) {
 			} );
 			process.exit( 1 );
 		}
+
+		// Fetch the full schema for deprecation checking
+		const fullSchema = await fetchSchema( data.$schema );
+
+		// Resolve root schema if it has $ref
+		let rootSchema = fullSchema;
+		if ( fullSchema.$ref ) {
+			rootSchema = resolveRef( fullSchema.$ref, fullSchema );
+		}
+
+		if ( ! rootSchema ) {
+			console.error( 'Failed to resolve root schema' );
+			return;
+		}
+
+		const deprecations = validateDeprecated( rootSchema, data, fullSchema );
+
+		if ( deprecations.length > 0 ) {
+			console.error( `\nDeprecation errors in ${ filePath }:` );
+			deprecations.forEach( ( warning ) => {
+				console.error( `- /${ warning.path }: ${ warning.message }` );
+			} );
+			process.exit( 1 );
+		}
 	} catch ( error ) {
 		console.error( `Error validating ${ filePath }: ${ error.message }` );
 		process.exit( 1 );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@b1ink0 Here's what Gemini came up with: 7bd8138

Co-authored-by: Aditya Dhade <76063440+b1ink0@users.noreply.github.com>
@westonruter westonruter force-pushed the add/json-validation-and-fix-blueprint branch from 94a98ac to 17bbc45 Compare January 6, 2026 20:00
Comment on lines 4 to 6
"phpExtensionBundles": [
"kitchen-sink"
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

phpExtensionBundles is also no longer needed, as all the required PHP extensions are now bundled by default.

Image

New PHP Extensions Support Modern Development Workflows
In 2024, Playground established a solid foundation with core PHP extensions such as bcmath, xml, curl, and mbstring. In 2025, the support extended to:
XDebug – available in CLI with IDE integration.
SOAP, OPCache, ImageMagick, GD 2.3.3, Intl, and Exif PHP extensions.
WebP and AVIF image formats.
Networking is now enabled by default on playground.wordpress.net, allowing PHP to request any URL.
There’s also an ongoing exploration to use XDebug through browser devtools and without an IDE.

ref: https://make.wordpress.org/playground/2025/12/03/wordpress-playground-2025-year-in-review/

Co-authored-by: Aditya Dhade <76063440+b1ink0@users.noreply.github.com>
@westonruter
Copy link
Copy Markdown
Member Author

For some reason the PHP 8.2 tests are failing: https://github.com/WordPress/performance/actions/runs/20760428538/job/59613625259

Co-authored-by: Aditya Dhade <76063440+b1ink0@users.noreply.github.com>
@westonruter
Copy link
Copy Markdown
Member Author

Tests are failing in PHP 8.2 with:

1) Test_Admin_Load::test_perflab_render_settings_page
syntax error, unexpected identifier "SERIALIZATION_FORMAT_USE_UNSER...", expecting "="
Script phpunit --strict-coverage handling the test event returned with error code 2
Script @test --verbose --testsuite performance-lab was called via test:performance-lab
✖ Command failed with exit code 2

That problematic code is apparently coming from:

vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php

One of these two lines:

34:    public const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C';
128:            is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,

westonruter and others added 2 commits January 6, 2026 14:20
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@westonruter
Copy link
Copy Markdown
Member Author

It looks like the test failures are due to doctrine/instantiator now requiring PHP 8.4: doctrine/instantiator@21dcb5f#diff-2df8a5c5513815c4adb6b34cccc7d1b1233aa0474ec4c3c055446106ab33e075

-    private const SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
-    private const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
+    private const string SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
+    private const string SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';

westonruter and others added 2 commits January 6, 2026 15:02
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces JSON schema validation for JSON files with a $schema property to improve code quality and catch configuration errors early. It also fixes the WordPress Playground blueprint configuration for the Performance Lab plugin, which was using deprecated properties and preventing the preview button from appearing on wordpress.org.

Key changes:

  • Implements a new validation script using Ajv to validate JSON files against their declared schemas
  • Integrates JSON schema validation into lint-staged and GitHub Actions CI pipeline
  • Updates blueprint.json to use pluginData instead of deprecated pluginZipFile and removes the deprecated password property
  • Fixes trailing comma in tsconfig.json to make it valid JSON

Reviewed changes

Copilot reviewed 5 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
bin/validate-json-schema.js New script that validates JSON files with $schema properties using Ajv, with custom deprecation keyword support
package.json Adds ajv 8.17.1 and ajv-formats 3.0.1 dependencies; adds lint-json script
package-lock.json Locks dependency versions for ajv and ajv-formats, updates dependency tree
lint-staged.config.js Adds JSON validation to pre-commit hooks for all **/*.json files
.github/workflows/js-lint.yml Integrates JSON schema validation step in CI workflow
plugins/performance-lab/.wordpress-org/blueprints/blueprint.json Fixes deprecated properties: replaces pluginZipFile with pluginData, removes password field, removes phpExtensionBundles
tsconfig.json Removes trailing comma on line 15 to make file valid JSON
.github/workflows/php-test-plugins.yml Adds doctrine/instantiator version constraint for PHP 8.2 (appears unrelated to this PR's stated purpose)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 6, 2026

@westonruter I've opened a new pull request, #2326, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 8 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.87%. Comparing base (53be9e0) to head (c6f877a).
⚠️ Report is 119 commits behind head on trunk.

Additional details and impacted files
@@           Coverage Diff           @@
##            trunk    #2325   +/-   ##
=======================================
  Coverage   68.87%   68.87%           
=======================================
  Files          90       90           
  Lines        7618     7618           
=======================================
  Hits         5247     5247           
  Misses       2371     2371           
Flag Coverage Δ
multisite 68.87% <ø> (ø)
single 35.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@b1ink0 b1ink0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +33 to +44
ajv.removeKeyword( 'deprecated' );
ajv.addKeyword( {
keyword: 'deprecated',
validate: ( /** @type {string|boolean} */ deprecation ) => ! deprecation,
error: {
message: ( cxt ) => {
return cxt.schema && typeof cxt.schema === 'string'
? `is deprecated: ${ cxt.schema }`
: 'is deprecated';
},
},
} );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thing: these are what get logged when the lint is run with the deprecated password field. It seems like it’s logging some extra things see the logs below.

Show log
$ npm run lint-json

> lint-json
> node bin/validate-json-schema.js

Validating .wp-env.json against schema: https://schemas.wp.org/trunk/wp-env.json
Validating plugins/performance-lab/.wordpress-org/blueprints/blueprint.json against schema: https://playground.wordpress.net/blueprint-schema.json
Validation failed for plugins/performance-lab/.wordpress-org/blueprints/blueprint.json:
- /steps/0 must have required property 'pluginPath'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'themeFolderName'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'fromPath'
- /steps/0 must have required property 'toPath'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'consts'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'siteUrl'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'file'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'wordPressFilesZip'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'pluginData'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'themeData'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0/password is deprecated: The password field is deprecated and will be removed in a future version.
Only the username field is required for user authentication.
- /steps/0 must have required property 'path'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'fromPath'
- /steps/0 must have required property 'toPath'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'request'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'path'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'path'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'code'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'options'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'options'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'sql'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'options'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'extractToPath'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'meta'
- /steps/0 must have required property 'userId'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'data'
- /steps/0 must have required property 'path'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'filesTree'
- /steps/0 must have required property 'writeToPath'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'command'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must have required property 'language'
- /steps/0 must NOT have additional properties
- /steps/0 must NOT have additional properties
- /steps/0/step must be equal to constant
- /steps/0 must match exactly one schema in oneOf
- /steps/0 must be string
- /steps/0 must NOT be valid
- /steps/0 must be boolean
- /steps/0 must be equal to constant
- /steps/0 must be null
- /steps/0 must match a schema in anyOf

But for root-level properties like phpExtensionBundles, it doesn’t log any extra things.

$ npm run lint-json

> lint-json
> node bin/validate-json-schema.js

Validating .wp-env.json against schema: https://schemas.wp.org/trunk/wp-env.json
Validating plugins/performance-lab/.wordpress-org/blueprints/blueprint.json against schema: https://playground.wordpress.net/blueprint-schema.json
Validation failed for plugins/performance-lab/.wordpress-org/blueprints/blueprint.json:
- /phpExtensionBundles is deprecated: No longer used. Feel free to remove it from your Blueprint.

If this is expected behavior, then we can ignore it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's expected. It gets really noisy. You can see this in other JSON validator too: https://www.jsonschemavalidator.net/s/EsS95EwE

@westonruter westonruter merged commit 1510693 into trunk Jan 7, 2026
35 of 36 checks passed
@westonruter westonruter deleted the add/json-validation-and-fix-blueprint branch January 7, 2026 06:49
@westonruter
Copy link
Copy Markdown
Member Author

Follow-up: #2329

@westonruter westonruter added the skip changelog PRs that should not be mentioned in changelogs label Jan 8, 2026
@westonruter westonruter changed the title Add JSON Schema validation for JSON files with a $schema property Add JSON Schema validation for JSON files with a $schema property Jan 8, 2026
westonruter added a commit to westonruter/post-date-modified that referenced this pull request Jan 24, 2026
This incorporates changes from <WordPress/performance#2325>

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: b1ink0 <b1ink0@git.wordpress.org>
Co-authored-by: iqbal-web <iqbal1hossain@git.wordpress.org>
westonruter added a commit to westonruter/post-date-modified that referenced this pull request Jan 24, 2026
This incorporates changes from <WordPress/performance#2325>

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: b1ink0 <b1ink0@git.wordpress.org>
Co-authored-by: iqbal-web <iqbal1hossain@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no milestone PRs that do not have a defined milestone for release skip changelog PRs that should not be mentioned in changelogs [Type] Enhancement A suggestion for improvement of an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance Lab Playground Integration is not working

4 participants