Skip to content
ChatfuelSDK
Guides

Update an app to newer content

npx @chatfuel/wizard@latest update reads the lock in your app, overwrites what you never touched, and hands you the files where your edits and ours landed on the same lines.

There is no npm update to run. Nothing in your package.json points back at this repository — the design system, the API client, the proxy and the schema were copied into your project as source, and the CLI that put them there was gone the moment it finished.

What makes an update possible anyway is .chatfuel/lock.json, written into the app at scaffold time. For every file it records where the bytes came from, what they hashed to when they landed, and what upstream held at that moment. So the question an update has to answer — has anybody edited this since? — is a digest comparison rather than a guess, and the answer decides each file on its own.

npx @chatfuel/wizard@latest update --dry-run   # what would change
npx @chatfuel/wizard@latest update             # do it

The command moves the app to the content the wizard's branch holds now, not to whatever the published package was built against — that commit is only the floor a resolution has to descend from. So running it twice in a week does something the second time.

What it decides, file by file

UpstreamOn diskWhat happens
UnchangedAnythingSkipped.
ChangedStill the bytes the wizard wroteOverwritten, no question asked.
ChangedEdited since, or deletedLeft exactly as it is, reported as a conflict.
ChangedThe wizard rewrote it on the way inA conflict too — the upstream bytes alone would drop the edit that makes the app run, so the lock names the transforms to re-apply.
Gone upstreamAnythingSkipped, and named. Usually a rename, which arrives here as a disappearance; the app keeps its copy for good.
Generated hereAnythingNever written. It has no upstream to compare against — see step 5.

Conflicts are the normal outcome, not a failure. They are the files where your work and ours landed on the same lines, and the command exits 0 with them outstanding.

Three apps it will not touch at all: one with no lock, one scaffolded from a repository checkout — which pins a commit nothing outside that machine can resolve — and one built from a different content repository than the wizard you are running carries.

Commit what you have

git status --porcelain

The update refuses to write into a tree with uncommitted changes, and the refusal is the point: an update is a pile of overwrites, and git checkout . undoes all of them at once — but only while there is nothing else uncommitted to lose. Without the check, its writes would mix with your unsaved work and neither could be taken back on its own. A directory that is not a git repository is refused for the same reason, with the three commands that fix it.

See what it would change

npx @chatfuel/wizard@latest update --dry-run

Nothing is written. What you get is the commit range, then the lists: what would be updated, the conflicts with the reason beside each one, anything gone upstream, anything whose generated client would need regenerating, and a count of the skips broken down by why. The first two lists and the last are the whole story; the middle two are named rather than counted, because a number is not something anybody can act on.

Add --json for the machine-readable form — from and to, the wizard versions at each end, the release notes for the range between them, and the three lists, with upstream's copy of every conflicting file fetched into the wizard's cache and its path recorded as theirs. That fetch happens under --dry-run too, and it is the one thing that does: it lands in the cache, not in your app, and a plan whose conflicts have no upstream side to open is a plan nothing can act on.

The notes are the changelog between the two wizard versions, so they are empty on an update that moved content under one version — the common case. When they are there, they are the only thing that says why upstream changed, which is the difference between a merge and a guess.

Take the safe half

npx @chatfuel/wizard@latest update

This overwrites the files nobody touched, moves the pin in the lock, and leaves every conflict alone. Each file is fetched into the wizard's cache and checked against its digest before it is copied, and each destination is re-checked on the way in: a directory on the path to it that turns out to be a symlink pointing out of the app stops the run rather than being written through.

The lock is rewritten with what was actually copied, whether the run finished or not — an interrupted update records the files it had already written, so the next one does not read them as edits you made.

Even an update with no files to copy moves the pin, which is what makes the next one ask about the right range.

Resolve the conflicts

Every app the wizard writes carries a chatfuel-update skill, whatever modules were picked, because it is about the app's relationship to the wizard rather than about anything the app does. Ask your coding agent to update the app and that skill takes the conflicts from here: it reads the plan as JSON, opens each conflicting file beside upstream's copy of it, and merges them one at a time with the release notes in hand.

The rule it works under is worth knowing, because it is what keeps the result reviewable: it may change the files the CLI listed under conflicts and nothing else. Not the files the CLI wrote itself, not the skipped ones, not the lock, and not .env. The one file it may create is src/operationDocs.ts, for an app scaffolded before that barrel existed — update cannot add a file the app never had, and the three hosts it just overwrote import it.

How each reason is resolved:

ReasonWhat to do
edited hereMerge upstream's change into your file. Keep your intent; take their fix.
wizard rewrote itStart from upstream's copy and re-apply the transforms the lock names. Copying it over the file as it stands is what drops the transform and breaks the app.
deleted hereDecide. Nobody should resurrect a file you removed on purpose.

Regenerate the client

The typed GraphQL client under src/vendor/api/generated/ is marked generated in the lock, so the update never writes one — but both of its inputs, the schema and the operation documents, are files it does move. When it has moved either, it prints a fourth list, regenerate the client, naming the inputs, and then this:

npm run codegen

The generator is 265 packages the app does not otherwise need, so it is not one of its dependencies: the first run prints the exact install line, with the versions the vendored client was generated with, and stops. Run that line, then npm run codegen again.

The whole directory changing is what a correct regeneration looks like, not a conflict. Commit it together with the documents above it — a client that does not match the documents beside it is worse than an old one. An app embedded in a host project ships no generator at all, and the command says so instead of naming one.

Say what you settled

npx @chatfuel/wizard@latest update --resolved src/vendor/ui/Button.tsx

This records the merge in the lock, and it is not optional. Left unsaid, the lock still describes each file as it was before you merged it, so every future update reports the same conflict again and fetches upstream's copy again, for work that was already done.

Only a file the update is actually holding back may be settled, and every path is judged before any is recorded — one bad path refuses the whole call and names all of them, so nothing is half-recorded after a merge you have already made. Pass only the files you really resolved: one you left for somebody else stays a conflict, which is correct. A file you settled by leaving it deleted drops out of the lock entirely.

Check the app still runs

npm run check && npm test

check is tsc --noEmit over the app and a second pass over its scripts; test is the suite that ships with the app, including the render smoke test every module carries. A build that fails on a field or a type name that does not exist is a regeneration that did not run, or one that ran before the merges were finished. After that, the break is in the files you touched.

Then start it and click into each module:

npm run dev

What it never touches

  • A file the app has never had. The lock maps your files to their origins, not the trees they came from, so an added upstream file has no place to land that the command could work out on its own. That is the skill's job, with the release notes in hand.
  • Anything the lock marks generated — the typed client, the module registry, the nav table, src/operationDocs.ts, and src/config/app.ts, which is your deployment's own settings and is recorded that way for exactly this effect. The defaults it overrides sit beside it in src/config/defaults.ts, which is a plain copy and does keep receiving updates.
  • .env. It is not in the lock and never will be.
  • A skill directory the wizard did not write. One that was already there when the app was scaffolded is recorded as kept, and the update skips it too — which is why the scaffold's closing summary named it at the time.

Where the content comes from is the mechanism behind the commit this page keeps naming. Vendoring is the decision that makes an update a merge rather than a version bump, and Agent skills covers what else is in those directories. Every flag is on the CLI reference.

On this page