@faable/faable 1.20.0 → 1.21.0

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.
@@ -9,6 +9,7 @@ import { git_context } from './git_context.js';
9
9
  import { deploy_remote } from './remote/index.js';
10
10
  import { resolve_app_id } from './resolve_app_id.js';
11
11
  import { secrets } from './secrets/index.js';
12
+ import { is_superseded } from './superseded.js';
12
13
  import { mark_build_failure, start_log_sync, upload_logs } from './upload_logs.js';
13
14
  import { upload_tag } from './upload_tag.js';
14
15
 
@@ -179,6 +180,10 @@ const deploy = {
179
180
  log.info(`⏳ Waiting for deployment to be promoted...`);
180
181
  let promoted = false;
181
182
  let failure = null;
183
+ let superseded = null;
184
+ // The pointer at wait-start is the PREVIOUS deployment (older than ours)
185
+ // — only a pointer CHANGE to something that isn't us needs a look.
186
+ const initialActiveId = app.status?.deployment;
182
187
  while (Date.now() - start < timeoutMs) {
183
188
  await wait(intervalMs);
184
189
  try {
@@ -187,6 +192,18 @@ const deploy = {
187
192
  promoted = true;
188
193
  break;
189
194
  }
195
+ // Superseded: the pointer moved to a deployment at least as new as
196
+ // ours (twin workflows on the same push, or a rapid follow-up
197
+ // deploy). It only moves forward — waiting longer can never succeed.
198
+ const activeId = current.status?.deployment;
199
+ if (activeId && activeId !== initialActiveId) {
200
+ const active = await api.getDeployment(activeId).catch(() => null);
201
+ if (active &&
202
+ is_superseded(deployment, active)) {
203
+ superseded = activeId;
204
+ break;
205
+ }
206
+ }
190
207
  // Watch for a terminal runtime failure so we fail fast (and red)
191
208
  // instead of timing out green. The controller marks a crash-looping
192
209
  // deployment ERROR — with a reason (e.g. a missing module) — within
@@ -197,6 +214,12 @@ const deploy = {
197
214
  failure = { phase, reason: dep.status?.reason };
198
215
  break;
199
216
  }
217
+ // CANCELED: the platform superseded this build before it produced a
218
+ // runnable (a sibling deployment of the app won the promotion).
219
+ if (phase === "CANCELED") {
220
+ superseded = dep.status?.reason ?? "canceled";
221
+ break;
222
+ }
200
223
  }
201
224
  catch (_error) {
202
225
  // Ignore transient errors while polling and keep waiting
@@ -206,6 +229,12 @@ const deploy = {
206
229
  if (promoted) {
207
230
  log.info(`🌍 Deployment promoted and live, visit: https://${app.url}`);
208
231
  }
232
+ else if (superseded) {
233
+ // Not a failure: the app IS live, just on a sibling deployment (e.g.
234
+ // duplicated workflows firing on the same push). Green exit — there is
235
+ // nothing for the user to fix in THIS run.
236
+ log.warn(`⚠️ Deployment superseded — a newer deployment of this app was promoted (${superseded}). The app is live: https://${app.url}`);
237
+ }
209
238
  else if (failure) {
210
239
  // Fail the command (non-zero exit → red GitHub Action) and surface the
211
240
  // reason the controller captured so the user sees WHY without digging.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Superseded detection for the promotion wait (deploy v3, twin-workflow
3
+ * case): the app's promoted pointer only ever moves FORWARD (the controller
4
+ * refuses to promote a deployment older than the active one), so once a
5
+ * deployment at-least-as-new as ours is promoted, waiting longer can never
6
+ * succeed. Twins from duplicated workflows land here — one wins the pointer,
7
+ * the other must exit cleanly instead of hanging to the timeout.
8
+ *
9
+ * `>=` on purpose: same-instant twins (created ms apart) supersede each
10
+ * other in whichever order the promoter resolved them.
11
+ */
12
+ const is_superseded = (mine, active) => Boolean(active &&
13
+ active.id !== mine.id &&
14
+ new Date(active.createdAt).getTime() >=
15
+ new Date(mine.createdAt).getTime());
16
+
17
+ export { is_superseded };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",