From 98190086a304db5f357f13f7b9e838728248c41e Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Tue, 27 Oct 2026 14:06:24 -0300 Subject: [PATCH 1/2] docs: add AI guidance and the Foxy integration skill. --- .agents/skills/php-forge-foxy | 1 + .claude/skills/php-forge-foxy | 1 + .editorconfig | 2 +- .gitattributes | 6 + .github/linters/.codespellrc | 2 +- .github/linters/.editorconfig-checker.json | 7 +- .github/linters/.markdown-lint.yml | 15 +- .github/linters/.yamllint.yml | 9 + .github/workflows/quality.yml | 4 +- .prettierignore | 18 ++ .prettierrc.json | 30 +++ AGENTS.md | 15 ++ CHANGELOG.md | 2 + README.md | 63 +++--- UPGRADE.md | 219 +++++---------------- composer.json | 58 +++++- docs/agents.md | 53 +++++ docs/config.md | 193 +++++++++--------- docs/events.md | 13 +- docs/faqs.md | 63 +++--- docs/index.md | 17 +- docs/migration.md | 179 +++++++++++++++++ docs/svgs/features-mobile.svg | 28 +-- docs/svgs/features.svg | 28 +-- docs/testing.md | 72 ++++++- docs/usage.md | 111 +++++++---- skills/php-forge-foxy/SKILL.md | 66 +++++++ 27 files changed, 855 insertions(+), 420 deletions(-) create mode 120000 .agents/skills/php-forge-foxy create mode 120000 .claude/skills/php-forge-foxy create mode 100644 .github/linters/.yamllint.yml create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 AGENTS.md create mode 100644 docs/agents.md create mode 100644 docs/migration.md create mode 100644 skills/php-forge-foxy/SKILL.md diff --git a/.agents/skills/php-forge-foxy b/.agents/skills/php-forge-foxy new file mode 120000 index 0000000..5bc18ee --- /dev/null +++ b/.agents/skills/php-forge-foxy @@ -0,0 +1 @@ +../../skills/php-forge-foxy \ No newline at end of file diff --git a/.claude/skills/php-forge-foxy b/.claude/skills/php-forge-foxy new file mode 120000 index 0000000..5bc18ee --- /dev/null +++ b/.claude/skills/php-forge-foxy @@ -0,0 +1 @@ +../../skills/php-forge-foxy \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index b4ea01d..2d85e4c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,7 +17,7 @@ indent_size = 2 indent_size = 2 [*.md] -indent_size = 2 +indent_size = unset trim_trailing_whitespace = false [*.php] diff --git a/.gitattributes b/.gitattributes index fdf0ca4..aaf5ad5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -26,10 +26,16 @@ /CHANGELOG.md merge=union # Exclude files from the archive +/.agents export-ignore +/.claude export-ignore +/AGENTS.md export-ignore +/internal export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.github export-ignore /.gitignore export-ignore +/.prettierignore export-ignore +/.prettierrc.json export-ignore /.styleci.yml export-ignore /codeception.yml export-ignore /composer-require-checker.json export-ignore diff --git a/.github/linters/.codespellrc b/.github/linters/.codespellrc index 7d7f71c..867dc82 100644 --- a/.github/linters/.codespellrc +++ b/.github/linters/.codespellrc @@ -1,2 +1,2 @@ [codespell] -skip = ./.git,.vscode/*,composer.lock,runtime/*,vendor/* +skip = ./.git,./.vscode/*,./composer.lock,./runtime/*,./vendor/*,./internal/*,./node_modules/*,./foxy.lock,./.phpunit.cache/*,./code_coverage/* diff --git a/.github/linters/.editorconfig-checker.json b/.github/linters/.editorconfig-checker.json index 944060a..6f39e75 100644 --- a/.github/linters/.editorconfig-checker.json +++ b/.github/linters/.editorconfig-checker.json @@ -2,6 +2,11 @@ "Exclude": [ "^composer\\.lock$", "^runtime/", - "^vendor/" + "^vendor/", + "^internal/", + "^node_modules/", + "^foxy\\.lock$", + "^\\.phpunit\\.cache/", + "^code_coverage/" ] } diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index 0a6d8fc..07bec2a 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -1,5 +1,12 @@ --- -MD007: - indent: 2 -MD013: false -MD033: false +config: + MD007: + indent: 2 + MD013: false + MD033: false +ignores: + - .vscode/** + - vendor/** + - node_modules/** + - internal/** + - runtime/** diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml new file mode 100644 index 0000000..d8586da --- /dev/null +++ b/.github/linters/.yamllint.yml @@ -0,0 +1,9 @@ +--- +extends: default +rules: + comments: + min-spaces-from-content: 1 + line-length: disable + truthy: disable +ignore: | + .yii2-framework-actions/ diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c3c4298..3ba4649 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -17,5 +17,7 @@ jobs: codespell-config: .github/linters/.codespellrc editorconfig-checker-config: .github/linters/.editorconfig-checker.json markdownlint-config: .github/linters/.markdown-lint.yml - prettier-ignore-path: .gitignore + prettier-config: .prettierrc.json + prettier-ignore-path: .prettierignore + yamllint-config: .github/linters/.yamllint.yml yamllint-targets: .github diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..26d27e9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,18 @@ +# Local editor and generated runtime state. +.vscode/** +runtime/** + +# Dependencies. +node_modules/** +vendor/** + +# Dependency lockfiles. +composer.lock +foxy.lock + +# License texts ship verbatim. +**/LICENSE* + +# Private maintainer files and coverage reports. +code_coverage/** +internal/** diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4816f8b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,30 @@ +{ + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 80, + "overrides": [ + { + "files": ["**/*.{cjs,css,js,mjs,yml,yaml}"], + "options": { + "tabWidth": 2 + } + }, + { + "files": "**/*.md", + "options": { + "embeddedLanguageFormatting": "off", + "printWidth": 120, + "proseWrap": "always", + "tabWidth": 2 + } + }, + { + "files": "CHANGELOG.md", + "options": { + "proseWrap": "preserve" + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..837e91b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,15 @@ +# Foxy repository guide + +Foxy is the `php-forge/foxy` Composer plugin. Inspect `composer.json`, the current Git diff, relevant source and tests +before changing behavior. Preserve existing staged and unstaged work. Public documentation is English; use `docs/` for +usage and configuration and `skills/php-forge-foxy/` as the canonical agent skill. Client discovery paths link to that +source. + +The plugin merges eligible Composer assets and uses the selected frontend manager or its own native manager. Preserve +user-owned dependencies, manager and lockfile semantics, managed-directory safeguards, audit failure reporting and +fallback behavior. The root `composer.json` is not restored by Foxy's fallback. Check `docs/config.md` and +`docs/usage.md` for the precise contracts before suggesting changes. + +Use the applicable checks in `docs/testing.md`; commands such as `composer ecs` and `composer rector` modify files. Run +only appropriate non-mutating checks and report missing dependencies or skipped scenarios. Keep changes local and do not +stage, commit or publish unless explicitly requested. diff --git a/CHANGELOG.md b/CHANGELOG.md index b44de6a..e1bfd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.4.1 Under development +- docs: add AI guidance and the Foxy integration skill. + ## 0.4.0 October 11, 2026 - refactor: deduplicate internals, centralize exception messages in `Foxy\Exception\Message`, and normalize their wording. diff --git a/README.md b/README.md index 9ba1b94..7577622 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ Selecting a manager explicitly is recommended for reproducible local and CI beha } ``` -Valid manager values are `bun`, `deno`, `native`, `npm`, `pnpm`, and `yarn`. When automatic manager execution is -enabled and `manager` is omitted, Foxy first looks for one recognized lockfile and then checks available executables. -Configure the manager explicitly when the project contains lockfiles from more than one manager. +Valid manager values are `bun`, `deno`, `native`, `npm`, `pnpm`, and `yarn`. When automatic manager execution is enabled +and `manager` is omitted, Foxy first looks for one recognized lockfile and then checks available executables. Configure +the manager explicitly when the project contains lockfiles from more than one manager. During automatic Composer processing, `run-asset-manager=false` prevents Foxy from requiring or probing a manager binary. Automatic selection uses the single recognized lockfile when present, or npm as the manifest adapter when no @@ -83,9 +83,9 @@ lockfile exists. An explicit `composer foxy:audit` still validates and runs the ## Without Node.js -Set `manager` to `native` and Foxy resolves the merged `package.json` against the npm registry, downloads the -tarballs, verifies their integrity, and extracts them into a flat `node_modules` directory using PHP alone (or into -the directory `native-install-dir` names): +Set `manager` to `native` and Foxy resolves the merged `package.json` against the npm registry, downloads the tarballs, +verifies their integrity, and extracts them into a flat `node_modules` directory using PHP alone (or into the directory +`native-install-dir` names): ```json { @@ -99,8 +99,8 @@ the directory `native-install-dir` names): The selected versions are recorded in `foxy.lock`; commit it so that `composer install` reinstalls the same versions without any registry metadata request. Tarballs come from the Composer cache, or are downloaded again from the URLs -recorded in the lock when the cache is empty. The native manager keeps one version per package, like asset-packagist, and -does not run lifecycle scripts. `native-install-dir` moves the install directory, for example to `vendor/npm-asset` +recorded in the lock when the cache is empty. The native manager keeps one version per package, like asset-packagist, +and does not run lifecycle scripts. `native-install-dir` moves the install directory, for example to `vendor/npm-asset` for Yii 2's default `@npm` alias. See the [native manager reference](docs/config.md#native-manager) for its scope. ## Frontend security audit @@ -115,9 +115,8 @@ composer foxy:audit --format=summary --no-dev --audit-level=high Foxy normalizes the current npm, pnpm, Yarn, Bun, Deno, and native audit reports and identifies the affected package, advisory, severity, vulnerable range, and CVE identifiers when GitHub maps the advisory to a CVE. The command returns `0` when no advisory meets the configured threshold, `1` when at least one does, and `2` when the audit cannot be -completed reliably. With Deno and the `native` manager, `--no-dev` is rejected because neither can exclude -development dependencies. -See the [usage guide](docs/usage.md#security-auditing) for formats and CI examples. +completed reliably. With Deno and the `native` manager, `--no-dev` is rejected because neither can exclude development +dependencies. See the [usage guide](docs/usage.md#security-auditing) for formats and CI examples. ## Quick start @@ -145,18 +144,32 @@ Composer may remain at the repository root while frontend tooling runs from `web } ``` -Foxy reads and writes `web/package.json` and runs the selected manager from `web/`. Relative paths are resolved from -the Composer project directory. +Foxy reads and writes `web/package.json` and runs the selected manager from `web/`. Relative paths are resolved from the +Composer project directory. ## Documentation -- [Getting started](docs/index.md) -- [Configuration reference](docs/config.md) -- [Usage guide](docs/usage.md) -- [Events reference](docs/events.md) -- [Frequently asked questions](docs/faqs.md) -- [Upgrade guide](UPGRADE.md) -- [Testing guide](docs/testing.md) +- ๐Ÿ“š [Getting started](docs/index.md) +- โš™๏ธ [Configuration reference](docs/config.md) +- ๐Ÿ’ก [Usage guide and examples](docs/usage.md) +- ๐Ÿ”Œ [Events reference](docs/events.md) +- โ“ [Frequently asked questions](docs/faqs.md) +- ๐Ÿค– [AI agents guide](docs/agents.md) +- โฌ†๏ธ [Upgrade guide](UPGRADE.md) and [migration guide](docs/migration.md) +- ๐Ÿงช [Testing guide](docs/testing.md) + +## AI assistance + +AI agents can use the [documentation index](https://yii2-framework.github.io/docs/llms.txt) to discover pages and fetch +a page's Markdown version by appending `index.md` to its URL. The +[AI agents guide](https://yii2-framework.github.io/docs/project/ai-agents/) covers skill installation, client setup, and +verification. + +- ๐Ÿค– [`php-forge-foxy` skill](docs/agents.md): configure asset integration and audits. + +Match the documentation to the installed Foxy revision. If the hosted documentation is unavailable or does not match, +use the [repository guides](#documentation), [skill source](skills/php-forge-foxy/SKILL.md), source PHPDoc, and +[changelog](CHANGELOG.md). ## Package information @@ -164,17 +177,23 @@ the Composer project directory. [![Latest Stable Version](https://img.shields.io/packagist/v/php-forge/foxy.svg?style=for-the-badge&logo=packagist&logoColor=white&label=Stable)](https://packagist.org/packages/php-forge/foxy) [![Total Downloads](https://img.shields.io/packagist/dt/php-forge/foxy.svg?style=for-the-badge&logo=composer&logoColor=white&label=Downloads)](https://packagist.org/packages/php-forge/foxy) -## Code quality +## Project status [![Codecov](https://img.shields.io/codecov/c/github/php-forge/foxy.svg?style=for-the-badge&logo=codecov&logoColor=white&label=Coverage)](https://codecov.io/gh/php-forge/foxy) [![PHPStan Level 5](https://img.shields.io/badge/PHPStan-Level%205-4F5D95.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/php-forge/foxy/actions/workflows/static.yml) [![Quality](https://img.shields.io/github/actions/workflow/status/php-forge/foxy/quality.yml?style=for-the-badge&label=Quality&logo=github)](https://github.com/php-forge/foxy/actions/workflows/quality.yml) [![StyleCI](https://img.shields.io/badge/StyleCI-Passed-44CC11.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.styleci.io/repos/745652761?branch=main) -## Community +## Our social networks [![Follow on X](https://img.shields.io/badge/-Follow%20on%20X-1DA1F2.svg?style=for-the-badge&logo=x&logoColor=white&labelColor=000000)](https://x.com/Terabytesoftw) +## Built with AI + +[![Built with Claude Code](https://img.shields.io/badge/Built%20with-Claude%20Code-D97757.svg?style=for-the-badge&logo=claude&logoColor=white)](https://claude.com/claude-code) +[![Built with Codex](https://img.shields.io/badge/Built%20with-Codex-10A37F.svg?style=for-the-badge&logo=openaigym&logoColor=white)](https://openai.com/codex) +[![Reviewed by CodeRabbit](https://img.shields.io/badge/Reviewed%20by-CodeRabbit-FF570A.svg?style=for-the-badge&logo=coderabbit&logoColor=white)](https://www.coderabbit.ai) + ## License [![License](https://img.shields.io/badge/License-BSD--3--Clause-brightgreen.svg?style=for-the-badge&logo=opensourceinitiative&logoColor=white&labelColor=555555)](LICENSE) diff --git a/UPGRADE.md b/UPGRADE.md index c1c1579..a50e0e8 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,173 +1,62 @@ -# Upgrading Foxy +# Upgrade Guide -This file lists, for each release, the changes that need an action from you or that change what you see when Foxy -runs. Features that need neither are described in the [documentation](docs/index.md) and the [changelog](CHANGELOG.md). +The [migration guide](docs/migration.md) explains the actions and configuration examples for each release. These entries +list released incompatibilities. -## Upgrading from 0.3.x to 0.4.x + -### Update the constraint +## 0.4.0 -```bash -composer require php-forge/foxy:^0.4 --with-all-dependencies -``` + -Foxy 0.4 accepts `symfony/console` `^7.4 || ^8.0`. Console 8 requires PHP 8.4, so a project on PHP 8.3 keeps 7.4; -`--with-all-dependencies` lets Composer move the shared Symfony packages when the project allows it. +- Update `php-forge/foxy` to `^0.4` with `--with-all-dependencies`; PHP 8.3 projects must use a compatible Symfony + Console 7.4 release. -### The manager is selected on first use + -Foxy no longer runs ` --version` each time Composer loads the plugin. The manager is selected when an -install, an update, or `composer foxy:audit` needs it: +- Manager validation now happens when an install, update or explicit audit needs it. CI that depended on an earlier + failure must run `composer install` or `composer foxy:audit`. -- Composer commands that need no manager (`about`, `show`, `validate`, ...) run without probing one and never fail - because a manager is missing or unsupported. -- An unknown `manager` value, several recognized lockfiles, a lockfile whose manager is not installed, or no manager at - all are reported by the first command that needs the manager; `composer foxy:audit` reports them with exit status - `2`. - -A CI job that relied on any Composer command failing early because of a missing manager must run `composer install` -or `composer foxy:audit` to keep that check. - -### Native manager - -- `composer foxy:audit` audits the `native` manager through the registry's bulk advisories endpoint - (`POST /-/npm/v1/security/advisories/bulk`). `--no-dev` is rejected, as for Deno, because the native manager - installs the development dependencies together with the others. A private registry must implement that endpoint - (Verdaccio: enable its `audit` middleware); otherwise the audit fails with status `2`. -- The install directory gains a hidden `.foxy-installed.json` file that records what Foxy installed. The first install - after the upgrade extracts every registry package once and writes it; later installs keep the registry packages - whose directory and recorded entry still match `foxy.lock`, copy the `file:` packages again, and report only what - changed, printing `Nothing to install, update or remove` when nothing did. Run Composer with `-v` to list the kept - and refreshed packages and `-vv` to follow the registry traffic. Delete the file to extract every registry package - again. - -### Rollbacks - -- The `package.json` restore that follows a failed dependency merge or manager run now happens before the - `foxy.post-solve` event. A `post-solve` listener that throws no longer triggers it; only the Composer rollback runs. -- The Composer rollback runs on a private event dispatcher: command and autoload events never reach the project's - scripts or other plugins. Package events (`pre-package-install`, `post-package-update`, ...) are still dispatched by - Composer's installation manager and do reach them, where 0.3 skipped them. A root script bound to a package event - that must not run during a rollback has to guard itself. - -### PHP API - -The classes that custom managers, audit parsers, and CVE resolvers extend changed in this release. A project that -configures Foxy through `composer.json` is not affected. One change binds silently: -`AbstractAssetManager::buildCommand()` and `buildUnconfiguredCommand()` lost their first parameter, the default binary, -so a subclass that still passes it has the binary bound as the action. Drop that argument and return a custom binary -from `getDefaultBinary()`. - -## Upgrading from 0.2.x to 0.3.x - -### Runtime requirements - -Before updating, make sure the environment provides: - -- PHP 8.3 or later. PHP 8.1 and 8.2 are not supported. -- Composer 2.10.2 or later. -- One supported frontend manager when Foxy runs it or audits with it: Bun `^1.4.0`, Deno `^2.9.7`, npm `>=10.9.8`, - pnpm `^11.23.0`, or Yarn `^4.18.0`. npm needs a Node.js version its release supports, pnpm needs Node.js - `>=22.13.0`, and Yarn needs Node.js `>=18.12.0` on a release that still receives security updates. - -Earlier manager versions are rejected. The `manager-version` setting can narrow the built-in constraint for a -project but cannot widen it. The Ctype and Mbstring extensions are no longer required. - -### Update the constraint and authorize the plugin - -```bash -composer config allow-plugins.php-forge/foxy true -composer require php-forge/foxy:^0.3 --with-all-dependencies -``` - -The equivalent `composer.json` configuration is: - -```json -{ - "require": { - "php-forge/foxy": "^0.3" - }, - "config": { - "allow-plugins": { - "php-forge/foxy": true - } - } -} -``` - -A library that keeps Foxy in `require-dev` updates that constraint to `^0.3` as well. - -### Choose the manager - -Foxy selects the manager from a single recognized lockfile or from an available executable. Several recognized -lockfiles require an explicit choice. For predictable upgrades and CI runs, configure the manager and commit its -lockfile: - -```json -{ - "config": { - "foxy": { - "manager": "npm" - } - } -} -``` - -Remove stale lockfiles of other managers before the first Composer operation with Foxy 0.3, then bring the manager -to a supported version: - -- **npm**: `npm --version` must satisfy `>=10.9.8`; run `npm install` and commit the `package-lock.json` changes. npm - 10.9.8 requires Node.js `^18.17.0 || >=20.5.0`; see the [npm documentation](https://docs.npmjs.com/cli/) for the - release in use. -- **pnpm**: pnpm 11 requires Node.js 22. Upgrade Node.js, then run `pnpm self-update 11`, - `pnpx codemod run pnpm-v10-to-v11`, and `pnpm install`; `pnpm --version` must satisfy `^11.23.0`. Review the codemod - result with the [pnpm migration guide](https://pnpm.io/migration) and commit `pnpm-lock.yaml` and the configuration - changes. -- **Bun**: run `bun upgrade`; `bun --version` must satisfy `^1.4.0`. Foxy recognizes only the text lockfile, so convert - a legacy `bun.lockb` without changing the resolved versions with - `bun install --save-text-lockfile --frozen-lockfile --lockfile-only`, commit `bun.lock`, and delete `bun.lockb`. A - project that relied on `yarn.lock` for Bun's automatic selection generates `bun.lock` or sets `manager` to `bun`. -- **Yarn**: Yarn 1, 2, and 3 are not supported. Install Corepack (`npm install --global corepack`, `corepack enable`), - run `yarn set version 4.x` and `yarn install`; `yarn --version` must satisfy `^4.18.0`. Commit the `packageManager` - field, `yarn.lock`, and the Yarn configuration. The [installation guide](https://yarnpkg.com/getting-started/install), - the [migration guide](https://yarnpkg.com/migration/guide), and the - [Yarn 4 release notes](https://yarnpkg.com/blog/release/4.0) document the configuration and command changes, - including the move from `.npmrc` or `.yarnrc` to `.yarnrc.yml`. - -### Embedded package metadata and paths - -Foxy copies into its local package representation only the metadata it needs to resolve dependencies and check -compatibility. Lifecycle `scripts`, `devDependencies`, executable definitions, entry points, and publishing metadata of -an installed Composer dependency are no longer propagated. A library moves its runtime requirements to -`dependencies`, `optionalDependencies`, or `peerDependencies`, ships prebuilt assets with the Composer package, or -asks the consuming application to declare its own build steps in `package.json`. - -A `root-package-json-dir` of a library must resolve inside that library's installation directory; a value that leaves -it is rejected with a `RuntimeException`. Foxy regenerates the local `file:` references relative to the consuming -`package.json`: review a project with a custom root after the first update and commit the resulting manifest changes. - -Foxy resets `composer-asset-dir` recursively and therefore checks that it owns the directory. Filesystem, project, -and vendor roots, directories containing either, and symbolic links are rejected. An existing, non-empty custom -directory must contain Foxy's `.foxy-managed` marker: before upgrading, verify that the directory holds only generated -Foxy data, empty it, and let the first Foxy 0.3 run recreate and mark it. A new or empty directory is marked -automatically. - -### Fallbacks and disabled modes - -Both fallbacks stay enabled by default; a project that disabled one should confirm that keeping partial state after a -manager error is still intended. - -The asset rollback runs when package merging or the manager fails. The Composer rollback runs after every solve -exception and every non-zero manager result. When the Composer restore succeeds, Foxy rethrows the original error; -when it fails too, Foxy reports the rollback error with the original failure as its previous exception. The rollback -does not run project scripts, and a non-zero result of the rollback installer is reported instead of being treated as a -success. - -The rollback snapshot of `composer.lock` and the vendor directory is taken at Composer's `pre-operations-exec` event. -The root `composer.json` is not part of it, and `composer require` or `composer remove` may already have changed it: -inspect and, when necessary, revert the root manifest after a failed operation. - -`enabled: false` bypasses manager discovery, fallback snapshots, package merging, and manager execution. -`run-asset-manager: false` keeps package merging but skips manager probing, version validation, execution, and the -npm cleanup of `node_modules/@composer-asset/*`. `composer foxy:audit` still validates and invokes the selected -manager in that mode; it never installs, updates, or repairs dependencies. + + +- Native audits require the registry bulk advisories endpoint and reject `--no-dev`; use a compatible registry and audit + the complete graph. The first native install writes `.foxy-installed.json` and extracts registry packages once. + + + +- Asset restoration now precedes `foxy.post-solve`; listeners that throw there no longer trigger that restoration. + Composer rollback now dispatches package events: guard root package-event scripts that must not run during rollback. + + + +- Custom `AbstractAssetManager` subclasses must remove the former default-binary argument to `buildCommand()` and + `buildUnconfiguredCommand()` and override `getDefaultBinary()` when needed. + + + +## 0.3.0 + + + +- Upgrade to PHP 8.3 or later and Composer 2.10.2 or later, and install a supported manager version when execution or + audit requires one; see the [requirements](docs/index.md#requirements). + + + +- Update the Foxy constraint to `^0.3` and explicitly allow the Composer plugin. + + + +- Select a manager explicitly when several recognized lockfiles exist, remove obsolete lockfiles and upgrade older + manager versions as described in the [migration guide](docs/migration.md#choose-the-manager). + + + +- Move runtime frontend requirements into `dependencies`, `optionalDependencies` or `peerDependencies`; embedded + lifecycle scripts and other publishing metadata are no longer propagated. Keep embedded manifests within the library + installation directory and verify custom generated directories contain only Foxy-managed data. + + + +- Review fallback settings and the root `composer.json` after failed `require` or `remove` operations: Composer rollback + does not restore that file. Disabled manager execution still allows explicit audits. diff --git a/composer.json b/composer.json index d7785ee..d41a364 100644 --- a/composer.json +++ b/composer.json @@ -64,23 +64,69 @@ }, "extra": { "class": "Foxy\\Foxy", + "skills": { + "source": "skills" + }, "branch-alias": { "dev-main": "0.5.x-dev" } }, "scripts": { - "check-dependencies": "./vendor/bin/composer-require-checker check", - "ecs": "./vendor/bin/ecs --fix", + "actionlint": "actionlint", + "check-dependencies": "composer-require-checker check", + "codespell": "codespell --config .github/linters/.codespellrc", + "ecs": "ecs check --fix", + "ecs-check": "ecs check --ansi", + "editorconfig-checker": "editorconfig-checker -config .github/linters/.editorconfig-checker.json", + "gitleaks": "gitleaks git --config .github/linters/.gitleaks.toml --redact", + "markdownlint": "markdownlint-cli2 --config .github/linters/.markdown-lint.yml '**/*.md'", "mutation": [ "Composer\\Config::disableProcessTimeout", "@php -d memory_limit=-1 ./vendor/bin/infection --threads=4 --with-uncovered --min-msi=100 --min-covered-msi=100" ], "mutation-static": [ "Composer\\Config::disableProcessTimeout", - "@php -d memory_limit=-1 ./vendor/bin/infection --threads=4 --with-uncovered --min-msi=100 --min-covered-msi=100 --static-analysis-tool=phpstan --static-analysis-tool-options='--memory-limit=-1'" + "@php -d memory_limit=-1 ./vendor/bin/infection --initial-tests-php-options='-d memory_limit=-1' --test-framework-extra-args='-d memory_limit=-1' --threads=4 --with-uncovered --min-msi=100 --min-covered-msi=100 --static-analysis-tool=phpstan --static-analysis-tool-options='--memory-limit=-1'" ], - "rector": "./vendor/bin/rector process", - "static": "./vendor/bin/phpstan --memory-limit=-1", - "tests": "./vendor/bin/phpunit" + "prettier": "prettier --check --config .prettierrc.json --ignore-path .prettierignore '**/*.md' '**/*.{yml,yaml}'", + "quality": [ + "@actionlint", + "@codespell", + "@editorconfig-checker", + "@markdownlint", + "@prettier", + "@yamllint" + ], + "rector": "rector process", + "rector-check": "rector process --dry-run", + "security": [ + "@zizmor", + "@gitleaks" + ], + "static": "phpstan --memory-limit=-1", + "tests": "phpunit", + "yamllint": "yamllint -c .github/linters/.yamllint.yml .github", + "zizmor": "zizmor --persona=pedantic .github" + }, + "scripts-descriptions": { + "actionlint": "Lints GitHub Actions workflows with actionlint.", + "check-dependencies": "Verifies that used symbols are declared as Composer dependencies.", + "codespell": "Checks spelling with codespell.", + "ecs": "Applies coding standard fixes with ECS.", + "ecs-check": "Checks coding standards with ECS without modifying files.", + "editorconfig-checker": "Checks files against the EditorConfig rules.", + "gitleaks": "Scans Git history for secrets with Gitleaks.", + "markdownlint": "Lints Markdown files with markdownlint-cli2.", + "mutation": "Runs mutation testing with Infection.", + "mutation-static": "Runs mutation testing with Infection and PHPStan static analysis.", + "prettier": "Checks Markdown and YAML formatting with Prettier.", + "quality": "Runs all repository quality checks.", + "rector": "Applies automated refactoring with Rector.", + "rector-check": "Previews Rector refactoring without modifying files.", + "security": "Runs the workflow security and secret scanning checks.", + "static": "Runs static analysis with PHPStan.", + "tests": "Runs PHPUnit.", + "yamllint": "Lints YAML files with yamllint.", + "zizmor": "Audits GitHub Actions workflows with zizmor." } } diff --git a/docs/agents.md b/docs/agents.md new file mode 100644 index 0000000..3dab28d --- /dev/null +++ b/docs/agents.md @@ -0,0 +1,53 @@ +# AI assistance + +The canonical source is [`skills/php-forge-foxy/SKILL.md`](../skills/php-forge-foxy/SKILL.md). It helps agents configure +Foxy, select a frontend manager and verify merges and audits against the installed revision. + +## Install in an application + +### Automatic Composer installation + +Complete the [one-time Composer setup](https://yii2-framework.github.io/docs/project/ai-agents/#composer-installation) +for `llm/skills`, enable `auto-sync` in `skills.json`, and explicitly trust `php-forge/foxy` in +`dependencies.composer.trusted`. The installed package must include its skill and declare `extra.skills.source`. + +```bash +composer install +composer skills:show +``` + +With `auto-sync` disabled, run `composer skills:update` after installing or updating dependencies. Installing Foxy alone +does not make its skill discoverable from the application root. + +### Optional npx installation + +List and install the skill from a checkout or a release that contains it, using the application root: + +```bash +npx skills@1.7.2 add /absolute/path/to/foxy --list +npx skills@1.7.2 add /absolute/path/to/foxy \ + --skill php-forge-foxy --agent claude-code codex opencode +``` + +A vendor path can be used when its installed version includes the skill. List a remote release before installing it; +local changes are not necessarily published. Installer links and copies are outputs, not separately maintained sources. + +## Discovery and verification + +The checkout's `.agents/skills/` and `.claude/skills/` links point to the canonical bundle. For manual installation copy +or link the whole `php-forge-foxy` directory under the client's project skill directory. Codex uses `.agents/skills/` +and can invoke `$php-forge-foxy`; Claude Code uses `.claude/skills/` and `/php-forge-foxy`; OpenCode can use +`.agents/skills/` through its skill tool. + +Verify installation, discovery in a fresh client session, explicit activation, and successful task behavior separately. +`--list` and manually reading the skill do not prove client activation. Follow the installed revision's guides and check +the generated manifest and selected manager in a disposable project. + +## Next steps + +- ๐Ÿ“š [Getting started](index.md) +- โš™๏ธ [Configuration reference](config.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) +- ๐Ÿงช [Testing guide](testing.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/config.md b/docs/config.md index 30f86ce..578633a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -23,16 +23,16 @@ Values are resolved in this order, from highest to lowest priority: 5. Foxy defaults. Global values must be placed under `config.foxy` in one of the two Composer home files. Composer does not accept -arbitrary nested options through `composer global config`, but `composer global config --editor` can be used to edit -the global file directly. +arbitrary nested options through `composer global config`, but `composer global config --editor` can be used to edit the +global file directly. ## Environment variables Convert an option name to uppercase, replace hyphens with underscores, and prefix it with `FOXY__`. For example, `run-asset-manager` becomes `FOXY__RUN_ASSET_MANAGER`. -Foxy accepts strings, integers, case-insensitive boolean values (`true`, `false`, `yes`, `no`, `y`, `n`, `1`, and -`0`), and JSON arrays or objects. +Foxy accepts strings, integers, case-insensitive boolean values (`true`, `false`, `yes`, `no`, `y`, `n`, `1`, and `0`), +and JSON arrays or objects. ```bash FOXY__ENABLED=false composer install @@ -103,9 +103,9 @@ Foxy resolves the manager when the first install, update, or audit needs it, not Composer commands that do none of these (for example `about`, `show`, or `validate`) never probe an executable. Selection errors are therefore reported by the first command that needs the manager. -When `run-asset-manager` is `false`, automatic selection does not probe executables. Foxy uses the manager identified -by a single recognized lockfile, or npm as the manifest adapter when no lockfile exists. Multiple lockfiles still -require an explicit `manager` value. +When `run-asset-manager` is `false`, automatic selection does not probe executables. Foxy uses the manager identified by +a single recognized lockfile, or npm as the manifest adapter when no lockfile exists. Multiple lockfiles still require +an explicit `manager` value. ## Manager version constraints @@ -123,11 +123,10 @@ The `native` manager has no binary and no version constraint; `manager-version`, `manager-install-options`, `manager-update-options`, and `manager-timeout` do not apply to it. The `manager-version` option adds another Composer constraint that is evaluated together with the built-in constraint. -It can narrow the accepted versions for a project, but it cannot replace or widen Foxy's supported range. -Foxy treats the reported value as one concrete release and validates it from `root-package-json-dir` before every -manager command. During automatic Composer processing, `run-asset-manager=false` prevents probing, execution, and -validation of the manager binary. An explicit `composer foxy:audit` remains an exception because it is a direct user -request. +It can narrow the accepted versions for a project, but it cannot replace or widen Foxy's supported range. Foxy treats +the reported value as one concrete release and validates it from `root-package-json-dir` before every manager command. +During automatic Composer processing, `run-asset-manager=false` prevents probing, execution, and validation of the +manager binary. An explicit `composer foxy:audit` remains an exception because it is a direct user request. Narrow the npm constraint for one project: @@ -157,8 +156,8 @@ Manager-prefixed options may also use a map when a shared configuration supports } ``` -For example, configuring npm with `<10.9.8` does not enable an older release because the built-in `>=10.9.8` -constraint remains in force. Remove `manager-version` to accept the complete built-in range for the selected manager. +For example, configuring npm with `<10.9.8` does not enable an older release because the built-in `>=10.9.8` constraint +remains in force. Remove `manager-version` to accept the complete built-in range for the selected manager. ## Manager executable and options @@ -190,11 +189,11 @@ Use the three manager option settings only when native manager configuration fil } ``` -These values are appended to an external command. Treat project, global, and environment configuration as trusted -input. Prefer native files such as `.npmrc`, `.yarnrc.yml`, `pnpm-workspace.yaml`, or `bunfig.toml` where appropriate. -They apply to install and update operations only. `foxy:audit` owns its machine-output, advisory-filter, and -dependency-scope flags and does not inherit these options. It does honor `manager-bin`, `manager-version`, -`manager-timeout`, and `root-package-json-dir`. +These values are appended to an external command. Treat project, global, and environment configuration as trusted input. +Prefer native files such as `.npmrc`, `.yarnrc.yml`, `pnpm-workspace.yaml`, or `bunfig.toml` where appropriate. They +apply to install and update operations only. `foxy:audit` owns its machine-output, advisory-filter, and dependency-scope +flags and does not inherit these options. It does honor `manager-bin`, `manager-version`, `manager-timeout`, and +`root-package-json-dir`. Where the native manager supports an explicit override, Foxy neutralizes inherited settings that could exclude dependencies or advisories. Bun 1.4 cannot reset every inherited dependency-scope setting without also discarding @@ -222,10 +221,10 @@ registers each Composer asset directory as a member of the `workspaces` list in - Existing `workspaces` entries are preserved and kept before the Foxy-managed members. Foxy removes a member when its Composer asset is removed, and removes the `workspaces` field when no entry remains. - `workspaces` must be a list of strings. The object form with a `packages` key is rejected. -- Composer assets must be located in a subdirectory of the root package directory because Deno rejects workspace - members outside it. Foxy writes each member as a normalized path relative to that directory. A - `root-package-json-dir` that excludes the Composer vendor directory, or a `composer-asset-dir` outside the root - package directory, is not supported with Deno. +- Composer assets must be located in a subdirectory of the root package directory because Deno rejects workspace members + outside it. Foxy writes each member as a normalized path relative to that directory. A `root-package-json-dir` that + excludes the Composer vendor directory, or a `composer-asset-dir` outside the root package directory, is not supported + with Deno. - Updates run `deno update --lockfile-only --recursive && deno install`. The first step updates `deno.lock` within the declared version ranges, and the second installs the locked dependencies. `manager-options` applies to both steps, `manager-update-options` to the update step, and `manager-install-options` to the install step. @@ -261,47 +260,47 @@ Set `manager` to `native` to install the frontend dependencies without Bun, Deno Foxy then performs the installation itself, in PHP: -- It reads `dependencies`, `devDependencies`, and `optionalDependencies` from the merged root `package.json`; as in - npm, an `optionalDependencies` entry overrides a `dependencies` entry of the same name. Each `file:` entry (the - Composer assets and any other local package) is copied into `node_modules/`, and its own `dependencies`, - non-optional `peerDependencies`, and `optionalDependencies` join the resolution with the same override rule. -- It resolves every other dependency against the registry with asset-packagist semantics: a flat `node_modules` - with one version per package. Constraints from the root manifest, the local packages, and the transitive - `dependencies`, `peerDependencies`, and `optionalDependencies` are intersected per package name, and the highest - satisfying version wins. When a later constraint forces a package to a lower version, the constraints that its - abandoned version had introduced are withdrawn, and optional dependencies never block a required one. Dist-tags - such as `latest` or `next` are honored. The resolver never backtracks to an older version of a package to escape - a conflict that the dependencies of its highest acceptable version introduce; such a conflict, like one that only - nested `node_modules` could solve, is reported as an error that names every constraint and its origin. Install - such a project with a JavaScript manager instead. - Unsatisfiable or missing optional dependencies are skipped with a warning, together with the whole subtree of an - optional package whose own dependencies cannot be installed, and a deprecated selection is reported with the +- It reads `dependencies`, `devDependencies`, and `optionalDependencies` from the merged root `package.json`; as in npm, + an `optionalDependencies` entry overrides a `dependencies` entry of the same name. Each `file:` entry (the Composer + assets and any other local package) is copied into `node_modules/`, and its own `dependencies`, non-optional + `peerDependencies`, and `optionalDependencies` join the resolution with the same override rule. +- It resolves every other dependency against the registry with asset-packagist semantics: a flat `node_modules` with one + version per package. Constraints from the root manifest, the local packages, and the transitive `dependencies`, + `peerDependencies`, and `optionalDependencies` are intersected per package name, and the highest satisfying version + wins. When a later constraint forces a package to a lower version, the constraints that its abandoned version had + introduced are withdrawn, and optional dependencies never block a required one. Dist-tags such as `latest` or `next` + are honored. The resolver never backtracks to an older version of a package to escape a conflict that the dependencies + of its highest acceptable version introduce; such a conflict, like one that only nested `node_modules` could solve, is + reported as an error that names every constraint and its origin. Install such a project with a JavaScript manager + instead. Unsatisfiable or missing optional dependencies are skipped with a warning, together with the whole subtree of + an optional package whose own dependencies cannot be installed, and a deprecated selection is reported with the registry's deprecation text. A `file:` package keeps the version its manifest declares; a constraint from another - package that this version does not satisfy is reported as a conflict, unless the `file:` entry is itself optional, - in which case the local package is dropped like any other optional dependency. -- It downloads each tarball from the URL published by the registry, verifies it against the registry's - `integrity` value (SHA-512, with the legacy SHA-1 `shasum` as a fallback), and extracts it into - `node_modules/`, stripping the archive's top-level directory like npm does. Tarballs are cached in - `/foxy/` and are subject to Composer's `cache-files-ttl`, `cache-files-maxsize`, and - `cache-read-only` settings; every cached tarball is verified again before use. -- It writes `foxy.lock`, which records the root requirements and the requirements of every local package (regular - and optional ones apart), and the selected version, tarball URL, and integrity of every installed package. `composer install` with a lock that - matches the current manifests installs exactly those tarballs without a single metadata request; - `composer update`, or a lock that no longer matches, resolves again and rewrites the lock. Commit `foxy.lock`. + package that this version does not satisfy is reported as a conflict, unless the `file:` entry is itself optional, in + which case the local package is dropped like any other optional dependency. +- It downloads each tarball from the URL published by the registry, verifies it against the registry's `integrity` value + (SHA-512, with the legacy SHA-1 `shasum` as a fallback), and extracts it into `node_modules/`, stripping the + archive's top-level directory like npm does. Tarballs are cached in `/foxy/` and are subject to + Composer's `cache-files-ttl`, `cache-files-maxsize`, and `cache-read-only` settings; every cached tarball is verified + again before use. +- It writes `foxy.lock`, which records the root requirements and the requirements of every local package (regular and + optional ones apart), and the selected version, tarball URL, and integrity of every installed package. + `composer install` with a lock that matches the current manifests installs exactly those tarballs without a single + metadata request; `composer update`, or a lock that no longer matches, resolves again and rewrites the lock. Commit + `foxy.lock`. - It records what it installed in `.foxy-installed.json` inside the install directory, the counterpart of npm's `node_modules/.package-lock.json`: the `foxy.lock` entry of every package left in place. A registry package whose directory exists and whose entry matches the lock is kept untouched, so a warm install extracts nothing; a package - whose entry differs or whose directory is missing is extracted again. `file:` packages are always copied again, - since their content can change without a version change. The file is removed at the start of each installation and - written at its end, so an interrupted installation leaves none behind. A missing or malformed file only forces every - registry package to be extracted again, as happens once on the first install that creates it; deleting it is always - safe. Like every dot-entry, the pruning leaves it alone. Do not commit it. -- It removes entries of the install directory that are not part of the installation (dot-entries such as `.bin` - are left alone). - -The install directory is `node_modules` next to `package.json` by default. `native-install-dir` changes it; a -relative value is resolved from `root-package-json-dir`, an absolute value is used as is. A Yii 2 application that -keeps the framework's default `@npm` alias can install straight into `vendor/npm-asset`: + whose entry differs or whose directory is missing is extracted again. `file:` packages are always copied again, since + their content can change without a version change. The file is removed at the start of each installation and written + at its end, so an interrupted installation leaves none behind. A missing or malformed file only forces every registry + package to be extracted again, as happens once on the first install that creates it; deleting it is always safe. Like + every dot-entry, the pruning leaves it alone. Do not commit it. +- It removes entries of the install directory that are not part of the installation (dot-entries such as `.bin` are left + alone). + +The install directory is `node_modules` next to `package.json` by default. `native-install-dir` changes it; a relative +value is resolved from `root-package-json-dir`, an absolute value is used as is. A Yii 2 application that keeps the +framework's default `@npm` alias can install straight into `vendor/npm-asset`: ```json { @@ -314,16 +313,15 @@ keeps the framework's default `@npm` alias can install straight into `vendor/npm } ``` -Foxy owns that directory: every entry that the lock does not list is removed on every install, except entries whose -name starts with a dot (such as `.bin`), so do not point it at a directory that also receives asset-packagist -packages from Composer. The root package directory, any of its parents, filesystem roots, and a directory that -overlaps a local `file:` package in either direction are rejected; symbolic links in the path are resolved before -that check as far as PHP's `realpath()` resolves them, which is incomplete on Windows, so treat the check as lexical -there. A symbolic link found inside the directory is removed as a link, never followed. Package -names read from `package.json`, `foxy.lock`, and the registry must follow npm's name grammar (an optional `@scope/` -followed by a name of letters, digits, `.`, `_`, and `-`, up to 214 characters); any other name is rejected before a -path is built from it. Scoped packages keep the npm layout (`vendor/npm-asset/@popperjs/core`), not asset-packagist's -`popperjs--core` form. +Foxy owns that directory: every entry that the lock does not list is removed on every install, except entries whose name +starts with a dot (such as `.bin`), so do not point it at a directory that also receives asset-packagist packages from +Composer. The root package directory, any of its parents, filesystem roots, and a directory that overlaps a local +`file:` package in either direction are rejected; symbolic links in the path are resolved before that check as far as +PHP's `realpath()` resolves them, which is incomplete on Windows, so treat the check as lexical there. A symbolic link +found inside the directory is removed as a link, never followed. Package names read from `package.json`, `foxy.lock`, +and the registry must follow npm's name grammar (an optional `@scope/` followed by a name of letters, digits, `.`, `_`, +and `-`, up to 214 characters); any other name is rejected before a path is built from it. Scoped packages keep the npm +layout (`vendor/npm-asset/@popperjs/core`), not asset-packagist's `popperjs--core` form. The native manager requires the PHP `zlib` extension. Its output follows Composer's verbosity levels: at normal verbosity it prints only changes (`Installing`, `Updating`, and `Removing` lines), or @@ -331,13 +329,13 @@ verbosity it prints only changes (`Installing`, `Updating`, and `Removing` lines unchanged `file:` package copied again; `-vv` adds the registry traffic (metadata requests, tarball downloads, prefetches, and cache hits); `-vvv` adds Composer's own HTTP log. -Its registry requests go through Composer's HTTP layer: proxies, `cafile`, `disable-tls`, and the credentials stored -in `auth.json` for the registry host all apply, so a private registry can be configured with `registry-url` plus -Composer authentication. Credentials are matched against the request's `host:port` exactly as written in -`registry-url`: `localhost` and `127.0.0.1` are not aliases of each other, and the `auth.json` key must include the -port when it is not the scheme's default (`npm.example.com:4873`). A plain `http://` registry also needs Composer's -`config.secure-http` set to `false`. A registry that answers 401 or 403 fails the request with Composer's error text, -as any other registry failure does: +Its registry requests go through Composer's HTTP layer: proxies, `cafile`, `disable-tls`, and the credentials stored in +`auth.json` for the registry host all apply, so a private registry can be configured with `registry-url` plus Composer +authentication. Credentials are matched against the request's `host:port` exactly as written in `registry-url`: +`localhost` and `127.0.0.1` are not aliases of each other, and the `auth.json` key must include the port when it is not +the scheme's default (`npm.example.com:4873`). A plain `http://` registry also needs Composer's `config.secure-http` set +to `false`. A registry that answers 401 or 403 fails the request with Composer's error text, as any other registry +failure does: ```json { @@ -357,27 +355,27 @@ implement it, and a registry that does not fails the audit with status `2`. Verd `audit` middleware enabled (`middlewares: audit: enabled: true`); its default configuration answers 404. HTTP 429 responses are retried up to three times, honoring `Retry-After` up to 60 seconds; other failures are reported immediately. Tarballs missing from the cache are first downloaded in parallel through Composer's HTTP downloader -(`COMPOSER_MAX_PARALLEL_HTTP` sets the limit, 12 by default) and then verified and installed one by one; a tarball -whose parallel download fails is requested again on its own, with the retries above, and its error is reported then. -With `cache-read-only` or an unwritable cache, tarballs are downloaded one by one only. +(`COMPOSER_MAX_PARALLEL_HTTP` sets the limit, 12 by default) and then verified and installed one by one; a tarball whose +parallel download fails is requested again on its own, with the retries above, and its error is reported then. With +`cache-read-only` or an unwritable cache, tarballs are downloaded one by one only. The native manager implements the subset that frontend assets need. It does not support: -- `npm:` aliases, `git`, `github:`, `http(s)`, `workspace:`, and `link:` specifications; each is rejected with an - error that names the dependency. +- `npm:` aliases, `git`, `github:`, `http(s)`, `workspace:`, and `link:` specifications; each is rejected with an error + that names the dependency. - Nested `node_modules`: two packages that require incompatible versions of the same dependency cannot be installed together. - Lifecycle scripts (`preinstall`, `install`, `postinstall`, `prepare`), `bin` links, `os`, `cpu`, and `engines` - filters, `overrides`, and `resolutions`. Scripts are never executed; packages that need a build step at install - time must be installed with a JavaScript manager. -- Prerelease identifiers that Composer's version parser does not understand, such as `5.0.0-next.3` or - `1.0.0-canary.1`: those versions are skipped, and a range or dist-tag that selects one is rejected. Prereleases - named `alpha`, `beta`, `RC`, `dev`, and `patch` are matched with npm's rule: only a range that names a prerelease - of the same `major.minor.patch` can select it. + filters, `overrides`, and `resolutions`. Scripts are never executed; packages that need a build step at install time + must be installed with a JavaScript manager. +- Prerelease identifiers that Composer's version parser does not understand, such as `5.0.0-next.3` or `1.0.0-canary.1`: + those versions are skipped, and a range or dist-tag that selects one is rejected. Prereleases named `alpha`, `beta`, + `RC`, `dev`, and `patch` are matched with npm's rule: only a range that names a prerelease of the same + `major.minor.patch` can select it. - Symbolic and hard links inside tarballs (skipped) and `..` or absolute entry paths (rejected). -Selection by availability is also disabled for this manager: without `manager: native` or a `foxy.lock` file, a -project without a JavaScript manager still reports `No asset manager was found.`. +Selection by availability is also disabled for this manager: without `manager: native` or a `foxy.lock` file, a project +without a JavaScript manager still reports `No asset manager was found.`. ## Manager timeout @@ -434,8 +432,8 @@ Both fallbacks are enabled by default. They can be controlled independently: Foxy restores `package.json` first and Composer state second, and attempts the Composer restoration even when the `package.json` restoration fails. It rethrows the original solve error when restoration succeeds. If a restoration also -fails, Foxy reports the rollback failure and retains the original error as the previous exception. Disabled fallbacks -do not create snapshots. +fails, Foxy reports the rollback failure and retains the original error as the previous exception. Disabled fallbacks do +not create snapshots. Composer lock and vendor state are captured at Composer's `pre-operations-exec` event. This is not a fully atomic transaction for `composer require` or `composer remove`: Composer may update the root `composer.json` before that event, @@ -516,15 +514,14 @@ Relative root-project paths are resolved from the Composer project directory. Ab supported. In an installed Composer library, the same option is resolved relative to that library's installation directory and -identifies the directory containing its embedded `package.json`. The resolved manifest must remain inside the -library's Composer installation directory. +identifies the directory containing its embedded `package.json`. The resolved manifest must remain inside the library's +Composer installation directory. ## Next steps - ๐Ÿ“š [Getting started](index.md) -- ๐Ÿ’ก [Usage guide](usage.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) - ๐Ÿ”Œ [Events reference](events.md) - โ“ [Frequently asked questions](faqs.md) -- ๐Ÿงช [Testing guide](testing.md) -- โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿ“– [README](../README.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/events.md b/docs/events.md index 6e5b89d..08e94d7 100644 --- a/docs/events.md +++ b/docs/events.md @@ -42,9 +42,9 @@ The path passed to `addAsset()` may identify a package definition file, which Fo ## post-solve `PostSolveEvent` exposes the common getters and `getRunResult()`, which returns the frontend manager process exit code. -The event is dispatched before Foxy attempts to restore captured Composer lock and vendor state after a non-zero -result; by then `package.json` has already been restored when `fallback-asset` is enabled. It is not dispatched when -manager execution throws before returning a result. An exception thrown by a listener restores Composer state only, not +The event is dispatched before Foxy attempts to restore captured Composer lock and vendor state after a non-zero result; +by then `package.json` has already been restored when `fallback-asset` is enabled. It is not dispatched when manager +execution throws before returning a result. An exception thrown by a listener restores Composer state only, not `package.json`. ## Subscriber example @@ -94,8 +94,7 @@ final class FoxySubscriber implements EventSubscriberInterface - ๐Ÿ“š [Getting started](index.md) - โš™๏ธ [Configuration reference](config.md) -- ๐Ÿ’ก [Usage guide](usage.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) - โ“ [Frequently asked questions](faqs.md) -- ๐Ÿงช [Testing guide](testing.md) -- โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿ“– [README](../README.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/faqs.md b/docs/faqs.md index d8dbe87..bed6612 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -2,8 +2,7 @@ ## Which PHP and Composer versions are required? -Foxy 0.4 requires PHP 8.3 or later and Composer 2.10.2 or later. See the -[requirements](index.md#requirements). +Foxy 0.4 requires PHP 8.3 or later and Composer 2.10.2 or later. See the [requirements](index.md#requirements). ## Why use Foxy instead of documenting frontend dependencies manually? @@ -12,8 +11,8 @@ automation, every application must copy dependency names and constraints manuall library release. Foxy creates local frontend package representations for eligible Composer dependencies, merges them into the project -`package.json`, and delegates version solving and installation to Bun, Deno, npm, pnpm, or Yarn, or performs both -itself with the `native` manager. +`package.json`, and delegates version solving and installation to Bun, Deno, npm, pnpm, or Yarn, or performs both itself +with the `native` manager. ## How does package activation work? @@ -38,35 +37,34 @@ different location. ## How does Foxy select a frontend manager? Set `config.foxy.manager` to `bun`, `deno`, `native`, `npm`, `pnpm`, or `yarn` for deterministic selection. When it is -omitted and manager execution is enabled, Foxy first looks for one recognized lockfile and then for an available -manager executable. With execution disabled, it uses a single recognized lockfile or npm as the manifest adapter -without probing binaries. Multiple recognized lockfiles require explicit selection in either mode. The `native` -manager is selected only explicitly or through its `foxy.lock` file, never by availability. The choice is made when the -first install, update, or audit needs a manager, so other Composer commands neither probe executables nor report -selection errors. +omitted and manager execution is enabled, Foxy first looks for one recognized lockfile and then for an available manager +executable. With execution disabled, it uses a single recognized lockfile or npm as the manifest adapter without probing +binaries. Multiple recognized lockfiles require explicit selection in either mode. The `native` manager is selected only +explicitly or through its `foxy.lock` file, never by availability. The choice is made when the first install, update, or +audit needs a manager, so other Composer commands neither probe executables nor report selection errors. Explicit selection and a committed lockfile are recommended for CI. ## Can Foxy install frontend dependencies without Node.js? -Yes. Set `config.foxy.manager` to `native`. Foxy reads the merged `package.json`, resolves every dependency against -the npm registry with one version per package, downloads and verifies the tarballs, extracts them into `node_modules`, -and records the result in `foxy.lock`. PHP with the `zlib` extension is the only requirement. The native manager does -not run lifecycle scripts and does not create `bin` links. See [Native manager](config.md#native-manager) for the -complete scope. +Yes. Set `config.foxy.manager` to `native`. Foxy reads the merged `package.json`, resolves every dependency against the +npm registry with one version per package, downloads and verifies the tarballs, extracts them into `node_modules`, and +records the result in `foxy.lock`. PHP with the `zlib` extension is the only requirement. The native manager does not +run lifecycle scripts and does not create `bin` links. See [Native manager](config.md#native-manager) for the complete +scope. ## How can frontend dependencies be checked for known CVEs? -Run `composer foxy:audit`. Foxy audits the dependency graph from the selected manager's own lockfile. The default -table, plain, and JSON formats identify the affected package, advisory, severity, vulnerable range, and available CVE +Run `composer foxy:audit`. Foxy audits the dependency graph from the selected manager's own lockfile. The default table, +plain, and JSON formats identify the affected package, advisory, severity, vulnerable range, and available CVE identifiers. Use `--format=summary` for compact counts in CI or `--format=json` for a versioned machine-readable document. The registry's audit response commonly contains a GHSA but no CVE. Foxy resolves that relationship through GitHub's -public advisory API unless `--no-cve` or `--format=summary` is supplied. The lookups run concurrently through -Composer's HTTP downloader (at most 12 at once; `COMPOSER_MAX_PARALLEL_HTTP` changes the limit), and a `github-oauth` -token in Composer's `auth.json` applies to them. A lookup failure marks only its own advisory as unavailable without -hiding the security finding itself. See [Security auditing](usage.md#security-auditing) for options and exit statuses. +public advisory API unless `--no-cve` or `--format=summary` is supplied. The lookups run concurrently through Composer's +HTTP downloader (at most 12 at once; `COMPOSER_MAX_PARALLEL_HTTP` changes the limit), and a `github-oauth` token in +Composer's `auth.json` applies to them. A lookup failure marks only its own advisory as unavailable without hiding the +security finding itself. See [Security auditing](usage.md#security-auditing) for options and exit statuses. ## Why is a dependency's package.json not detected? @@ -75,7 +73,8 @@ Check the following: 1. The package uses one of the documented activation methods. 2. Its `package.json` exists at the package root or configured Foxy root. 3. The root application's `enable-packages` configuration does not exclude it. -4. When manager execution is enabled, the selected frontend manager is installed and allowed by its configured version constraint. +4. When manager execution is enabled, the selected frontend manager is installed and allowed by its configured version + constraint. Composer must run before a standalone frontend manager command because Foxy creates the local package representations during Composer install and update operations. @@ -112,8 +111,8 @@ Foxy mock packages contain only metadata needed for runtime dependency resolutio lifecycle scripts, executable definitions, `devDependencies`, and unrelated publishing metadata prevents an installed Composer dependency from injecting those behaviors into the root frontend install. -Publish built assets with the Composer package, or configure intentional build commands and development dependencies -in the consuming application's own `package.json`. +Publish built assets with the Composer package, or configure intentional build commands and development dependencies in +the consuming application's own `package.json`. ## Why is my custom composer-asset-dir rejected? @@ -127,21 +126,21 @@ Composer. Foxy accepts an empty custom directory and creates the ownership marke ## How can the PHP memory limit be increased? -See Composer's [memory limit troubleshooting guide](https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors). +See Composer's +[memory limit troubleshooting guide](https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors). ## How is Foxy related to Fxp Composer Asset Plugin? [Fxp Composer Asset Plugin](https://github.com/fxpio/composer-asset-plugin) made frontend packages available through -Composer's solver. Foxy takes the opposite approach: Composer packages expose frontend definitions and a native -frontend manager performs frontend version solving and installation. Foxy is not backward compatible with Fxp Composer -Asset Plugin configuration. +Composer's solver. Foxy takes the opposite approach: Composer packages expose frontend definitions and a native frontend +manager performs frontend version solving and installation. Foxy is not backward compatible with Fxp Composer Asset +Plugin configuration. ## Next steps - ๐Ÿ“š [Getting started](index.md) - โš™๏ธ [Configuration reference](config.md) -- ๐Ÿ’ก [Usage guide](usage.md) -- ๐Ÿ”Œ [Events reference](events.md) -- ๐Ÿงช [Testing guide](testing.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) - โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿ“– [README](../README.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/index.md b/docs/index.md index 9709db1..3d930d7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,6 +44,14 @@ composer require php-forge/foxy:^0.4 The plugin is installed in the configured Composer vendor directory, normally `vendor/php-forge/foxy`. +### Verify the installation + +Check the installed package version before configuring and installing project assets: + +```bash +composer show php-forge/foxy +``` + ## Manager selection Set `config.foxy.manager` to `bun`, `deno`, `native`, `npm`, `pnpm`, or `yarn` when reproducible manager selection is @@ -61,9 +69,8 @@ Commit the selected manager's own lockfile and use the same explicit manager in ## Next steps - โš™๏ธ [Configuration reference](config.md) -- ๐Ÿ’ก [Usage guide](usage.md) -- ๐Ÿ”Œ [Events reference](events.md) -- โ“ [Frequently asked questions](faqs.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) +- ๐Ÿค– [AI agents guide](agents.md) - โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿงช [Testing guide](testing.md) -- ๐Ÿ“– [README](../README.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..1644a1e --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,179 @@ +# Migration guide + +The [upgrade guide](../UPGRADE.md) is the compact incompatibility checklist. This guide provides the commands, +configuration examples and operational detail for each required migration. Features that need no migration are described +in the [documentation](index.md) and the [changelog](../CHANGELOG.md). + +## Upgrading from 0.3.x to 0.4.x + +### Update the constraint + +```bash +composer require php-forge/foxy:^0.4 --with-all-dependencies +``` + +Foxy 0.4 accepts `symfony/console` `^7.4 || ^8.0`. Console 8 requires PHP 8.4, so a project on PHP 8.3 keeps 7.4; +`--with-all-dependencies` lets Composer move the shared Symfony packages when the project allows it. + +### The manager is selected on first use + +Foxy no longer runs ` --version` each time Composer loads the plugin. The manager is selected when an install, +an update, or `composer foxy:audit` needs it: + +- Composer commands that need no manager (`about`, `show`, `validate`, ...) run without probing one and never fail + because a manager is missing or unsupported. +- An unknown `manager` value, several recognized lockfiles, a lockfile whose manager is not installed, or no manager at + all are reported by the first command that needs the manager; `composer foxy:audit` reports them with exit status `2`. + +A CI job that relied on any Composer command failing early because of a missing manager must run `composer install` or +`composer foxy:audit` to keep that check. + +### Native manager + +- `composer foxy:audit` audits the `native` manager through the registry's bulk advisories endpoint + (`POST /-/npm/v1/security/advisories/bulk`). `--no-dev` is rejected, as for Deno, because the native manager installs + the development dependencies together with the others. A private registry must implement that endpoint (Verdaccio: + enable its `audit` middleware); otherwise the audit fails with status `2`. +- The install directory gains a hidden `.foxy-installed.json` file that records what Foxy installed. The first install + after the upgrade extracts every registry package once and writes it; later installs keep the registry packages whose + directory and recorded entry still match `foxy.lock`, copy the `file:` packages again, and report only what changed, + printing `Nothing to install, update or remove` when nothing did. Run Composer with `-v` to list the kept and + refreshed packages and `-vv` to follow the registry traffic. Delete the file to extract every registry package again. + +### Rollbacks + +- The `package.json` restore that follows a failed dependency merge or manager run now happens before the + `foxy.post-solve` event. A `post-solve` listener that throws no longer triggers it; only the Composer rollback runs. +- The Composer rollback runs on a private event dispatcher: command and autoload events never reach the project's + scripts or other plugins. Package events (`pre-package-install`, `post-package-update`, ...) are still dispatched by + Composer's installation manager and do reach them, where 0.3 skipped them. A root script bound to a package event that + must not run during a rollback has to guard itself. + +### PHP API + +The classes that custom managers, audit parsers, and CVE resolvers extend changed in this release. A project that +configures Foxy through `composer.json` is not affected. One change binds silently: +`AbstractAssetManager::buildCommand()` and `buildUnconfiguredCommand()` lost their first parameter, the default binary, +so a subclass that still passes it has the binary bound as the action. Drop that argument and return a custom binary +from `getDefaultBinary()`. + +## Upgrading from 0.2.x to 0.3.x + +### Runtime requirements + +Before updating, make sure the environment provides: + +- PHP 8.3 or later. PHP 8.1 and 8.2 are not supported. +- Composer 2.10.2 or later. +- One supported frontend manager when Foxy runs it or audits with it: Bun `^1.4.0`, Deno `^2.9.7`, npm `>=10.9.8`, pnpm + `^11.23.0`, or Yarn `^4.18.0`. npm needs a Node.js version its release supports, pnpm needs Node.js `>=22.13.0`, and + Yarn needs Node.js `>=18.12.0` on a release that still receives security updates. + +Earlier manager versions are rejected. The `manager-version` setting can narrow the built-in constraint for a project +but cannot widen it. The Ctype and Mbstring extensions are no longer required. + +### Update the constraint and authorize the plugin + +```bash +composer config allow-plugins.php-forge/foxy true +composer require php-forge/foxy:^0.3 --with-all-dependencies +``` + +The equivalent `composer.json` configuration is: + +```json +{ + "require": { + "php-forge/foxy": "^0.3" + }, + "config": { + "allow-plugins": { + "php-forge/foxy": true + } + } +} +``` + +A library that keeps Foxy in `require-dev` updates that constraint to `^0.3` as well. + +### Choose the manager + +Foxy selects the manager from a single recognized lockfile or from an available executable. Several recognized lockfiles +require an explicit choice. For predictable upgrades and CI runs, configure the manager and commit its lockfile: + +```json +{ + "config": { + "foxy": { + "manager": "npm" + } + } +} +``` + +Remove stale lockfiles of other managers before the first Composer operation with Foxy 0.3, then bring the manager to a +supported version: + +- **npm**: `npm --version` must satisfy `>=10.9.8`; run `npm install` and commit the `package-lock.json` changes. npm + 10.9.8 requires Node.js `^18.17.0 || >=20.5.0`; see the [npm documentation](https://docs.npmjs.com/cli/) for the + release in use. +- **pnpm**: pnpm 11 requires Node.js 22. Upgrade Node.js, then run `pnpm self-update 11`, + `pnpx codemod run pnpm-v10-to-v11`, and `pnpm install`; `pnpm --version` must satisfy `^11.23.0`. Review the codemod + result with the [pnpm migration guide](https://pnpm.io/migration) and commit `pnpm-lock.yaml` and the configuration + changes. +- **Bun**: run `bun upgrade`; `bun --version` must satisfy `^1.4.0`. Foxy recognizes only the text lockfile, so convert + a legacy `bun.lockb` without changing the resolved versions with + `bun install --save-text-lockfile --frozen-lockfile --lockfile-only`, commit `bun.lock`, and delete `bun.lockb`. A + project that relied on `yarn.lock` for Bun's automatic selection generates `bun.lock` or sets `manager` to `bun`. +- **Yarn**: Yarn 1, 2, and 3 are not supported. Install Corepack (`npm install --global corepack`, `corepack enable`), + run `yarn set version 4.x` and `yarn install`; `yarn --version` must satisfy `^4.18.0`. Commit the `packageManager` + field, `yarn.lock`, and the Yarn configuration. The [installation guide](https://yarnpkg.com/getting-started/install), + the [migration guide](https://yarnpkg.com/migration/guide), and the + [Yarn 4 release notes](https://yarnpkg.com/blog/release/4.0) document the configuration and command changes, including + the move from `.npmrc` or `.yarnrc` to `.yarnrc.yml`. + +### Embedded package metadata and paths + +Foxy copies into its local package representation only the metadata it needs to resolve dependencies and check +compatibility. Lifecycle `scripts`, `devDependencies`, executable definitions, entry points, and publishing metadata of +an installed Composer dependency are no longer propagated. A library moves its runtime requirements to `dependencies`, +`optionalDependencies`, or `peerDependencies`, ships prebuilt assets with the Composer package, or asks the consuming +application to declare its own build steps in `package.json`. + +A `root-package-json-dir` of a library must resolve inside that library's installation directory; a value that leaves it +is rejected with a `RuntimeException`. Foxy regenerates the local `file:` references relative to the consuming +`package.json`: review a project with a custom root after the first update and commit the resulting manifest changes. + +Foxy resets `composer-asset-dir` recursively and therefore checks that it owns the directory. Filesystem, project, and +vendor roots, directories containing either, and symbolic links are rejected. An existing, non-empty custom directory +must contain Foxy's `.foxy-managed` marker: before upgrading, verify that the directory holds only generated Foxy data, +empty it, and let the first Foxy 0.3 run recreate and mark it. A new or empty directory is marked automatically. + +### Fallbacks and disabled modes + +Both fallbacks stay enabled by default; a project that disabled one should confirm that keeping partial state after a +manager error is still intended. + +The asset rollback runs when package merging or the manager fails. The Composer rollback runs after every solve +exception and every non-zero manager result. When the Composer restore succeeds, Foxy rethrows the original error; when +it fails too, Foxy reports the rollback error with the original failure as its previous exception. The rollback does not +run project scripts, and a non-zero result of the rollback installer is reported instead of being treated as a success. + +The rollback snapshot of `composer.lock` and the vendor directory is taken at Composer's `pre-operations-exec` event. +The root `composer.json` is not part of it, and `composer require` or `composer remove` may already have changed it: +inspect and, when necessary, revert the root manifest after a failed operation. + +`enabled: false` bypasses manager discovery, fallback snapshots, package merging, and manager execution. +`run-asset-manager: false` keeps package merging but skips manager probing, version validation, execution, and the npm +cleanup of `node_modules/@composer-asset/*`. `composer foxy:audit` still validates and invokes the selected manager in +that mode; it never installs, updates, or repairs dependencies. + +## Next steps + +- ๐Ÿ“š [Getting started](index.md) +- โš™๏ธ [Configuration reference](config.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) +- โฌ†๏ธ [Upgrade summary](../UPGRADE.md) +- ๐Ÿงช [Testing guide](testing.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/svgs/features-mobile.svg b/docs/svgs/features-mobile.svg index 8525f21..f4614f6 100644 --- a/docs/svgs/features-mobile.svg +++ b/docs/svgs/features-mobile.svg @@ -44,32 +44,32 @@
-

Dependency aggregation

-

Collects frontend requirements from installed Composer packages

+

Automatic selection

+

Detects one native lockfile, then an available executable

-

Native manager support

-

Delegates solving to Bun, Deno, npm, pnpm, or Yarn

+

Configurable recovery

+

Restores package.json and Composer lock/vendor state after failures

-

Automatic selection

-

Detects one native lockfile, then an available executable

+

Defensive I/O

+

Throws an explicit RuntimeException on read, write, or remove failures

-

Flexible project roots

-

Supports package.json outside Composer root via root-package-json-dir

+

Dependency aggregation

+

Collects frontend requirements from installed Composer packages

-

Protected mock directory

-

Validates paths and ownership before recursive replacement

+

Flexible project roots

+

Supports package.json outside Composer root via root-package-json-dir

-

Configurable recovery

-

Restores package.json and Composer lock/vendor state after failures

+

Native manager support

+

Delegates solving to Bun, Deno, npm, pnpm, or Yarn

-

Defensive I/O

-

Throws an explicit RuntimeException on read, write, or remove failures

+

Protected mock directory

+

Validates paths and ownership before recursive replacement

Stable execution

diff --git a/docs/svgs/features.svg b/docs/svgs/features.svg index f8b7b88..47da459 100644 --- a/docs/svgs/features.svg +++ b/docs/svgs/features.svg @@ -42,32 +42,32 @@
-

Dependency aggregation

-

Collects frontend requirements from installed Composer packages

+

Automatic selection

+

Detects one native lockfile, then an available executable

-

Native manager support

-

Delegates solving to Bun, Deno, npm, pnpm, or Yarn

+

Configurable recovery

+

Restores package.json and Composer lock/vendor state after failures

-

Automatic selection

-

Detects one native lockfile, then an available executable

+

Defensive I/O

+

Throws an explicit RuntimeException on read, write, or remove failures

-

Flexible project roots

-

Supports package.json outside Composer root via root-package-json-dir

+

Dependency aggregation

+

Collects frontend requirements from installed Composer packages

-

Protected mock directory

-

Validates paths and ownership before recursive replacement

+

Flexible project roots

+

Supports package.json outside Composer root via root-package-json-dir

-

Configurable recovery

-

Restores package.json and Composer lock/vendor state after failures

+

Native manager support

+

Delegates solving to Bun, Deno, npm, pnpm, or Yarn

-

Defensive I/O

-

Throws an explicit RuntimeException on read, write, or remove failures

+

Protected mock directory

+

Validates paths and ownership before recursive replacement

Stable execution

diff --git a/docs/testing.md b/docs/testing.md index 887d958..5e3919b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -4,16 +4,24 @@ This package provides a consistent set of [Composer](https://getcomposer.org/) s Tool references: +- [Actionlint](https://github.com/rhysd/actionlint) for GitHub Actions workflow linting. +- [Codespell](https://github.com/codespell-project/codespell) for spelling checks. - [Composer Require Checker](https://github.com/maglnet/ComposerRequireChecker) for dependency definition checks. - [Easy Coding Standard (ECS)](https://github.com/easy-coding-standard/easy-coding-standard) for coding standards. +- [EditorConfig Checker](https://github.com/editorconfig-checker/editorconfig-checker) for EditorConfig compliance. +- [Gitleaks](https://github.com/gitleaks/gitleaks) for secret scanning. - [Infection](https://infection.github.io/) for mutation testing. +- [Markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) for Markdown linting. - [PHPStan](https://phpstan.org/) for static analysis. - [PHPUnit](https://phpunit.de/) for unit tests. +- [Prettier](https://prettier.io/) for Markdown and YAML formatting checks. - [Rector](https://github.com/rectorphp/rector) for automated refactoring. +- [Yamllint](https://github.com/adrienverge/yamllint) for YAML linting. +- [Zizmor](https://github.com/zizmorcore/zizmor) for GitHub Actions security audits. ## Automated refactoring (Rector) -Run Rector to apply automated code refactoring. +Run Rector to apply automated code refactoring. This command modifies files. ```bash composer rector @@ -22,12 +30,18 @@ composer rector Validate Rector without modifying files. ```bash -composer rector -- --dry-run +composer rector-check ``` ## Coding standards (ECS) -Run Easy Coding Standard (ECS) and apply fixes. +Check coding standards without modifying files. + +```bash +composer ecs-check +``` + +Apply coding standard fixes. This command modifies files. ```bash composer ecs @@ -55,6 +69,29 @@ Run mutation testing with static analysis enabled. composer mutation-static ``` +## Repository quality checks + +Make actionlint (and its ShellCheck helper), codespell, EditorConfig Checker, markdownlint-cli2, Prettier and yamllint +available on `PATH`. Run all repository quality checks: + +```bash +composer quality +``` + +Each tool can also be run separately: + +```bash +composer actionlint +composer codespell +composer editorconfig-checker +composer markdownlint +composer prettier +composer yamllint +``` + +The codespell, EditorConfig Checker and Markdownlint configurations are in `.github/linters`. Prettier uses `.gitignore` +as its ignore path; yamllint checks `.github`. + ## Static analysis (PHPStan) Run static analysis. @@ -71,6 +108,25 @@ Run the full test suite. composer tests ``` +## Workflow security checks + +Make zizmor and Gitleaks available on `PATH`. Run both checks, or either one separately: + +```bash +composer security +composer zizmor +composer gitleaks +``` + +Zizmor uses the pedantic persona and may need GitHub API access for online audits. To request an offline audit: + +```bash +composer zizmor -- --offline +``` + +Report offline limitations and hosted CI results separately. Gitleaks scans Git history with the repository's +configuration. Review findings instead of disabling the checks. + ## Passing extra arguments Composer scripts support forwarding additional arguments using `--`. @@ -90,9 +146,7 @@ composer static -- --memory-limit=512M ## Next steps - ๐Ÿ“š [Getting started](index.md) -- โš™๏ธ [Configuration reference](config.md) -- ๐Ÿ’ก [Usage guide](usage.md) -- ๐Ÿ”Œ [Events reference](events.md) -- โ“ [Frequently asked questions](faqs.md) -- โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿ“– [README](../README.md) +- ๐Ÿ’ก [Usage guide and examples](usage.md) +- ๐Ÿค– [AI agents guide](agents.md) + +[Back to documentation](../README.md#documentation) diff --git a/docs/usage.md b/docs/usage.md index 0e70678..8cd8666 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -31,9 +31,40 @@ The project may provide its own frontend dependencies in `package.json`: } ``` -During Composer install and update operations, Foxy merges eligible Composer package assets into this file and runs -the selected frontend manager. Existing non-Foxy dependencies are preserved. With `"manager": "native"`, Foxy -installs the merged file itself and writes `foxy.lock`; see [Native manager](config.md#native-manager). +During Composer install and update operations, Foxy merges eligible Composer package assets into this file and runs the +selected frontend manager. Existing non-Foxy dependencies are preserved. With `"manager": "native"`, Foxy installs the +merged file itself and writes `foxy.lock`; see [Native manager](config.md#native-manager). + +### Merge manifests without running a frontend manager + +```json +{ + "config": { + "foxy": { + "run-asset-manager": false + } + } +} +``` + +Foxy still merges eligible packages; it skips automatic manager probing and execution. This is not an asset install, and +an explicit `composer foxy:audit` still validates and runs the selected manager. + +### Use a project manifest in `web/` + +```json +{ + "config": { + "foxy": { + "manager": "npm", + "root-package-json-dir": "web" + } + } +} +``` + +Foxy reads and writes `web/package.json` and runs npm from `web/`. Paths are relative to the Composer project. See +[package.json directory](config.md#packagejson-directory). ## Security auditing @@ -49,22 +80,22 @@ report version 2 starting with npm 10.9.8, the current report schemas emitted by npm registry's bulk advisories document read by the `native` manager; legacy report formats are rejected instead of being interpreted heuristically. -With the `native` manager, `composer foxy:audit` reads `foxy.lock` and asks the configured `registry-url` for -advisories in one request to npm's bulk advisories endpoint (`POST /-/npm/v1/security/advisories/bulk`), -sending every locked registry package with its version; local `file:` packages are not sent, and a lock without -registry packages is clean without a request. `--no-dev` is rejected with status `2` because the native manager -installs development dependencies together with the others. A registry that does not implement the endpoint fails -the audit with status `2`. +With the `native` manager, `composer foxy:audit` reads `foxy.lock` and asks the configured `registry-url` for advisories +in one request to npm's bulk advisories endpoint (`POST /-/npm/v1/security/advisories/bulk`), sending +every locked registry package with its version; local `file:` packages are not sent, and a lock without registry +packages is clean without a request. `--no-dev` is rejected with status `2` because the native manager installs +development dependencies together with the others. A registry that does not implement the endpoint fails the audit with +status `2`. Deno does not provide a machine-readable report. Foxy runs `deno audit --level=low` with `NO_COLOR=1` against -`deno.lock` and parses its text report strictly. The parser is validated against Deno 2.9.7 and returns status `2` -when the report layout is not recognized instead of guessing. Deno has no configuration setting that filters audit -results, and Foxy does not forward manager options to the audit command. +`deno.lock` and parses its text report strictly. The parser is validated against Deno 2.9.7 and returns status `2` when +the report layout is not recognized instead of guessing. Deno has no configuration setting that filters audit results, +and Foxy does not forward manager options to the audit command. Foxy reports every advisory returned by the manager. `--audit-level` controls only the CI exit threshold: ```bash -composer foxy:audit --audit-level=high +composer foxy:audit --audit-level=high --format=summary ``` Valid levels are `low`, `moderate`, `high`, and `critical`; the default is `low`. Use `--no-dev` to audit only the @@ -77,8 +108,8 @@ composer foxy:audit --no-dev For npm workspace roots, Foxy explicitly includes every workspace and the root package so ambient npm workspace selection cannot silently narrow the audited lock graph. -`deno audit` cannot exclude development dependencies, so a Deno audit with `--no-dev` returns status `2`. Run the -Deno audit without `--no-dev` to cover the complete dependency graph. +`deno audit` cannot exclude development dependencies, so a Deno audit with `--no-dev` returns status `2`. Run the Deno +audit without `--no-dev` to cover the complete dependency graph. Foxy also overrides pnpm and Yarn settings that could silently filter the requested dependency graph or known advisories. Bun 1.4 cannot safely reset every inherited scope setting while retaining project registry and @@ -108,8 +139,8 @@ The npm registry's audit data normally identifies advisories by a numeric ID and [GitHub global security advisories API](https://docs.github.com/en/rest/security-advisories/global-advisories). The lookups run concurrently through Composer's HTTP downloader, at most 12 at once by default; the `COMPOSER_MAX_PARALLEL_HTTP` environment variable changes that limit. A `github-oauth` token for `github.com` in -Composer's `auth.json` applies to these requests and raises GitHub's rate limit. A failed optional lookup marks only -its own advisory as `unavailable` and does not discard the finding itself or change its audit exit status. +Composer's `auth.json` applies to these requests and raises GitHub's rate limit. A failed optional lookup marks only its +own advisory as `unavailable` and does not discard the finding itself or change its audit exit status. Bun can skip packages from a registry that does not answer its audit request while still returning a successful native status. Foxy therefore treats any diagnostic emitted by `bun audit --json` as an unreliable partial report and returns @@ -131,15 +162,15 @@ Gate a production build on high and critical findings while retaining a compact composer foxy:audit --no-dev --audit-level=high --format=summary ``` -The explicit command still runs when `run-asset-manager` is `false`; that setting controls automatic install and -update execution, not a user-requested audit. Foxy must be enabled, the selected binary must satisfy its supported -version constraint, and the manager's own lockfile must exist. The `native` manager has no binary, so only Foxy and -`foxy.lock` are required for it. +The explicit command still runs when `run-asset-manager` is `false`; that setting controls automatic install and update +execution, not a user-requested audit. Foxy must be enabled, the selected binary must satisfy its supported version +constraint, and the manager's own lockfile must exist. The `native` manager has no binary, so only Foxy and `foxy.lock` +are required for it. ## Library setup -A Composer library becomes eligible for Foxy when it contains the selected manager's package definition and uses one -of the activation methods below. +A Composer library becomes eligible for Foxy when it contains the selected manager's package definition and uses one of +the activation methods below. ### Required Foxy dependency @@ -165,8 +196,8 @@ Use a development dependency when Foxy is optional for library development: } ``` -The consuming application must still require and authorize Foxy because dependencies of a library are not installed -from its `require-dev` section. +The consuming application must still require and authorize Foxy because dependencies of a library are not installed from +its `require-dev` section. ### Activation through Composer extra @@ -205,18 +236,18 @@ The embedded manifest must resolve inside the installed library's Composer direc ## Embedded package metadata -Foxy creates a local package representation for each eligible Composer dependency. It copies only metadata required -for dependency resolution and compatibility checks. Package lifecycle `scripts`, `devDependencies`, executable -definitions, entry points, and unrelated publishing metadata are not propagated to the consuming application. +Foxy creates a local package representation for each eligible Composer dependency. It copies only metadata required for +dependency resolution and compatibility checks. Package lifecycle `scripts`, `devDependencies`, executable definitions, +entry points, and unrelated publishing metadata are not propagated to the consuming application. Library authors should declare runtime frontend requirements in `dependencies`, `optionalDependencies`, or -`peerDependencies`. Any build step needed to publish those assets should run before the Composer package is released, -or be configured explicitly by the consuming application. +`peerDependencies`. Any build step needed to publish those assets should run before the Composer package is released, or +be configured explicitly by the consuming application. ## Manually enabling or disabling packages -The root application can use `config.foxy.enable-packages` to include a package that does not declare Foxy or to -exclude a package that does. See [Package selection](config.md#package-selection). +The root application can use `config.foxy.enable-packages` to include a package that does not declare Foxy or to exclude +a package that does. See [Package selection](config.md#package-selection). ## Behavior and guarantees @@ -226,23 +257,23 @@ exclude a package that does. See [Package selection](config.md#package-selection - `root-package-json-dir` controls project reads, writes, and manager working directory. - Manager execution does not change the PHP process working directory. - The `native` manager installs one version per package into a flat directory (`node_modules` unless - `native-install-dir` says otherwise), verifies every tarball against its registry integrity value, and reinstalls - from `foxy.lock` without registry metadata requests. + `native-install-dir` says otherwise), verifies every tarball against its registry integrity value, and reinstalls from + `foxy.lock` without registry metadata requests. - Asset restoration occurs when package merging fails or manager execution throws or returns a non-zero result, and `fallback-asset` is enabled. It runs before Composer restoration. - Composer lock and vendor restoration occurs after any solve exception or non-zero manager result when `fallback-composer` is enabled. - Read, write, and remove failures are reported through exceptions instead of being silently ignored. -Composer restoration does not include the root `composer.json` bytes. Review that file after a failed -`composer require` or `composer remove` operation. +Composer restoration does not include the root `composer.json` bytes. Review that file after a failed `composer require` +or `composer remove` operation. ## Next steps - ๐Ÿ“š [Getting started](index.md) - โš™๏ธ [Configuration reference](config.md) -- ๐Ÿ”Œ [Events reference](events.md) - โ“ [Frequently asked questions](faqs.md) -- ๐Ÿงช [Testing guide](testing.md) -- โฌ†๏ธ [Upgrade guide](../UPGRADE.md) -- ๐Ÿ“– [README](../README.md) +- ๐Ÿ”Œ [Events reference](events.md) +- ๐Ÿค– [AI agents guide](agents.md) + +[Back to documentation](../README.md#documentation) diff --git a/skills/php-forge-foxy/SKILL.md b/skills/php-forge-foxy/SKILL.md new file mode 100644 index 0000000..11fe5dc --- /dev/null +++ b/skills/php-forge-foxy/SKILL.md @@ -0,0 +1,66 @@ +--- +name: php-forge-foxy +description: + Use when configuring php-forge/foxy in a Composer application or library, + selecting an asset manager, troubleshooting package merges, or auditing + frontend dependencies. +license: BSD-3-Clause +--- + +# Configure Foxy + +Foxy is a Composer plugin that merges eligible Composer libraries' frontend manifests into an application and invokes a +selected frontend manager, or installs assets itself with the native manager. Match the installed revision before +applying these instructions. + +## Establish the revision and scope + +For a root checkout, inspect `composer show --self --format=json`, its HEAD and working tree. For an installed +dependency, resolve the configured vendor directory and inspect `composer/installed.php` for `php-forge/foxy` +(`install_path`, `pretty_version`, and selected `reference`), then check `composer.lock`. A symlinked checkout can +differ from both metadata and lock: inspect its HEAD and current files. Uncommitted changes have no matching revision. +Use documentation from the matching revision; if unavailable, inspect installed source and report the gap. + +Read only the relevant canonical local guide: `docs/index.md` for requirements and installation, `docs/config.md` for +configuration, `docs/usage.md` for workflows and examples, `docs/events.md` for extension events, or `docs/faqs.md` for +troubleshooting. For an upgrade, use `UPGRADE.md` for the incompatibility summary and `docs/migration.md` for detailed +actions. Guides can be absent from Composer distributions; use the matching repository revision. Check `composer.json`, +the root and library manifests and their lockfiles before proposing commands. Preserve existing project dependencies and +manager choice. + +## Choose a setup + +- The application must require `php-forge/foxy` and explicitly authorize it in Composer `allow-plugins`. A library's + `require-dev` does not install Foxy for consumers. A library may declare `extra.foxy=true`; its consumer still needs + the plugin. +- Select `bun`, `deno`, `npm`, `pnpm`, `yarn`, or `native` explicitly for reproducible CI. Without a selection, one + recognized lockfile chooses the manager; multiple lockfiles cause an error. Native is selected only explicitly or by a + single `foxy.lock`, never by executable discovery. +- `run-asset-manager=false` skips automatic binary probing and execution but still updates manifests; an explicit + `composer foxy:audit` still executes the selected manager. Do not present this mode as an install or an audit. +- Native mode requires PHP zlib, owns its install directory and removes unlisted non-dot entries. Do not share that + directory with another installer. It does not run lifecycle scripts, create executable links, support nested + incompatible versions, or accept Git, URL, alias, workspace or link specifications. Use a JavaScript manager when + those features are needed. +- `root-package-json-dir` sets the application manifest and manager working directory. In a library it is relative to + the installed library and must remain inside it. Deno workspace members must remain inside the root package directory. + +## Handle failures and audit + +Manager version overrides narrow rather than widen built-in support. Inspect the selected revision's requirements and +actual binary before changing a constraint. `composer foxy:audit` needs the selected lockfile; exit 0 means no finding +reached the threshold, 1 means a finding did, and 2 means a reliable audit could not be produced. Deno and native reject +`--no-dev`. Do not treat status 2 as a clean audit or run an automatic fix as a substitute. + +Foxy can restore the asset manifest and Composer lock/vendor state after a solve failure, but the root `composer.json` +bytes are not part of the rollback. Inspect it after failed `require` or `remove`. Never point generated asset storage +at user-owned data; Foxy resets its managed directory and validates ownership. + +## Verify behavior + +Test in a disposable project or fixture before touching real lockfiles. Verify the effective manager, merged manifest +and preserved unrelated dependencies, then repeat the same operation to check idempotence. Exercise the applicable error +path (multiple lockfiles, missing binary, invalid options or unsupported native specification) and confirm the error is +surfaced and expected state is preserved. For audit changes, test statuses 0, 1 and 2 independently. Run the consuming +project's checks and report whether actual manager execution, native skill discovery and activation were verified; +reading this file alone proves neither activation nor a successful installation. From ef52dfb7bb373cfc6bd576947fbc538a5d419e20 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Tue, 27 Oct 2026 14:32:34 -0300 Subject: [PATCH 2/2] Apply fixed Coderabbitai review. --- UPGRADE.md | 5 +++-- docs/migration.md | 10 ++++++---- docs/testing.md | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index a50e0e8..4831ab6 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -19,8 +19,9 @@ list released incompatibilities. -- Native audits require the registry bulk advisories endpoint and reject `--no-dev`; use a compatible registry and audit - the complete graph. The first native install writes `.foxy-installed.json` and extracts registry packages once. +- Native audits require the registry bulk advisories endpoint when `foxy.lock` contains registry packages. A lock with + only local `file:` packages skips the registry request. Native audits still reject `--no-dev`, so audit the complete + graph. The first native install writes `.foxy-installed.json` and extracts registry packages once. diff --git a/docs/migration.md b/docs/migration.md index 1644a1e..3e1e4f9 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -30,10 +30,12 @@ A CI job that relied on any Composer command failing early because of a missing ### Native manager -- `composer foxy:audit` audits the `native` manager through the registry's bulk advisories endpoint - (`POST /-/npm/v1/security/advisories/bulk`). `--no-dev` is rejected, as for Deno, because the native manager installs - the development dependencies together with the others. A private registry must implement that endpoint (Verdaccio: - enable its `audit` middleware); otherwise the audit fails with status `2`. +- `composer foxy:audit` sends the registry packages in `foxy.lock` to the registry's bulk advisories endpoint + (`POST /-/npm/v1/security/advisories/bulk`). Local `file:` packages are excluded, and a lock containing only local + packages is clean without a registry request. When registry packages remain, a private registry must implement that + endpoint (Verdaccio: enable its `audit` middleware); otherwise the audit fails with status `2`. Independently, + `--no-dev` is rejected, as for Deno, because the native manager installs the development dependencies together with + the others. - The install directory gains a hidden `.foxy-installed.json` file that records what Foxy installed. The first install after the upgrade extracts every registry package once and writes it; later installs keep the registry packages whose directory and recorded entry still match `foxy.lock`, copy the `file:` packages again, and report only what changed, diff --git a/docs/testing.md b/docs/testing.md index 5e3919b..b84c55a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -89,8 +89,8 @@ composer prettier composer yamllint ``` -The codespell, EditorConfig Checker and Markdownlint configurations are in `.github/linters`. Prettier uses `.gitignore` -as its ignore path; yamllint checks `.github`. +The codespell, EditorConfig Checker and Markdownlint configurations are in `.github/linters`. Prettier uses +`.prettierignore` as its ignore path; yamllint checks `.github`. ## Static analysis (PHPStan)