mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-29 00:01:42 +08:00
fix(ci): initialize cloud e2e command errors
This commit is contained in:
@@ -3,6 +3,13 @@ const CLIENT_TRANSPORT_FAILURE = /(?:\b502\b|ERR_(?:FAILED|TUNNEL_CONNECTION_FAI
|
||||
const TUNNEL_CONTEXT_CANCELED = /(?:Incoming request ended abruptly|Request failed)[^\n]*context canceled/i
|
||||
const QUICK_TUNNEL_REQUEST = /trycloudflare\.com/i
|
||||
|
||||
export class CloudE2eCommandError extends Error {
|
||||
constructor(command, commandArgs, code, output) {
|
||||
super(`${command} ${commandArgs.join(' ')} exited with ${code}`)
|
||||
this.output = output
|
||||
}
|
||||
}
|
||||
|
||||
export function isRetryableQuickTunnelFailure({ commandOutput, tunnelOutput }) {
|
||||
if (QUICK_TUNNEL_502.test(commandOutput)) return true
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { cloudE2eAttemptCount, isRetryableQuickTunnelFailure } from './cloud-e2e-resilience.mjs'
|
||||
import {
|
||||
CloudE2eCommandError,
|
||||
cloudE2eAttemptCount,
|
||||
isRetryableQuickTunnelFailure,
|
||||
} from './cloud-e2e-resilience.mjs'
|
||||
|
||||
describe('cloud E2E resilience', () => {
|
||||
it('provides the command error type before the runner executes', () => {
|
||||
const error = new CloudE2eCommandError('node', ['playwright', 'test'], 1, 'gateway response')
|
||||
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
expect(error).toBeInstanceOf(CloudE2eCommandError)
|
||||
expect(error.message).toBe('node playwright test exited with 1')
|
||||
expect(error.output).toBe('gateway response')
|
||||
})
|
||||
|
||||
it('retries a Cloudflare Quick Tunnel gateway page', () => {
|
||||
expect(
|
||||
isRetryableQuickTunnelFailure({
|
||||
|
||||
@@ -2,7 +2,11 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { Resolver } from 'node:dns/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { cloudE2eAttemptCount, isRetryableQuickTunnelFailure } from './cloud-e2e-resilience.mjs'
|
||||
import {
|
||||
CloudE2eCommandError,
|
||||
cloudE2eAttemptCount,
|
||||
isRetryableQuickTunnelFailure,
|
||||
} from './cloud-e2e-resilience.mjs'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const require = createRequire(import.meta.url)
|
||||
@@ -49,7 +53,7 @@ for (let attempt = 1; attempt <= maxRunAttempts; attempt += 1) {
|
||||
break
|
||||
} catch (error) {
|
||||
const retryable =
|
||||
error instanceof CommandError &&
|
||||
error instanceof CloudE2eCommandError &&
|
||||
tunnel &&
|
||||
isRetryableQuickTunnelFailure({
|
||||
commandOutput: error.output,
|
||||
@@ -227,13 +231,6 @@ async function waitForPublicTunnelIp(hostname) {
|
||||
throw new Error(`Timed out waiting for public tunnel DNS: ${hostname}`)
|
||||
}
|
||||
|
||||
class CommandError extends Error {
|
||||
constructor(command, commandArgs, code, output) {
|
||||
super(`${command} ${commandArgs.join(' ')} exited with ${code}`)
|
||||
this.output = output
|
||||
}
|
||||
}
|
||||
|
||||
function run(command, commandArgs, env = {}, captureOutput = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, commandArgs, {
|
||||
@@ -253,7 +250,7 @@ function run(command, commandArgs, env = {}, captureOutput = false) {
|
||||
}
|
||||
child.on('exit', (code) => {
|
||||
if (code === 0) resolve()
|
||||
else reject(new CommandError(command, commandArgs, code, output))
|
||||
else reject(new CloudE2eCommandError(command, commandArgs, code, output))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user