Updating
Components live in your project, so an update is never a package bump that happens to you. The CLI knows three versions of every file — the pristine upstream you installed, your local copy, and the new upstream — and merges them. Files you never touched update in place. Files you edited get a three-way merge, and anything it cannot merge is staged for you to decide. Nothing is overwritten silently.
Note
The merge base for every installed file is stored, compressed, in the committed
ui-lock.json; .jml/cache is only a fast local copy.
That is why a fresh clone, CI, or a second machine can tell your edits from upstream changes without any
extra setup — commit the lock file, ignore the cache.
The update loop
See what changed upstream
ui:status lists every installed item with its state
(current, update available,
upstream changed) and checks that the registry is reachable.
ui:update --check goes one step further and classifies each update.
Neither writes a file.
php artisan ui:status
php artisan ui:update --check
Updates available:
button ........................... 1.6.0 -> 1.8.1 (safe)
dialog ........................... 2.1.0 -> 2.2.0 (local changes detected)
card ............................. 1.4.2 is already current.
data-table ....................... 3.0.0 -> 4.0.0 (breaking)
WARN data-table has breaking changes: selection now emits row ids, not indexes.
WARN Skipping data-table; breaking updates require `--major`.
| Classification | Meaning |
|---|---|
| safe | Upstream changed; you never edited the file. Applied in place. |
| local changes detected | Upstream changed and so did you. Three-way merge. |
| local changes | You edited it; upstream has nothing new. Left alone. |
| breaking | The item declares a breaking change. Skipped unless you pass --major. |
| DOWNGRADE | The registry is behind your project. Skipped unless you pass --allow-downgrade. |
Read the diff before you merge
ui:diff prints two unified diffs per file: base → your local file
(your edits) and base → new upstream (what you are about to merge). Read the second one; that is
the change you accept.
php artisan ui:diff dialog
dialog 2.1.0 -> 2.2.0 (local changes detected)
resources/views/components/ui/dialog.blade.php
--- base (2.1.0)
+++ local
@@ -12,7 +12,7 @@
- 'size' => 'md',
+ 'size' => 'lg',
--- base (2.1.0)
+++ upstream (2.2.0)
@@ -40,6 +40,9 @@
+ // Return focus to the trigger after close.
+ x-on:close="$refs.trigger?.focus()"
Apply the update
Name an item to update just that one, or run without arguments to update everything in the lock
file. --dry-run prints the full plan without writing;
--interactive confirms each item; --yes
accepts the whole plan for CI. Every changed file is labelled: updated
(replaced, you never edited it), merged (three-way merge applied), or
unchanged locally.
php artisan ui:update --dry-run
php artisan ui:update button dialog
php artisan ui:update --yes
2 item(s) will change
button ........................... 1.6.0 -> 1.8.1
dialog ........................... 2.1.0 -> 2.2.0
resources/views/components/ui/button.blade.php ................. updated
resources/js/ui/button.js ...................................... updated
resources/views/components/ui/dialog.blade.php ................. merged
resources/js/ui/index.js ............................. updated imports
The write is transactional per run: if any file cannot be written the run rolls back and reports, so a half-applied update never lands in your tree.
Resolve a conflict
When your edit and the upstream change touch the same lines, the merge stops for that item. Your
file is untouched; the incoming file and a .patch are staged under
.jml/updates/<item>.
ERROR dialog: merge conflict in resources/views/components/ui/dialog.blade.php.
WARN Conflicts staged under [.jml/updates/dialog]. Resolve them with `php artisan ui:resolve dialog`.
ui:resolve finishes it: keep yours, take theirs, or open both in
$EDITOR. A resolution records the incoming version as the next merge
base and clears the staging directory.
php artisan ui:resolve --list
php artisan ui:resolve dialog --edit
php artisan ui:resolve dialog --theirs
php artisan ui:resolve dialog --ours
Pending conflicts (1)
dialog ........................... resources/views/components/ui/dialog.blade.php
Pin what must not move
A pinned item is skipped by bulk updates until you unpin it or pass --force.
Pin at install time with ui:add --pin or afterwards:
php artisan ui:pin data-table
php artisan ui:pin --list
php artisan ui:pin data-table --unpin
Update the foundation
resources/css/ui.css and _styles.php are
shared by every component and versioned separately as the foundation. Items declare the
minimum foundation they need, and ui:update refuses an install that
would outrun it. ui:foundation three-way merges those two files the
same way, with --ours / --theirs for
overlapping hunks.
php artisan ui:foundation --check
php artisan ui:foundation --dry-run
php artisan ui:foundation --yes
Breaking, unstable, and declarative migrations
-
Breaking updates carry a structured
migrations[]entry and are skipped until you pass--major. The CLI prints the migration summary and any manual steps first. -
Declared API migrations (a renamed prop, a moved
slot) can be applied to your Blade views by
ui:migrate <item> --dry-run, then--yes. Manual steps land in a report under.jml/migrations. -
Experimental and incubating items can break on any
version increase; their updates need
--accept-unstable. -
Downgrades are a hazard, not an update: a registry
behind your project would revert component code and can strip props your templates use. They are
skipped unless you pass
--allow-downgrade.
php artisan ui:update data-table --major
php artisan ui:migrate data-table --dry-run
php artisan ui:outdated --fail
Note
In CI, ui:outdated --fail rejects a build with outdated or deprecated items
and ui:check --offline --fail-on=warning lints your views against the
installed component contracts. The full option reference is on the
CLI page.