Repository navigation
fix(deploy): finish an interrupted deploy whose classes landed with a pubspec-only push - #115
Conversation
…th a pubspec-only push When step 2 (the class write) succeeds but its response is lost or expires, the classes land in FlutterFlow but step 3 (the pubspec.yaml push) never runs, leaving the project unable to build. Now that commit 5231e76 re-reads the project after an unconfirmed provision, extend the terminal: when the reconcile proves every attempted class landed, offer a "Finish deploy" action that pushes the already-merged pubspec (reusing resolveProjectPubspec's output) without re-running the class write. The landed classes are excluded from the finish file map so they are never re-provisioned.
|
This PR does not match any of the 1 configured review trigger rule. |
| // needs — the deploy-time credentials, the full file map, the already | ||
| // merged yaml, and the landed entry list — so the terminal action can | ||
| // push without re-provisioning the classes. | ||
| if (isDeployFinishable(reconciled.reconcilePartition)) { |
There was a problem hiding this comment.
🔴 Failed reconciliation falsely marks deploy failed
When the project re-read fails, isDeployFinishable receives no partition and throws. The original unconfirmed outcome becomes a failed deploy, although classes can still land.
| if (isDeployFinishable(reconciled.reconcilePartition)) { | |
| if (reconciled.reconcilePartition && isDeployFinishable(reconciled.reconcilePartition)) { |
Was this helpful? React with 👍 or 👎 to provide feedback.
| zipped_custom_code: zippedCustomCode, | ||
| uid: `web_${Date.now()}`, | ||
| branch_name: apiClient.branchName, | ||
| serialized_yaml: finish.serializedYaml, |
There was a problem hiding this comment.
🔴 Delayed finish overwrites newer project dependencies
If dependencies change after reconciliation, finishProvisionedDeploy pushes its earlier complete YAML snapshot. Newer project dependencies disappear from FlutterFlow.
Learn more
The normal deploy merges generated dependencies with the current project manifest in resolveProjectPubspec. The finish action instead retains that earlier merged manifest and sends it as the complete serialized_yaml. The user can leave the terminal open while another deploy or project editor adds packages, and the finish push replaces that newer manifest. Its bundle file map can also contain non-class artifacts created before the delay.
Example: Deploy A prepares a manifest containing intl, then provisioning times out and the Finish button appears. Deploy B adds http; clicking Finish for A uploads A's older manifest and removes http.
Recommended fix: Before constructing the finish request, fetch the current project manifest and merge only the interrupted deploy's required dependencies into it. Also reconcile remaining bundle artifacts against the fresh project export before writing them.
Was this helpful? React with 👍 or 👎 to provide feedback.
| finish.landed, | ||
| finish.serializedYaml, | ||
| ); | ||
| const syncMetadata = await buildApiSyncMetadata(finishFileMap, new Map()); |
There was a problem hiding this comment.
🔴 Existing bundle artifacts lose update metadata
For bundles with existing actions, widgets, or functions, buildApiSyncMetadata treats retained files as new. The empty remote map drops original checksums and marks existing functions for addition, so the finish push can fail.
Learn more
The finish map removes only classes that the runner attempted to create. Other bundle entries can already exist in FlutterFlow. buildFlutterFlowSyncMetadata consults remote files to attach original_checksum to updates and to avoid adding functions that already exist. Unlike the normal bundle push, Finish supplies an empty map, even when these remote entries are present.
Example: A bundle has a new model.dart and an existing read_tag.dart. After model.dart lands, Finish retains read_tag.dart but omits its original checksum; FlutterFlow receives a create-shaped update instead of an update.
Recommended fix: Fetch fresh remote files at finish time and pass them to buildApiSyncMetadata; reconcile their contents and handle concurrent edits before constructing the push.
Was this helpful? React with 👍 or 👎 to provide feedback.
| } catch (error) { | ||
| renderCommitTerminal({ | ||
| ...finish.result, | ||
| success: false, | ||
| error: error.message, | ||
| unconfirmed: error instanceof UnconfirmedDeployError, | ||
| remoteRefusal: error.remoteRefusal === true, | ||
| }); |
Summary
A deploy whose step-2 response is lost or expired used to leave the classes written to FlutterFlow but the pubspec.yaml push (step 3) never run, so the project could not build. Commit 5231e76 added
reconcileUnconfirmedProvision, which re-reads the project after an unconfirmed provision and reports which classes landed. This PR extends that: when the reconcile proves every attempted class landed, the unconfirmed terminal now offers a "Finish deploy" action that runs step 3 only — the pubspec push — without re-running the class write.Item 1 — the "Finish deploy" action
src/flutterFlowCodeFileProvisioning.js: two new pure helpers,isDeployFinishable(partition)(true only when every attempted class landed) andbuildFinishPushFileMap(fileMap, landedEntries, serializedYaml)(builds the finish-push file map: the landed classes excluded, the merged pubspec added).app.js:reconcileUnconfirmedProvisionreturns the error carryingreconcilePartition(landed/notLanded), and the all-landed message now points at "Finish deploy".provisionMissingCodeFiles's unconfirmed branch, when finishable, attachesfinishDeploy— the deploy-time creds, the full file map, the already-mergedserializedYaml(produced byresolveProjectPubspec— reused, not re-invented), and the landed entries.executeCommitandexecuteBundleCommitmerge that onto the unconfirmed result viamergeFinishDeployContext.showCommitUnconfirmedModaloffers a "Finish deploy" button in the terminal modal (same place as the existing failure guidance) whenresult.finishDeployis present.finishProvisionedDeployruns the async finish: rebuilds the deploy-time API client, builds the pubspec-only push viabuildFinishPushFileMap, reusesbuildApiSyncMetadata/createZipFromFileMap/pushCodeWithUiTimeout, and renders the result through the normalrenderCommitTerminal. The landed classes are excluded from the sync map, so they are never re-provisioned.src/flutterFlowCodeFileProvisioning.test.jsdrive the exact pure decision path app.js uses after a timed-out step 2 — re-read reports all landed → finishable → finish file map carries the merged pubspec and omits the landed classes.Testability note (per the workstream contract): app.js is a Vite-browser monolith with no end-to-end unit path (DOM, fetch, the Cloud Run runner, and live FlutterFlow credentials), so I factored the finish decision + file-map construction into pure functions in
src/and unit-tested those. The app.js wiring is verified bynode --check, a cleanvite build, and the fullnode --test src/*.test.jssuite (402 pass). I am not claiming end-to-end app.js coverage.Item 2 — can the pubspec be pushed before/with the classes? (decided: leave the order alone)
Evidence based on reading the code:
deployCustomClasses) and the pubspec write (step 3,syncCustomCodeChanges) go through different endpoints. They cannot share a single call; "together with" is not achievable.syncCustomCodeChangesrequest through its own retry-with-uncertain-outcome machinery (pushCodeWithUiTimeout). That request has its own lost-response/unconfirmed failure modes, so it would add a second write we cannot safely confirm — making the unconfirmed window worse, not better.provisionMissingCodeFilessends the runner averificationmanifest built from the mergedserialized_yaml, so classes are compiled against the new dependencies regardless of push order. Reordering buys nothing for the compile gate.Conclusion: reordering is not safe enough to force. Item 1 is the mitigation, and this PR implements it.
Out of scope / notes
commitToFlutterFlowpath (not used by the current UI, which routes throughexecuteCommit/executeBundleCommit) has notargetIdentity, so it still returns the plain unconfirmed result without a Finish button; it degrades gracefully to the existing unconfirmed guidance.buildApiSyncMetadata, which is correct because after an all-landed reconcile the finish file map is pubspec-only in the normal single/multi-artifact fresh-deploy case (type "D" is excluded from the file_map), matching what a successful post-provision step-3 already sends.Verification
node --test src/*.test.js→ 402 passing (399 baseline + 3 new), 0 failingnode --check app.js→ OKnpm run build(vite) → builds clean, 42 modules