@faable/faable 4.18.2 → 4.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,14 +5,34 @@ const POLL_INTERVAL_MS = 4000;
5
5
  // 30min, abandoned janitor 60min) resolve the deployment first in practice.
6
6
  const FOLLOW_TIMEOUT_MS = 45 * 60 * 1000;
7
7
  const wait = (ms) => new Promise((r) => setTimeout(r, ms));
8
+ /** The clone the platform created to retry this failed build, if any. */
9
+ const retry_of = (deployment) => {
10
+ const retry = deployment?.runtime_fallback_retry;
11
+ if (!retry?.deployment_id)
12
+ return null;
13
+ // The api stores the clone as bare hex (a `.lean()` ObjectId).
14
+ const id = retry.deployment_id.startsWith("deployment_")
15
+ ? retry.deployment_id
16
+ : `deployment_${retry.deployment_id}`;
17
+ return { id, to: retry.to };
18
+ };
8
19
  /**
9
20
  * Follow a remote build until the image handoff: poll the phase and tail the
10
21
  * build logs the builder uploads every ~10s (replace semantics → print the
11
22
  * suffix beyond what we already showed). Returns on INITIALIZING/READY
12
23
  * (image landed — the regular promotion poll takes over); throws on
13
24
  * BUILD_ERROR/ERROR with the server-provided reason.
25
+ *
26
+ * One exception to "BUILD_ERROR throws": when the platform already retried
27
+ * the build on the previous Python (`runtime_fallback_retry`, api §12), the
28
+ * failure is not the outcome — the clone's is. Follow the clone instead and
29
+ * return ITS id, so the promotion poll and `--json` speak about the deployment
30
+ * that will go live. Until 2026-10-18 the CLI exited red for a deploy that
31
+ * was about to succeed (backlog §379).
14
32
  */
15
- const follow_remote_build = async (api, deployment_id,
33
+ const follow_remote_build = async (api,
34
+ // `let`: a platform retry switches the follow to its clone.
35
+ initial_deployment_id,
16
36
  // Where the build log goes. stdout for a person; stderr when stdout carries
17
37
  // a JSON document (--json, or a program driving the CLI): one build line in
18
38
  // front of it and the caller can't parse the outcome — the MCP reported a
@@ -20,6 +40,8 @@ const follow_remote_build = async (api, deployment_id,
20
40
  out = process.stdout) => {
21
41
  let printed = 0;
22
42
  let last_phase = "";
43
+ let followed_retry = false;
44
+ let deployment_id = initial_deployment_id;
23
45
  const started = Date.now();
24
46
  const tail_logs = async () => {
25
47
  try {
@@ -49,6 +71,15 @@ out = process.stdout) => {
49
71
  }
50
72
  await tail_logs();
51
73
  if (phase === "BUILD_ERROR" || phase === "ERROR") {
74
+ const retry = retry_of(deployment);
75
+ if (retry && !followed_retry) {
76
+ followed_retry = true;
77
+ log.info(`↩️ The default Python failed with your dependencies — the platform is retrying on Python ${retry.to} · ${retry.id}`);
78
+ deployment_id = retry.id;
79
+ printed = 0;
80
+ last_phase = "";
81
+ continue;
82
+ }
52
83
  const reason = deployment?.status?.reason;
53
84
  throw new Error(`Remote build failed${reason ? `: ${reason}` : ""}`.trim());
54
85
  }
@@ -56,7 +87,7 @@ out = process.stdout) => {
56
87
  // already live (READY) — the promotion poll takes over from here.
57
88
  if (phase === "INITIALIZING" || phase === "READY") {
58
89
  await tail_logs();
59
- return;
90
+ return { id: deployment_id };
60
91
  }
61
92
  await wait(POLL_INTERVAL_MS);
62
93
  }
@@ -34,7 +34,12 @@ const deploy_remote = async (props) => {
34
34
  });
35
35
  props.on_deployment?.(deployment.id);
36
36
  log.info(`Preparing to build in faable cloud · ${deployment.id}`);
37
- await follow_remote_build(api, deployment.id, props.log_stream);
37
+ const landed = await follow_remote_build(api, deployment.id, props.log_stream);
38
+ // The platform retried the build on the previous Python (§12): what goes
39
+ // live is the clone, so the promotion poll must wait for IT.
40
+ if (landed.id !== deployment.id) {
41
+ return await api.getDeployment(landed.id);
42
+ }
38
43
  return deployment;
39
44
  };
40
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "4.18.2",
3
+ "version": "4.18.3",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",