diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc5..e062017 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ npx lint-staged +npx knip diff --git a/CLAUDE.md b/CLAUDE.md index f3945c8..2d54193 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,15 +9,18 @@ npm install npm run build # Build with tsup → dist/ npm test # Run vitest npm run typecheck # TypeScript type check -npm run lint # ESLint + markdownlint + Prettier check +npm run lint # ESLint + stylelint + markdownlint + Prettier + knip check npm run lint:fix # Auto-fix all lint and formatting issues +npm run knip # Find unused files, dependencies & exports (knip.jsonc) npm run dev # Watch mode build npm run dev:wp # Start a local WordPress on http://127.0.0.1:9400 via wp-playground-cli (ephemeral, Gutenberg latest, collab enabled) ``` ## Git Hooks -Pre-commit hook (via husky + lint-staged) auto-formats with Prettier and lints with ESLint + markdownlint on staged files. Hooks are installed automatically by `npm install` (husky's `prepare` script). +Pre-commit hook (via husky + lint-staged) auto-formats and lints staged files — Prettier, ESLint, and markdownlint on JS/TS/Markdown/JSON; stylelint on `wordpress-plugin/src/**/*.scss`; and phpcbf + PHPStan on `wordpress-plugin/**/*.php` — then runs `knip` (a full-project unused files/dependencies/exports check). Hooks are installed automatically by `npm install` (husky's `prepare` script). + +`knip` is configured in `knip.jsonc` (root + `wordpress-plugin` as separate workspaces) and runs as the last step of `npm run lint`, so CI covers it too. ## Architecture @@ -148,4 +151,4 @@ path (which forks a PHP subprocess via `system()`) is skipped with `WP_TESTS_SKIP_INSTALL=1` because Playground already provides a fresh SQLite DB per boot. -Run `npm run lint` from the repo root to lint everything (ESLint + stylelint + markdownlint + Prettier). +Run `npm run lint` from the repo root to lint everything (ESLint + stylelint + markdownlint + Prettier + knip). diff --git a/README.md b/README.md index b25a855..55716d9 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ npm install npm run build # Build with tsup → dist/ npm test # Run vitest npm run typecheck # TypeScript type check -npm run lint # ESLint + markdownlint + Prettier check +npm run lint # ESLint + stylelint + markdownlint + Prettier + knip check npm run dev # Watch mode build npm run dev:wp # Start a local WordPress via wp-playground-cli (no Docker) ``` @@ -112,7 +112,7 @@ composer phpcs # PHP CodeSniffer composer phpstan # PHPStan static analysis ``` -Run `npm run lint` from the repo root to lint everything (ESLint + stylelint + markdownlint + Prettier). +Run `npm run lint` from the repo root to lint everything (ESLint + stylelint + markdownlint + Prettier + knip). ## License diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 0000000..a431227 --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,29 @@ +{ + "$schema": "./node_modules/knip/schema-jsonc.json", + // System binaries invoked from scripts, not provided by any npm package: + // `composer` (PHP toolchain) and `claude` (the Claude Code CLI). + "ignoreBinaries": ["composer", "claude"], + "workspaces": { + ".": { + "project": [ + "src/**/*.ts", + "shared/**/*.ts", + "tests/**/*.ts", + "bin/**/*.js" + ] + }, + "wordpress-plugin": { + // `src/index.ts` registers the Gutenberg plugins (no package.json + // `main`/`exports` to infer it from); the shim is the webpack alias + // target for `@wordpress/interface`; the globs cover Jest tests, + // which live in `test/` folders the Jest plugin doesn't auto-detect. + "entry": [ + "src/index.ts", + "src/shims/wordpress-interface.ts", + "src/**/test/**/*.{ts,tsx}", + "src/**/*.test.{ts,tsx}" + ], + "project": ["src/**/*.{ts,tsx}"] + } + } +} diff --git a/package-lock.json b/package-lock.json index 326a978..5d8b7f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@babel/core": "^7.29.0", "@eslint/js": "^10.0.1", "@playwright/test": "^1.59.1", - "@types/eslint__js": "^8.42.3", "@types/node": "^25.5.2", "@vitest/coverage-v8": "^4.1.2", "@wordpress/e2e-test-utils-playwright": "^1.43.0", @@ -35,6 +34,7 @@ "eslint-plugin-package-json": "^0.91.1", "globals": "^17.4.0", "husky": "^9.1.7", + "knip": "^6.16.1", "lint-staged": "^16.4.0", "markdownlint-cli2": "^0.22.0", "prettier": "^3.8.1", @@ -4034,16 +4034,723 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.133.0.tgz", + "integrity": "sha512-l/44caGse+VpnY9gx0yvvc5QnnG3yG1FO3KZgYvNL1GZrfK86zIwAOgGEVlxDyRymzrU/KHiblPFpevKOmJmUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.133.0.tgz", + "integrity": "sha512-KUHmPMziLBp4u+zbrLdB7iWS7KshuZe+RAp7ELnY9SI9nNXBZ+dp8fiBqWOxhXqn+FQg3a4UcQhwmsJOKV8Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.133.0.tgz", + "integrity": "sha512-q8dWmnU/8ea2tga9w2f1PinQ5rcMPDUGkF64T189b65YMjUomET4oy5oRldOr4AwOQkneOG/Zttnz1Dvrc62wg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.133.0.tgz", + "integrity": "sha512-cOKeIELIB2bJnCKwqx4Rdj+1Lss/U6uCbLxRySZrhyOOQa1flKhwZFjEHRHxk8fU1NKmhK5OnTdPQ4CpjuFuVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.133.0.tgz", + "integrity": "sha512-OpaSv4pW3KgFrMYQxTaS0aOE4T1DQF3qZE/4B6uqqv1KgPWWd4UQhJALi8PJPX1RRV5K7ThKXRfF7qGg2+3l1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.133.0.tgz", + "integrity": "sha512-JGK1wlGrGwxBIlVSF7KWTX1/ru6BEtf28fRROztDRkLfiW+Kxa4onnriezMIiogfn9hVw2KzYcKiLjkLR2ns8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.133.0.tgz", + "integrity": "sha512-yuZO533Ftonxn/iyoqQzURzLQHMspvsIyfiCSNi1t/ER4eIQaR0SsmUOUm5b/lmSig7IWIUa5/BrbEkAPwcilQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.133.0.tgz", + "integrity": "sha512-hvpbqT5pN2rR+3+xtWeizwfR/aZ0vGceg6TqYMl+ToxMpk9/tmnX7kSvQnfEUkoua8mhogzvIKsAkn0wxgblBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.133.0.tgz", + "integrity": "sha512-wJQGamIosQBoJHW9+S5XxrtKRo3eyJxsnS1XCPrqN0LHi8uw1pTqqTfn3t/NVuvbBg7Pumn4ez9Eidgcn0xbEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.133.0.tgz", + "integrity": "sha512-Koaz32/O5+abIfrNGdyndgRvdOZ9jEf5/z3Ep9h3h2QWpdDiUQpVwgH0OcMXCs+l9aXxPLtkupqyVig9W6FDKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.133.0.tgz", + "integrity": "sha512-R4vOjWzxhnNWHnVLeiB6jNuIifdy9vcMXZGPc7StXcxBovI+U2zg1QhZ9o8OjV80oGivs1lX5NfPLzk4IPqlRA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.133.0.tgz", + "integrity": "sha512-iwgBNUTHiMdxARLYuM0SBlnYeb19iw1Ea5M+4ERZupCsBMLArti6FyZ6UfFjJxIiTDr2oW2DGQFxlQVQ/dW9rA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.133.0.tgz", + "integrity": "sha512-ZwZNo8FZmB/gVfboQl+wXilBigGl+6nQQs+nITOeAP/HcAOjiHl6XZJL9F/KXNEspODQcbjAiyjUbeCJd9a0fA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.133.0.tgz", + "integrity": "sha512-govCvWx1dBlED3uu4qXctxpRcouu9I8Kn+DBktGCl760JtlGJzc9l/OmPJKlYWSbrRqKkMZehNeZ/4Wfma7uSA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.133.0.tgz", + "integrity": "sha512-ssTlpXD5Mq9uCssDJPzlRWqBt4Y7Zzd9i+XZhWmK/9Y6KUIuAxVYTYiI8lxcGWi0+3/Cz4A8q9UrD4NK9Y2j7g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.133.0.tgz", + "integrity": "sha512-51aByfXhPtLEdWG4a2Ihdw6cPWV1ei1AarALpFdDP8MLWDLE2NuUMgbo3DERR2Kt8fT/ok1GUvBiLxVGke9uUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.133.0.tgz", + "integrity": "sha512-2e16tkKp+wDO2GTAmXfxbBcCmGEaFPIJEIRBBmVKNVXSc8/fJsSIaBGyFTPHM9ST5GNWgJcYIt94rDTks+PLwA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.133.0.tgz", + "integrity": "sha512-KPTNDKbxH1cglrqTyVeXHb4Pk4oksz8EcE1/v8zqU7N4UXbiHfA/IwtXZ2U77fnRAWBbgVkl/lZbL7o3hRdejg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.133.0.tgz", + "integrity": "sha512-Una1bNYv9zCavQrfnDR9wuZVB3itLjCEH4Oz7i6CwAJN/Xq9b+zbbcxmvdkKvvJt4Ngc/MBmIYlbLo3zS4TQ0A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.133.0.tgz", + "integrity": "sha512-kjBhCiOGSYTwDJQuuZa7a94JbP8htWu7J0X1KwH74kV2K5eYf6eyJRYmkpCDvr0XEL8tMxYI4WU1VekblFCLgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-project/types": { "version": "0.124.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.20.0.tgz", + "integrity": "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.20.0.tgz", + "integrity": "sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.20.0.tgz", + "integrity": "sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.20.0.tgz", + "integrity": "sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.20.0.tgz", + "integrity": "sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.20.0.tgz", + "integrity": "sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.20.0.tgz", + "integrity": "sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.20.0.tgz", + "integrity": "sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.20.0.tgz", + "integrity": "sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.20.0.tgz", + "integrity": "sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.20.0.tgz", + "integrity": "sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.20.0.tgz", + "integrity": "sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.20.0.tgz", + "integrity": "sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.20.0.tgz", + "integrity": "sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.20.0.tgz", + "integrity": "sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.20.0.tgz", + "integrity": "sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.20.0.tgz", + "integrity": "sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.20.0.tgz", + "integrity": "sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.20.0.tgz", + "integrity": "sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@paulirish/trace_engine": { "version": "0.0.59", "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.59.tgz", @@ -5251,27 +5958,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint__js": { - "version": "8.42.3", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", - "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*" - } - }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -10058,6 +10744,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -10199,6 +10895,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -11588,6 +12300,16 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", @@ -11888,6 +12610,74 @@ "json-buffer": "3.0.1" } }, + "node_modules/knip": { + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.16.1.tgz", + "integrity": "sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.3.0", + "get-tsconfig": "4.14.0", + "jiti": "^2.7.0", + "oxc-parser": "^0.133.0", + "oxc-resolver": "^11.20.0", + "picomatch": "^4.0.4", + "smol-toml": "^1.6.1", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.16", + "unbash": "^3.0.0", + "yaml": "^2.9.0", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/knip/node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/knip/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -13893,6 +14683,85 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-parser": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.133.0.tgz", + "integrity": "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.133.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.133.0", + "@oxc-parser/binding-android-arm64": "0.133.0", + "@oxc-parser/binding-darwin-arm64": "0.133.0", + "@oxc-parser/binding-darwin-x64": "0.133.0", + "@oxc-parser/binding-freebsd-x64": "0.133.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", + "@oxc-parser/binding-linux-arm64-musl": "0.133.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", + "@oxc-parser/binding-linux-x64-gnu": "0.133.0", + "@oxc-parser/binding-linux-x64-musl": "0.133.0", + "@oxc-parser/binding-openharmony-arm64": "0.133.0", + "@oxc-parser/binding-wasm32-wasi": "0.133.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", + "@oxc-parser/binding-win32-x64-msvc": "0.133.0" + } + }, + "node_modules/oxc-parser/node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/oxc-resolver": { + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.20.0.tgz", + "integrity": "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.20.0", + "@oxc-resolver/binding-android-arm64": "11.20.0", + "@oxc-resolver/binding-darwin-arm64": "11.20.0", + "@oxc-resolver/binding-darwin-x64": "11.20.0", + "@oxc-resolver/binding-freebsd-x64": "11.20.0", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", + "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", + "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", + "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-x64-musl": "11.20.0", + "@oxc-resolver/binding-openharmony-arm64": "11.20.0", + "@oxc-resolver/binding-wasm32-wasi": "11.20.0", + "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", + "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -15955,6 +16824,19 @@ "node": ">=4" } }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strnum": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", @@ -16649,6 +17531,16 @@ "dev": true, "license": "MIT" }, + "node_modules/unbash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-3.0.0.tgz", + "integrity": "sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -17115,6 +18007,16 @@ } } }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/wasm-feature-detect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", diff --git a/package.json b/package.json index 7a06795..2bdbb45 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,9 @@ "dev": "tsup --watch", "dev:wp": "NODE_USE_ENV_PROXY=1 wp-playground-cli server --mount-before-install=./wordpress-plugin:/wordpress/wp-content/plugins/claudaborative-editing --blueprint=./playground/dev.blueprint.json --port=9400", "generate:defs": "node bin/generate-shared-defs.js && prettier --write wordpress-plugin/src/utils/command-i18n.ts", - "lint": "eslint --max-warnings 0 . && npm --prefix wordpress-plugin run lint:style && markdownlint-cli2 && prettier --check -u .", - "lint:fix": "eslint --fix --max-warnings 0 . && npm --prefix wordpress-plugin run lint:style:fix && markdownlint-cli2 --fix && prettier --write -u .", + "knip": "knip", + "lint": "eslint --max-warnings 0 . && npm --prefix wordpress-plugin run lint:style && markdownlint-cli2 && prettier --check -u . && knip", + "lint:fix": "eslint --fix --max-warnings 0 . && npm --prefix wordpress-plugin run lint:style:fix && markdownlint-cli2 --fix && prettier --write -u . && knip", "prepare": "husky && npm run build", "prepublishOnly": "npm run typecheck && npm run lint && npm test", "start": "node dist/index.js", @@ -89,7 +90,6 @@ "@babel/core": "^7.29.0", "@eslint/js": "^10.0.1", "@playwright/test": "^1.59.1", - "@types/eslint__js": "^8.42.3", "@types/node": "^25.5.2", "@vitest/coverage-v8": "^4.1.2", "@wordpress/e2e-test-utils-playwright": "^1.43.0", @@ -101,6 +101,7 @@ "eslint-plugin-package-json": "^0.91.1", "globals": "^17.4.0", "husky": "^9.1.7", + "knip": "^6.16.1", "lint-staged": "^16.4.0", "markdownlint-cli2": "^0.22.0", "prettier": "^3.8.1", diff --git a/src/wordpress/types.ts b/src/wordpress/types.ts index 27e58a4..4d72572 100644 --- a/src/wordpress/types.ts +++ b/src/wordpress/types.ts @@ -28,7 +28,7 @@ export type AwarenessState = Record; // --- Client → Server --- -export interface SyncEnvelopeFromClient { +interface SyncEnvelopeFromClient { room: string; client_id: number; after: number; @@ -42,7 +42,7 @@ export interface SyncPayload { // --- Server → Client --- -export interface SyncEnvelopeFromServer { +interface SyncEnvelopeFromServer { room: string; end_cursor: number; awareness: AwarenessState; diff --git a/src/yjs/types.ts b/src/yjs/types.ts index 0ccb927..6d006a4 100644 --- a/src/yjs/types.ts +++ b/src/yjs/types.ts @@ -4,7 +4,6 @@ * Verified from @wordpress/sync and @wordpress/core-data source code. * The Y.Doc has two root Y.Maps: 'document' and 'state'. */ -import * as Y from 'yjs'; // --- Root-level keys --- @@ -17,9 +16,6 @@ export const CRDT_DOC_VERSION = 1; // --- Block types --- -/** Block attributes map: rich-text attributes are Y.Text, others are plain values. */ -export type YBlockAttributes = Y.Map; - /** A plain-object block for reading/writing (not Yjs types). */ export interface Block { name: string; @@ -41,7 +37,7 @@ export interface CollaboratorInfo { enteredAt: number; } -export interface AwarenessCursorPosition { +interface AwarenessCursorPosition { relativePosition: { type: { client: number; clock: number }; tname: null; @@ -51,7 +47,7 @@ export interface AwarenessCursorPosition { absoluteOffset: number; } -export interface AwarenessEditorState { +interface AwarenessEditorState { selection: | { type: 'none' } | { type: 'cursor'; cursorPosition: AwarenessCursorPosition }; diff --git a/tests/e2e/helpers/playground.ts b/tests/e2e/helpers/playground.ts index cb78275..d7dfc09 100644 --- a/tests/e2e/helpers/playground.ts +++ b/tests/e2e/helpers/playground.ts @@ -6,9 +6,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; export const WP_BASE_URL = process.env.WP_BASE_URL ?? 'http://127.0.0.1:8889'; -export const WP_ADMIN_USER = process.env.WP_E2E_ADMIN_USER ?? 'admin'; -export const WP_ADMIN_PASSWORD = - process.env.WP_E2E_ADMIN_PASSWORD ?? 'password'; +const WP_ADMIN_USER = process.env.WP_E2E_ADMIN_USER ?? 'admin'; +const WP_ADMIN_PASSWORD = process.env.WP_E2E_ADMIN_PASSWORD ?? 'password'; const PLAYGROUND_PORT = Number.parseInt( new URL(WP_BASE_URL).port || '8889', @@ -50,9 +49,7 @@ function writeState(state: PlaygroundState): void { writeFileSync(STATE_FILE, JSON.stringify(state), { mode: 0o600 }); } -export async function waitForWordPress( - timeoutMs: number = 180_000 -): Promise { +async function waitForWordPress(timeoutMs: number = 180_000): Promise { const deadline = Date.now() + timeoutMs; let lastError: unknown; @@ -242,7 +239,7 @@ async function bootstrapAppPassword(): Promise { /** App password created during global setup, shared by all tests. */ let sharedAppPassword: string | null = null; -export function getSharedAppPassword(): string { +function getSharedAppPassword(): string { if (!sharedAppPassword) { const state = readState(); if (state.appPassword) { @@ -451,25 +448,6 @@ export async function listCommands( return apiFetch(`/wpce/v1/commands${query}`, {}, auth); } -export async function updateCommand( - commandId: number, - params: { - status: string; - message?: string; - result_data?: string; - }, - auth?: { username: string; appPassword: string } -): Promise { - return apiFetch( - `/wpce/v1/commands/${commandId}`, - { - method: 'PATCH', - body: JSON.stringify(params), - }, - auth - ); -} - // --------------------------------------------------------------------------- // Per-test user isolation — each test gets a unique WordPress user so that // commands in the shared Yjs room are user-scoped and don't cross-contaminate. diff --git a/tests/e2e/test.ts b/tests/e2e/test.ts index fe6a58c..a335406 100644 --- a/tests/e2e/test.ts +++ b/tests/e2e/test.ts @@ -44,7 +44,7 @@ function isSuppressed(args: unknown[]): boolean { return SUPPRESSED_MESSAGES.some((msg) => text.includes(msg)); } -export interface McpClientFixture { +interface McpClientFixture { client: Client; stderr: string[]; } diff --git a/tests/unit/prompts/helpers.ts b/tests/unit/prompts/helpers.ts index 12de958..42b8b7a 100644 --- a/tests/unit/prompts/helpers.ts +++ b/tests/unit/prompts/helpers.ts @@ -9,12 +9,7 @@ import { vi } from 'vitest'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; // Re-export session helpers so prompt tests don't import from tools/ -export { - createMockSession, - fakePost, - fakeUser, - fakeNote, -} from '../tools/helpers.js'; +export { createMockSession, fakePost, fakeNote } from '../tools/helpers.js'; export interface RegisteredPrompt { name: string; diff --git a/wordpress-plugin/package-lock.json b/wordpress-plugin/package-lock.json index 1f376cb..1e567c4 100644 --- a/wordpress-plugin/package-lock.json +++ b/wordpress-plugin/package-lock.json @@ -18,13 +18,17 @@ "@wordpress/data": "^10.44.0", "@wordpress/editor": "^14.44.0", "@wordpress/element": "^6.44.0", + "@wordpress/hooks": "^4.44.0", "@wordpress/i18n": "^6.17.0", "@wordpress/icons": "^12.2.0", "@wordpress/interface": "^9.29.0", "@wordpress/notices": "^5.44.0", "@wordpress/plugins": "^7.44.0", "@wordpress/scripts": "^32.0.0", + "@wordpress/stylelint-config": "^23.36.0", + "@wordpress/sync": "^1.44.0", "react": "^18.3.1", + "stylelint": "^16.26.1", "stylelint-config-prettier-scss": "^1.0.0", "typescript": "^6.0.3", "webpack": "^5.105.4", @@ -21018,10 +21022,9 @@ } }, "node_modules/prettier": { - "name": "wp-prettier", - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz", - "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { diff --git a/wordpress-plugin/package.json b/wordpress-plugin/package.json index 6134d71..38c4a29 100644 --- a/wordpress-plugin/package.json +++ b/wordpress-plugin/package.json @@ -14,13 +14,17 @@ "@wordpress/data": "^10.44.0", "@wordpress/editor": "^14.44.0", "@wordpress/element": "^6.44.0", + "@wordpress/hooks": "^4.44.0", "@wordpress/i18n": "^6.17.0", "@wordpress/icons": "^12.2.0", "@wordpress/interface": "^9.29.0", "@wordpress/notices": "^5.44.0", "@wordpress/plugins": "^7.44.0", "@wordpress/scripts": "^32.0.0", + "@wordpress/stylelint-config": "^23.36.0", + "@wordpress/sync": "^1.44.0", "react": "^18.3.1", + "stylelint": "^16.26.1", "stylelint-config-prettier-scss": "^1.0.0", "typescript": "^6.0.3", "webpack": "^5.105.4", @@ -38,5 +42,8 @@ "test": "wp-scripts test-unit-js", "test:coverage": "wp-scripts test-unit-js --coverage", "typecheck": "tsc --noEmit" + }, + "overrides": { + "prettier": "^3.8.1" } } diff --git a/wordpress-plugin/src/components/ConversationPanel/use-resizable-sidebar.tsx b/wordpress-plugin/src/components/ConversationPanel/use-resizable-sidebar.tsx index 27fb5b4..e9d8c73 100644 --- a/wordpress-plugin/src/components/ConversationPanel/use-resizable-sidebar.tsx +++ b/wordpress-plugin/src/components/ConversationPanel/use-resizable-sidebar.tsx @@ -35,8 +35,8 @@ const KEYBOARD_STEP = 20; const WIDTH_PROPS = ['width', 'flexBasis', 'maxWidth', 'minWidth'] as const; const HANDLE_SLOT_CLASS = 'wpce-conversation-panel__resize-handle-slot'; -export const MIN_WIDTH = 280; -export const DEFAULT_WIDTH = 280; +const MIN_WIDTH = 280; +const DEFAULT_WIDTH = 280; function getMaxWidth(): number { return Math.max(MIN_WIDTH, Math.floor(window.innerWidth * 0.8)); diff --git a/wordpress-plugin/src/store/types.ts b/wordpress-plugin/src/store/types.ts index 0d36d03..465c02c 100644 --- a/wordpress-plugin/src/store/types.ts +++ b/wordpress-plugin/src/store/types.ts @@ -38,7 +38,7 @@ export interface Command { result_data: Record | null; } -export interface McpStatus { +interface McpStatus { mcpConnected: boolean; mcpLastSeenAt: string | null; version: string | null; @@ -47,7 +47,7 @@ export interface McpStatus { error: string | null; } -export interface CommandsState { +interface CommandsState { active: Command | null; history: Command[]; isSubmitting: boolean; diff --git a/wordpress-plugin/src/sync/command-sync.ts b/wordpress-plugin/src/sync/command-sync.ts index 56e05f4..f44f465 100644 --- a/wordpress-plugin/src/sync/command-sync.ts +++ b/wordpress-plugin/src/sync/command-sync.ts @@ -19,9 +19,7 @@ */ import { dispatch, resolveSelect } from '@wordpress/data'; import apiFetch from '@wordpress/api-fetch'; -// eslint-disable-next-line import/no-extraneous-dependencies -- externalized by @wordpress/scripts import { Y, Awareness } from '@wordpress/sync'; -// eslint-disable-next-line import/no-extraneous-dependencies -- externalized by @wordpress/scripts import { addFilter } from '@wordpress/hooks'; import { TERMINAL_STATUSES, @@ -426,23 +424,6 @@ export function writeCommandToSync(command: Command): void { }, WRITE_RETRY_INTERVAL_MS); } -/** - * Remove a specific command from the Y.Doc's state map. - * - * @param commandId The command ID to remove. - */ -export function removeCommandFromSync(commandId: number): void { - const stateMap = getStateMap(); - if (!stateMap) return; - - commandDoc?.transact(() => { - stateMap.delete(commandKey(commandId)); - stateMap.set('savedAt', Date.now()); - }); - - commandDocDirty = true; -} - /** * Collect all commands stored as `cmd_${id}` entries in the state map. * Returns an ID-keyed record so callers can keep the previous shape. @@ -465,15 +446,6 @@ function collectCommandsFromStateMap(stateMap: YMap): Record { return result; } -/** - * Get the current commands from the Y.Doc's state map. - */ -export function getCommandsFromSync(): Record { - const stateMap = getStateMap(); - if (!stateMap) return {}; - return collectCommandsFromStateMap(stateMap); -} - /** * Subscribe to command changes from the Y.Doc's state map. * Returns an unsubscribe function. diff --git a/wordpress-plugin/src/sync/test/command-sync.test.ts b/wordpress-plugin/src/sync/test/command-sync.test.ts index 0568f27..db0c149 100644 --- a/wordpress-plugin/src/sync/test/command-sync.test.ts +++ b/wordpress-plugin/src/sync/test/command-sync.test.ts @@ -310,9 +310,12 @@ describe('command-sync', () => { expect(awareness).toBeDefined(); expect(typeof awareness.on).toBe('function'); - // After createAwareness, the doc should be captured and commands readable. - doc.getMap('state').set('cmd_42', MOCK_COMMAND); - expect(mod.getCommandsFromSync()['42']).toEqual(MOCK_COMMAND); + // After createAwareness, the doc should be captured: a write via + // the production path lands in this doc's state map. + mod.writeCommandToSync(MOCK_COMMAND); + expect(doc.getMap('state').get('cmd_42')).toEqual( + expect.objectContaining({ id: 42 }) + ); }); }); @@ -427,59 +430,6 @@ describe('command-sync', () => { }); }); - describe('getCommandsFromSync', () => { - it('reads from state map', async () => { - const mod = loadModule(); - await mod.initCommandSync(); - - const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; - const doc = new MockYDoc(); - syncConfig.createAwareness(doc); - - // Write a command manually. - doc.getMap('state').set('cmd_42', MOCK_COMMAND); - - const commands = mod.getCommandsFromSync(); - expect(commands['42']).toEqual(MOCK_COMMAND); - }); - - it('returns empty object when state map has no commands', async () => { - const mod = loadModule(); - await mod.initCommandSync(); - - const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; - const doc = new MockYDoc(); - syncConfig.createAwareness(doc); - - const commands = mod.getCommandsFromSync(); - expect(commands).toEqual({}); - }); - - it('returns empty object when commandDoc is not available', () => { - const mod = loadModule(); - const commands = mod.getCommandsFromSync(); - expect(commands).toEqual({}); - }); - - it('drops entries whose stored id does not match the key suffix', async () => { - const mod = loadModule(); - await mod.initCommandSync(); - - const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; - const doc = new MockYDoc(); - syncConfig.createAwareness(doc); - - // Good entry — key and stored id agree. - doc.getMap('state').set('cmd_42', { ...MOCK_COMMAND, id: 42 }); - // Corrupt entry — stored under cmd_43 but carries id 99. Must - // not appear under either 43 or 99 in the collected record. - doc.getMap('state').set('cmd_43', { ...MOCK_COMMAND, id: 99 }); - - const commands = mod.getCommandsFromSync(); - expect(commands).toEqual({ '42': { ...MOCK_COMMAND, id: 42 } }); - }); - }); - describe('isMcpConnected', () => { afterEach(() => { delete (window as any).wpceInitialState; @@ -729,35 +679,6 @@ describe('command-sync', () => { }); }); - describe('removeCommandFromSync', () => { - it('removes a specific command from the state map', async () => { - const mod = loadModule(); - await mod.initCommandSync(); - - const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; - const doc = new MockYDoc(); - syncConfig.createAwareness(doc); - - // Write two commands. - mod.writeCommandToSync(MOCK_COMMAND); - const secondCommand: Command = { - ...MOCK_COMMAND, - id: 43, - }; - mod.writeCommandToSync(secondCommand); - - const stateMap = doc.getMap('state'); - expect(stateMap.get('cmd_42')).toBeDefined(); - expect(stateMap.get('cmd_43')).toBeDefined(); - - // Remove command 42. - mod.removeCommandFromSync(42); - - expect(stateMap.get('cmd_42')).toBeUndefined(); - expect(stateMap.get('cmd_43')).toBeDefined(); - }); - }); - describe('fetch interceptor', () => { let originalFetch: typeof window.fetch; @@ -1250,6 +1171,34 @@ describe('command-sync', () => { jest.useRealTimers(); }); + it('drops entries whose stored id does not match the key suffix', async () => { + jest.useFakeTimers(); + const mod = loadModule(); + await mod.initCommandSync(); + + const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; + const doc = new MockYDoc(); + syncConfig.createAwareness(doc); + + const callback = jest.fn(); + mod.subscribeToCommandSync(callback); + + // Advance timers so the setInterval in subscribeToCommandSync fires. + jest.advanceTimersByTime(200); + + // Good entry — key and stored id agree. + doc.getMap('state').set('cmd_42', { ...MOCK_COMMAND, id: 42 }); + // Corrupt entry — stored under cmd_43 but carries id 99. Must not + // appear under either 43 or 99 in the rebuilt record. + doc.getMap('state').set('cmd_43', { ...MOCK_COMMAND, id: 99 }); + + expect(callback).toHaveBeenLastCalledWith({ + '42': { ...MOCK_COMMAND, id: 42 }, + }); + + jest.useRealTimers(); + }); + it('does not call callback when commands have not changed', async () => { jest.useFakeTimers(); const mod = loadModule(); @@ -1322,4 +1271,43 @@ describe('command-sync', () => { jest.useRealTimers(); }); }); + + describe('__getInternals_UNSAFE_FOR_TESTS', () => { + const originalHatch = process.env.WPCE_TEST_HATCH; + + afterEach(() => { + if (originalHatch === undefined) { + delete process.env.WPCE_TEST_HATCH; + } else { + process.env.WPCE_TEST_HATCH = originalHatch; + } + }); + + it('returns null internals in a normal (non-hatch) build', () => { + // The body is gated on the build-time WPCE_TEST_HATCH constant; + // without it the live Y.Doc / Awareness are never exposed. + delete process.env.WPCE_TEST_HATCH; + const mod = loadModule(); + + expect(mod.__getInternals_UNSAFE_FOR_TESTS()).toEqual({ + commandDoc: null, + commandAwareness: null, + }); + }); + + it('exposes the captured doc and awareness when the build hatch is enabled', async () => { + process.env.WPCE_TEST_HATCH = 'true'; + const mod = loadModule(); + await mod.initCommandSync(); + + const syncConfig = mockAddEntities.mock.calls[0][0][0].syncConfig; + const doc = new MockYDoc(); + syncConfig.createAwareness(doc); + + expect(mod.__getInternals_UNSAFE_FOR_TESTS()).toEqual({ + commandDoc: doc, + commandAwareness: capturedAwareness, + }); + }); + }); }); diff --git a/wordpress-plugin/src/test-hatch.ts b/wordpress-plugin/src/test-hatch.ts index f37139b..52ee869 100644 --- a/wordpress-plugin/src/test-hatch.ts +++ b/wordpress-plugin/src/test-hatch.ts @@ -9,7 +9,6 @@ /** * WordPress dependencies */ -// eslint-disable-next-line import/no-extraneous-dependencies -- externalized by @wordpress/scripts import { Y, Awareness } from '@wordpress/sync'; // y-protocols is pinned to an exact version (no caret) in // wordpress-plugin/package.json devDependencies, matching the version diff --git a/wordpress-plugin/src/test/index.test.ts b/wordpress-plugin/src/test/index.test.ts index c6a965f..446a957 100644 --- a/wordpress-plugin/src/test/index.test.ts +++ b/wordpress-plugin/src/test/index.test.ts @@ -26,6 +26,9 @@ jest.mock('../components/ConversationPanel', () => ({ default: () => null, })); +const mockInstallTestHatch = jest.fn(); +jest.mock('../test-hatch', () => ({ installTestHatch: mockInstallTestHatch })); + import { registerPlugin } from '@wordpress/plugins'; // Import the entry point (side effect: calls registerPlugin). @@ -77,3 +80,29 @@ describe('AI Actions entry point', () => { ); }); }); + +describe('test hatch', () => { + const originalHatch = process.env.WPCE_TEST_HATCH; + + afterEach(() => { + if (originalHatch === undefined) { + delete process.env.WPCE_TEST_HATCH; + } else { + process.env.WPCE_TEST_HATCH = originalHatch; + } + }); + + it('loads the test hatch only when WPCE_TEST_HATCH is enabled', async () => { + process.env.WPCE_TEST_HATCH = 'true'; + jest.resetModules(); + require('../index'); + + // The hatch import is dynamic; flush the microtask chain so its + // `.then(installTestHatch)` callback runs before the assertion. + await new Promise((resolve) => { + setTimeout(resolve, 0); + }); + + expect(mockInstallTestHatch).toHaveBeenCalledTimes(1); + }); +});