Repository navigation
feat(runner): record deploy runs and serve their status so a lost outcome is known #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,10 @@ import { | |
| partitionProvisionedCodeFiles, | ||
| } from "./src/flutterFlowCodeFileProvisioning.js"; | ||
| import { buildReviewPresentation } from "./src/reviewPresentation.js"; | ||
| import { | ||
| generateDeployRunId, | ||
| queryRunStatusWithinBudget, | ||
| } from "./src/runStatus.js"; | ||
| import { | ||
| expectedWidgetClassFromFileName, | ||
| findUnbalancedBracketError, | ||
|
|
@@ -3254,6 +3258,15 @@ async function provisionMissingCodeFiles( | |
| return { remoteFiles, syncFileMap: fileMap, unverified: [], approximate: [] }; | ||
| } | ||
|
|
||
| // The client generates the run id so that, if this response is ever lost, | ||
| // a 404 on GET /runStatus/<runId> is proof the request never reached the | ||
| // runner (nothing written, retry safe) rather than an ambiguous "unknown". | ||
| const runId = generateDeployRunId(); | ||
|
|
||
| // True once the run-status lookup has decided this attempt's real outcome, | ||
| // so the late-arriving provision stream cannot double-credit its writes. | ||
| let runStatusResolved = false; | ||
|
|
||
| // The runner compiles these against the project's own package versions | ||
| // before it writes anything, so an API the generated code invented - a named | ||
| // argument the package never declared - stops the deploy instead of landing | ||
|
|
@@ -3310,6 +3323,7 @@ async function provisionMissingCodeFiles( | |
| projectId: apiClient.projectId, | ||
| baseUrl: apiClient.baseUrl, | ||
| commitMessage, | ||
| runId, | ||
| customClasses: missingCodeFiles, | ||
| verification, | ||
| stream: true, | ||
|
|
@@ -3413,6 +3427,9 @@ async function provisionMissingCodeFiles( | |
| // retires them outright. | ||
| provisionWork?.then( | ||
| (late) => { | ||
| // When the run-status lookup already decided this attempt's real | ||
| // outcome, the late stream must not double-credit its writes. | ||
| if (runStatusResolved) return; | ||
| if (late?.finalResultReceived && late.success) { | ||
| adjustProjectSourceDirty(apiClient, missingCodeFiles, { | ||
| uncertain: -1, | ||
|
|
@@ -3429,6 +3446,49 @@ async function provisionMissingCodeFiles( | |
| }, | ||
| () => {}, | ||
| ); | ||
| // The record is the authority here: ask the runner what actually | ||
| // happened instead of re-reading the project and guessing. Only when the | ||
| // answer is definitive does it replace the estimate; a 404, a 5xx or an | ||
| // unrecognised body (an older runner) falls through to the reconcile. | ||
| let statusOutcome = { definitive: null }; | ||
| try { | ||
| statusOutcome = await queryRunStatusWithinBudget({ | ||
| provisionEndpoint: FLUTTERFLOW_CLASS_PROVISION_ENDPOINT, | ||
| runId, | ||
| apiKey: apiClient.apiKey, | ||
| }); | ||
| } catch { | ||
| // The status query must fail closed: any unexpected throw from it (a | ||
| // browser abort surfacing unusually, a half-updated bundle) falls back | ||
| // to the existing reconcile instead of fabricating a new failure. | ||
| statusOutcome = { definitive: null }; | ||
| } | ||
| if (statusOutcome.definitive === "success") { | ||
| // The runner confirms every class it was asked to write landed, so the | ||
| // provision succeeds and step 3 (the pubspec push) runs as usual — this | ||
| // is a known success, not an estimate. | ||
| runStatusResolved = true; | ||
| adjustProjectSourceDirty(apiClient, missingCodeFiles, { | ||
| uncertain: -1, | ||
| verified: 1, | ||
| }); | ||
| return { | ||
| remoteFiles, | ||
| syncFileMap: excludeProvisionedCodeFiles(fileMap, missingCodeFiles), | ||
| unverified, | ||
| approximate, | ||
| provisionSucceeded: true, | ||
| }; | ||
| } | ||
| if (statusOutcome.definitive === "failed") { | ||
| // A provable pre-write refusal: nothing could have been written, so it | ||
| // is a real failure, not an unknown outcome. | ||
| runStatusResolved = true; | ||
| throw new Error( | ||
| statusOutcome.error || "The deploy failed before writing anything.", | ||
| ); | ||
|
Comment on lines
+3483
to
+3489
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Pre-write failures leave project exports uncached When a lost response resolves to Learn moreThe unconfirmed-error handler calls markProjectSourceDirty before querying status. The failed status is definitive only when the runner stopped before any class write, so that attempt cannot create a later file. The new failure branch leaves its uncertain credits in Example: A compile check rejects GaugeWidget, but its stream disconnects. Status confirms a pre-write failure. Future deploys see no GaugeWidget in FlutterFlow and fetch a new full export every time, instead of reusing the cached project source. Recommended fix: On a definitive pre-write failure, decrement that attempt's uncertain credits with Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| } | ||
|
|
||
| const reconciled = await reconcileUnconfirmedProvision( | ||
| apiClient, | ||
| missingCodeFiles, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Recovered deploys lose compile-check warnings
When verification fails to run and the provision response is lost,
statusOutcomereports success without the runner'sverificationSkippedwarning. The deploy finishes without telling the user their classes were not compiled.Learn more
The runner returns
verificationSkippedwhen the compile check cannot run. The normal provisioning result appends that warning tounverifiedbefore returning it. The recovered-success branch bypasses the result and returns only the pre-request warnings, while recordDone does not persist the skip reason.Example: Flutter package resolution fails, then GaugeWidget deploys successfully but its stream drops. A
donestatus recovers the deploy, yet the user receives no warning that GaugeWidget was never compiled.Recommended fix: Persist the runner's verification outcome in its run record and return it through the status endpoint. Add any recovered skip reason to
unverifiedjust as the normal result path does.Was this helpful? React with 👍 or 👎 to provide feedback.