@imwz/wp-pattern-sentinel 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -212,6 +212,10 @@ node -e "
212
212
 
213
213
  `block_validation` errors also surface Gutenberg's human-readable issue messages (e.g. `"Expected attribute 'class' of value '…' but got '…'"`), so you no longer need to open the browser console to identify what failed.
214
214
 
215
+ A block can also pass validation only because one of its block type's deprecations accepted the markup and migrated it. The editor reports such a block as valid, but it saves different markup, and it shows as broken the next time the page is opened. Sentinel re-validates every parsed block against its original markup and reports these as `block_validation` errors reading "Block was migrated by a deprecation and will save different markup". A common cause is an attribute the block does not support, such as `aria-hidden` on `core/paragraph`.
216
+
217
+ `--cache` entries record the Sentinel version that passed them, so upgrading Sentinel re-validates every pattern once.
218
+
215
219
  ## npm publish
216
220
 
217
221
  When ready to publish:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imwz/wp-pattern-sentinel",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Browser-based WordPress block pattern validator using Playwright",
5
5
  "type": "module",
6
6
  "engines": {
package/src/main.js CHANGED
@@ -17,6 +17,12 @@ import { log, formatResult, printSummary } from './format.js';
17
17
 
18
18
  const CACHE_FILE = '.sentinel-cache.json';
19
19
 
20
+ // Cache entries record the Sentinel version that passed them, so a release
21
+ // that adds or tightens a check re-validates patterns that passed before it.
22
+ const SENTINEL_VERSION = JSON.parse(
23
+ fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')
24
+ ).version;
25
+
20
26
  function loadCache() {
21
27
  try {
22
28
  return JSON.parse(fs.readFileSync(path.join(process.cwd(), CACHE_FILE), 'utf8'));
@@ -67,7 +73,7 @@ export async function main() {
67
73
  try {
68
74
  const content = fs.readFileSync(file, 'utf8');
69
75
  const key = path.relative(process.cwd(), file);
70
- if (cache[key]?.passed && cache[key]?.hash === hashContent(content)) {
76
+ if (cache[key]?.passed && cache[key]?.hash === hashContent(content) && cache[key]?.version === SENTINEL_VERSION) {
71
77
  skipped.push(file);
72
78
  } else {
73
79
  pending.push(file);
@@ -153,7 +159,7 @@ export async function main() {
153
159
  for (const result of results) {
154
160
  const key = path.relative(process.cwd(), result.patternPath);
155
161
  if (result.passed) {
156
- cache[key] = { hash: result.hash, passed: true, checkedAt: new Date().toISOString() };
162
+ cache[key] = { hash: result.hash, passed: true, version: SENTINEL_VERSION, checkedAt: new Date().toISOString() };
157
163
  } else {
158
164
  delete cache[key];
159
165
  }
package/src/validation.js CHANGED
@@ -1,30 +1,59 @@
1
1
  import { log } from './format.js';
2
2
 
3
3
  /**
4
- * Walk the block tree and collect any blocks where isValid === false.
4
+ * Walk the block tree and collect any blocks where isValid === false, plus any
5
+ * block a deprecation silently migrated.
6
+ *
7
+ * When a block fails validation, the parser tries the block type's
8
+ * deprecations; if one accepts the markup, the block is migrated and reported
9
+ * as isValid: true with no issues, yet its attributes have changed and it will
10
+ * save different markup. The editor then shows the block as broken on the next
11
+ * load. Example: core/paragraph with aria-hidden="true" is taken by an old
12
+ * paragraph deprecation that drops fontFamily and keeps the whole <p> as the
13
+ * block's text, so the first save writes a <p> nested inside another <p>.
14
+ * Re-running validateBlock() on the parsed block compares what it will save
15
+ * against the original markup, which catches this.
5
16
  */
6
17
  export async function checkBlockValidation(page, verbose = false) {
7
18
  const start = Date.now();
8
19
  if (verbose) log(' → Checking block validation...', 'gray');
9
20
  try {
10
21
  const errors = await page.evaluate(() => {
11
- const walk = blocks => blocks.flatMap(block => [
12
- ...(block.isValid === false
13
- ? [{
14
- blockId: block.clientId,
15
- blockName: block.name,
16
- error: 'Block validation failed',
17
- validationIssues: (block.validationIssues ?? []).map(issue => {
18
- try {
19
- return (issue.args ?? [])
20
- .map(a => (typeof a === 'string' ? a : JSON.stringify(a)))
21
- .join(' ');
22
- } catch { return 'unknown issue'; }
23
- }),
24
- }]
25
- : []),
26
- ...walk(block.innerBlocks ?? []),
27
- ]);
22
+ const formatIssues = issues => (issues ?? []).map(issue => {
23
+ try {
24
+ return (issue.args ?? [])
25
+ .map(a => (typeof a === 'string' ? a : JSON.stringify(a)))
26
+ .join(' ');
27
+ } catch { return 'unknown issue'; }
28
+ });
29
+ const validateBlock = window.wp.blocks.validateBlock;
30
+ const migratedIssues = block => {
31
+ if (!validateBlock || block.isValid !== true || block.name === 'core/missing') return null;
32
+ const [isValid, issues] = validateBlock(block);
33
+ return isValid ? null : issues;
34
+ };
35
+ const walk = blocks => blocks.flatMap(block => {
36
+ const migrated = migratedIssues(block);
37
+ return [
38
+ ...(block.isValid === false
39
+ ? [{
40
+ blockId: block.clientId,
41
+ blockName: block.name,
42
+ error: 'Block validation failed',
43
+ validationIssues: formatIssues(block.validationIssues),
44
+ }]
45
+ : []),
46
+ ...(migrated
47
+ ? [{
48
+ blockId: block.clientId,
49
+ blockName: block.name,
50
+ error: 'Block was migrated by a deprecation and will save different markup',
51
+ validationIssues: formatIssues(migrated),
52
+ }]
53
+ : []),
54
+ ...walk(block.innerBlocks ?? []),
55
+ ];
56
+ });
28
57
  return walk(window.wp.data.select('core/block-editor').getBlocks());
29
58
  });
30
59
  if (verbose) log(` → Block validation complete (${Date.now() - start}ms)`, 'gray');