@toast-ninja/toast-cli 0.1.0 → 0.1.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.
- package/README.md +3 -2
- package/package.json +1 -1
- package/src/cli.js +60 -19
package/README.md
CHANGED
|
@@ -49,11 +49,12 @@ toast review-ci wait \
|
|
|
49
49
|
--repo toast-ninja/backend \
|
|
50
50
|
--pr 1177 \
|
|
51
51
|
--head "$GITHUB_SHA" \
|
|
52
|
-
--timeout 20m
|
|
53
|
-
--interval 10s
|
|
52
|
+
--timeout 20m
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
`wait` exits `0` only when Toast returns `acknowledged` for the exact head SHA. It exits `1` for actionable non-green states and `2` for auth, network, argument, or timeout errors.
|
|
56
|
+
It waits on Toast's server-side Review CI events through bounded requests, so a
|
|
57
|
+
local polling interval is not needed.
|
|
57
58
|
|
|
58
59
|
CI can also provide a token directly:
|
|
59
60
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -11,7 +11,13 @@ const {
|
|
|
11
11
|
const { requestJson } = require('./http')
|
|
12
12
|
|
|
13
13
|
const DEFAULT_API_URL = 'https://api.toast.ninja'
|
|
14
|
-
const
|
|
14
|
+
const DEFAULT_WAIT_REQUEST_SECONDS = 25
|
|
15
|
+
const WAIT_REQUEST_GRACE_MS = 5000
|
|
16
|
+
const WAIT_STATUSES = new Set([
|
|
17
|
+
'pending',
|
|
18
|
+
'degraded:reconciliation_incomplete',
|
|
19
|
+
'head_changed',
|
|
20
|
+
])
|
|
15
21
|
const REVIEW_CI_SKILL_PATH = path.join(
|
|
16
22
|
__dirname,
|
|
17
23
|
'..',
|
|
@@ -194,12 +200,25 @@ const openBrowserDefault = url =>
|
|
|
194
200
|
})
|
|
195
201
|
})
|
|
196
202
|
|
|
197
|
-
const buildReviewStateUrl = ({
|
|
198
|
-
|
|
203
|
+
const buildReviewStateUrl = ({
|
|
204
|
+
apiUrl,
|
|
205
|
+
repo,
|
|
206
|
+
pullNumber,
|
|
207
|
+
head,
|
|
208
|
+
endpoint,
|
|
209
|
+
query = {},
|
|
210
|
+
}) => {
|
|
211
|
+
const params = new URLSearchParams({
|
|
212
|
+
head,
|
|
213
|
+
...query,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return `${apiUrl}/api/review-ci/repos/${encodeURIComponent(
|
|
199
217
|
repo.owner,
|
|
200
218
|
)}/${encodeURIComponent(repo.repo)}/pulls/${encodeURIComponent(
|
|
201
219
|
pullNumber,
|
|
202
|
-
)}/${endpoint}
|
|
220
|
+
)}/${endpoint}?${params.toString()}`
|
|
221
|
+
}
|
|
203
222
|
|
|
204
223
|
const requestReviewState = async ({
|
|
205
224
|
options,
|
|
@@ -207,6 +226,8 @@ const requestReviewState = async ({
|
|
|
207
226
|
config,
|
|
208
227
|
request,
|
|
209
228
|
endpoint = 'status',
|
|
229
|
+
waitTimeoutSeconds,
|
|
230
|
+
timeoutMs,
|
|
210
231
|
}) => {
|
|
211
232
|
const repo = parseRepo(requireOption(options, 'repo'))
|
|
212
233
|
const pullNumber = requireOption(options, 'pr')
|
|
@@ -219,13 +240,26 @@ const requestReviewState = async ({
|
|
|
219
240
|
'Missing Toast token. Run `toast review-ci auth` or set TOAST_TOKEN.',
|
|
220
241
|
)
|
|
221
242
|
|
|
222
|
-
const
|
|
243
|
+
const requestOptions = {
|
|
223
244
|
method: 'GET',
|
|
224
|
-
url: buildReviewStateUrl({
|
|
245
|
+
url: buildReviewStateUrl({
|
|
246
|
+
apiUrl,
|
|
247
|
+
repo,
|
|
248
|
+
pullNumber,
|
|
249
|
+
head,
|
|
250
|
+
endpoint,
|
|
251
|
+
query: waitTimeoutSeconds
|
|
252
|
+
? { wait_timeout_seconds: String(waitTimeoutSeconds) }
|
|
253
|
+
: {},
|
|
254
|
+
}),
|
|
225
255
|
headers: {
|
|
226
256
|
Authorization: `Bearer ${token}`,
|
|
227
257
|
},
|
|
228
|
-
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (timeoutMs) requestOptions.timeoutMs = timeoutMs
|
|
261
|
+
|
|
262
|
+
const response = await request(requestOptions)
|
|
229
263
|
|
|
230
264
|
if (response.statusCode >= 400) {
|
|
231
265
|
throw new Error(`Review CI request failed with HTTP ${response.statusCode}`)
|
|
@@ -297,13 +331,28 @@ const handleWait = async context => {
|
|
|
297
331
|
const config = context.readConfig()
|
|
298
332
|
const { options } = parseArgs(getReviewStateArgs(context))
|
|
299
333
|
const timeoutMs = parseDurationMs(options.timeout || '20m')
|
|
300
|
-
|
|
334
|
+
if (options.interval) parseDurationMs(options.interval)
|
|
301
335
|
const startedAt = toMillis(context.now())
|
|
302
|
-
let elapsedMs = 0
|
|
303
336
|
|
|
304
|
-
while (
|
|
337
|
+
while (true) {
|
|
338
|
+
const elapsedMs = toMillis(context.now()) - startedAt
|
|
339
|
+
const remainingMs = timeoutMs - elapsedMs
|
|
340
|
+
|
|
341
|
+
if (remainingMs <= 0) break
|
|
342
|
+
|
|
343
|
+
const waitTimeoutSeconds = Math.max(
|
|
344
|
+
1,
|
|
345
|
+
Math.min(DEFAULT_WAIT_REQUEST_SECONDS, Math.ceil(remainingMs / 1000)),
|
|
346
|
+
)
|
|
305
347
|
// eslint-disable-next-line no-await-in-loop
|
|
306
|
-
const state = await requestReviewState({
|
|
348
|
+
const state = await requestReviewState({
|
|
349
|
+
...context,
|
|
350
|
+
config,
|
|
351
|
+
options,
|
|
352
|
+
endpoint: 'wait',
|
|
353
|
+
waitTimeoutSeconds,
|
|
354
|
+
timeoutMs: waitTimeoutSeconds * 1000 + WAIT_REQUEST_GRACE_MS,
|
|
355
|
+
})
|
|
307
356
|
|
|
308
357
|
if (state.status === 'acknowledged') {
|
|
309
358
|
writeReviewState({ state, stdout: context.stdout, asJson: options.json })
|
|
@@ -314,14 +363,6 @@ const handleWait = async context => {
|
|
|
314
363
|
writeReviewState({ state, stdout: context.stdout, asJson: options.json })
|
|
315
364
|
return 1
|
|
316
365
|
}
|
|
317
|
-
|
|
318
|
-
// eslint-disable-next-line no-await-in-loop
|
|
319
|
-
await context.sleep(intervalMs)
|
|
320
|
-
elapsedMs += intervalMs
|
|
321
|
-
|
|
322
|
-
if (toMillis(context.now()) - startedAt > timeoutMs) {
|
|
323
|
-
break
|
|
324
|
-
}
|
|
325
366
|
}
|
|
326
367
|
|
|
327
368
|
context.stderr.write('Timed out waiting for Review CI acknowledgement\n')
|