Public: anyone can read this; the crew can change it.
A preview that is not serving says the truth about its pull and links back: to the pull, to production and home #8168
Check · Review · Land · Live done — Landed and live in production.
torque/forge:torque/8154-a-preview-that-is-not-serving-says-the-t → main 2 commits fork
2 files
changed +234
−8
src/apps/proxy.ts +40 −8
| @@ -739,7 +739,7 @@ function admit( | ||
| 739 | 739 | runtime.rouse(target.app, target.env); |
| 740 | 740 | const status = runtime.envStatus(target.app, target.env); |
| 741 | 741 | if (status === undefined || status.port === undefined) { |
| 742 | return err(unavailable(runtime, target, status)); | |
| 742 | return err(unavailable(runtime, target, status, undefined, grant?.who)); | |
| 743 | 743 | } |
| 744 | 744 | return ok( |
| 745 | 745 | grant === undefined |
| @@ -1538,11 +1538,12 @@ function cookieValue(request: Request, name: string): string | undefined { | ||
| 1538 | 1538 | * The page an environment that is not serving shows: what it is doing, in |
| 1539 | 1539 | * words, with the run to look at. Never a bare gateway error. |
| 1540 | 1540 | */ |
| 1541 | function unavailable( | |
| 1541 | export function unavailable( | |
| 1542 | 1542 | runtime: AppsRuntime, |
| 1543 | 1543 | target: AppTarget, |
| 1544 | 1544 | status: EnvStatus | undefined, |
| 1545 | 1545 | reason?: string, |
| 1546 | who?: GrantWho, | |
| 1546 | 1547 | ): Response { |
| 1547 | 1548 | const name = target.app.slug; |
| 1548 | 1549 | const where = |
| @@ -1551,10 +1552,21 @@ function unavailable( | ||
| 1551 | 1552 | : `the preview of pull #${target.env.slice(5)}`; |
| 1552 | 1553 | const state = status?.state ?? "stopped"; |
| 1553 | 1554 | const deploying = state === "deploying"; |
| 1554 | // A preview the forge no longer knows is gone, not broken: its pull closed | |
| 1555 | // and its data went with it. That is a 404, which also lets this page | |
| 1556 | // through an edge that replaces every 5xx with one of its own. | |
| 1557 | const gone = status === undefined && target.env !== "production"; | |
| 1555 | // A preview is gone when its pull is closed, merged or no longer exists: | |
| 1556 | // its data went with it. That is a 404, which also lets this page through | |
| 1557 | // an edge that replaces every 5xx with one of its own. An open pull with no | |
| 1558 | // environment is not gone: it deploys again on the next push. | |
| 1559 | const at = pillWhere(runtime, target, who); | |
| 1560 | const pull = | |
| 1561 | at.pull === undefined | |
| 1562 | ? undefined | |
| 1563 | : getIssue(runtime.app.db, target.app.repoId, at.pull); | |
| 1564 | const preview = target.env !== "production"; | |
| 1565 | const gone = preview && (pull === undefined || pull.state === "closed"); | |
| 1566 | // Nothing was ever deployed for an open pull: also a 404, for the same | |
| 1567 | // edge, since there is nothing at this address yet. A stopped or failed | |
| 1568 | // environment keeps its 502. | |
| 1569 | const absent = preview && !gone && status === undefined; | |
| 1558 | 1570 | const message = |
| 1559 | 1571 | reason ?? |
| 1560 | 1572 | (deploying |
| @@ -1563,13 +1575,32 @@ function unavailable( | ||
| 1563 | 1575 | ? (status?.error ?? "The last deploy failed.") |
| 1564 | 1576 | : gone |
| 1565 | 1577 | ? "This preview is gone: its pull was closed, and its data with it." |
| 1566 | : "Nothing is running here yet."); | |
| 1578 | : absent | |
| 1579 | ? "The preview is not running. It starts again with the next push to the pull, or with app_deploy redeploy." | |
| 1580 | : "Nothing is running here yet."); | |
| 1567 | 1581 | const links = discoveryLinks(runtime, target); |
| 1568 | 1582 | const report = links.length > 0; |
| 1569 | 1583 | const runLink = |
| 1570 | 1584 | status?.runId === undefined |
| 1571 | 1585 | ? "" |
| 1572 | 1586 | : `<p><a href="${escape(runtime.options.forgeUrl)}/runs/${escape(status.runId)}">See the run on the forge</a></p>`; |
| 1587 | const forge = runtime.options.forgeUrl; | |
| 1588 | const exits: string[] = []; | |
| 1589 | if (preview && at.repo !== undefined && at.pull !== undefined) { | |
| 1590 | const href = new URL(`/${at.repo}/pulls/${at.pull}`, forge).href; | |
| 1591 | exits.push( | |
| 1592 | `<a href="${escape(href)}">Pull #${at.pull}${at.title === undefined ? "" : `: ${escape(at.title)}`}</a>`, | |
| 1593 | ); | |
| 1594 | } | |
| 1595 | // A private app's address, like its repository's name, goes only to a reader. | |
| 1596 | const mayNameProduction = target.app.access === "public" || at.repo !== undefined; | |
| 1597 | if ( | |
| 1598 | preview && | |
| 1599 | mayNameProduction && | |
| 1600 | runtime.envStatus(target.app, "production")?.port !== undefined | |
| 1601 | ) | |
| 1602 | exits.push(`<a href="${escape(runtime.urlFor(target.app, "production"))}">Production</a>`); | |
| 1603 | exits.push(`<a href="${escape(new URL("/", forge).href)}">Forge home</a>`); | |
| 1573 | 1604 | const html = `<!doctype html> |
| 1574 | 1605 | <meta charset="utf-8"> |
| 1575 | 1606 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| @@ -1586,10 +1617,11 @@ ${deploying ? '<meta http-equiv="refresh" content="3">' : ""} | ||
| 1586 | 1617 | <p>This is ${escape(where)}. ${escape(message)}</p> |
| 1587 | 1618 | ${deploying ? "<p>This page refreshes on its own.</p>" : ""} |
| 1588 | 1619 | ${runLink} |
| 1620 | <p>${exits.join(" · ")}</p> | |
| 1589 | 1621 | ${report ? '<p><a href="/__forge/report">Report a problem</a> to the people who keep it; no account needed.</p>' : ""} |
| 1590 | 1622 | `; |
| 1591 | 1623 | return new Response(html, { |
| 1592 | status: deploying ? 503 : gone ? 404 : 502, | |
| 1624 | status: deploying ? 503 : gone || absent ? 404 : 502, | |
| 1593 | 1625 | headers: { |
| 1594 | 1626 | "content-type": "text/html; charset=utf-8", |
| 1595 | 1627 | "cache-control": "no-store", |
new above
offline