@yadurajfleetos/cli 0.9.0 → 0.9.2

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.
@@ -52,7 +52,10 @@ export const applyCommand = {
52
52
  return console.log(JSON.stringify(body, null, 2));
53
53
  if (!body.valid) {
54
54
  for (const issue of body.issues ?? []) {
55
- console.log(`${glyph.fail} ${c.red('invalid')} ${issue}`);
55
+ const text = typeof issue === 'string'
56
+ ? issue
57
+ : [issue.path, issue.message].filter(Boolean).join(': ');
58
+ console.log(`${glyph.fail} ${c.red('invalid')} ${text}`);
56
59
  }
57
60
  throw new CliError('The manifest was not applied.', EXIT.usage);
58
61
  }
@@ -147,7 +147,18 @@ async function deployOne(service, opts) {
147
147
  const result = (await request('POST', `/services/${service.id}/deploy`, {
148
148
  body: { gitSha: opts.gitSha, contextId },
149
149
  })).body;
150
- walker.finish(`scheduled onto ${result.placedOn.name}`);
150
+ // Deliberately not walker.finish().
151
+ //
152
+ // The control plane answers as soon as a node is chosen and builds
153
+ // afterwards, so this response arrives before the build starts.
154
+ // Finishing here marked build, push, schedule and container as "not
155
+ // needed" — while the build ran for three minutes — and left a silent
156
+ // counter that reads as a hang. The steps are real; only the reply is
157
+ // early. Progress keeps driving the ladder until the phases are
158
+ // genuinely done.
159
+ walker.advance(2, `scheduled onto ${result.placedOn.name}`);
160
+ await progress.untilSettled({ deadlineMs: 45 * 60_000 });
161
+ walker.finish();
151
162
  return result;
152
163
  }
153
164
  finally {
package/dist/progress.js CHANGED
@@ -45,6 +45,11 @@ export function follow(serviceId, sink, opts = {}) {
45
45
  let stopped = false;
46
46
  let misses = 0;
47
47
  let wake = null;
48
+ /** Resolves once the deploy has left the phases this ladder draws. */
49
+ let settled = () => { };
50
+ const settledWhen = new Promise((resolve) => {
51
+ settled = resolve;
52
+ });
48
53
  const rest = (ms) => new Promise((resolve) => {
49
54
  const timer = setTimeout(resolve, ms);
50
55
  wake = () => {
@@ -60,8 +65,24 @@ export function follow(serviceId, sink, opts = {}) {
60
65
  try {
61
66
  const progress = await fetchProgress(serviceId);
62
67
  misses = 0;
63
- if (progress)
68
+ if (progress) {
64
69
  sink(progress);
70
+ // `deploying` means the build and push are behind us and the node
71
+ // has been told; everything after that is the rollout, which the
72
+ // caller reports on separately.
73
+ // `status` carries the phase: queued → building → pushing →
74
+ // scheduling → deploying. Anything at or past `deploying` means the
75
+ // build and push are behind us; the rest is the rollout, which the
76
+ // caller reports on separately.
77
+ if (progress.status === 'deploying' || progress.status === 'running')
78
+ settled();
79
+ }
80
+ else {
81
+ // No progress row at all means the build phases are over: the row is
82
+ // dropped once a deploy leaves them, and a deploy that never wrote
83
+ // one had nothing to build.
84
+ settled();
85
+ }
65
86
  }
66
87
  catch {
67
88
  // A control plane that predates the endpoint answers 404 every time, so
@@ -73,12 +94,35 @@ export function follow(serviceId, sink, opts = {}) {
73
94
  }
74
95
  }
75
96
  })();
97
+ // The loop ending for any reason — including a control plane with no
98
+ // progress endpoint — has to release anybody waiting, or a missing feature
99
+ // becomes a hang.
100
+ void loop.then(() => settled()).catch(() => settled());
76
101
  return {
77
102
  stop: async () => {
78
103
  stopped = true;
79
104
  wake?.();
80
105
  await loop.catch(() => { });
81
106
  },
107
+ /**
108
+ * Wait until the build phases are done.
109
+ *
110
+ * Bounded: a deploy whose progress never arrives must not hold the ladder
111
+ * open for ever. On the deadline this returns rather than throwing — the
112
+ * caller's own wait is what decides whether the deploy failed, and two
113
+ * things reporting the same failure is worse than one.
114
+ */
115
+ untilSettled: async ({ deadlineMs = 45 * 60_000 } = {}) => {
116
+ let timer;
117
+ await Promise.race([
118
+ settledWhen,
119
+ new Promise((resolve) => {
120
+ timer = setTimeout(resolve, deadlineMs);
121
+ }),
122
+ ]);
123
+ if (timer)
124
+ clearTimeout(timer);
125
+ },
82
126
  };
83
127
  }
84
128
  /** Ladder steps for a deploy, in the order the control plane reports them. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yadurajfleetos/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Fleet OS command-line interface for deploying and orchestrating services on user-owned hardware",
5
5
  "type": "module",
6
6
  "license": "MIT",