41 Commits

Author SHA1 Message Date
29622dd54c fix: move sync-wiki.yml secrets to env block to resolve SonarCloud S7636
Move github.actor, secrets.GITHUB_TOKEN, and github.repository from
inline run block string interpolation to step-level env: block.
References via env vars prevent secret expansion in workflow logs.

Resolves the remaining S7636 hotspot in sync-wiki.yml.

Closes #106
2026-03-20 07:10:45 +00:00
1afa6b71d7 fix: streamline npm lint and quality scripts (issue #109) (#116)
Rename 'lint' to 'lint:php-all' for clarity, make 'lint' run all
linters (PHP, JS, CSS), and simplify 'quality' to avoid duplication.

Addresses gemini review feedback from PR #103.
2026-03-19 23:20:17 +00:00
8bb4784204 fix: relax PHPCS grep boundary to match plural summaries (issue #110) (#115) 2026-03-19 22:40:02 +00:00
b223165012 fix: align multisite activation phrasing in Playground docs (issue #111) (#114)
* fix: align multisite activation phrasing in Playground docs (issue #111)

* fix: align single-site playground activation assertion with blueprint plugins

* fix: add retries for flaky playground single-site startup
2026-03-19 22:29:00 +00:00
4228bcc330 fix: simplify responsive breakpoint comment in admin-styles.css (issue #93) (#105)
* fix: remove redundant Responsive Styles comment in admin-styles.css (issue #93)

Removes the generic '/* Responsive Styles */' comment that was redundant
alongside the specific '/* 782px is the WordPress mobile admin breakpoint. */'
comment, per Gemini Code Assist review feedback on PR #87.

Closes #93

* fix: add missing trailing commas to fix comma-dangle lint errors

Fixes ESLint comma-dangle errors in:
- cypress.config.js (lines 15-16): missing trailing commas on
  chromeWebSecurity property and closing e2e object brace
- cypress/e2e/playground-single-site.cy.js (line 23): missing trailing
  comma on expect() message argument

Also updates eslint-plugin-cypress from ^2.15.1 to ^6.2.0 to resolve
peer dependency conflict that prevented local lint verification.
2026-03-18 22:57:17 +00:00
7bac0dc63d t096: fix unconditional plugin activation assertion in cypress test (#101)
* fix: make plugin activation assertion unconditional in cypress test

Addresses Gemini Code Assist review feedback on PR #84 (issue #96).
The 'Plugin is activated' test was using an if/else guard that caused
it to silently pass when the plugin row was missing. Replaced with a
direct unconditional cy.get() + cy.within() assertion so the test
fails clearly if the plugin is not found.

Optional plugin checks (Plugin Toggle, Kadence Blocks) retain their
conditional logic as those are genuinely optional in the test env.

Closes #96

* fix: reduce duplicated Cypress assertion for SonarCloud

* fix: lower Sonar new-code duplication in playground test
2026-03-18 21:38:00 +00:00
7c272b5399 t095: fix package.json lint coverage and quality gate (#103)
* fix: address PR #85 package.json review feedback (issue #95)

* fix: add trailing commas to cypress.config.js for comma-dangle rule

ESLint comma-dangle rule (always-multiline) requires trailing commas on
the last property and closing brace of multiline objects. Adding lint:js
coverage of cypress.config.js in package.json exposed these two missing
trailing commas at lines 15 and 16.

* fix: resolve ESLint errors blocking CI on PR #103

- Remove 'cypress/globals' env key from .eslintrc.js; this key requires
  eslint-plugin-cypress to be globally installed, which CI tools (Codacy,
  CodeFactor) do not have. Cypress globals are already declared explicitly
  in the globals block, making the env key redundant.
- Remove trailing commas from cypress.config.js to comply with the
  comma-dangle: never ESLint rule defined in .eslintrc.js.
- Update package-lock.json to include stylelint and stylelint-config-standard
  which were in devDependencies but missing from the lock file.
2026-03-18 21:33:54 +00:00
81e5b14604 fix: consolidate duplicate deployment echo into single printf in build.sh (#99)
Addresses Gemini review feedback from PR #80 (build.sh:88): replaces the
redundant pair of printf+echo deployment messages with a single printf call,
which is more concise and idiomatic for formatted shell output.

Closes #97
2026-03-18 21:27:26 +00:00
9dca8880cc fix: use word boundaries in grep patterns to prevent false positives (#100)
Address PR #86 review feedback from Gemini:
- Line 145: replace 'grep -i error|fail|exception' with grep -E -i '\b(error|fail|exception)'
- Line 224: replace 'grep -i ERROR|WARNING' with grep -E -i '\b(ERROR|WARNING)\b'

Using \b word boundaries prevents substring matches (e.g. 'exception' in
'unexceptional') and removes the unnecessary cat pipe (UUOC).

Closes #94
2026-03-18 21:27:22 +00:00
ad03358e2a fix: address PR #81 review feedback in Playground-Testing.md (issue #98) (#102) 2026-03-18 21:27:12 +00:00
7d0ee0adea fix: resolve ShellCheck violations in shell scripts (#91)
- SC2155 (bin/localwp-setup.sh): declare local variables separately from
  command substitution assignments to avoid masking return values (13 fixes)
- SC2034 (bin/localwp-setup.sh): remove unused PLUGIN_TEXT_DOMAIN variable
- SC2162 (bin/localwp-setup.sh): add -r flag to read to avoid backslash mangling
- SC2154 (bin/setup-test-env.sh): add shellcheck disable for PHP variables in
  heredoc that ShellCheck incorrectly identifies as unassigned shell variables
- bin/setup-test-env.sh: remove self-modifying chmod +x $0 (unnecessary and
  bad practice; file permissions should be set once in version control)
- bin/setup-test-env.sh: change == to = in POSIX [ ] test expressions
- build.sh: add ./ prefix to directory glob copies for clarity
- build.sh: use subshell (cd build || exit 1; zip ...) instead of bare cd/cd..
  to avoid SC2103 and ensure working directory is always restored

Fixes part of #20 (shell script quality issues)
2026-03-18 11:47:38 +00:00
9fdfa7a8a9 fix: pin all GitHub Actions to full commit SHAs (resolves SonarCloud S7637) (#90)
Pin all floating version tags (@v1, @v2, @v3, @v4, @master) to full commit
SHAs across all workflow files to eliminate supply chain security risk.

Actions pinned:
- actions/checkout@v3 -> f43a0e5 (v3.6.0) in release.yml, sync-wiki.yml
- actions/checkout@v4 -> 34e1148 (v4) in tests.yml
- shivammathur/setup-php@v2 -> accd612 (v2) in all workflows
- actions/setup-node@v4 -> 49933ea (v4) in playground-tests*.yml, wordpress-tests.yml
- actions/upload-artifact@v4 -> ea165f8 (v4) in playground-tests*.yml, wordpress-tests.yml
- softprops/action-gh-release@v1 -> de2c0eb (v1) in release.yml
- codacy/codacy-analysis-cli-action@v4 -> 562ee3e (v4) in code-quality.yml
- github/codeql-action/upload-sarif@v3 -> 603b797 (v3) in code-quality.yml
- swissspidy/wp-performance-action@v2.0.3 -> b7e3ffc (v2.0.3) in playground-tests.yml
- SonarSource/sonarqube-scan-action@master -> 9598b8a in sonarcloud.yml

Closes #89
2026-03-18 04:58:57 +00:00
d6dcda908c fix: change comma-dangle rule to always-multiline per review feedback (#88)
Addresses Gemini review feedback from PR #66 (issue #73).
Changes 'comma-dangle' from 'never' to 'always-multiline' to improve
maintainability — cleaner git diffs and easier line reordering.
Updates admin/js files to comply with the new rule.

Closes #73
2026-03-17 21:00:19 +00:00
7640f01d0c quality-debt: document 782px WordPress mobile admin breakpoint (#87)
* fix: document 782px WordPress mobile admin breakpoint in media query

Adds an inline comment explaining that 782px is the WordPress mobile
admin breakpoint (wp-admin responsive threshold), addressing the
maintainability feedback from Gemini in PR #68.

Closes #69

* fix: add empty line before CSS comment to satisfy comment-empty-line-before rule

CodeFactor flagged missing empty line before the breakpoint comment (comment-empty-line-before).
Also simplifies comment text per Gemini Code Assist suggestion (removes redundant parenthetical).

Fixes CodeFactor FAILURE and Codacy ACTION_REQUIRED on PR #87.
2026-03-17 20:50:19 +00:00
632dda5952 fix: address PR #65 review feedback on error-checking-feedback-loops.md (#86)
- Fix yaml code block to bash at line 80 (shell command, not YAML)
- Remove useless cat pipe: use grep directly on test-output.log
- Remove useless cat pipe: use grep directly on phpcs-output.log

Closes #71
2026-03-17 20:45:53 +00:00
8fda3f1163 fix: add lint:js to quality script (#85)
Include JavaScript linting in the quality gate alongside PHP and CSS
linting, as suggested in PR #55 review feedback (Gemini, medium).

Closes #77
2026-03-17 20:37:05 +00:00
79f78882a6 fix: remove redundant existence check, use cy.within() for scoped selector (#84)
Addresses Gemini review feedback on PR #50 line 23: the .should('exist')
check was redundant since the if-condition already confirms element presence.
Refactored to use cy.within() to scope the .deactivate a check, eliminating
selector repetition and improving readability.

Closes #79
2026-03-17 20:29:00 +00:00
e1ee99ac9c fix: validate type parameter against allow-list in showMessage (#83)
Adds allow-list validation for the 'type' parameter in showMessage()
to prevent class injection vulnerabilities. The type is now checked
against ['success', 'error'] before being passed to addClass(), with
a safe fallback to 'error' for any unexpected values.

Addresses review feedback from PR #47 (gemini-code-assist finding).
Closes #76
2026-03-17 19:33:04 +00:00
6300f1c545 fix: remove duplicate deployment echo in build.sh (#80)
* fix: remove duplicate deployment echo in build.sh

Remove duplicate 'Deploying to local WordPress installation...' message.
Replace printf with echo "" for blank line separator to avoid \n
rendering issues without -e flag.

Closes #70

* fix: use ${WP_LOCAL_PLUGIN_DIR:-} for bash nounset safety in build.sh

Addresses CodeRabbit Major review feedback on PR #80.
Direct dereference of $WP_LOCAL_PLUGIN_DIR under set -u causes the script
to exit when the variable is unset, even though local deployment is optional.
Using ${WP_LOCAL_PLUGIN_DIR:-} safely handles the unset case without
triggering nounset.
2026-03-17 19:33:00 +00:00
c3738a3106 fix: remove stale /tmp/wordpress-develop before git clone (#82)
Prevents git clone failure on re-runs when the destination directory
already exists from a previous test suite installation.

Closes #74
2026-03-17 19:26:41 +00:00
e5d2994e40 docs: mention both WP-CLI network activation commands in Playground-Testing.md (#81)
Address review feedback from PR #63 (gemini-code-assist): document both
`wp plugin activate --network` (for installed plugins) and
`wp plugin install --activate-network` (to install and activate in one step).

Closes #72
2026-03-17 19:22:48 +00:00
7eb7aedc39 chore: add .superset/ to .gitignore (local tool config) 2026-03-17 03:52:37 +00:00
9cddf28c09 fix: use standard max-width media query syntax in admin-styles.css (issue #43) (#68)
Replace invalid CSS media-feature range notation with universally-supported
traditional syntax for better browser compatibility.

- admin/css/admin-styles.css:127: (width <= 782px) → (max-width: 782px)
2026-03-17 01:17:05 +00:00
a1e5b166ff fix: address PR #10 CodeRabbit review feedback (issue #45) (#51)
* fix: address PR #10 CodeRabbit review feedback

- Fix SC2115 shellcheck warning in build.sh (use ${var:?} for safe rm -rf)
- Fix SC2164 shellcheck warning in build.sh (cd build || exit 1)
- Standardise bullet points from hyphens to asterisks in .wiki/Contributing.md
- Refine verb formality in readme.txt, README.md, .wiki/Contributing.md, .wiki/Coding-Standards.md
- Clarify PHPDoc wording in .wiki/Coding-Standards.md
- Add clarifying comment to phpcs-simple.xml arg value

* fix: re-enable PHPUnit test step in tests.yml

Tests were commented out in PR #10. PHPUnit test files exist in
tests/phpunit/ and phpunit.xml is configured. Re-enabling the step
so tests actually run in CI.

Closes #45

* fix: remove redundant composer require steps in code-quality workflow

szepeviktor/phpstan-wordpress, wp-coding-standards/wpcs, and
dealerdirect/phpcodesniffer-composer-installer are already declared in
composer.json require-dev. The extra 'composer require' steps after
'composer install' caused a second packagist.org network hit that timed
out in CI, failing the PHPStan Static Analysis job.

Fixes the Code Quality CI failure on PR #51 (issue #45).
2026-03-17 00:04:23 +00:00
79829ddce0 fix: address PR #15 review feedback for error-checking-feedback-loops.md (issue #24) (#65)
- Add blank lines around all fenced code blocks (MD031)
- Add language specifiers to all bare fenced code blocks (MD040)
- Rename duplicate 'Processing Steps' heading to 'SonarCloud Processing Steps' (MD024)

Closes #24
2026-03-16 23:57:57 +00:00
ef43525c4a docs: fix inaccuracies in Playground-Testing.md (issue #27) (#63)
- Correct blueprint description: single-site does not enable multisite;
  both blueprints activate Plugin Toggle and Kadence Blocks
- Fix incorrect WP-CLI flag: --activate-network → --network
- Update multisite blueprint description to name both network-activated plugins
2026-03-16 23:42:34 +00:00
708acc39de fix: add comma-dangle rule to .eslintrc.js to prevent trailing commas (issue #25) (#66)
Add 'comma-dangle': ['error', 'never'] rule to .eslintrc.js to enforce
no trailing commas in JavaScript files. This addresses the Codacy review
findings from PR #15 that flagged trailing commas in object and array
literals as ErrorProne issues. The rule ensures ESLint catches trailing
commas automatically going forward.

Closes #25
2026-03-16 23:41:05 +00:00
40f6f596fa fix: resolve ESLint sourceType mismatch for cypress.config.js (issue #31) (#64)
Add overrides block in .eslintrc.js to parse cypress.config.js as CommonJS
(sourceType: 'script') rather than ESM. The global sourceType: 'module' caused
ESLint to flag require() as undefined, since require is not available in ESM
scope. The project has no 'type: module' in package.json, so CommonJS is correct.

Closes #31
2026-03-16 23:40:05 +00:00
0b17fe8ad9 fix: remove stale cache-busting parameter from playground/index.html blueprint URL (#67)
The CodeRabbit review of PR #15 flagged that playground/index.html had an
iframe src pointing to the feature/testing-framework branch. That URL fix
was applied in commit f48276c (updating to main branch).

This commit also removes the stale cache-busting `_t=2` query parameter
that was added during development. The blueprint URL now references the
canonical main branch path without any ephemeral query parameters.

Closes #36
2026-03-16 23:39:31 +00:00
a8f968562c fix: add graceful termination for Python HTTP server in playground modes (#62)
Adds PYTHON_PID variable and cleanup() function with EXIT/INT/TERM trap
to ensure the background Python HTTP server is always stopped when the
script exits, whether normally or due to an unexpected interruption.

Applies to both playground-single and playground-multisite branches.

Closes #30
2026-03-16 23:08:34 +00:00
595855ce10 fix: add specific plugin functionality tests to single-site.cy.js (issue #33) (#61)
Address CodeRabbit PR #15 review feedback: replace generic settings page
stub with tests that verify actual plugin functionality — plugin row
visibility, update source selector link presence, modal open/close
behaviour, and source option rendering.
2026-03-16 23:08:29 +00:00
1d41af86c3 fix: add dialog semantics and accessible close button to update source modal (#58)
Addresses PR #18 review feedback (issue #22):
- Add role="dialog", aria-modal="true", aria-labelledby to modal container
- Add tabindex="-1" to make modal keyboard-focusable
- Add unique id to <h2> heading for aria-labelledby reference
- Replace <span class="wpst-modal-close"> with <button type="button"> and aria-label

Fixes #22
2026-03-16 23:08:09 +00:00
a6db436a48 fix: address bin/install-wp-tests.sh PR #15 review feedback (issue #29) (#54)
* fix: address PR #15 review feedback in bin/install-wp-tests.sh

- Harden download(): use curl -fsSL/-o, wget -qO, add else branch for
  missing curl/wget (HIGH finding from coderabbit)
- Fix beta/RC version logic: set WP_TESTS_TAG='branches/$WP_BRANCH' so
  the computed tag is not silently discarded (MEDIUM finding)
- Use --branch "$WP_TESTS_TAG" in git clone so the correct WP version
  is checked out instead of always pulling master (MEDIUM finding)
- Add failure checks (if ! cmd) after git clone and cp operations in
  install_test_suite (MEDIUM finding)
- Quote $ioption in all sed calls to prevent word splitting
- Replace sed trailing-slash strip with ${WP_CORE_DIR%/} (SC2001)
- Quote ${SKIP_DB_CREATE} and $EXTRA to prevent word splitting (SC2086)
- Suppress SC2001 for VERSION_ESCAPED sed (regex dot-escaping requires sed)
- ShellCheck: zero violations

Closes #29

* fix: strip SVN prefix from WP_TESTS_TAG for git clone --branch

WP_TESTS_TAG uses SVN-style paths (tags/X.Y.Z, branches/X.Y, trunk)
but git clone --branch requires bare ref names (X.Y.Z, X.Y, trunk).

Derive GIT_REF by stripping the tags/ or branches/ prefix before
passing to git clone. Also remove the no-op grep line in the latest
version lookup and add head -1 to guard against multiple matches.

Fixes CI failure: 'fatal: Remote branch tags/6.9.4 not found'
Closes #29
2026-03-16 23:08:00 +00:00
4a817ab231 fix: namespace mismatch, XSS in showNotice/showMessage, tab indentation (#47)
- Fix namespace in includes/Multisite/class-multisite.php from
  WP_Plugin_Starter_Template_For_AI_Coding\Multisite to
  WPALLSTARS\PluginStarterTemplate\Multisite so autoloader can
  resolve the class correctly (critical: breaks multisite autoloading)
- Fix XSS in admin/js/admin-scripts.js showNotice(): replace HTML
  string interpolation with safe jQuery DOM API (.text() + .addClass())
- Fix XSS in admin/js/update-source-selector.js showMessage(): replace
  .html(message) with .text(message) to prevent admin-side XSS
- Fix tab indentation in includes/Admin/class-admin.php (3 comment
  lines using tabs replaced with 4-space project standard)

Closes #19
2026-03-16 23:07:55 +00:00
1f96fe9965 fix: address PR #9 review feedback quality-debt (#59)
- .markdownlint.json: enforce MD004 asterisk style to match project conventions
- .wiki/Contributing.md: convert all dash list markers to asterisks (MD004)
- .wiki/Coding-Standards.md: fix PHPDoc bullet wording (add missing preposition)
- build.sh: add set -euo pipefail for strict error handling
- build.sh: fix SC2115 (use ${var:?} to prevent rm -rf /)
- build.sh: fix SC2164 (cd build || exit 1)
- build.sh: fix SC2028 (use printf instead of echo for escape sequences)
- build.sh: fix SC2035 (use ./*.php glob to avoid dash-named files)

Closes #46
2026-03-16 22:44:49 +00:00
5d148f8af9 fix: address CSS quality-debt from PR #15 review feedback (#55)
- Convert tab indentation to 4 spaces in admin/css/admin-styles.css
  and admin/css/update-source-selector.css per project coding standards
- Add stylelint and stylelint-config-standard to devDependencies
- Add lint:css npm script to enable CSS quality checking
- Update quality script to include CSS linting

The playground/multisite.html CSS rule-empty-line-before fix was already
applied in commit 3ca2fe5. This PR formally closes the quality-debt
tracking issue by verifying the fix and adding CSS linting tooling to
prevent similar regressions.

Closes #38
2026-03-16 22:44:46 +00:00
0e906eb981 fix: remove redundant excludePaths.analyse block in phpstan.neon (#52)
Addresses CodeRabbit review feedback from PR #13. The excludePaths
section had both 'analyse' and 'analyseAndScan' with identical paths,
which is redundant. Since analyseAndScan is the superset (excludes from
both analysis and file scanning), the 'analyse' block is unnecessary.

Closes #44
2026-03-16 22:44:38 +00:00
02a635f72c fix: address PR review feedback for class-admin.php (issue #41) (#49)
- Fix mixed tab/space indentation on phpcs:disable/enable comment lines
- Exclude PHPMD Superglobals rule: WordPress plugins legitimately use
  $_GET in testing branches with filter_input() for production; the
  rule produces false positives in this pattern
- All other findings (wp_unslash, comment punctuation, else clause)
  were already addressed in prior commits on main
2026-03-16 22:44:36 +00:00
6625e8ca4a fix: disable networking in singleSiteBlueprint to prevent multisite (#48)
Set networking to false in the single site playground blueprint.
Networking: true can inadvertently enable multisite behaviour.

Closes #39
2026-03-16 21:45:24 +00:00
52632ec322 fix(tests): add plugin activation verification by slug in Cypress test (#50)
Addresses CodeRabbit review feedback on PR #15 (issue #32): the 'Plugin is
activated' test now also checks that the starter template plugin itself
(wp-plugin-starter-template-for-ai-coding) exists and has a .deactivate link,
confirming it is active rather than just present in the plugins list.
2026-03-16 21:45:23 +00:00
1c1980bb22 chore: improve workflow names and fix CSS indentation consistency (#18)
* fix: resolve plugin class loading reliability issues

* fix: address CodeRabbit XSS and accessibility findings from PR #18

- admin/js/admin-scripts.js: replace HTML string interpolation in showNotice
  with DOM API construction and .text() to prevent XSS; whitelist type values
- admin/js/update-source-selector.js: replace .html(message) with .text(message)
  in showMessage to prevent XSS from AJAX response content
- admin/templates/modal.php: add role=dialog, aria-modal=true, aria-labelledby
  for screen reader semantics; replace <span> close control with <button> for
  keyboard operability and proper ARIA role
2026-03-16 18:40:09 +00:00
38 changed files with 2158 additions and 794 deletions

View File

@@ -23,34 +23,39 @@ AI assistants can directly monitor GitHub Actions workflows using the GitHub API
This helps identify failures and diagnose issues: This helps identify failures and diagnose issues:
``` ```text
github-api /repos/{owner}/{repo}/actions/runs github-api /repos/{owner}/{repo}/actions/runs
``` ```
#### Step-by-Step Process #### Step-by-Step Process
1. **Get Recent Workflow Runs**: 1. **Get Recent Workflow Runs**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs
``` ```
2. **Filter for Failed Runs**: 2. **Filter for Failed Runs**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs?status=failure github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs?status=failure
``` ```
3. **Get Details for a Specific Run**: 3. **Get Details for a Specific Run**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id} github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}
``` ```
4. **Get Jobs for a Workflow Run**: 4. **Get Jobs for a Workflow Run**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}/jobs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}/jobs
``` ```
5. **Analyze Job Logs** (if accessible via API): 5. **Analyze Job Logs** (if accessible via API):
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/jobs/{job_id}/logs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/jobs/{job_id}/logs
``` ```
@@ -61,6 +66,7 @@ github-api /repos/{owner}/{repo}/actions/runs
**Error**: `Missing download info for actions/upload-artifact@v3` **Error**: `Missing download info for actions/upload-artifact@v3`
**Solution**: Update to the latest version of the action: **Solution**: Update to the latest version of the action:
```yaml ```yaml
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
``` ```
@@ -70,7 +76,8 @@ uses: actions/upload-artifact@v4
**Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.` **Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.`
**Solution**: Use port 80 for multisite environments: **Solution**: Use port 80 for multisite environments:
```yaml
```bash
npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --port 80 --login & npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --port 80 --login &
``` ```
@@ -79,6 +86,7 @@ npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --
**Error**: Invalid path syntax for artifacts **Error**: Invalid path syntax for artifacts
**Solution**: Use multi-line format for better readability: **Solution**: Use multi-line format for better readability:
```yaml ```yaml
path: | path: |
cypress/videos cypress/videos
@@ -90,6 +98,7 @@ path: |
**Problem**: Redundant workflow runs when multiple commits land quickly **Problem**: Redundant workflow runs when multiple commits land quickly
**Solution**: Add concurrency control to cancel in-progress runs: **Solution**: Add concurrency control to cancel in-progress runs:
```yaml ```yaml
concurrency: concurrency:
group: playground-tests-${{ github.ref }} group: playground-tests-${{ github.ref }}
@@ -125,16 +134,19 @@ npm run test:playground:multisite
### Capturing and Analyzing Test Output ### Capturing and Analyzing Test Output
1. **Run Tests with Output Capture**: 1. **Run Tests with Output Capture**:
```bash ```bash
npm run test:single > test-output.log 2>&1 npm run test:single > test-output.log 2>&1
``` ```
2. **Analyze Output for Errors**: 2. **Analyze Output for Errors**:
```bash ```bash
cat test-output.log | grep -i 'error\|fail\|exception' grep -E -i '\b(error|fail|exception)' test-output.log
``` ```
3. **Parse Structured Test Results** (if available): 3. **Parse Structured Test Results** (if available):
```bash ```bash
cat cypress/results/results.json cat cypress/results/results.json
``` ```
@@ -146,6 +158,7 @@ npm run test:playground:multisite
**Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.` **Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.`
**Solution**: Modify the port in the blueprint or test configuration: **Solution**: Modify the port in the blueprint or test configuration:
```json ```json
{ {
"features": { "features": {
@@ -159,6 +172,7 @@ npm run test:playground:multisite
**Error**: `Timed out retrying after 4000ms: expected '<body...>' to have class 'wp-admin'` **Error**: `Timed out retrying after 4000ms: expected '<body...>' to have class 'wp-admin'`
**Solution**: Update selectors to be more robust and handle login states: **Solution**: Update selectors to be more robust and handle login states:
```javascript ```javascript
cy.get('body').then(($body) => { cy.get('body').then(($body) => {
if ($body.hasClass('login')) { if ($body.hasClass('login')) {
@@ -199,16 +213,19 @@ npm run lint:css
### Parsing Code Quality Tool Output ### Parsing Code Quality Tool Output
1. **Run Code Quality Check**: 1. **Run Code Quality Check**:
```bash ```bash
composer run phpcs > phpcs-output.log 2>&1 composer run phpcs > phpcs-output.log 2>&1
``` ```
2. **Analyze Output for Errors**: 2. **Analyze Output for Errors**:
```bash ```bash
cat phpcs-output.log | grep -i 'ERROR\|WARNING' grep -E -i '\b(ERROR|WARNING)' phpcs-output.log
``` ```
3. **Automatically Fix Issues** (when possible): 3. **Automatically Fix Issues** (when possible):
```bash ```bash
composer run phpcbf composer run phpcbf
``` ```
@@ -221,13 +238,13 @@ AI assistants can check these comments to identify and address issues.
#### Accessing PR Comments via GitHub API #### Accessing PR Comments via GitHub API
``` ```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments
``` ```
#### Accessing PR Review Comments #### Accessing PR Review Comments
``` ```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/reviews github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/reviews
``` ```
@@ -236,7 +253,8 @@ github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pul
CodeRabbit provides AI-powered code review comments via the GitHub API. CodeRabbit provides AI-powered code review comments via the GitHub API.
1. **Get PR Comments**: 1. **Get PR Comments**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments
``` ```
@@ -254,12 +272,14 @@ CodeRabbit provides AI-powered code review comments via the GitHub API.
These tools provide automated code quality checks and post results as PR comments. These tools provide automated code quality checks and post results as PR comments.
1. **Check PR Status Checks**: 1. **Check PR Status Checks**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs
``` ```
2. **Get Detailed Reports** (if available via API): 2. **Get Detailed Reports** (if available via API):
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs/{id} github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs/{id}
``` ```
@@ -275,7 +295,8 @@ These tools provide automated code quality checks and post results as PR comment
SonarCloud provides detailed code quality and security analysis. SonarCloud provides detailed code quality and security analysis.
1. **Check SonarCloud Status**: 1. **Check SonarCloud Status**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs?check_name=SonarCloud github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs?check_name=SonarCloud
``` ```
@@ -293,6 +314,7 @@ SonarCloud provides detailed code quality and security analysis.
**Error**: `ERROR: Expected snake_case for function name, but found camelCase` **Error**: `ERROR: Expected snake_case for function name, but found camelCase`
**Solution**: Rename functions to follow snake_case convention: **Solution**: Rename functions to follow snake_case convention:
```php ```php
// Before // Before
function getPluginVersion() { ... } function getPluginVersion() { ... }
@@ -306,6 +328,7 @@ function get_plugin_version() { ... }
**Error**: `ERROR: Missing doc comment for function` **Error**: `ERROR: Missing doc comment for function`
**Solution**: Add proper docblocks: **Solution**: Add proper docblocks:
```php ```php
/** /**
* Get the plugin version. * Get the plugin version.
@@ -332,7 +355,8 @@ function get_plugin_version() { ... }
#### Extracting Actionable Items from PR Comments #### Extracting Actionable Items from PR Comments
1. **Collect All Feedback**: 1. **Collect All Feedback**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/comments
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/reviews github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/reviews
``` ```
@@ -364,7 +388,7 @@ function get_plugin_version() { ... }
### Complete Feedback Loop System ### Complete Feedback Loop System
``` ```text
Code Changes ──► Local Testing ──► GitHub Actions Code Changes ──► Local Testing ──► GitHub Actions
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
@@ -395,7 +419,7 @@ These can be directly accessed and processed.
#### Example CodeRabbit Feedback #### Example CodeRabbit Feedback
``` ```text
coderabbitai bot left a comment coderabbitai bot left a comment
Actionable comments posted: 1 Actionable comments posted: 1
@@ -424,14 +448,14 @@ These tools provide structured feedback that can be systematically addressed.
#### Example SonarCloud Feedback #### Example SonarCloud Feedback
``` ```text
SonarCloud Quality Gate failed SonarCloud Quality Gate failed
- 3 Bugs - 3 Bugs
- 5 Code Smells - 5 Code Smells
- 1 Security Hotspot - 1 Security Hotspot
``` ```
#### Processing Steps #### SonarCloud Processing Steps
1. **Access Detailed Reports**: 1. **Access Detailed Reports**:
* Use the SonarCloud API or web interface * Use the SonarCloud API or web interface
@@ -479,6 +503,7 @@ AI assistants can contribute fixes upstream.
### Workflow for External Contributions ### Workflow for External Contributions
1. **Clone the Repository Locally**: 1. **Clone the Repository Locally**:
```bash ```bash
cd ~/Git cd ~/Git
git clone https://github.com/owner/repo.git git clone https://github.com/owner/repo.git
@@ -487,6 +512,7 @@ AI assistants can contribute fixes upstream.
``` ```
2. **Make Changes and Commit**: 2. **Make Changes and Commit**:
```bash ```bash
# Make your changes # Make your changes
git add -A git add -A
@@ -498,6 +524,7 @@ AI assistants can contribute fixes upstream.
``` ```
3. **Fork and Push**: 3. **Fork and Push**:
```bash ```bash
# Create a fork (if not already forked) # Create a fork (if not already forked)
gh repo fork owner/repo --clone=false --remote=true gh repo fork owner/repo --clone=false --remote=true
@@ -510,6 +537,7 @@ AI assistants can contribute fixes upstream.
``` ```
4. **Create Pull Request**: 4. **Create Pull Request**:
```bash ```bash
gh pr create \ gh pr create \
--repo owner/repo \ --repo owner/repo \

View File

@@ -2,8 +2,7 @@ module.exports = {
env: { env: {
browser: true, browser: true,
es2021: true, es2021: true,
node: true, node: true
'cypress/globals': true
}, },
extends: [ extends: [
'eslint:recommended' 'eslint:recommended'
@@ -16,9 +15,23 @@ module.exports = {
sourceType: 'module' sourceType: 'module'
}, },
rules: { rules: {
'comma-dangle': ['error', 'always-multiline'],
'no-console': 'warn', 'no-console': 'warn',
'no-unused-vars': 'warn' 'no-unused-vars': 'warn'
}, },
overrides: [
{
// cypress.config.js uses CommonJS (require/module.exports).
// Override sourceType to 'script' so ESLint does not flag require as undefined.
files: ['cypress.config.js', 'cypress.config.cjs'],
parserOptions: {
sourceType: 'script'
},
env: {
node: true
}
}
],
globals: { globals: {
cy: 'readonly', cy: 'readonly',
Cypress: 'readonly', Cypress: 'readonly',

View File

@@ -20,7 +20,7 @@ jobs:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -47,7 +47,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -68,7 +68,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -119,7 +119,7 @@ jobs:
# #
# - name: SonarCloud Scan # - name: SonarCloud Scan
# if: steps.check_sonar_token.outputs.skip != 'true' # if: steps.check_sonar_token.outputs.skip != 'true'
# uses: SonarSource/sonarqube-scan-action@master # uses: SonarSource/sonarqube-scan-action@9598b8a83feef37de07f549027fab50ecffe6a6e # master
# env: # env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -152,7 +152,7 @@ jobs:
fi fi
- name: Run Codacy Analysis CLI - name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4 uses: codacy/codacy-analysis-cli-action@562ee3e92b8e92df8b67e0a5ff8aa8e261919c08 # v4
with: with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
verbose: true verbose: true
@@ -167,7 +167,7 @@ jobs:
- name: Upload SARIF results file - name: Upload SARIF results file
if: steps.check_codacy_token.outputs.skip_upload != 'true' if: steps.check_codacy_token.outputs.skip_upload != 'true'
uses: github/codeql-action/upload-sarif@v3 uses: github/codeql-action/upload-sarif@603b797f8b14b413fe025cd935a91c16c4782713 # v3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
continue-on-error: true continue-on-error: true

View File

@@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pdo, mysql, pdo_mysql, bcmath, soap, intl, gd, exif, iconv extensions: dom, curl, libxml, mbstring, zip, pdo, mysql, pdo_mysql, bcmath, soap, intl, gd, exif, iconv

View File

@@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -98,7 +98,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: cypress-playground-results name: cypress-playground-results
path: | path: |

View File

@@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
@@ -67,7 +67,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -142,7 +142,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: cypress-single-site-results name: cypress-single-site-results
path: | path: |
@@ -163,7 +163,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -239,7 +239,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: cypress-multisite-results name: cypress-multisite-results
path: | path: |
@@ -261,7 +261,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: WordPress Performance Tests - name: WordPress Performance Tests
uses: swissspidy/wp-performance-action@v2.0.3 uses: swissspidy/wp-performance-action@b7e3ffcf0fc4a48b62492e021e0ebeb51430ff11 # v2.0.3
with: with:
plugins: | plugins: |
./ ./

View File

@@ -14,10 +14,10 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: '7.4' php-version: '7.4'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -62,7 +62,7 @@ jobs:
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with: with:
files: wp-plugin-starter-template-for-ai-coding-${{ env.VERSION }}.zip files: wp-plugin-starter-template-for-ai-coding-${{ env.VERSION }}.zip
name: v${{ env.VERSION }} - WordPress Plugin Starter Template name: v${{ env.VERSION }} - WordPress Plugin Starter Template

View File

@@ -49,7 +49,7 @@ jobs:
- name: SonarCloud Scan - name: SonarCloud Scan
if: steps.check_token.outputs.skip != 'true' if: steps.check_token.outputs.skip != 'true'
uses: SonarSource/sonarqube-scan-action@master uses: SonarSource/sonarqube-scan-action@9598b8a83feef37de07f549027fab50ecffe6a6e # master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARCLOUD_GITHUB }} SONAR_TOKEN: ${{ secrets.SONARCLOUD_GITHUB }}

View File

@@ -15,7 +15,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v3 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Configure Git - name: Configure Git
run: | run: |
@@ -50,4 +50,8 @@ jobs:
git commit -m "Sync wiki from source repository" git commit -m "Sync wiki from source repository"
# Push changes # Push changes
git push https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git git push https://${WIKI_ACTOR}:${WIKI_TOKEN}@github.com/${WIKI_REPO}.wiki.git
env:
WIKI_ACTOR: ${{ github.actor }}
WIKI_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WIKI_REPO: ${{ github.repository }}

View File

@@ -17,12 +17,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with: with:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -43,12 +43,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with: with:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with: with:
php-version: '7.4' php-version: '7.4'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip

View File

@@ -37,7 +37,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
@@ -73,7 +73,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -124,7 +124,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: cypress-results name: cypress-results
path: | path: |

3
.gitignore vendored
View File

@@ -63,3 +63,6 @@ temp/
coverage/ coverage/
.phpunit.result.cache .phpunit.result.cache
.agents/loop-state/ .agents/loop-state/
# Local tool configs
.superset/

View File

@@ -1,4 +1,7 @@
{ {
"MD004": {
"style": "asterisk"
},
"MD012": false, "MD012": false,
"MD022": false, "MD022": false,
"MD031": false, "MD031": false,

View File

@@ -6,11 +6,11 @@ Thank you for considering contributing to this project! This document provides g
By participating in this project, you agree to abide by our code of conduct: By participating in this project, you agree to abide by our code of conduct:
- Be respectful and inclusive * Be respectful and inclusive
- Be patient and welcoming * Be patient and welcoming
- Be considerate * Be considerate
- Be collaborative * Be collaborative
- Be open-minded * Be open-minded
## How to Contribute ## How to Contribute
@@ -26,12 +26,12 @@ If you find a bug, please report it by creating an issue on GitHub:
Please include: Please include:
- A clear, descriptive title * A clear, descriptive title
- Steps to reproduce the bug * Steps to reproduce the bug
- Expected behavior * Expected behavior
- Actual behavior * Actual behavior
- Screenshots (if applicable) * Screenshots (if applicable)
- Your environment (WordPress version, PHP version, browser, etc.) * Your environment (WordPress version, PHP version, browser, etc.)
### Suggesting Enhancements ### Suggesting Enhancements
@@ -45,10 +45,10 @@ If you have an idea for an enhancement:
Please include: Please include:
- A clear, descriptive title * A clear, descriptive title
- A detailed description of the enhancement * A detailed description of the enhancement
- Why this enhancement would be useful * Why this enhancement would be useful
- Any relevant examples or mockups * Any relevant examples or mockups
### Pull Requests ### Pull Requests

View File

@@ -24,9 +24,10 @@ The easiest way to test our plugin with WordPress Playground is to use the onlin
[playground-single]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2 [playground-single]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2
[playground-multisite]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/multisite-blueprint.json&_t=2 [playground-multisite]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/multisite-blueprint.json&_t=2
These links automatically set up WordPress with multisite enabled and WP_DEBUG enabled. Both links automatically set up WordPress with WP_DEBUG enabled and the Plugin Toggle and
Kadence Blocks plugins pre-installed and activated.
Both the Plugin Toggle and Kadence Blocks plugins are pre-activated. The multisite link additionally enables WordPress multisite and network-activates both plugins.
## WP-CLI Commands for WordPress Playground ## WP-CLI Commands for WordPress Playground
@@ -101,14 +102,16 @@ In a WordPress multisite environment, there are two ways to activate plugins:
1. **Network Activation**: Activates a plugin for all sites in the network 1. **Network Activation**: Activates a plugin for all sites in the network
* In the WordPress admin, go to Network Admin > Plugins * In the WordPress admin, go to Network Admin > Plugins
* Click "Network Activate" under the plugin * Click "Network Activate" under the plugin
* Or use WP-CLI: `wp plugin install plugin-name --activate-network` * Or use WP-CLI:
* To activate an already installed plugin: `wp plugin activate plugin-name --network`
* To install and activate in one step: `wp plugin install plugin-name --activate-network`
2. **Per-Site Activation**: Activates a plugin for a specific site 2. **Per-Site Activation**: Activates a plugin for a specific site
* In the WordPress admin, go to the specific site's admin area * In the WordPress admin, go to the specific site's admin area
* Go to Plugins and activate the plugin for that site only * Go to Plugins and activate the plugin for that site only
* Or use WP-CLI: `wp plugin activate plugin-name --url=site-url` * Or use WP-CLI: `wp plugin activate plugin-name --url=site-url`
Our multisite blueprint uses network activation for the Plugin Toggle plugin as an example. Our multisite blueprint uses network activation for both the Plugin Toggle and Kadence Blocks plugins.
## Running Tests with WordPress Playground ## Running Tests with WordPress Playground

View File

@@ -6,140 +6,140 @@
/* General Admin Styles */ /* General Admin Styles */
.wpst-admin-page { .wpst-admin-page {
max-width: 1200px; max-width: 1200px;
margin: 20px auto; margin: 20px auto;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%); box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
} }
.wpst-admin-header { .wpst-admin-header {
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 20px; padding-bottom: 20px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.wpst-admin-header h1 { .wpst-admin-header h1 {
margin-top: 0; margin-top: 0;
color: #23282d; color: #23282d;
} }
/* Admin Form Styles */ /* Admin Form Styles */
.wpst-form-table { .wpst-form-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
.wpst-form-table th { .wpst-form-table th {
text-align: left; text-align: left;
padding: 15px 10px 15px 0; padding: 15px 10px 15px 0;
width: 200px; width: 200px;
vertical-align: top; vertical-align: top;
} }
.wpst-form-table td { .wpst-form-table td {
padding: 15px 0; padding: 15px 0;
vertical-align: middle; vertical-align: middle;
} }
.wpst-form-table input[type="text"], .wpst-form-table input[type="text"],
.wpst-form-table input[type="number"], .wpst-form-table input[type="number"],
.wpst-form-table select, .wpst-form-table select,
.wpst-form-table textarea { .wpst-form-table textarea {
width: 400px; width: 400px;
max-width: 100%; max-width: 100%;
} }
.wpst-form-table textarea { .wpst-form-table textarea {
min-height: 100px; min-height: 100px;
} }
.wpst-form-description { .wpst-form-description {
color: #666; color: #666;
font-style: italic; font-style: italic;
margin-top: 5px; margin-top: 5px;
} }
/* Admin Notices */ /* Admin Notices */
.wpst-notice { .wpst-notice {
padding: 10px 15px; padding: 10px 15px;
margin: 15px 0; margin: 15px 0;
border-radius: 3px; border-radius: 3px;
border-left: 4px solid #00a0d2; border-left: 4px solid #00a0d2;
background: #f7fcff; background: #f7fcff;
} }
.wpst-notice.success { .wpst-notice.success {
border-left-color: #46b450; border-left-color: #46b450;
background: #ecf7ed; background: #ecf7ed;
} }
.wpst-notice.error { .wpst-notice.error {
border-left-color: #dc3232; border-left-color: #dc3232;
background: #fbeaea; background: #fbeaea;
} }
.wpst-notice.warning { .wpst-notice.warning {
border-left-color: #ffb900; border-left-color: #ffb900;
background: #fff8e5; background: #fff8e5;
} }
/* Admin Cards */ /* Admin Cards */
.wpst-card-container { .wpst-card-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin: 0 -10px; margin: 0 -10px;
} }
.wpst-card { .wpst-card {
flex: 1 0 300px; flex: 1 0 300px;
margin: 10px; margin: 10px;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%); box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
} }
.wpst-card-header { .wpst-card-header {
margin-bottom: 15px; margin-bottom: 15px;
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.wpst-card-title { .wpst-card-title {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
} }
.wpst-card-content { .wpst-card-content {
margin-bottom: 15px; margin-bottom: 15px;
} }
.wpst-card-footer { .wpst-card-footer {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
text-align: right; text-align: right;
} }
/* Responsive Styles */ /* 782px is the WordPress mobile admin breakpoint. */
@media screen and (width <= 782px) { @media screen and (max-width: 782px) {
.wpst-form-table th { .wpst-form-table th {
width: 100%; width: 100%;
display: block; display: block;
padding-bottom: 0; padding-bottom: 0;
} }
.wpst-form-table td { .wpst-form-table td {
width: 100%; width: 100%;
display: block; display: block;
} }
.wpst-form-table input[type="text"], .wpst-form-table input[type="text"],
.wpst-form-table input[type="number"], .wpst-form-table input[type="number"],
.wpst-form-table select, .wpst-form-table select,
.wpst-form-table textarea { .wpst-form-table textarea {
width: 100%; width: 100%;
} }
} }

View File

@@ -6,138 +6,138 @@
/* Modal Styles */ /* Modal Styles */
.wpst-modal { .wpst-modal {
display: none; display: none;
position: fixed; position: fixed;
z-index: 100000; z-index: 100000;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: rgb(0 0 0 / 40%); background-color: rgb(0 0 0 / 40%);
} }
.wpst-modal-content { .wpst-modal-content {
position: relative; position: relative;
background-color: #fefefe; background-color: #fefefe;
margin: 10% auto; margin: 10% auto;
padding: 20px; padding: 20px;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 4px 8px rgb(0 0 0 / 10%); box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
width: 500px; width: 500px;
max-width: 90%; max-width: 90%;
} }
.wpst-modal-header { .wpst-modal-header {
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
margin-bottom: 15px; margin-bottom: 15px;
} }
.wpst-modal-title { .wpst-modal-title {
margin: 0; margin: 0;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
} }
.wpst-modal-close { .wpst-modal-close {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 15px; right: 15px;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
color: #666; color: #666;
cursor: pointer; cursor: pointer;
} }
.wpst-modal-close:hover, .wpst-modal-close:hover,
.wpst-modal-close:focus { .wpst-modal-close:focus {
color: #000; color: #000;
text-decoration: none; text-decoration: none;
} }
.wpst-modal-body { .wpst-modal-body {
margin-bottom: 20px; margin-bottom: 20px;
} }
.wpst-modal-footer { .wpst-modal-footer {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
text-align: right; text-align: right;
} }
/* Source Selection Styles */ /* Source Selection Styles */
.wpst-source-options { .wpst-source-options {
margin: 15px 0; margin: 15px 0;
} }
.wpst-source-option { .wpst-source-option {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
} }
.wpst-source-option:hover { .wpst-source-option:hover {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
.wpst-source-option.selected { .wpst-source-option.selected {
border-color: #0073aa; border-color: #0073aa;
background-color: #f0f6fc; background-color: #f0f6fc;
} }
.wpst-source-option input[type="radio"] { .wpst-source-option input[type="radio"] {
margin-right: 10px; margin-right: 10px;
} }
.wpst-source-option-label { .wpst-source-option-label {
font-weight: 600; font-weight: 600;
} }
.wpst-source-option-description { .wpst-source-option-description {
margin-top: 5px; margin-top: 5px;
color: #666; color: #666;
font-size: 13px; font-size: 13px;
} }
/* Loading Indicator */ /* Loading Indicator */
.wpst-loading { .wpst-loading {
display: inline-block; display: inline-block;
width: 20px; width: 20px;
height: 20px; height: 20px;
border: 2px solid rgb(0 0 0 / 10%); border: 2px solid rgb(0 0 0 / 10%);
border-radius: 50%; border-radius: 50%;
border-top-color: #0073aa; border-top-color: #0073aa;
animation: wpst-spin 1s ease-in-out infinite; animation: wpst-spin 1s ease-in-out infinite;
margin-right: 10px; margin-right: 10px;
vertical-align: middle; vertical-align: middle;
} }
@keyframes wpst-spin { @keyframes wpst-spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
/* Message Styles */ /* Message Styles */
.wpst-modal-message { .wpst-modal-message {
padding: 10px; padding: 10px;
margin: 10px 0; margin: 10px 0;
border-radius: 3px; border-radius: 3px;
display: none; display: none;
} }
.wpst-modal-message.success { .wpst-modal-message.success {
background-color: #ecf7ed; background-color: #ecf7ed;
border: 1px solid #46b450; border: 1px solid #46b450;
color: #2a6f31; color: #2a6f31;
} }
.wpst-modal-message.error { .wpst-modal-message.error {
background-color: #fbeaea; background-color: #fbeaea;
border: 1px solid #dc3232; border: 1px solid #dc3232;
color: #8a1f1f; color: #8a1f1f;
} }

View File

@@ -78,7 +78,7 @@
data: { data: {
action: 'wpst_save_settings', action: 'wpst_save_settings',
nonce: wpstData.nonce, nonce: wpstData.nonce,
formData: formData formData: formData,
}, },
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
@@ -93,8 +93,8 @@
complete: function () { complete: function () {
// Re-enable submit button and remove loading state. // Re-enable submit button and remove loading state.
$submitButton.prop( 'disabled', false ).removeClass( 'loading' ); $submitButton.prop( 'disabled', false ).removeClass( 'loading' );
} },
} },
); );
}, },
@@ -105,10 +105,16 @@
* @param {string} message Notice message * @param {string} message Notice message
*/ */
showNotice: function (type, message) { showNotice: function (type, message) {
const $notice = $( '<div class="wpst-notice ' + type + '"><p>' + message + '</p></div>' ); const allowedTypes = [ 'success', 'error', 'warning' ];
const safeType = allowedTypes.includes( type ) ? type : 'error';
const $p = $( '<p>' );
const $notice = $( '<div>' ).addClass( 'wpst-notice ' + safeType ).append( $p );
// Set message as plain text to prevent XSS.
$p.text( message );
// Add notice to the page. // Add notice to the page.
$( '.wpst-notices' ).html( $notice ); $( '.wpst-notices' ).empty().append( $notice );
// Automatically remove notice after 5 seconds. // Automatically remove notice after 5 seconds.
setTimeout( setTimeout(
@@ -117,19 +123,19 @@
300, 300,
function () { function () {
$( this ).remove(); $( this ).remove();
} },
); );
}, },
5000 5000,
); );
} },
}; };
// Initialize when document is ready. // Initialize when document is ready.
$( document ).ready( $( document ).ready(
function () { function () {
WPSTAdmin.init(); WPSTAdmin.init();
} },
); );
})( jQuery ); })( jQuery );

View File

@@ -48,7 +48,7 @@
if ($( e.target ).hasClass( 'wpst-modal' )) { if ($( e.target ).hasClass( 'wpst-modal' )) {
WPSTUpdateSourceSelector.closeModal(); WPSTUpdateSourceSelector.closeModal();
} }
} },
); );
// Select source option. // Select source option.
@@ -116,7 +116,7 @@
data: { data: {
action: 'wpst_set_update_source', // AJAX action hook. action: 'wpst_set_update_source', // AJAX action hook.
nonce: wpstModalData.nonce, // Security nonce. nonce: wpstModalData.nonce, // Security nonce.
source: this.selectedSource source: this.selectedSource,
}, },
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
@@ -127,7 +127,7 @@
function () { function () {
WPSTUpdateSourceSelector.closeModal(); WPSTUpdateSourceSelector.closeModal();
}, },
1500 1500,
); );
} else { } else {
WPSTUpdateSourceSelector.showMessage( 'error', response.data.message ); WPSTUpdateSourceSelector.showMessage( 'error', response.data.message );
@@ -139,40 +139,44 @@
complete: function () { complete: function () {
// Reset button state. // Reset button state.
$saveButton.prop( 'disabled', false ).text( wpstModalData.i18n.confirm ); $saveButton.prop( 'disabled', false ).text( wpstModalData.i18n.confirm );
} },
} },
); );
}, },
/** /**
* Show a message in the modal * Show a message in the modal
* *
* @param {string} type Message type (success, error) * @param {string} type Message type (success, error)
* @param {string} message Message text * @param {string} message Message text
*/ */
showMessage: function (type, message) { showMessage: function (type, message) {
const $message = this.$modal.find( '.wpst-modal-message' ); const $message = this.$modal.find( '.wpst-modal-message' );
// Set message content and type. // Validate type against allow-list to prevent class injection vulnerabilities.
$message.html( message ).removeClass( 'success error' ).addClass( type ).show(); const allowedTypes = [ 'success', 'error' ];
const safeType = allowedTypes.includes( type ) ? type : 'error';
// Set message as plain text to prevent XSS, then apply validated type class.
$message.text( message ).removeClass( 'success error' ).addClass( safeType ).show();
// Hide message after a delay for success messages. // Hide message after a delay for success messages.
if (type === 'success') { if (safeType === 'success') {
setTimeout( setTimeout(
function () { function () {
$message.fadeOut( 300 ); $message.fadeOut( 300 );
}, },
3000 3000,
); );
} }
} },
}; };
// Initialize when document is ready. // Initialize when document is ready.
$( document ).ready( $( document ).ready(
function () { function () {
WPSTUpdateSourceSelector.init(); WPSTUpdateSourceSelector.init();
} },
); );
})( jQuery ); })( jQuery );

View File

@@ -12,11 +12,22 @@ if ( ! defined( 'ABSPATH' ) ) {
?> ?>
<!-- Update Source Modal --> <!-- Update Source Modal -->
<div id="wpst-update-source-modal" class="wpst-modal"> <div
id="wpst-update-source-modal"
class="wpst-modal"
role="dialog"
aria-modal="true"
aria-labelledby="wpst-update-source-modal-title"
tabindex="-1"
>
<div class="wpst-modal-content"> <div class="wpst-modal-content">
<div class="wpst-modal-header"> <div class="wpst-modal-header">
<h2 class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2> <h2 id="wpst-update-source-modal-title" class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2>
<span class="wpst-modal-close">&times;</span> <button
type="button"
class="wpst-modal-close"
aria-label="<?php esc_attr_e( 'Close dialog', 'wp-plugin-starter-template' ); ?>"
>&times;</button>
</div> </div>
<div class="wpst-modal-body"> <div class="wpst-modal-body">

View File

@@ -17,15 +17,19 @@ WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/}
download() { download() {
if command -v curl > /dev/null; then if command -v curl >/dev/null 2>&1; then
curl -s "$1" > "$2"; curl -fsSL "$1" -o "$2"
elif command -v wget > /dev/null; then elif command -v wget >/dev/null 2>&1; then
wget -nv -O "$2" "$1" wget -qO "$2" "$1"
fi else
echo "Error: Neither curl nor wget is installed" >&2
exit 1
fi
} }
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
WP_BRANCH=${WP_VERSION%\-*} WP_BRANCH=${WP_VERSION%\-*}
WP_TESTS_TAG="branches/$WP_BRANCH"
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_TESTS_TAG="branches/$WP_VERSION" WP_TESTS_TAG="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
@@ -40,27 +44,38 @@ elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
else else
# http serves a single offer, whereas https serves multiple. we only want one # http serves a single offer, whereas https serves multiple. we only want one
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//' | head -1)
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
if [[ -z "$LATEST_VERSION" ]]; then if [[ -z "$LATEST_VERSION" ]]; then
echo "Latest WordPress version could not be found" echo "Latest WordPress version could not be found"
exit 1 exit 1
fi fi
WP_TESTS_TAG="tags/$LATEST_VERSION" WP_TESTS_TAG="tags/$LATEST_VERSION"
fi fi
# Derive a git ref from WP_TESTS_TAG by stripping the SVN-style prefix.
# WP_TESTS_TAG uses "tags/X.Y.Z", "branches/X.Y", or "trunk".
# git clone --branch requires the bare ref name ("X.Y.Z", "X.Y", or "trunk").
if [[ "$WP_TESTS_TAG" == tags/* ]]; then
GIT_REF="${WP_TESTS_TAG#tags/}"
elif [[ "$WP_TESTS_TAG" == branches/* ]]; then
GIT_REF="${WP_TESTS_TAG#branches/}"
else
GIT_REF="$WP_TESTS_TAG"
fi
set -ex set -ex
install_wp() { install_wp() {
if [ -d "$WP_CORE_DIR" ]; then if [ -d "$WP_CORE_DIR" ]; then
return; return
fi fi
mkdir -p "$WP_CORE_DIR" mkdir -p "$WP_CORE_DIR"
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p "$WP_CORE_DIR" mkdir -p "$WP_CORE_DIR"
download https://wordpress.org/nightly-builds/wordpress-latest.zip "$WP_CORE_DIR/wordpress-nightly.zip" download https://wordpress.org/nightly-builds/wordpress-latest.zip "$WP_CORE_DIR/wordpress-nightly.zip"
unzip -q "$WP_CORE_DIR/wordpress-nightly.zip" -d "$WP_CORE_DIR" unzip -q "$WP_CORE_DIR/wordpress-nightly.zip" -d "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress-nightly.zip" rm "$WP_CORE_DIR/wordpress-nightly.zip"
else else
@@ -71,6 +86,7 @@ install_wp() {
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
LATEST_VERSION=${WP_VERSION%??} LATEST_VERSION=${WP_VERSION%??}
else else
# shellcheck disable=SC2001
VERSION_ESCAPED=$(echo "$WP_VERSION" | sed 's/\./\\\\./g') VERSION_ESCAPED=$(echo "$WP_VERSION" | sed 's/\./\\\\./g')
LATEST_VERSION=$(grep -o '"version":"'"$VERSION_ESCAPED"'[^"]*' "$WP_CORE_DIR/wp-latest.json" | sed 's/"version":"//' | head -1) LATEST_VERSION=$(grep -o '"version":"'"$VERSION_ESCAPED"'[^"]*' "$WP_CORE_DIR/wp-latest.json" | sed 's/"version":"//' | head -1)
fi fi
@@ -82,7 +98,7 @@ install_wp() {
else else
local ARCHIVE_NAME="wordpress-$WP_VERSION" local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi fi
download https://wordpress.org/"${ARCHIVE_NAME}".tar.gz "$WP_CORE_DIR/wordpress.tar.gz" download https://wordpress.org/"${ARCHIVE_NAME}".tar.gz "$WP_CORE_DIR/wordpress.tar.gz"
tar --strip-components=1 -zxmf "$WP_CORE_DIR/wordpress.tar.gz" -C "$WP_CORE_DIR" tar --strip-components=1 -zxmf "$WP_CORE_DIR/wordpress.tar.gz" -C "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress.tar.gz" rm "$WP_CORE_DIR/wordpress.tar.gz"
fi fi
@@ -101,12 +117,22 @@ install_test_suite() {
# set up testing suite if it doesn't yet exist # set up testing suite if it doesn't yet exist
if [ ! -d "$WP_TESTS_DIR" ]; then if [ ! -d "$WP_TESTS_DIR" ]; then
mkdir -p "$WP_TESTS_DIR" mkdir -p "$WP_TESTS_DIR"
git clone --quiet --depth=1 https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop rm -rf /tmp/wordpress-develop
if ! git clone --quiet --depth=1 --branch "$GIT_REF" https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop; then
echo "Error: Failed to clone wordpress-develop at branch/tag $GIT_REF" >&2
exit 1
fi
if [ -d /tmp/wordpress-develop/tests/phpunit/includes ]; then if [ -d /tmp/wordpress-develop/tests/phpunit/includes ]; then
cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/" if ! cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/"; then
echo "Error: Failed to copy phpunit includes to $WP_TESTS_DIR" >&2
exit 1
fi
fi fi
if [ -d /tmp/wordpress-develop/tests/phpunit/data ]; then if [ -d /tmp/wordpress-develop/tests/phpunit/data ]; then
cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/" if ! cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/"; then
echo "Error: Failed to copy phpunit data to $WP_TESTS_DIR" >&2
exit 1
fi
fi fi
fi fi
@@ -116,15 +142,15 @@ install_test_suite() {
else else
download https://raw.githubusercontent.com/WordPress/wordpress-develop/master/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php download https://raw.githubusercontent.com/WordPress/wordpress-develop/master/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
fi fi
WP_CORE_DIR=$(echo "$WP_CORE_DIR" | sed "s:/\+$::") WP_CORE_DIR="${WP_CORE_DIR%/}"
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
if [ "$MULTISITE" = "true" ]; then if [ "$MULTISITE" = "true" ]; then
sed $ioption "s:// define( 'WP_TESTS_MULTISITE', true );:define( 'WP_TESTS_MULTISITE', true );:" "$WP_TESTS_DIR"/wp-tests-config.php sed "$ioption" "s:// define( 'WP_TESTS_MULTISITE', true );:define( 'WP_TESTS_MULTISITE', true );:" "$WP_TESTS_DIR"/wp-tests-config.php
fi fi
fi fi
@@ -132,27 +158,27 @@ install_test_suite() {
install_db() { install_db() {
if [ ${SKIP_DB_CREATE} = "true" ]; then if [ "${SKIP_DB_CREATE}" = "true" ]; then
return 0 return 0
fi fi
local PARTS local PARTS
IFS=':' read -ra PARTS <<< "$DB_HOST" IFS=':' read -ra PARTS <<<"$DB_HOST"
local DB_HOSTNAME=${PARTS[0]}; local DB_HOSTNAME=${PARTS[0]}
local DB_SOCK_OR_PORT=${PARTS[1]}; local DB_SOCK_OR_PORT=${PARTS[1]}
local EXTRA="" local EXTRA=""
if [ -n "$DB_HOSTNAME" ] ; then if [ -n "$DB_HOSTNAME" ]; then
if [[ $DB_SOCK_OR_PORT =~ ^[0-9]+$ ]]; then if [[ $DB_SOCK_OR_PORT =~ ^[0-9]+$ ]]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
elif [ -n "$DB_SOCK_OR_PORT" ] ; then elif [ -n "$DB_SOCK_OR_PORT" ]; then
EXTRA=" --socket=$DB_SOCK_OR_PORT" EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif [ -n "$DB_HOSTNAME" ] ; then elif [ -n "$DB_HOSTNAME" ]; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
fi fi
fi fi
mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS"$EXTRA || true mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS""$EXTRA" || true
} }
install_wp install_wp

View File

@@ -28,7 +28,6 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
PLUGIN_SLUG="wp-plugin-starter-template" PLUGIN_SLUG="wp-plugin-starter-template"
PLUGIN_TEXT_DOMAIN="wp-plugin-starter-template"
# LocalWP paths (macOS) # LocalWP paths (macOS)
LOCAL_SITES_DIR="$HOME/Local Sites" LOCAL_SITES_DIR="$HOME/Local Sites"
@@ -51,363 +50,375 @@ NC='\033[0m' # No Color
# Helper functions # Helper functions
log_info() { log_info() {
echo -e "${BLUE}[INFO]${NC} $1" echo -e "${BLUE}[INFO]${NC} $1"
} }
log_success() { log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" echo -e "${GREEN}[SUCCESS]${NC} $1"
} }
log_warning() { log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" echo -e "${YELLOW}[WARNING]${NC} $1"
} }
log_error() { log_error() {
echo -e "${RED}[ERROR]${NC} $1" echo -e "${RED}[ERROR]${NC} $1"
} }
log_step() { log_step() {
echo -e "${CYAN}[STEP]${NC} $1" echo -e "${CYAN}[STEP]${NC} $1"
} }
# Check if LocalWP is installed # Check if LocalWP is installed
check_localwp() { check_localwp() {
if [ ! -d "$LOCAL_APP" ]; then if [ ! -d "$LOCAL_APP" ]; then
log_error "LocalWP is not installed at $LOCAL_APP" log_error "LocalWP is not installed at $LOCAL_APP"
log_info "Download from: https://localwp.com/" log_info "Download from: https://localwp.com/"
exit 1 exit 1
fi fi
if [ ! -f "$LOCAL_WP_CLI" ]; then if [ ! -f "$LOCAL_WP_CLI" ]; then
log_error "WP-CLI not found in LocalWP installation" log_error "WP-CLI not found in LocalWP installation"
exit 1 exit 1
fi fi
local version=$("$LOCAL_WP_CLI" --version 2>/dev/null || echo "unknown") local version
log_info "LocalWP WP-CLI version: $version" version=$("$LOCAL_WP_CLI" --version 2>/dev/null || echo "unknown")
log_info "LocalWP WP-CLI version: $version"
} }
# Get site path # Get site path
get_site_path() { get_site_path() {
local site_name="$1" local site_name="$1"
echo "$LOCAL_SITES_DIR/$site_name" echo "$LOCAL_SITES_DIR/$site_name"
} }
# Get WordPress path within site # Get WordPress path within site
get_wp_path() { get_wp_path() {
local site_name="$1" local site_name="$1"
local site_path=$(get_site_path "$site_name") local site_path
site_path=$(get_site_path "$site_name")
# LocalWP uses app/public for WordPress files # LocalWP uses app/public for WordPress files
echo "$site_path/app/public" echo "$site_path/app/public"
} }
# Check if site exists # Check if site exists
site_exists() { site_exists() {
local site_name="$1" local site_name="$1"
local site_path=$(get_site_path "$site_name") local site_path
[ -d "$site_path" ] site_path=$(get_site_path "$site_name")
[ -d "$site_path" ]
} }
# Get plugin destination path # Get plugin destination path
get_plugin_path() { get_plugin_path() {
local site_name="$1" local site_name="$1"
local wp_path=$(get_wp_path "$site_name") local wp_path
echo "$wp_path/wp-content/plugins/$PLUGIN_SLUG" wp_path=$(get_wp_path "$site_name")
echo "$wp_path/wp-content/plugins/$PLUGIN_SLUG"
} }
# Sync plugin files to LocalWP site # Sync plugin files to LocalWP site
sync_plugin() { sync_plugin() {
local site_name="$1" local site_name="$1"
local plugin_dest=$(get_plugin_path "$site_name") local plugin_dest
plugin_dest=$(get_plugin_path "$site_name")
if ! site_exists "$site_name"; then if ! site_exists "$site_name"; then
log_error "Site '$site_name' does not exist" log_error "Site '$site_name' does not exist"
return 1 return 1
fi fi
log_info "Syncing plugin to $site_name..." log_info "Syncing plugin to $site_name..."
# Create plugin directory if it doesn't exist # Create plugin directory if it doesn't exist
mkdir -p "$plugin_dest" mkdir -p "$plugin_dest"
# Sync files using rsync (excludes dev files) # Sync files using rsync (excludes dev files)
rsync -av --delete \ rsync -av --delete \
--exclude 'node_modules' \ --exclude 'node_modules' \
--exclude 'vendor' \ --exclude 'vendor' \
--exclude '.git' \ --exclude '.git' \
--exclude 'dist' \ --exclude 'dist' \
--exclude 'tests' \ --exclude 'tests' \
--exclude 'cypress' \ --exclude 'cypress' \
--exclude '.github' \ --exclude '.github' \
--exclude '.agents' \ --exclude '.agents' \
--exclude '.wiki' \ --exclude '.wiki' \
--exclude 'reference-plugins' \ --exclude 'reference-plugins' \
--exclude '*.zip' \ --exclude '*.zip' \
--exclude '.playground.*' \ --exclude '.playground.*' \
--exclude 'composer.lock' \ --exclude 'composer.lock' \
--exclude 'package-lock.json' \ --exclude 'package-lock.json' \
"$PROJECT_DIR/" "$plugin_dest/" "$PROJECT_DIR/" "$plugin_dest/"
log_success "Plugin synced to: $plugin_dest" log_success "Plugin synced to: $plugin_dest"
} }
# Create a new LocalWP site # Create a new LocalWP site
create_site() { create_site() {
local multisite=false local multisite=false
# Parse arguments # Parse arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--multisite) --multisite)
multisite=true multisite=true
shift shift
;; ;;
*) *)
shift shift
;; ;;
esac esac
done done
local site_name="$SINGLE_SITE_NAME" local site_name="$SINGLE_SITE_NAME"
local domain="$SINGLE_SITE_DOMAIN" local domain="$SINGLE_SITE_DOMAIN"
local mode="single site" local mode="single site"
if [ "$multisite" = true ]; then if [ "$multisite" = true ]; then
site_name="$MULTISITE_NAME" site_name="$MULTISITE_NAME"
domain="$MULTISITE_DOMAIN" domain="$MULTISITE_DOMAIN"
mode="multisite" mode="multisite"
fi fi
check_localwp check_localwp
local site_path=$(get_site_path "$site_name") local site_path
site_path=$(get_site_path "$site_name")
if site_exists "$site_name"; then if site_exists "$site_name"; then
log_warning "Site '$site_name' already exists at: $site_path" log_warning "Site '$site_name' already exists at: $site_path"
log_info "Use 'npm run localwp:reset' to reset it, or 'npm run localwp:sync' to update files" log_info "Use 'npm run localwp:reset' to reset it, or 'npm run localwp:sync' to update files"
return 0 return 0
fi fi
echo "" echo ""
echo "============================================" echo "============================================"
echo " LocalWP Site Setup ($mode)" echo " LocalWP Site Setup ($mode)"
echo "============================================" echo "============================================"
echo "" echo ""
echo "This script will guide you through creating a" echo "This script will guide you through creating a"
echo "LocalWP site for testing the plugin." echo "LocalWP site for testing the plugin."
echo "" echo ""
echo "Site Details:" echo "Site Details:"
echo " Name: $site_name" echo " Name: $site_name"
echo " Domain: $domain" echo " Domain: $domain"
echo " Path: $site_path" echo " Path: $site_path"
echo "" echo ""
log_step "Creating LocalWP Site" log_step "Creating LocalWP Site"
echo "" echo ""
log_info "LocalWP doesn't have a CLI for site creation." log_info "LocalWP doesn't have a CLI for site creation."
log_info "Please create the site manually in LocalWP:" log_info "Please create the site manually in LocalWP:"
echo "" echo ""
echo "1. Open LocalWP application" echo "1. Open LocalWP application"
echo "2. Click the '+' button to create a new site" echo "2. Click the '+' button to create a new site"
echo "3. Use these settings:" echo "3. Use these settings:"
echo " - Site name: ${CYAN}$site_name${NC}" echo " - Site name: ${CYAN}$site_name${NC}"
echo " - Local site domain: ${CYAN}$domain${NC}" echo " - Local site domain: ${CYAN}$domain${NC}"
echo " - PHP version: 8.0 or higher" echo " - PHP version: 8.0 or higher"
echo " - Web server: nginx (preferred)" echo " - Web server: nginx (preferred)"
echo " - MySQL version: 8.0+" echo " - MySQL version: 8.0+"
if [ "$multisite" = true ]; then if [ "$multisite" = true ]; then
echo "" echo ""
echo "4. After site creation, convert to multisite:" echo "4. After site creation, convert to multisite:"
echo " - Open Site Shell in LocalWP" echo " - Open Site Shell in LocalWP"
echo " - Run: wp core multisite-convert --subdomains=0" echo " - Run: wp core multisite-convert --subdomains=0"
echo " - Update wp-config.php if needed" echo " - Update wp-config.php if needed"
fi fi
echo "" echo ""
log_info "After creating the site, run: npm run localwp:sync" log_info "After creating the site, run: npm run localwp:sync"
echo "" echo ""
# Wait for user to create site # Wait for user to create site
read -p "Press Enter after you've created the site in LocalWP..." read -r -p "Press Enter after you've created the site in LocalWP..."
if site_exists "$site_name"; then if site_exists "$site_name"; then
log_success "Site detected at: $site_path" log_success "Site detected at: $site_path"
sync_plugin "$site_name" sync_plugin "$site_name"
# Install recommended plugins # Install recommended plugins
install_recommended_plugins "$site_name" install_recommended_plugins "$site_name"
show_site_info "$site_name" "$domain" "$multisite" show_site_info "$site_name" "$domain" "$multisite"
else else
log_warning "Site not found at expected location" log_warning "Site not found at expected location"
log_info "Expected path: $site_path" log_info "Expected path: $site_path"
log_info "You can run 'npm run localwp:sync' later to sync files" log_info "You can run 'npm run localwp:sync' later to sync files"
fi fi
} }
# Install recommended plugins (matching Playground blueprint) # Install recommended plugins (matching Playground blueprint)
install_recommended_plugins() { install_recommended_plugins() {
local site_name="$1" local site_name="$1"
local wp_path=$(get_wp_path "$site_name") local wp_path
wp_path=$(get_wp_path "$site_name")
log_info "Note: Install these plugins to match Playground environment:" log_info "Note: Install these plugins to match Playground environment:"
echo " - Plugin Toggle (plugin-toggle)" echo " - Plugin Toggle (plugin-toggle)"
echo " - Kadence Blocks (kadence-blocks)" echo " - Kadence Blocks (kadence-blocks)"
echo "" echo ""
log_info "You can install them via LocalWP's WP Admin or Site Shell" log_info "You can install them via LocalWP's WP Admin or Site Shell"
} }
# Show site information # Show site information
show_site_info() { show_site_info() {
local site_name="$1" local site_name="$1"
local domain="$2" local domain="$2"
local multisite="$3" local multisite="$3"
local site_path=$(get_site_path "$site_name") local site_path
local plugin_path=$(get_plugin_path "$site_name") site_path=$(get_site_path "$site_name")
local plugin_path
plugin_path=$(get_plugin_path "$site_name")
echo "" echo ""
echo "============================================" echo "============================================"
echo " LocalWP Site Ready" echo " LocalWP Site Ready"
echo "============================================" echo "============================================"
echo " Site: $site_name" echo " Site: $site_name"
echo " URL: http://$domain" echo " URL: http://$domain"
echo " Admin: http://$domain/wp-admin/" echo " Admin: http://$domain/wp-admin/"
echo " Plugin Path: $plugin_path" echo " Plugin Path: $plugin_path"
echo "============================================" echo "============================================"
if [ "$multisite" = true ]; then if [ "$multisite" = true ]; then
echo " Network Admin: http://$domain/wp-admin/network/" echo " Network Admin: http://$domain/wp-admin/network/"
echo "============================================" echo "============================================"
fi fi
echo "" echo ""
log_info "Remember to:" log_info "Remember to:"
echo " 1. Start the site in LocalWP" echo " 1. Start the site in LocalWP"
echo " 2. Activate the plugin in WordPress admin" echo " 2. Activate the plugin in WordPress admin"
echo " 3. Run 'npm run localwp:sync' after making changes" echo " 3. Run 'npm run localwp:sync' after making changes"
echo "" echo ""
} }
# Reset site to clean state # Reset site to clean state
reset_site() { reset_site() {
local site_name="${1:-$SINGLE_SITE_NAME}" local site_name="${1:-$SINGLE_SITE_NAME}"
if ! site_exists "$site_name"; then if ! site_exists "$site_name"; then
log_error "Site '$site_name' does not exist" log_error "Site '$site_name' does not exist"
exit 1 exit 1
fi fi
log_warning "This will delete the plugin files and resync them." log_warning "This will delete the plugin files and resync them."
read -p "Continue? (y/n) " -n 1 -r read -p "Continue? (y/n) " -n 1 -r
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
local plugin_path=$(get_plugin_path "$site_name") local plugin_path
plugin_path=$(get_plugin_path "$site_name")
log_info "Removing plugin files..." log_info "Removing plugin files..."
rm -rf "$plugin_path" rm -rf "$plugin_path"
log_info "Resyncing plugin..." log_info "Resyncing plugin..."
sync_plugin "$site_name" sync_plugin "$site_name"
log_success "Site reset complete" log_success "Site reset complete"
else else
log_info "Reset cancelled" log_info "Reset cancelled"
fi fi
} }
# Sync all existing sites # Sync all existing sites
sync_all() { sync_all() {
local synced=0 local synced=0
for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do
if site_exists "$site_name"; then if site_exists "$site_name"; then
sync_plugin "$site_name" sync_plugin "$site_name"
synced=$((synced + 1)) synced=$((synced + 1))
fi fi
done done
if [ $synced -eq 0 ]; then if [ $synced -eq 0 ]; then
log_warning "No LocalWP sites found for this plugin" log_warning "No LocalWP sites found for this plugin"
log_info "Run 'npm run localwp:create' to create one" log_info "Run 'npm run localwp:create' to create one"
else else
log_success "Synced $synced site(s)" log_success "Synced $synced site(s)"
fi fi
} }
# Show info about all sites # Show info about all sites
show_info() { show_info() {
echo "" echo ""
echo "LocalWP Sites for $PLUGIN_SLUG" echo "LocalWP Sites for $PLUGIN_SLUG"
echo "===============================" echo "==============================="
for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do
local site_path=$(get_site_path "$site_name") local site_path
site_path=$(get_site_path "$site_name")
if site_exists "$site_name"; then if site_exists "$site_name"; then
echo "" echo ""
echo " ${GREEN}${NC} $site_name" echo " ${GREEN}${NC} $site_name"
echo " Path: $site_path" echo " Path: $site_path"
local plugin_path=$(get_plugin_path "$site_name") local plugin_path
if [ -d "$plugin_path" ]; then plugin_path=$(get_plugin_path "$site_name")
echo " Plugin: ${GREEN}Installed${NC}" if [ -d "$plugin_path" ]; then
else echo " Plugin: ${GREEN}Installed${NC}"
echo " Plugin: ${YELLOW}Not synced${NC}" else
fi echo " Plugin: ${YELLOW}Not synced${NC}"
else fi
echo "" else
echo " ${YELLOW}${NC} $site_name (not created)" echo ""
fi echo " ${YELLOW}${NC} $site_name (not created)"
done fi
done
echo "" echo ""
echo "Commands:" echo "Commands:"
echo " npm run localwp:create Create single site" echo " npm run localwp:create Create single site"
echo " npm run localwp:create:multisite Create multisite" echo " npm run localwp:create:multisite Create multisite"
echo " npm run localwp:sync Sync plugin files" echo " npm run localwp:sync Sync plugin files"
echo " npm run localwp:reset Reset plugin files" echo " npm run localwp:reset Reset plugin files"
echo "" echo ""
} }
# Main command handler # Main command handler
case "${1:-}" in case "${1:-}" in
create) create)
shift shift
create_site "$@" create_site "$@"
;; ;;
sync) sync)
sync_all sync_all
;; ;;
reset) reset)
shift shift
reset_site "$@" reset_site "$@"
;; ;;
info) info)
show_info show_info
;; ;;
*) *)
echo "LocalWP Integration Script" echo "LocalWP Integration Script"
echo "" echo ""
echo "Usage:" echo "Usage:"
echo " $0 create [--multisite] Create a new LocalWP site" echo " $0 create [--multisite] Create a new LocalWP site"
echo " $0 sync Sync plugin files to all sites" echo " $0 sync Sync plugin files to all sites"
echo " $0 reset [site-name] Reset site plugin to clean state" echo " $0 reset [site-name] Reset site plugin to clean state"
echo " $0 info Show info about LocalWP sites" echo " $0 info Show info about LocalWP sites"
echo "" echo ""
echo "npm scripts:" echo "npm scripts:"
echo " npm run localwp:create Create single site" echo " npm run localwp:create Create single site"
echo " npm run localwp:create:multisite Create multisite" echo " npm run localwp:create:multisite Create multisite"
echo " npm run localwp:sync Sync plugin files" echo " npm run localwp:sync Sync plugin files"
echo " npm run localwp:reset Reset plugin files" echo " npm run localwp:reset Reset plugin files"
echo "" echo ""
echo "URL Patterns:" echo "URL Patterns:"
echo " Single site: http://${PLUGIN_SLUG}-single.local" echo " Single site: http://${PLUGIN_SLUG}-single.local"
echo " Multisite: http://${PLUGIN_SLUG}-multisite.local" echo " Multisite: http://${PLUGIN_SLUG}-multisite.local"
echo "" echo ""
exit 1 exit 1
;; ;;
esac esac

View File

@@ -1,126 +1,139 @@
#!/bin/bash #!/bin/bash
# Make this script executable
chmod +x "$0"
# Check if environment type is provided # Check if environment type is provided
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "Usage: $0 [single|multisite|playground-single|playground-multisite]" echo "Usage: $0 [single|multisite|playground-single|playground-multisite]"
exit 1 exit 1
fi fi
ENV_TYPE=$1 ENV_TYPE=$1
# Function to check if a command exists # Function to check if a command exists
command_exists() { command_exists() {
command -v "$1" &> /dev/null command -v "$1" &>/dev/null
return $?
} }
# PID of the background Python HTTP server (set when started).
PYTHON_PID=""
# Function to clean up resources on exit.
cleanup() {
if [ -n "$PYTHON_PID" ]; then
echo "Stopping Python HTTP server (PID: $PYTHON_PID)..."
kill "$PYTHON_PID" 2>/dev/null || true
fi
return 0
}
# Trap EXIT, INT, and TERM so the server is always stopped on script exit.
trap cleanup EXIT INT TERM
# Function to install wp-env if needed # Function to install wp-env if needed
install_wp_env() { install_wp_env() {
if ! command_exists wp-env; then if ! command_exists wp-env; then
echo "wp-env is not installed. Installing..." echo "wp-env is not installed. Installing..."
npm install -g @wordpress/env npm install -g @wordpress/env
fi fi
} }
# Function to install wp-playground if needed # Function to install wp-playground if needed
install_wp_playground() { install_wp_playground() {
# Check if we have a local installation # Check if we have a local installation
if [ ! -d "node_modules/@wp-playground" ]; then if [ ! -d "node_modules/@wp-playground" ]; then
echo "WordPress Playground is not installed locally. Installing..." echo "WordPress Playground is not installed locally. Installing..."
npm install --save-dev @wp-playground/client @wp-playground/blueprints npm install --save-dev @wp-playground/client @wp-playground/blueprints
fi fi
} }
if [ "$ENV_TYPE" == "single" ]; then if [ "$ENV_TYPE" = "single" ]; then
echo "Setting up single site environment..." echo "Setting up single site environment..."
# Install wp-env if needed # Install wp-env if needed
install_wp_env install_wp_env
# Start the environment # Start the environment
wp-env start wp-env start
# Wait for WordPress to be ready with a timeout # Wait for WordPress to be ready with a timeout
MAX_ATTEMPTS=30 MAX_ATTEMPTS=30
ATTEMPT=0 ATTEMPT=0
echo "Waiting for WordPress to be ready..." echo "Waiting for WordPress to be ready..."
until wp-env run cli wp core is-installed || [ $ATTEMPT -ge $MAX_ATTEMPTS ]; do until wp-env run cli wp core is-installed || [ $ATTEMPT -ge $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT+1)) ATTEMPT=$((ATTEMPT + 1))
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS..." echo "Attempt $ATTEMPT/$MAX_ATTEMPTS..."
sleep 2 sleep 2
done done
if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then
echo "Timed out waiting for WordPress to be ready." echo "Timed out waiting for WordPress to be ready."
exit 1 exit 1
fi fi
# Activate our plugin # Activate our plugin
if ! wp-env run cli wp plugin activate wp-plugin-starter-template-for-ai-coding; then if ! wp-env run cli wp plugin activate wp-plugin-starter-template-for-ai-coding; then
echo "Failed to activate plugin. Exiting." echo "Failed to activate plugin. Exiting."
exit 1 exit 1
fi fi
echo "WordPress Single Site environment is ready!" echo "WordPress Single Site environment is ready!"
echo "Site: http://localhost:8888" echo "Site: http://localhost:8888"
echo "Admin login: admin / password" echo "Admin login: admin / password"
elif [ "$ENV_TYPE" == "multisite" ]; then elif [ "$ENV_TYPE" = "multisite" ]; then
echo "Setting up multisite environment..." echo "Setting up multisite environment..."
# Install wp-env if needed # Install wp-env if needed
install_wp_env install_wp_env
# Start the environment with multisite configuration # Start the environment with multisite configuration
wp-env start --config=.wp-env.multisite.json wp-env start --config=.wp-env.multisite.json
# Wait for WordPress to be ready with a timeout # Wait for WordPress to be ready with a timeout
MAX_ATTEMPTS=30 MAX_ATTEMPTS=30
ATTEMPT=0 ATTEMPT=0
echo "Waiting for WordPress to be ready..." echo "Waiting for WordPress to be ready..."
until wp-env run cli wp core is-installed || [ $ATTEMPT -ge $MAX_ATTEMPTS ]; do until wp-env run cli wp core is-installed || [ $ATTEMPT -ge $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT+1)) ATTEMPT=$((ATTEMPT + 1))
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS..." echo "Attempt $ATTEMPT/$MAX_ATTEMPTS..."
sleep 2 sleep 2
done done
if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then
echo "Timed out waiting for WordPress to be ready." echo "Timed out waiting for WordPress to be ready."
exit 1 exit 1
fi fi
# Create a test site # Create a test site
if ! wp-env run cli wp site create --slug=testsite --title="Test Site" --email=admin@example.com; then if ! wp-env run cli wp site create --slug=testsite --title="Test Site" --email=admin@example.com; then
echo "Failed to create test site. Exiting." echo "Failed to create test site. Exiting."
exit 1 exit 1
fi fi
# Network activate our plugin # Network activate our plugin
if ! wp-env run cli wp plugin activate wp-plugin-starter-template-for-ai-coding --network; then if ! wp-env run cli wp plugin activate wp-plugin-starter-template-for-ai-coding --network; then
echo "Failed to activate plugin. Exiting." echo "Failed to activate plugin. Exiting."
exit 1 exit 1
fi fi
echo "WordPress Multisite environment is ready!" echo "WordPress Multisite environment is ready!"
echo "Main site: http://localhost:8888" echo "Main site: http://localhost:8888"
echo "Test site: http://localhost:8888/testsite" echo "Test site: http://localhost:8888/testsite"
echo "Admin login: admin / password" echo "Admin login: admin / password"
elif [ "$ENV_TYPE" == "playground-single" ]; then elif [ "$ENV_TYPE" = "playground-single" ]; then
echo "Setting up WordPress Playground single site environment..." echo "Setting up WordPress Playground single site environment..."
# Install wp-playground if needed # Install wp-playground if needed
install_wp_playground install_wp_playground
# Create plugin zip # Create plugin zip
echo "Creating plugin zip..." echo "Creating plugin zip..."
mkdir -p dist mkdir -p dist
zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*" zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*"
# Update blueprint to use local plugin # Update blueprint to use local plugin
cat > playground/blueprint.json << EOF cat >playground/blueprint.json <<EOF
{ {
"landingPage": "/wp-admin/", "landingPage": "/wp-admin/",
"preferredVersions": { "preferredVersions": {
@@ -148,46 +161,49 @@ elif [ "$ENV_TYPE" == "playground-single" ]; then
} }
EOF EOF
# Start WordPress Playground # Start WordPress Playground
echo "Starting WordPress Playground..." echo "Starting WordPress Playground..."
if command_exists python3; then if command_exists python3; then
python3 -m http.server 8888 --directory playground & python3 -m http.server 8888 --directory playground &
echo "Opening WordPress Playground in your browser..." PYTHON_PID=$!
if command_exists open; then echo "Started Python HTTP server with PID: $PYTHON_PID"
open http://localhost:8888/index.html echo "Opening WordPress Playground in your browser..."
elif command_exists xdg-open; then if command_exists open; then
xdg-open http://localhost:8888/index.html open http://localhost:8888/index.html
elif command_exists start; then elif command_exists xdg-open; then
start http://localhost:8888/index.html xdg-open http://localhost:8888/index.html
else elif command_exists start; then
echo "Please open http://localhost:8888/index.html in your browser" start http://localhost:8888/index.html
fi else
else echo "Please open http://localhost:8888/index.html in your browser"
echo "Python3 is not installed. Please open playground/index.html in your browser." fi
fi else
echo "Python3 is not installed. Please open playground/index.html in your browser."
fi
# Wait for WordPress Playground to be ready # Wait for WordPress Playground to be ready
echo "Waiting for WordPress Playground to be ready..." echo "Waiting for WordPress Playground to be ready..."
sleep 5 sleep 5
echo "WordPress Playground Single Site environment is ready!" echo "WordPress Playground Single Site environment is ready!"
echo "Site: http://localhost:8888" echo "Site: http://localhost:8888"
echo "Admin login: admin / password" echo "Admin login: admin / password"
echo "Press Ctrl+C to stop the server when done." echo "Press Ctrl+C to stop the server when done."
elif [ "$ENV_TYPE" == "playground-multisite" ]; then elif [ "$ENV_TYPE" = "playground-multisite" ]; then
echo "Setting up WordPress Playground multisite environment..." echo "Setting up WordPress Playground multisite environment..."
# Install wp-playground if needed # Install wp-playground if needed
install_wp_playground install_wp_playground
# Create plugin zip # Create plugin zip
echo "Creating plugin zip..." echo "Creating plugin zip..."
mkdir -p dist mkdir -p dist
zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*" zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*"
# Update blueprint to use local plugin # Update blueprint to use local plugin
cat > playground/multisite-blueprint.json << EOF # shellcheck disable=SC2154
cat >playground/multisite-blueprint.json <<EOF
{ {
"landingPage": "/wp-admin/network/", "landingPage": "/wp-admin/network/",
"preferredVersions": { "preferredVersions": {
@@ -255,35 +271,37 @@ elif [ "$ENV_TYPE" == "playground-multisite" ]; then
} }
EOF EOF
# Start WordPress Playground # Start WordPress Playground
echo "Starting WordPress Playground..." echo "Starting WordPress Playground..."
if command_exists python3; then if command_exists python3; then
python3 -m http.server 8888 --directory playground & python3 -m http.server 8888 --directory playground &
echo "Opening WordPress Playground in your browser..." PYTHON_PID=$!
if command_exists open; then echo "Started Python HTTP server with PID: $PYTHON_PID"
open http://localhost:8888/multisite.html echo "Opening WordPress Playground in your browser..."
elif command_exists xdg-open; then if command_exists open; then
xdg-open http://localhost:8888/multisite.html open http://localhost:8888/multisite.html
elif command_exists start; then elif command_exists xdg-open; then
start http://localhost:8888/multisite.html xdg-open http://localhost:8888/multisite.html
else elif command_exists start; then
echo "Please open http://localhost:8888/multisite.html in your browser" start http://localhost:8888/multisite.html
fi else
else echo "Please open http://localhost:8888/multisite.html in your browser"
echo "Python3 is not installed. Please open playground/multisite.html in your browser." fi
fi else
echo "Python3 is not installed. Please open playground/multisite.html in your browser."
fi
# Wait for WordPress Playground to be ready # Wait for WordPress Playground to be ready
echo "Waiting for WordPress Playground to be ready..." echo "Waiting for WordPress Playground to be ready..."
sleep 5 sleep 5
echo "WordPress Playground Multisite environment is ready!" echo "WordPress Playground Multisite environment is ready!"
echo "Main site: http://localhost:8888" echo "Main site: http://localhost:8888"
echo "Test site: http://localhost:8888/testsite" echo "Test site: http://localhost:8888/testsite"
echo "Admin login: admin / password" echo "Admin login: admin / password"
echo "Press Ctrl+C to stop the server when done." echo "Press Ctrl+C to stop the server when done."
else else
echo "Invalid environment type. Use 'single', 'multisite', 'playground-single', or 'playground-multisite'." echo "Invalid environment type. Use 'single', 'multisite', 'playground-single', or 'playground-multisite'."
exit 1 exit 1
fi fi

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
# WordPress Plugin Build Script # WordPress Plugin Build Script
# This script creates a clean build of the plugin for distribution # This script creates a clean build of the plugin for distribution
@@ -39,15 +40,15 @@ composer install --no-dev --optimize-autoloader
# Copy plugin files to build directory # Copy plugin files to build directory
echo "Copying plugin files..." echo "Copying plugin files..."
cp -R *.php "$BUILD_DIR/" cp -R ./*.php "$BUILD_DIR/"
cp -R README.md LICENSE CHANGELOG.md readme.txt composer.json "$BUILD_DIR/" cp -R README.md LICENSE CHANGELOG.md readme.txt composer.json "$BUILD_DIR/"
# Copy directories # Copy directories
echo "Copying directories..." echo "Copying directories..."
mkdir -p "$BUILD_DIR/admin" "$BUILD_DIR/includes" "$BUILD_DIR/languages" "$BUILD_DIR/assets" mkdir -p "$BUILD_DIR/admin" "$BUILD_DIR/includes" "$BUILD_DIR/languages" "$BUILD_DIR/assets"
cp -R admin/* "$BUILD_DIR/admin/" cp -R ./admin/* "$BUILD_DIR/admin/"
cp -R includes/* "$BUILD_DIR/includes/" cp -R ./includes/* "$BUILD_DIR/includes/"
cp -R languages/* "$BUILD_DIR/languages/" cp -R ./languages/* "$BUILD_DIR/languages/"
# Create assets directory structure # Create assets directory structure
mkdir -p "$BUILD_DIR/assets/banner" "$BUILD_DIR/assets/icon" "$BUILD_DIR/assets/screenshots" mkdir -p "$BUILD_DIR/assets/banner" "$BUILD_DIR/assets/icon" "$BUILD_DIR/assets/screenshots"
@@ -72,9 +73,10 @@ fi
# Create ZIP file. # Create ZIP file.
echo "Creating ZIP file..." echo "Creating ZIP file..."
cd build || exit 1 (
zip -r "../$ZIP_FILE" "$PLUGIN_SLUG" -x "*.DS_Store" -x "*.git*" -x "*.github*" cd build || exit 1
cd .. zip -r "../$ZIP_FILE" "$PLUGIN_SLUG" -x "*.DS_Store" -x "*.git*" -x "*.github*"
)
# Check if ZIP file was created successfully # Check if ZIP file was created successfully
if [ -f "$ZIP_FILE" ]; then if [ -f "$ZIP_FILE" ]; then
@@ -82,9 +84,8 @@ if [ -f "$ZIP_FILE" ]; then
echo "File path: $(pwd)/$ZIP_FILE" echo "File path: $(pwd)/$ZIP_FILE"
# Deploy to local WordPress installation if environment variable is set # Deploy to local WordPress installation if environment variable is set
if [ -n "$WP_LOCAL_PLUGIN_DIR" ]; then if [ -n "${WP_LOCAL_PLUGIN_DIR:-}" ]; then
echo "\nDeploying to local WordPress installation..." printf '\nDeploying to local WordPress installation...\n'
echo "Deploying to local WordPress installation..."
# Remove existing plugin directory. # Remove existing plugin directory.
rm -rf "${WP_LOCAL_PLUGIN_DIR:?}/$PLUGIN_SLUG" rm -rf "${WP_LOCAL_PLUGIN_DIR:?}/$PLUGIN_SLUG"

View File

@@ -12,6 +12,6 @@ module.exports = defineConfig({
}, },
// Add configuration for WordPress Playground // Add configuration for WordPress Playground
experimentalWebKitSupport: true, experimentalWebKitSupport: true,
chromeWebSecurity: false chromeWebSecurity: false,
} },
}); });

View File

@@ -1,5 +1,10 @@
/* eslint-env mocha, jquery, cypress */ /* eslint-env mocha, jquery, cypress */
describe('WordPress Playground Single Site Tests', () => { describe('WordPress Playground Single Site Tests', {
retries: {
runMode: 2,
openMode: 0,
},
}, () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/', { timeout: 30000 }); cy.visit('/', { timeout: 30000 });
}); });
@@ -18,14 +23,22 @@ describe('WordPress Playground Single Site Tests', () => {
cy.visit('/wp-admin/plugins.php', { timeout: 30000 }); cy.visit('/wp-admin/plugins.php', { timeout: 30000 });
cy.get('body', { timeout: 15000 }).then(($body) => { cy.get('body', { timeout: 15000 }).then(($body) => {
if ($body.text().includes('Plugin Toggle')) { const hasPluginToggle = $body.text().includes('Plugin Toggle');
const hasKadenceBlocks = $body.text().includes('Kadence Blocks');
expect(
hasPluginToggle || hasKadenceBlocks,
'At least one blueprint plugin should be present in the plugins table',
).to.be.true;
if (hasPluginToggle) {
cy.contains('tr', 'Plugin Toggle').should('exist'); cy.contains('tr', 'Plugin Toggle').should('exist');
cy.contains('tr', 'Plugin Toggle').find('.deactivate').should('exist'); cy.contains('tr', 'Plugin Toggle').find('.deactivate').should('exist');
} else { } else {
cy.log('Plugin Toggle not found, skipping check'); cy.log('Plugin Toggle not found, skipping check');
} }
if ($body.text().includes('Kadence Blocks')) { if (hasKadenceBlocks) {
cy.contains('tr', 'Kadence Blocks').find('.deactivate').should('exist'); cy.contains('tr', 'Kadence Blocks').find('.deactivate').should('exist');
} else { } else {
cy.log('Kadence Blocks plugin not found, skipping check'); cy.log('Kadence Blocks plugin not found, skipping check');

View File

@@ -1,30 +1,75 @@
describe('WordPress Single Site Tests', () => { /* eslint-env mocha, jquery, cypress */
it('Can access the site', () => { describe( 'WordPress Single Site Tests', () => {
cy.visit('/'); it( 'Can access the site', () => {
cy.get('body').should('exist'); cy.visit( '/' );
}); cy.get( 'body' ).should( 'exist' );
} );
it('Can login to the admin area', () => { it( 'Can login to the admin area', () => {
cy.loginAsAdmin(); cy.loginAsAdmin();
cy.get('#wpadminbar').should('exist'); cy.get( '#wpadminbar' ).should( 'exist' );
cy.get('#dashboard-widgets').should('exist'); cy.get( '#dashboard-widgets' ).should( 'exist' );
}); } );
it('Plugin is activated', () => { it( 'Plugin is activated', () => {
// Use our custom command to check and activate the plugin if needed // Use our custom command to check and activate the plugin if needed.
cy.activatePlugin('wp-plugin-starter-template-for-ai-coding'); cy.activatePlugin( 'wp-plugin-starter-template-for-ai-coding' );
// Verify it's active // Verify it's active.
cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate').should('exist'); cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate' ).should( 'exist' );
}); } );
it('Plugin settings page loads correctly', () => { it( 'Plugin row is visible on the plugins page', () => {
cy.loginAsAdmin(); cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Navigate to the plugin settings page (if it exists) // Verify the plugin row exists with the correct slug.
cy.visit('/wp-admin/options-general.php?page=wp-plugin-starter-template'); cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"]' ).should( 'exist' );
// This is a basic check - adjust based on your actual plugin's settings page // Verify the plugin name is displayed.
cy.get('h1').should('contain', 'WP Plugin Starter Template'); cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .plugin-title strong' )
}); .should( 'contain', 'WordPress Plugin Starter Template' );
}); } );
it( 'Update source selector link is present in the plugin row', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// The update source selector link should be rendered in the plugin row.
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"]' )
.find( '.wpst-update-source-selector' )
.should( 'exist' );
} );
it( 'Update source modal opens and displays source options', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Click the update source selector link to open the modal.
cy.get( '.wpst-update-source-selector' ).first().click();
// Modal should be visible.
cy.get( '#wpst-update-source-modal' ).should( 'be.visible' );
// Modal should contain the three update source options.
cy.get( '#wpst-update-source-modal input[name="update_source"][value="wordpress.org"]' ).should( 'exist' );
cy.get( '#wpst-update-source-modal input[name="update_source"][value="github"]' ).should( 'exist' );
cy.get( '#wpst-update-source-modal input[name="update_source"][value="gitea"]' ).should( 'exist' );
// Save button should be present.
cy.get( '#wpst-save-source' ).should( 'exist' );
} );
it( 'Update source modal can be closed', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Open the modal.
cy.get( '.wpst-update-source-selector' ).first().click();
cy.get( '#wpst-update-source-modal' ).should( 'be.visible' );
// Close the modal via the close button.
cy.get( '#wpst-update-source-modal .wpst-modal-close' ).click();
cy.get( '#wpst-update-source-modal' ).should( 'not.be.visible' );
} );
} );

View File

@@ -46,8 +46,8 @@ class Admin {
*/ */
public function enqueue_admin_assets(): void { public function enqueue_admin_assets(): void {
// @phpcs:disable WordPress.Security.NonceVerification.Recommended // @phpcs:disable WordPress.Security.NonceVerification.Recommended
// @phpcs:disable WordPress.Security.NonceVerification.Missing // @phpcs:disable WordPress.Security.NonceVerification.Missing
// For production, use filter_input. // For production, use filter_input.
$page = ''; $page = '';
if ( defined( 'PHPUNIT_RUNNING' ) && PHPUNIT_RUNNING ) { if ( defined( 'PHPUNIT_RUNNING' ) && PHPUNIT_RUNNING ) {
@@ -64,15 +64,17 @@ class Admin {
if ( ! $page || 'wp_plugin_starter_template_settings' !== $page ) { if ( ! $page || 'wp_plugin_starter_template_settings' !== $page ) {
return; return;
} }
// @phpcs:enable // @phpcs:enable
// Get the plugin version. // Get the plugin version.
$plugin_version = $this->core->get_plugin_version(); $plugin_version = $this->core->get_plugin_version();
$plugin_url = $this->get_plugin_base_url();
// Enqueue styles. // Enqueue styles.
\wp_enqueue_style( \wp_enqueue_style(
'wpst-admin-styles', 'wpst-admin-styles',
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/css/admin-styles.css', $plugin_url . 'admin/css/admin-styles.css',
array(), // Dependencies. array(), // Dependencies.
$plugin_version // Version. $plugin_version // Version.
); );
@@ -80,7 +82,7 @@ class Admin {
// Enqueue admin scripts. // Enqueue admin scripts.
\wp_enqueue_script( \wp_enqueue_script(
'wpst-admin-script', 'wpst-admin-script',
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/js/admin-scripts.js', $plugin_url . 'admin/js/admin-scripts.js',
array( 'jquery' ), array( 'jquery' ),
$plugin_version, // Version. $plugin_version, // Version.
true true
@@ -99,4 +101,21 @@ class Admin {
$data $data
); );
} }
/**
* Get plugin base URL.
*
* @return string Plugin base URL with trailing slash.
*/
private function get_plugin_base_url(): string {
if ( defined( 'WP_PLUGIN_STARTER_TEMPLATE_URL' ) ) {
return WP_PLUGIN_STARTER_TEMPLATE_URL;
}
if ( defined( 'WPST_PLUGIN_URL' ) ) {
return WPST_PLUGIN_URL;
}
return \plugin_dir_url( dirname( __DIR__, 2 ) );
}
} }

View File

@@ -6,10 +6,10 @@
* Extend this file or create additional classes in this directory * Extend this file or create additional classes in this directory
* to implement multisite features for your plugin. * to implement multisite features for your plugin.
* *
* @package WP_Plugin_Starter_Template_For_AI_Coding * @package WPALLSTARS\PluginStarterTemplate
*/ */
namespace WP_Plugin_Starter_Template_For_AI_Coding\Multisite; namespace WPALLSTARS\PluginStarterTemplate\Multisite;
// Exit if accessed directly. // Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {

1180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,8 @@
"test:phpunit": "composer test", "test:phpunit": "composer test",
"test:phpunit:multisite": "WP_MULTISITE=1 composer test", "test:phpunit:multisite": "WP_MULTISITE=1 composer test",
"build": "./build.sh", "build": "./build.sh",
"lint:js": "eslint cypress/", "lint:js": "eslint cypress/ cypress.config.js",
"lint:css": "stylelint \"**/*.css\" --allow-empty-input",
"lint:php": "composer run-script phpcs", "lint:php": "composer run-script phpcs",
"lint:php:simple": "composer run-script phpcs:simple", "lint:php:simple": "composer run-script phpcs:simple",
"lint:phpstan": "composer run-script phpstan", "lint:phpstan": "composer run-script phpstan",
@@ -36,7 +37,8 @@
"fix:php": "composer run-script phpcbf", "fix:php": "composer run-script phpcbf",
"fix:php:simple": "composer run-script phpcbf:simple", "fix:php:simple": "composer run-script phpcbf:simple",
"test:php": "composer run-script test", "test:php": "composer run-script test",
"lint": "composer run-script lint", "lint:php-all": "composer run-script lint",
"lint": "npm run lint:php-all && npm run lint:js && npm run lint:css",
"fix": "composer run-script fix", "fix": "composer run-script fix",
"quality": "npm run lint && npm run test:php" "quality": "npm run lint && npm run test:php"
}, },
@@ -60,10 +62,12 @@
"devDependencies": { "devDependencies": {
"@wordpress/env": "^8.12.0", "@wordpress/env": "^8.12.0",
"@wp-playground/blueprints": "^3.0.22", "@wp-playground/blueprints": "^3.0.22",
"@wp-playground/client": "^3.0.22",
"@wp-playground/cli": "^3.0.22", "@wp-playground/cli": "^3.0.22",
"@wp-playground/client": "^3.0.22",
"cypress": "^13.17.0", "cypress": "^13.17.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-cypress": "^2.15.1" "eslint-plugin-cypress": "^6.2.0",
"stylelint": "^16.0.0",
"stylelint-config-standard": "^36.0.0"
} }
} }

View File

@@ -15,6 +15,8 @@
<exclude name="CamelCaseMethodName" /> <exclude name="CamelCaseMethodName" />
<exclude name="CamelCaseParameterName" /> <exclude name="CamelCaseParameterName" />
<exclude name="CamelCaseVariableName" /> <exclude name="CamelCaseVariableName" />
<!-- WordPress plugins use filter_input() for production and $_GET for testing; Superglobals rule is not applicable. -->
<exclude name="Superglobals" />
</rule> </rule>
<rule ref="rulesets/design.xml" /> <rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml"> <rule ref="rulesets/naming.xml">

View File

@@ -5,13 +5,6 @@ parameters:
- admin - admin
- wp-plugin-starter-template.php - wp-plugin-starter-template.php
excludePaths: excludePaths:
analyse:
- vendor
- node_modules
- tests
- bin
- build
- dist
analyseAndScan: analyseAndScan:
- vendor - vendor
- node_modules - node_modules

View File

@@ -21,6 +21,6 @@
</style> </style>
</head> </head>
<body> <body>
<iframe src="https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2" title="WordPress Playground Single Site Environment"></iframe> <iframe src="https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json" title="WordPress Playground Single Site Environment"></iframe>
</body> </body>
</html> </html>

View File

@@ -68,7 +68,7 @@
"landingPage": "/wp-admin/", "landingPage": "/wp-admin/",
"login": true, "login": true,
"features": { "features": {
"networking": true "networking": false
}, },
"steps": [ "steps": [
{ {

View File

@@ -46,7 +46,7 @@ if ( getenv( 'WP_PHPUNIT__DIR' ) ) {
// Include plugin files needed for tests. // Include plugin files needed for tests.
require_once WPST_PLUGIN_DIR . 'includes/class-core.php'; require_once WPST_PLUGIN_DIR . 'includes/class-core.php';
require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php'; require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php';
if ( file_exists( WPST_PLUGIN_DIR . 'admin/lib/admin.php' ) ) { if ( file_exists( WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php' ) ) {
require_once WPST_PLUGIN_DIR . 'admin/lib/admin.php'; require_once WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php';
} }
} }

View File

@@ -101,17 +101,12 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
'return' => 'wp_plugin_starter_template_settings', 'return' => 'wp_plugin_starter_template_settings',
]); ]);
// Mock WordPress functions used in the method
WP_Mock::userFunction('plugin_dir_url', [
'return' => 'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/',
]);
// Mock wp_enqueue_style // Mock wp_enqueue_style
WP_Mock::userFunction('wp_enqueue_style', [ WP_Mock::userFunction('wp_enqueue_style', [
'times' => 1, 'times' => 1,
'args' => [ 'args' => [
'wpst-admin-styles', 'wpst-admin-styles',
'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/css/admin-styles.css', 'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/css/admin-styles.css',
[], [],
'1.0.0', '1.0.0',
], ],
@@ -122,7 +117,7 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
'times' => 1, 'times' => 1,
'args' => [ 'args' => [
'wpst-admin-script', 'wpst-admin-script',
'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/js/admin-scripts.js', 'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/js/admin-scripts.js',
['jquery'], ['jquery'],
'1.0.0', '1.0.0',
true, true,

View File

@@ -55,13 +55,19 @@ spl_autoload_register(
// Get the relative class name. // Get the relative class name.
$relative_class = substr( $className, $len ); $relative_class = substr( $className, $len );
// Convert namespace to path. // Build class file path using WordPress-style class file names.
$file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . str_replace( '\\', '/', $relative_class ) . '.php'; $relative_path = str_replace( '\\', '/', $relative_class );
$path_parts = explode( '/', $relative_path );
$class_name = array_pop( $path_parts );
$directory = '';
// Convert class name format to file name format. if ( ! empty( $path_parts ) ) {
$file = str_replace( 'class-', '', $file ); $directory = implode( '/', $path_parts ) . '/';
$file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $file ); }
$file = strtolower( $file );
$class_file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $class_name );
$class_file = 'class-' . strtolower( $class_file ) . '.php';
$file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . $directory . $class_file;
// If the file exists, require it. // If the file exists, require it.
if ( file_exists( $file ) ) { if ( file_exists( $file ) ) {