Skip to content

The baseline

larapack:verify looks inward at a package: whether generated code is still coherent with its manifest. larapack:audit looks outward: whether it fits with the other packages of the ecosystem. It's what stops the alignment from coming undone on the next push, and that's why CI runs it before releasing.

Why it exists

Packages used to declare whatever they wanted, or nothing at all: 23 out of 29 had neither php nor illuminate/*, so Composer would have installed them on any version, and drift was only discovered when something broke in production.

Now the rule lives in a single file, ecosystem.json, inside LaraPack, and larapack:audit enforces it. It deliberately fixes nothing: it reports, and the fix is a human decision or an explicit CI step.

Running the audit

bash
php vendor/bin/builder larapack:audit                    # the current package
php vendor/bin/builder larapack:audit packages --all     # every subdirectory that's a package
php vendor/bin/builder larapack:audit --format=json --strict

CI runs it like this, and it's what you should run before pushing:

bash
php vendor/innoboxrr/larapack-generator/builder larapack:audit .
Argument or optionWhat it does
[path]The package directory, or the one containing several with --all. Defaults to .
--allAudits every subdirectory that has a composer.json or package.json
--format=txt|jsonText output (default) or a single JSON document
--strictWarnings also make the command fail

larapack:audit has no --root: the path is its argument. It exits 1 if there are errors, warnings with --strict, or if the path doesn't exist ("La ruta indicada no existe."). Messages are in Spanish:

json
{
    "ok": false,
    "errors": 1,
    "warnings": 1,
    "findings": [
        {
            "level": "error",
            "check": "internal-version",
            "package": "innoboxrr/laravel-setup",
            "message": "`innoboxrr/larapack-generator: ^7.10.2` no admite la línea base `^7.10`."
        },
        {
            "level": "warning",
            "check": "internal-version",
            "package": "innoboxrr/laravel-auth",
            "message": "`innoboxrr/larapack-generator: ^7.0` admite versiones fuera de la línea base `^7.10`; la matriz de CI tiene que cubrirlas."
        }
    ]
}

ecosystem.json

json
{
    "version": 1,
    "php": {
        "require": "^8.3",
        "matrix": ["8.3", "8.4"]
    },
    "laravel": {
        "illuminate": "^13.0",
        "testbench": "^11.0"
    },
    "dev": {
        "phpunit/phpunit": "^12.0 || ^13.0",
        "laravel/pint": "^1.18",
        "larastan/larastan": "^3.0"
    },
    "node": {
        "engines": ">=20",
        "ci": "22"
    },
    "js": {
        "vite": "^7.1.0",
        "vitest": "^3.0.0",
        "vue": "^3.5.0",
        "react": "^19.0.0"
    },
    "internal": {
        "innoboxrr/larapack-generator": "^7.10",
        "innoboxrr/traits": "^2.1",
        "innoboxrr/support": "^2.1",
        "innoboxrr/search-surge": "^3.0"
    },
    "internalNpm": {
        "innoboxrr-form-core": "^2.8.0",
        "innoboxrr-form-elements": "^6.7.0",
        "innoboxrr-react-form-elements": "^3.7.0",
        "innoboxrr-vue-datatable": "^3.1.0",
        "innoboxrr-react-datatable": "^3.1.0",
        "innoboxrr-http-request": "^2.0.0",
        "innoboxrr-i18n": "^1.2.0",
        "innoboxrr-locale-generator": "^2.0.0",
        "innoboxrr-js-validator": "^2.0.0",
        "innoboxrr-route-resolver": "^2.0.0"
    },
    "generated": {
        "require": {
            "laravel/sanctum": "^4.3",
            "maatwebsite/excel": "^4.0"
        }
    },
    "required": {
        "composer": ["name", "description", "license", "autoload"],
        "npm": ["name", "version", "license", "type", "exports", "files", "sideEffects"]
    },
    "workflows": {
        "composer": ["tests.yml", "release.yml"],
        "npm": ["tests.yml", "release.yml"]
    }
}

(Without the $comment* keys the real file carries.)

KeyWhat it setsWho uses it
php.requireThe PHP constraintaudit: php-missing, php-version
php.matrixCI PHP versionsMatches the default matrix of php-tests.yml
laravel.illuminateilluminate/* and laravel/frameworkaudit: illuminate-missing, illuminate-version
laravel.testbenchorchestra/testbenchaudit: testbench-version
devPHPUnit, Pint and Larastanlarapack:new, when writing require-dev. The audit doesn't check it
node.enginesAn npm package's engines.nodeaudit: node-engines
node.ciThe reference Node version in CIInformational: no command reads it
jsThe npm packages' test toolchainaudit: js-version
internalComposer dependencies between ecosystem packagesaudit: internal-version
internalNpmThe npm versions a generated module declaresA LaraPack test compares them with the templates. The audit doesn't check them
generated.requireWhat generated code uses beyond the baselinelarapack:new puts it in require; the EndToEnd suite installs it
requiredRequired fields in composer.json and package.jsonaudit: composer-field, npm-field
workflowsWorkflows each kind of package must haveaudit: workflow-missing

What it checks

larapack:audit works out what each directory is on its own: with composer.json it runs the Composer checks, with package.json the npm ones, and with both, both.

Composer packages

CodeLevelWhen it firesWhat to do
composer-fieldwarningname, description, license or autoload is missingAdd it. It still publishes, but is unidentified on Packagist
composer-versionerrorcomposer.json pins "version"Remove it. Composer discards every tag that doesn't match it, so the new version stays invisible
php-missingerrorrequire doesn't declare php"php": "^8.3"
php-versionerror or warningThe php constraint doesn't admit the baseline, or admits moreSee the rule
illuminate-missingerrorThe package uses Laravel and declares no illuminate/* or laravel/frameworkDeclare ^13.0
illuminate-versionerror or warningEach illuminate/* or laravel/framework against ^13.0See the rule
testbench-versionerror or warningorchestra/testbench, if declared, against ^11.0See the rule
internal-versionerror or warningEach package from internal that's declared, against its baselineSee the rule and the conflict pattern
phpunit-configerrorNo phpunit.xml or phpunit.xml.distAdd the config
no-testserrorNo *Test.php at allAdd at least one test
workflow-missingerror.github/workflows/tests.yml or release.yml is missingCreate it (see The workflows)
bump-patcherror or warningA bump-patch.yml is still thereReplace it with release.yml

Where each one looks:

  • php only in require. Illuminate, Testbench and internal packages in require and require-dev.
  • "Uses Laravel" means some src/*.php mentions Illuminate\, or src/Providers, database/migrations or routes exists. It's conservative on purpose: it only avoids demanding Illuminate from a framework-agnostic package.
  • no-tests looks for tests/*Test.php and tests/<folder>/*Test.php. A package whose tests all live deeper (tests/Feature/Models/) gets the error: keep at least one at the first level, like larapack:new's tests/Feature/PackageBootsTest.php.
  • bump-patch is an error if the bump hangs off push without waiting for the suite, and a warning if it waits (workflow_run: or needs: tests): it doesn't publish blind, but it guesses the version from the last tag instead of declaring it.

npm packages

CodeLevelWhen it firesWhat to do
npm-fieldwarningname, version, license, type, exports, files or sideEffects is missingAdd it. The key is what's checked: "sideEffects": false is valid
node-engineswarningengines.node isn't exactly >=20"engines": { "node": ">=20" }. It compares text: >=20.0.0 also warns
js-versionerror or warningvite, vitest, vue or react in dependencies or devDependencies, against jsSee the rule
workflow-missingerrortests.yml or release.yml is missingCreate it
bump-patcherror or warningA bump-patch.yml is still thereReplace it

peerDependencies are left out on purpose. A library declaring react: ^18 || ^19 is doing the right thing: it says what it can run against, not what it's built with.

Neither

CodeLevelWhen it fires
not-a-packagewarningThe path has neither composer.json nor package.json

It doesn't appear with --all: only subdirectories that are packages get audited.

The rule: exact, wide or narrow

What matters isn't that the text matches the baseline, but that the constraint admits it.

CaseWhat it meansExampleResult
ExactSays exactly the baseline"php": "^8.3"Nothing
WideAdmits the baseline and also versions outside it"illuminate/support": "^12.0 || ^13.0", "innoboxrr/larapack-generator": "^7.0"Warning: legitimate, but the CI matrix must really cover what it promises
NarrowDoesn't admit the whole baseline"php": "^8.4", "innoboxrr/larapack-generator": "^7.10.2"Error: something is actually broken
UnreadableComposer can't parse it"php": "8.3 or newer"Warning

A wide constraint makes sense: narrowing it to ^13.0 only takes away the option of installing the library on Laravel 12. innoboxrr/traits declares ^12.0 || ^13.0, which is why its CI tests both Laravel 12 and 13.

A narrow one is an error because it breaks installing packages together: a package demanding ^7.10.2 can't be installed where another sits on 7.10.0, which the baseline allows. And since tests.yml runs the audit, an error blocks the release.

Requiring a minimum above the baseline: conflict

Sometimes a package needs a fix that landed in a patch release above the baseline. laravel-setup needs LaraPack 7.10.2, and the baseline says ^7.10. Writing ^7.10.2 in require is a narrow constraint: the audit fails and nothing is released. That's what happened to laravel-setup 7.0.0.

The fix is to keep require at the baseline and exclude older versions with conflict:

json
{
    "require": {
        "php": "^8.3",
        "innoboxrr/larapack-generator": "^7.10",
        "laravel/framework": "^13.0"
    },
    "conflict": {
        "innoboxrr/larapack-generator": "<7.10.2"
    }
}

It works for two reasons:

  • Composer combines both: ^7.10 and not <7.10.2 is effectively >=7.10.2 <8.0.
  • The audit reads require and require-dev, not conflict: it sees an exact constraint.

Once ecosystem.json raises its baseline above that minimum, the conflict is redundant and gets removed.

TIP

Run the audit locally before pushing a release: php vendor/innoboxrr/larapack-generator/builder larapack:audit .. It's the same line CI runs.

An application doesn't go through the audit, so it can require the minimum directly: app:setup writes innoboxrr/larapack-generator: ^7.10.2 into the require-dev of the application it creates.

What it doesn't check

  • The dev versions (PHPUnit, Pint, Larastan): only larapack:new uses them.
  • internalNpm: the innoboxrr-* dependencies in a generated module's package.json are aligned by hand (step 8 of the upgrade guide).
  • peerDependencies.
  • Whether the CI matrix really covers a wide constraint. A person reviews that.
  • Formatting and types: the quality job handles that, with Pint and Larastan. See The workflows.
  • Whether CI runs the audit at all. A package whose tests.yml doesn't call php-tests.yml, or that doesn't install LaraPack, isn't audited in CI.