@imwz/wp-pattern-sentinel 0.2.3 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imwz/wp-pattern-sentinel",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Browser-based WordPress block pattern validator using Playwright",
5
5
  "type": "module",
6
6
  "engines": {
package/src/editor.js CHANGED
@@ -1,7 +1,36 @@
1
1
  import { log } from './format.js';
2
2
 
3
+ /**
4
+ * Replace inline PHP expressions with static equivalents so the block editor
5
+ * receives valid markup and the round-trip comparison stays accurate.
6
+ *
7
+ * Handles the PHP patterns that appear in Elayne theme patterns:
8
+ * <?php esc_html_e( 'Text', 'elayne' ); ?> → Text
9
+ * <?php echo esc_html__( 'Text', 'elayne' ); ?> → Text
10
+ * <?php esc_attr_e( 'Alt text', 'elayne' ); ?> → Alt text
11
+ * <?php echo esc_attr__( 'Alt text', 'elayne' ); ?> → Alt text
12
+ * <?php echo esc_url( get_template_directory_uri() ); ?> → http://example.com
13
+ * All other <?php ... ?> blocks → removed
14
+ */
15
+ function stripPhpForValidation(content) {
16
+ if (!content.includes('<?php')) return content;
17
+
18
+ return content
19
+ .replace(/<\?php\s+(?:esc_html_e|esc_html__)\s*\(\s*'([^']+)'\s*,\s*'[^']+'\s*\)\s*;?\s*\?>/g, '$1')
20
+ .replace(/<\?php\s+(?:esc_html_e|esc_html__)\s*\(\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)\s*;?\s*\?>/g, '$1')
21
+ .replace(/<\?php\s+echo\s+esc_html__\s*\(\s*'([^']+)'\s*,\s*'[^']+'\s*\)\s*;?\s*\?>/g, '$1')
22
+ .replace(/<\?php\s+(?:esc_attr_e|esc_attr__)\s*\(\s*'([^']+)'\s*,\s*'[^']+'\s*\)\s*;?\s*\?>/g, '$1')
23
+ .replace(/<\?php\s+(?:esc_attr_e|esc_attr__)\s*\(\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)\s*;?\s*\?>/g, '$1')
24
+ .replace(/<\?php\s+echo\s+esc_attr__\s*\(\s*'([^']+)'\s*,\s*'[^']+'\s*\)\s*;?\s*\?>/g, '$1')
25
+ .replace(/<\?php\s+echo\s+esc_url\s*\([\s\S]*?\)\s*;?\s*\?>/g, 'http://example.com')
26
+ .replace(/<\?php[\s\S]*?\?>/g, '')
27
+ .trim();
28
+ }
29
+
3
30
  /**
4
31
  * Strip the PHP file header (opening tag + docblock) and return the raw block markup.
32
+ * Inline PHP expressions within the block markup are replaced with static values
33
+ * so the editor receives valid content for round-trip validation.
5
34
  * Returns null if no block comment is found.
6
35
  *
7
36
  * WordPress pattern files look like:
@@ -21,7 +50,7 @@ export function extractBlockContent(fileContent) {
21
50
  .trim();
22
51
 
23
52
  if (!stripped.startsWith('<!--')) return null;
24
- return stripped;
53
+ return stripPhpForValidation(stripped);
25
54
  }
26
55
 
27
56
  /**
package/src/main.js CHANGED
@@ -30,20 +30,27 @@ export async function main() {
30
30
  args: ['--disable-web-security'],
31
31
  });
32
32
 
33
- // One authenticated context per worker sessions are fully isolated
33
+ // Login once, then share the session cookies across all worker contexts.
34
+ // Concurrent logins to WordPress (even with valid credentials) trigger a
35
+ // reauth=1 redirect loop — serialising login avoids this entirely.
34
36
  let contexts;
35
37
  try {
36
- contexts = await Promise.all(
37
- Array.from({ length: options.concurrency }, async () => {
38
- const context = await browser.newContext({ viewport: options.viewport });
39
- const page = await context.newPage();
40
- page.setDefaultTimeout(60000);
41
- const ok = await loginToWordPress(page, options.adminUrl, options.user, options.pass);
42
- await page.close();
43
- if (!ok) throw new Error('Failed to authenticate with WordPress');
44
- return context;
45
- })
46
- );
38
+ log('Logging in to WordPress...', 'cyan');
39
+ const firstContext = await browser.newContext({ viewport: options.viewport });
40
+ const loginPage = await firstContext.newPage();
41
+ loginPage.setDefaultTimeout(60000);
42
+ const ok = await loginToWordPress(loginPage, options.adminUrl, options.user, options.pass);
43
+ await loginPage.close();
44
+ if (!ok) throw new Error('Failed to authenticate with WordPress');
45
+ log(`Authenticated sharing session across ${options.concurrency} worker(s)`, 'green');
46
+
47
+ const cookies = await firstContext.cookies();
48
+ contexts = [firstContext];
49
+ for (let i = 1; i < options.concurrency; i++) {
50
+ const ctx = await browser.newContext({ viewport: options.viewport });
51
+ await ctx.addCookies(cookies);
52
+ contexts.push(ctx);
53
+ }
47
54
  } catch (error) {
48
55
  log(error.message, 'red');
49
56
  await browser.close();
@@ -78,6 +85,8 @@ async function validatePatternFile(patternPath, options, context) {
78
85
  const patternName = path.basename(patternPath);
79
86
  const startTime = Date.now();
80
87
 
88
+ log(` Validating: ${patternName}`, 'cyan');
89
+
81
90
  let fileContent;
82
91
  try {
83
92
  fileContent = await fs.promises.readFile(patternPath, 'utf8');