Builds say Replaced or Down once they are no longer live
Every successful build stayed 'Live' in a project's history, though each deploy replaces its app's Worker in place and taken-down previews are deleted. Now one build per app is Live: the one a newer build replaced is Replaced, and one whose app was taken down is Down. A migration corrects the builds already recorded.
5 files+34−10/5 viewed
| 5 | 5 | queued: { label: "Queued", tone: "text-muted" }, | |
| 6 | 6 | building: { label: "Building", tone: "text-warn" }, | |
| 7 | 7 | ready: { label: "Live", tone: "text-accent" }, | |
| 8 | + | replaced: { label: "Replaced", tone: "text-faint" }, | |
| 9 | + | down: { label: "Down", tone: "text-faint" }, | |
| 8 | 10 | failed: { label: "Failed", tone: "text-danger" }, | |
| 9 | 11 | skipped: { label: "Skipped", tone: "text-faint" }, | |
| 10 | 12 | }; |
| 23 | 23 | queued: "Waiting for a sandbox", | |
| 24 | 24 | building: "Building", | |
| 25 | 25 | ready: "Live", | |
| 26 | + | replaced: "Replaced by a newer build", | |
| 27 | + | down: "Taken down", | |
| 26 | 28 | failed: "Failed", | |
| 27 | 29 | skipped: "Skipped", | |
| 28 | 30 | } as const; |
| 46 | 46 | /** Waiting for a sandbox. */ | |
| 47 | 47 | | "queued" | |
| 48 | 48 | | "building" | |
| 49 | + | /** What its app serves now. */ | |
| 49 | 50 | | "ready" | |
| 51 | + | /** Built and served, until a newer build of the same app replaced it. */ | |
| 52 | + | | "replaced" | |
| 53 | + | /** Built and served, until its app was taken down. */ | |
| 54 | + | | "down" | |
| 50 | 55 | | "failed" | |
| 51 | 56 | /** Not built: the workspace's plan is off, or the build was replaced. */ | |
| 52 | 57 | | "skipped"; |
| 1 | + | -- A build stays 'ready' only while it is what its app serves. One a newer | |
| 2 | + | -- build replaced becomes 'replaced'; one whose app was taken down becomes | |
| 3 | + | -- 'down'. This corrects the builds already recorded. | |
| 4 | + | ||
| 5 | + | -- Taken down: no app holds the script any more. | |
| 6 | + | UPDATE deployments SET status = 'down' | |
| 7 | + | WHERE status = 'ready' AND script NOT IN (SELECT script FROM apps); | |
| 8 | + | ||
| 9 | + | -- Replaced: a later ready build of the same app exists. | |
| 10 | + | UPDATE deployments SET status = 'replaced' | |
| 11 | + | WHERE status = 'ready' | |
| 12 | + | AND EXISTS ( | |
| 13 | + | SELECT 1 FROM deployments newer | |
| 14 | + | WHERE newer.script = deployments.script AND newer.status = 'ready' | |
| 15 | + | AND COALESCE(newer.finished_at, newer.created_at) > COALESCE(deployments.finished_at, deployments.created_at) | |
| 16 | + | ); |
| 882 | 882 | WHERE id = ?`, | |
| 883 | 883 | ) | |
| 884 | 884 | .bind(JSON.stringify(Array.isArray(body.warnings) ? body.warnings : []), String(body.log ?? ""), seconds, at, id), | |
| 885 | + | // The build it replaces is no longer what the app serves. | |
| 885 | 886 | this.db | |
| 887 | + | .prepare(`UPDATE deployments SET status = 'replaced' WHERE script = ? AND id != ? AND status = 'ready'`) | |
| 888 | + | .bind(row.script, id), | |
| 889 | + | this.db | |
| 886 | 890 | .prepare( | |
| 887 | 891 | `INSERT INTO apps (script, project_id, workspace, slug, kind, branch, number, commit_sha, deployed_at, created_at) | |
| 888 | 892 | VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?9) | |
| 1054 | 1058 | ||
| 1055 | 1059 | private async removeApp(script: string): Promise<void> { | |
| 1056 | 1060 | await this.cloudflare?.deleteScript(script); | |
| 1057 | − | await this.db.prepare("DELETE FROM apps WHERE script = ?").bind(script).run(); | |
| 1061 | + | await this.db.batch([ | |
| 1062 | + | this.db.prepare("DELETE FROM apps WHERE script = ?").bind(script), | |
| 1063 | + | // Its build is no longer live anywhere. | |
| 1064 | + | this.db.prepare("UPDATE deployments SET status = 'down' WHERE script = ? AND status = 'ready'").bind(script), | |
| 1065 | + | ]); | |
| 1058 | 1066 | } | |
| 1059 | 1067 | ||
| 1060 | 1068 | private async takeDownWhere(projectId: string, kind: DeployKind | null, branch?: string): Promise<void> { |