@imwz/wp-pattern-sentinel 0.1.0 → 0.2.1

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
@@ -57,6 +57,8 @@ Sentinel auto-discovers the Trellis directory by walking up from the current wor
57
57
  | `--env` | `development` | Trellis environment (`development`, `staging`, `production`) |
58
58
  | `--subsite` | — | Multisite subsite slug (appended to URL) |
59
59
 
60
+ **Bedrock support:** When `--trellis` is used, sentinel auto-detects Bedrock installs by reading `WP_SITEURL` from the site's `.env` file. Bedrock puts WordPress core in `/wp/`, so admin URLs become `/wp/wp-admin/` instead of `/wp-admin/`. No extra flags needed — this is handled automatically.
61
+
60
62
  ---
61
63
 
62
64
  ## Quickstart with `.env`
@@ -111,6 +113,7 @@ node bin/sentinel.js --concurrency=6 --url=... path/to/patterns/
111
113
  | `--url` | `http://localhost` | WordPress site URL |
112
114
  | `--user` | `admin` | Admin username |
113
115
  | `--pass` | `password` | Admin password |
116
+ | `--wp-subdir` | — | WP core subdir when not using `--trellis` (e.g. `wp` for Bedrock). Sets admin URL to `{url}/{subdir}`. Auto-detected from `WP_SITEURL` when `--trellis` is used. |
114
117
  | `--headless` | `true` | Run browser headless |
115
118
  | `--concurrency` | `4` | Parallel workers |
116
119
  | `--json` | `false` | Output JSON (one result per line) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imwz/wp-pattern-sentinel",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Browser-based WordPress block pattern validator using Playwright",
5
5
  "type": "module",
6
6
  "engines": {
package/src/args.js CHANGED
@@ -24,6 +24,9 @@ const ARG_OPTIONS = {
24
24
  site: { type: 'string' },
25
25
  env: { type: 'string', default: 'development' },
26
26
  subsite: { type: 'string' },
27
+ // Bedrock / WP core subdir (e.g. "wp" for Bedrock installs where core lives at /wp/)
28
+ // Auto-detected via WP_SITEURL when --trellis is used; set manually otherwise.
29
+ 'wp-subdir': { type: 'string' },
27
30
  // Behaviour
28
31
  headless: { type: 'boolean', default: true },
29
32
  json: { type: 'boolean', default: false },
@@ -65,7 +68,7 @@ export async function parseArgs(args) {
65
68
  allowPositionals: true,
66
69
  });
67
70
 
68
- let url, user, pass;
71
+ let url, adminUrl, user, pass;
69
72
 
70
73
  // --- Source 1: Trellis vault ---
71
74
  if (values.trellis) {
@@ -73,7 +76,7 @@ export async function parseArgs(args) {
73
76
  ? path.resolve(values['trellis-dir'])
74
77
  : findTrellisDir();
75
78
 
76
- ({ url, user, pass } = loadTrellisCredentials({
79
+ ({ url, adminUrl, user, pass } = loadTrellisCredentials({
77
80
  trellisDir,
78
81
  site: values.site,
79
82
  env: values.env,
@@ -104,10 +107,20 @@ export async function parseArgs(args) {
104
107
  pass = await prompt('WordPress admin password: ', { hidden: true });
105
108
  if (!pass) throw new Error('WordPress password is required.');
106
109
  }
110
+
111
+ // --wp-subdir: manually specify the WP core subdir (e.g. "wp" for Bedrock)
112
+ const wpSubdir = values['wp-subdir'];
113
+ adminUrl = wpSubdir
114
+ ? `${url.replace(/\/$/, '')}/${wpSubdir}`
115
+ : url;
107
116
  }
108
117
 
118
+ const cleanUrl = url.replace(/\/$/, '');
119
+ const cleanAdminUrl = (adminUrl ?? url).replace(/\/$/, '');
120
+
109
121
  return {
110
- url: url.replace(/\/$/, ''),
122
+ url: cleanUrl,
123
+ adminUrl: cleanAdminUrl,
111
124
  user,
112
125
  pass,
113
126
  headless: values.headless,
package/src/login.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { log } from './format.js';
2
2
 
3
- export async function loginToWordPress(page, url, user, pass) {
3
+ export async function loginToWordPress(page, adminUrl, user, pass) {
4
4
  try {
5
- await page.goto(`${url}/wp-login.php`, {
5
+ await page.goto(`${adminUrl}/wp-login.php`, {
6
6
  waitUntil: 'domcontentloaded',
7
7
  timeout: 30000,
8
8
  });
9
9
  await page.fill('#user_login', user);
10
10
  await page.fill('#user_pass', pass);
11
11
  await page.click('#wp-submit');
12
- await page.waitForURL(`${url}/wp-admin/**`, { timeout: 15000 });
12
+ await page.waitForURL(/\/wp-admin\//, { timeout: 15000 });
13
13
  return true;
14
14
  } catch (error) {
15
15
  log(`Login failed: ${error.message}`, 'red');
package/src/main.js CHANGED
@@ -38,7 +38,7 @@ export async function main() {
38
38
  const context = await browser.newContext({ viewport: options.viewport });
39
39
  const page = await context.newPage();
40
40
  page.setDefaultTimeout(60000);
41
- const ok = await loginToWordPress(page, options.url, options.user, options.pass);
41
+ const ok = await loginToWordPress(page, options.adminUrl, options.user, options.pass);
42
42
  await page.close();
43
43
  if (!ok) throw new Error('Failed to authenticate with WordPress');
44
44
  return context;
@@ -94,13 +94,13 @@ async function validatePatternFile(patternPath, options, context) {
94
94
  page.setDefaultTimeout(60000);
95
95
 
96
96
  try {
97
- const pageId = await createDraftPage(page, options.url);
97
+ const pageId = await createDraftPage(page, options.adminUrl);
98
98
  if (pageId === null) {
99
99
  return fail(patternName, startTime, 'page_creation_error', 'Failed to create test page');
100
100
  }
101
101
 
102
102
  if (!(await insertPatternIntoEditor(page, blockContent))) {
103
- await deletePage(page, options.url, pageId);
103
+ await deletePage(page, options.adminUrl, pageId);
104
104
  return fail(patternName, startTime, 'insertion_error', 'Failed to insert pattern into editor');
105
105
  }
106
106
 
@@ -119,7 +119,7 @@ async function validatePatternFile(patternPath, options, context) {
119
119
  saveResult.warnings.push(...comparison.warnings);
120
120
 
121
121
  if (!options.keepPage) {
122
- await deletePage(page, options.url, pageId);
122
+ await deletePage(page, options.adminUrl, pageId);
123
123
  }
124
124
 
125
125
  return {
package/src/trellis.js CHANGED
@@ -108,6 +108,26 @@ export function loadTrellisCredentials({ trellisDir, site, env = 'development',
108
108
  ? `${protocol}://${canonical}/${subsite}`
109
109
  : `${protocol}://${canonical}`;
110
110
 
111
+ // For the main site, detect Bedrock's WP core subdir via WP_SITEURL in local .env.
112
+ // Bedrock puts WP core at /wp/, so admin ops must target /wp/wp-admin/ not /wp-admin/.
113
+ // Subsites are accessed at /<subsite>/wp-admin/ and don't carry the /wp/ prefix.
114
+ let adminUrl = url;
115
+ if (!subsite) {
116
+ const localPath = wordpressSites[siteKey].local_path;
117
+ if (localPath) {
118
+ const envFile = path.join(path.resolve(trellisDir, localPath), '.env');
119
+ if (fs.existsSync(envFile)) {
120
+ try {
121
+ const envContent = fs.readFileSync(envFile, 'utf8');
122
+ const match = envContent.match(/^WP_SITEURL=["']?(.+?)["']?\s*$/m);
123
+ if (match) adminUrl = match[1].trim();
124
+ } catch {
125
+ // Fall back to url
126
+ }
127
+ }
128
+ }
129
+ }
130
+
111
131
  // Trellis admin username is always "admin" — not stored in the vault
112
- return { url, user: 'admin', pass: adminPassword };
132
+ return { url, adminUrl, user: 'admin', pass: adminPassword };
113
133
  }