Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
099621b
WordPress Abilities API Client
emdashcodes Sep 5, 2025
a9171c8
Fix: minor formatting issues and test fix for pagination headers
emdashcodes Sep 5, 2025
175d97c
fix: correct return type of wp_abilities_register_client_assets
emdashcodes Sep 5, 2025
507d026
fix: auto-init assets & use class pattern for registration
emdashcodes Sep 17, 2025
feeb63b
Normalize paths for asset loading
emdashcodes Sep 17, 2025
fd80174
fix: package structure, pull in rules from gutenberg for eslint and t…
emdashcodes Sep 17, 2025
f84f026
docs: Remove NPM package mention for now
emdashcodes Sep 17, 2025
4bce669
refactor: Rename listAbilities to getAbilities & update reducer naming
emdashcodes Sep 17, 2025
4181cd5
fix: Update AbilityInput type to accept any JSON value
emdashcodes Sep 17, 2025
b534119
fix: correct package.json after rebase with trunk
emdashcodes Sep 17, 2025
94664a0
fix: Use per_page=-1 instead of recursion in resolver
emdashcodes Sep 17, 2025
d44074b
update: use addQueryArgs to build GET requests
emdashcodes Sep 17, 2025
2ec5107
fix: wrap strings with @wordpress/i18n
emdashcodes Sep 17, 2025
23c85b7
fix: phpstan issues in register_assets
emdashcodes Sep 17, 2025
03d8a20
fix: additional phpstan error
emdashcodes Sep 17, 2025
ec455e6
Add client-only ability registration (#69)
emdashcodes Sep 19, 2025
ae36d5c
Do not translate thrown developer errors
emdashcodes Sep 22, 2025
e6654a3
use rimraf for cleaning files, move to root package.json
emdashcodes Sep 22, 2025
cf979db
Use correct versions for CI
emdashcodes Sep 22, 2025
caa4fd7
fix: move scripts to main package.json and prefer wp-scripts
emdashcodes Sep 22, 2025
955fb2f
Simplify client readme by linking to CONTRIBUTING.md for dev and test…
emdashcodes Sep 22, 2025
ce1de7f
align tsconfig with gutenberg
emdashcodes Sep 22, 2025
af2e2c8
fix wp-scripts build command
emdashcodes Sep 22, 2025
2a05c12
Build JS assets for the plugin zip
emdashcodes Sep 22, 2025
f697d20
allow asset class prefix
emdashcodes Sep 22, 2025
17dfa62
Optimize npm dependencies
gziolo Sep 24, 2025
a42c858
Fix Prettier config and format files accordingly
gziolo Sep 24, 2025
9536f60
Use correct ajv-draft-04 import. Remove unusued jest libs
emdashcodes Sep 24, 2025
dea7b40
Only store valid ability keys in the store
emdashcodes Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
module.exports = {
root: true,
extends: [
'plugin:@wordpress/eslint-plugin/recommended',
'plugin:eslint-comments/recommended',
],
plugins: [ 'import' ],
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
},
env: {
browser: true,
es6: true,
node: true,
},
rules: {
// React best practices
'react/jsx-boolean-value': 'error',
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never' },
],

// WordPress-specific rules, lifted from gutenberg
'@wordpress/dependency-group': 'error',
'@wordpress/data-no-store-string-literals': 'error',
'@wordpress/wp-global-usage': 'error',
'@wordpress/react-no-unsafe-timeout': 'error',
'@wordpress/i18n-hyphenated-range': 'error',
'@wordpress/i18n-no-flanking-whitespace': 'error',
'@wordpress/i18n-text-domain': [
'error',
{
allowedTextDomain: 'default',
},
],
'@wordpress/no-unsafe-wp-apis': 'off',
'import/default': 'error',
'import/named': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.@(spec|test).@(j|t)s?(x)',
'**/@(webpack|jest).config.@(j|t)s',
'**/scripts/**',
],
},
],
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'lodash',
message: 'Please use native functionality instead.',
},
{
name: 'classnames',
message: "Please use `clsx` instead. It's a lighter and faster drop-in replacement for `classnames`.",
},
{
name: 'redux',
importNames: [ 'combineReducers' ],
message: 'Please use `combineReducers` from `@wordpress/data` instead.',
},
],
},
],
'no-restricted-syntax': [
'error',
{
selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]',
message: 'Path access on WordPress dependencies is not allowed.',
},
{
selector: 'JSXAttribute[name.name="id"][value.type="Literal"]',
message: 'Do not use string literals for IDs; use withInstanceId instead.',
},
{
selector: 'CallExpression[callee.object.name="Math"][callee.property.name="random"]',
message: 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you\'re not generating unique IDs: ignore this message.)',
},
],
},
overrides: [
{
files: [ '**/*.ts?(x)' ],
rules: {
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: false,
},
],
'@typescript-eslint/no-shadow': 'error',
'no-shadow': 'off',
'jsdoc/require-param': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
},
},
],
};
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
env:
CI: true

- name: Build JavaScript assets
run: npm run build

- name: Create Artifact
run: |
npm run plugin-zip
Expand Down
90 changes: 90 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,93 @@ jobs:
name: wp-code-coverage-${{ matrix.php }}-${{ matrix.wp }}
path: tests/_output/html
overwrite: true

# Runs JavaScript code quality checks.
#
# Performs the following steps:
# - Checks out the repository.
# - Sets up Node.js.
# - Installs NPM dependencies.
# - Runs Prettier formatting check.
# - Runs JavaScript linting.
# - Runs TypeScript type checking.
jslint:
name: Run JavaScript code quality checks
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 20

steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
cache: 'npm'
node-version-file: '.nvmrc'

- name: Install NPM dependencies
run: npm ci

- name: Run Prettier format check
run: npm run format

- name: Run JavaScript linting
run: npm run lint:js

- name: Run TypeScript type checking
run: npm run typecheck

# Runs JavaScript tests for the client package.
#
# Performs the following steps:
# - Checks out the repository.
# - Sets up Node.js.
# - Installs NPM dependencies.
# - Runs JavaScript tests with coverage.
# - Uploads coverage reports.
jstest:
name: Run JavaScript tests
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 20

steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
cache: 'npm'
node-version-file: '.nvmrc'

- name: Install NPM dependencies
run: npm ci

- name: Run JavaScript tests with coverage
run: npm run test:client:coverage

- name: Upload JavaScript coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: packages/client/coverage/lcov.info
flags: javascript
fail_ci_if_error: true

- name: Upload JavaScript coverage report as artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: javascript-coverage-report
path: packages/client/coverage/lcov-report
overwrite: true
48 changes: 47 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,39 @@ For more information on using `wp-env`, see the [wp-env documentation](https://d

#### Linting and Formatting

##### PHP Code Quality

- `npm run lint:php`: Runs PHPCS linting on the PHP code.
- `npm run lint:php:fix`: Autofixes PHPCS linting issues.
- `npm run lint:php:stan`: Runs PHPStan static analysis.
- `npm run format`: Formats non-PHP files using Prettier.

##### JavaScript Code Quality

The project uses ESLint for linting and Prettier for code formatting, configured through `@wordpress/scripts`.

```bash
# Lint all JavaScript/TypeScript files
npm run lint:js

# Auto-fix linting issues
npm run lint:js:fix

# Run TypeScript typecheck
npm run typecheck
```

##### Formatting Code

Format all files (JavaScript, JSON, Markdown, etc.):

```bash
npm run format
```

### Running Tests

#### PHP Tests

PHPUnit tests can be run using the following command:

```bash
Expand All @@ -106,6 +132,26 @@ npm run test:php

You should see the html coverage report in the `tests/_output/html` directory and the clover XML report in `tests/_output/php-coverage.xml`.

#### JavaScript Tests

The JavaScript client package tests can be run using the following commands:

```bash
# Run all JavaScript tests
npm run test:client

# Run tests in watch mode (great for development)
npm run test:client:watch
```

To generate a code coverage report, run:

```bash
npm run test:client:coverage
```

Coverage reports are generated in the `packages/client/coverage/` directory.

### Building the plugin for distribution

To build the plugin for distribution, you can use the following command:
Expand Down
95 changes: 95 additions & 0 deletions includes/assets/class-wp-abilities-assets-init.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Assets initialization for Abilities API.
*
* @package WordPress
* @subpackage Abilities_API
* @since 0.1.0
*/

declare( strict_types = 1 );

/**
* Handles initialization of Abilities API client assets.
*
* @since 0.1.0
*/
class WP_Abilities_Assets_Init {

/**
* Registers the Abilities API JavaScript client.
*
* Auto-detects whether running as a plugin or Composer package and registers
* the client script accordingly.
*
* @since 0.1.0
*
* @return void
*/
public static function register_assets(): void {
if ( wp_script_is( 'wp-abilities', 'registered' ) ) {
return;
}

$base_path = '';
$base_url = '';

if ( defined( 'WP_ABILITIES_API_DIR' ) ) {
// Running as a plugin
$base_path = wp_normalize_path( (string) WP_ABILITIES_API_DIR );
$base_url = plugins_url( '', dirname( __DIR__, 2 ) . '/abilities-api.php' );
} else {
// Running as a Composer package
$base_path = dirname( __DIR__, 2 );

$base_path = wp_normalize_path( (string) $base_path );
$plugin_dir = wp_normalize_path( (string) WP_PLUGIN_DIR );

// For Composer, we need to determine the URL based on the installation location
if ( 0 === strpos( $base_path, $plugin_dir ) ) {
// Inside a plugin directory
$relative_path = str_replace( $plugin_dir, '', $base_path );
$base_url = plugins_url( $relative_path );
} else {
// Assume standard Composer vendor structure
$base_url = plugins_url( 'vendor/wordpress/abilities-api', dirname( $base_path, 2 ) );
}
}

/** @var string $base_path PHPStan type assertion - base_path is always set above */
$client_path = trailingslashit( $base_path ) . 'packages/client/build/';

if ( ! file_exists( $client_path . 'index.js' ) ) {
return;
}

// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- asset file path can be variable based on plugin or Composer install
$asset = require_once $client_path . 'index.asset.php';

$client_url = trailingslashit( $base_url ) . 'packages/client/build/index.js';

wp_register_script(
'wp-abilities',
$client_url,
$asset['dependencies'],
$asset['version'],
true
);
}

/**
* Auto-enqueue Abilities API client on admin pages.
*
* This is primarily for development and testing purposes.
*
* @since 0.1.0
* @return void
*/
public static function admin_enqueue_scripts(): void {
if ( ! wp_script_is( 'wp-abilities', 'registered' ) ) {
return;
}

wp_enqueue_script( 'wp-abilities' );
}
}
Loading