orchestra/publisher
Orchestra Publisher is a Laravel package for managing and publishing package assets and resources, helping move files into public directories and integrate cleanly with Laravel’s asset publishing workflow.
Architecture fit. orchestra/publisher is a thin, chainable orchestration layer over Laravel's vendor:publish registry. It is a component of the Orchestra Platform ecosystem, not a standalone utility, and its distinctive capability — extension-aware path resolution via Publisher::extensions('vendor/package') — only earns its keep in an application that has adopted (or replicated) Orchestra's extension conventions. In a conventional Laravel app where every destination is config_path() / public_path(), the package reduces to a fluent wrapper around framework behavior already reachable in one Artisan call. The correct framing for a TPM is therefore not "should we use this library" but "do we have, or intend to build, a modular extension model whose onboarding and upgrade flows need a declared publish contract?"
Integration feasibility. Mechanically simple and well-bounded. It is not auto-discovered on the 3.x line: two lines in config/app.php (service provider plus Publisher facade alias), one Composer require pinned to ^3.5, and the integration is live. The real API surface is src/Publisher.php; the contract in src/Contracts/ is the type-hint target. Method names shifted across 2.x → 3.x (extensions(), foundation(), publishes(), publish(), queue(), flush()), so signatures must be verified against the pinned tag rather than copied from documentation or blog posts. Because it publishes only through the framework registry, it is observable with php artisan vendor:publish --list — a fast smoke test that the provider actually booted.
Technical risk. The dominant risk is lifecycle, not code quality. The public record shows a single release line topping out at Laravel 5.5 — end-of-life since 2019 — with zero dependents and no reported stars. There is no forward-compatibility evidence, no security/disclosure posture on file, and a changelog consisting of one line. That combination means: any upgrade of our own framework version is a fork-or-patch event, and we own the version constraint in our upgrade path. Secondary risks are behavioral and well-documented:
--force on operator-editable config silently destroys local edits. Resource groups must be split by ownership (code-owned vs. operator-editable) rather than by convenience.config interleave silently; every tag must be namespaced (acme-reports-config).Key questions. (1) What Laravel version do we actually ship, and does this package boot and publish on it — verified, not inferred from a changelog line? (2) Do we have an extension/module model with non-default paths, or are all destinations conventional? If the latter, the dependency buys nothing over a ~50-line internal helper. (3) Who owns upstream version bumps when Laravel moves — do we have an appetite to fork? (4) Does the roadmap require programmatic publishing (admin "Install module" action, CI step, seeder) versus a human running vendor:publish? (5) Can we confine every call behind our own abstraction so a replacement costs one class?
Stack fit. Conditional and narrow. Fit is strong only where two conditions hold simultaneously: the host application is on, or deliberately aligning with, Orchestra Platform conventions, and the modules being published ship multiple resource kinds (config, views, migrations, translations, public assets) into a host with non-default path resolution. Absent the Orchestra convention, this is a wrapper we would outgrow before we finished writing the wrapper's tests. Fit is otherwise poor: on any modern Laravel line the package is unverified at best and broken at worst.
Migration path. Greenfield adoption is trivial — provider plus alias, no data migration, no schema change, no service disruption, because publishing is a deploy-time operation with no runtime footprint. The harder migration is away from ad-hoc orchestration: today's likely state is $this->publishes() scattered across providers plus Artisan::call('vendor:publish', [...]) strings duplicated in install commands and deploy scripts. Consolidate first, before deciding on the dependency:
PublishManifest (a class or static method on each package's provider) that returns the full resource map — source path → destination path → tag → ownership class.Artisan::call internals.Publisher::extensions(...)->publishes(...)->queue()->publish(), if the spike validates the framework version.This sequencing means the build-vs-buy decision is deferred to a one-file change and the abstraction survives either outcome. It also produces the artifact — a declarative publish contract — that has standalone value even if the dependency is rejected.
Compatibility. Pin the constraint hard ("^3.5") and do not let Composer float the major. Verify the declared support matrix in composer.json of the installed tag, not the release notes. Read src/Publisher.php, src/PublisherServiceProvider.php, and src/Contracts/ in the vendor directory before writing integration code — the singleton binding and path resolution are the two things worth knowing whether we can rebind. Type-hint against the contract, not the concrete class, so a fork stays contained.
Sequencing. Time-boxed spike first (does it boot, publish, and resolve paths on our actual Laravel version, in a staging deploy, on the upgrade path — not just the install path). Manifest abstraction second, independent of spike outcome. Executor swap third, behind a feature flag or a single manifest method. Rollout to one non-critical extension package before any partner-facing or multi-tenant flow. Full adoption — including partner documentation and support commitments — only after a named owner accepts the fork-and-maintain burden.
What must not be skipped. Publishing a migration does not run it; the publisher has no lifecycle awareness. Every install flow that publishes migrations must follow with an explicit Artisan::call('migrate', ['--path' => ..., '--force' => true]). Anything involving transactional install, rollback, or inter-module dependency resolution is out of scope for this package and must be built (or bought) separately.
Maintenance. This is the decisive cost. Zero dependents plus a one-line changelog plus an EOL framework target means we are the maintenance plan. Budget for: owning the version constraint in every future Laravel upgrade; a fork or patch when the framework moves; and ongoing verification that publish semantics have not drifted. The mitigation — keeping all publish declarations behind our own PublishManifest so a replacement touches one class — is cheap now and expensive to retrofit later. Without it, the dependency's staleness propagates into every extension package.
Support. Two ticket classes are foreseeable and both are low-severity, high-volume:
--force upgrade overwrote operator-customized config. This one is high-severity when it happens, which argues for a governance policy rather than a flag: split resource groups by ownership, force only code-owned artifacts (assets, views, migrations), and diff-or-report for operator-editable ones (config, translations). Encode the policy in the install/upgrade commands so operators never invoke the raw publisher.Scaling. Publishing is deploy-time and provisioning-time; it does not scale with request volume and should never appear in a controller, model observer, or queued job. The scaling concerns are state and concurrency, not throughput: the publisher is a singleton with mutable accumulated state, so long-lived workers, Octane, or two sequential install flows require an explicit flush between batches or the second run re-publishes the first package's resources. Tags are global to the publish registry, so as the extension count grows, tag collisions become a near-certainty unless namespacing is enforced from day one. Multi-tenant and white-label deployments are the genuine scaling test of extension-aware path resolution — per-tenant asset paths and per-brand config sets are exactly what extensions() addresses, and exactly where a failure surfaces in a customer's deploy rather than in a test.
Failure modes. Ranked by blast radius:
| Mode | Signal | Blast radius | Mitigation |
|---|---|---|---|
| Silent no-op on upgrade | Exit 0, nothing shipped | Medium — user-visible staleness | Report per-tag published file counts in command output |
| Path resolution throws | Failure at publish time, in production deploy | High | Exercise the exact publish set in a staging deploy before operator exposure |
| Blind force clobbers config | Post-upgrade config regression | High | Ownership-split resource groups; never force operator-editable files |
| Singleton state leakage | Cross-package resource bleed | Medium | Flush between batches; do not hold the publisher across flows |
| Tag collision | Interleaved/confusing publishes | Low–medium | Namespace every tag; verify with vendor:publish --list |
| Published-but-unrun migration | Schema drift between file and DB | High | Explicit sequenced migrate --path --force step |
| Upstream abandonment | No fixes on framework bump | Strategic | Manifest abstraction; named fork owner |
Ramp-up. Low for a competent Laravel engineer: the API is a short chainable surface, and the mental model — queue then release, tags scope, force overwrites, paths resolve against extension conventions — is learnable in an afternoon. The non-obvious parts are the traps, not the API, and they are learnable only by reading src/Publisher.php and watching a publish happen. Budget a half-day spike plus a half-day writing the first install command and its feature test. Adoption by external extension authors is a different curve: they will hit the silent-no-op and tag-collision traps before they hit anything interesting, so partner documentation must lead with ownership policy and namespaced tags, not with the fluent example.
Testing discipline. Test the publish, not the publisher. Swap in a temp base path, invoke the install command, and assert on resulting filesystem state — including absence assertions (an accidentally shipped README.md in public/vendor/... is the failure mode that reaches production). Publishing has no rollback; the filesystem assertion is the only real safety net.
How can I help you explore Laravel packages today?