Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .github/workflows/test-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ on:
- "v1.*"
paths:
- "src/tools/**"
- "src/core/download.ts"
- "src/core/retry.ts"
- ".github/workflows/test-install.yml"
pull_request:
paths:
- "src/tools/**"
- "src/core/download.ts"
- "src/core/retry.ts"
- ".github/workflows/test-install.yml"
schedule:
# Weekly Monday 9am UTC — detect upstream CDN/API breakage
Expand All @@ -40,11 +44,31 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
quarto install tinytex
for attempt in 1 2 3; do
if quarto install tinytex; then
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "::warning::Attempt $attempt failed, retrying in 15s..."
sleep 15
fi
done
echo "::error::TinyTeX install failed after 3 attempts"
exit 1

- name: Install Chrome Headless Shell
run: |
quarto install chrome-headless-shell --no-prompt
for attempt in 1 2 3; do
if quarto install chrome-headless-shell --no-prompt; then
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "::warning::Attempt $attempt failed, retrying in 15s..."
sleep 15
fi
done
echo "::error::Chrome Headless Shell install failed after 3 attempts"
exit 1

- name: Verify tools with quarto check
run: |
Expand Down
85 changes: 53 additions & 32 deletions src/core/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { writeAll } from "io/write-all";
import { progressBar } from "./console.ts";
import { withRetry } from "./retry.ts";

export interface DownloadError extends Error {
statusCode: number;
Expand All @@ -17,42 +18,62 @@ export async function downloadWithProgress(
msg: string,
toFile: string,
) {
// Fetch the data
const response = await (typeof url === "string"
? fetch(
url,
{
redirect: "follow",
},
)
: url);
await withRetry(async () => {
// Fetch the data
const response = await (typeof url === "string"
? fetch(
url,
{
redirect: "follow",
},
)
: url);

// Write the data to a file
if (response.status === 200 && response.body) {
const pkgFile = await Deno.open(toFile, { create: true, write: true });
// Write the data to a file
if (response.status === 200 && response.body) {
const pkgFile = await Deno.open(
toFile,
{ create: true, write: true, truncate: true },
);

const contentLength =
(response.headers.get("content-length") || 0) as number;
const contentLengthMb = contentLength / 1024 / 1024;
const contentLength =
(response.headers.get("content-length") || 0) as number;
const contentLengthMb = contentLength / 1024 / 1024;

const prog = progressBar(contentLengthMb, msg);
const prog = progressBar(contentLengthMb, msg);

let totalLength = 0;
for await (const chunk of response.body) {
await writeAll(pkgFile, chunk);
totalLength = totalLength + chunk.length;
if (contentLength > 0) {
prog.update(
totalLength / 1024 / 1024,
`${(totalLength / 1024 / 1024).toFixed(1)}MB`,
);
try {
let totalLength = 0;
for await (const chunk of response.body) {
await writeAll(pkgFile, chunk);
totalLength = totalLength + chunk.length;
if (contentLength > 0) {
prog.update(
totalLength / 1024 / 1024,
`${(totalLength / 1024 / 1024).toFixed(1)}MB`,
);
}
}
prog.complete();
} finally {
pkgFile.close();
}
} else {
throw new Error(
`download failed (HTTP status ${response.status} - ${response.statusText})`,
);
}
prog.complete();
pkgFile.close();
} else {
throw new Error(
`download failed (HTTP status ${response.status} - ${response.statusText})`,
);
}
}, {
attempts: 3,
minWait: 2000,
maxWait: 10000,
retry: (err: Error) => {
// Don't retry HTTP status errors (4xx, 5xx) — they're deterministic
if (err.message.startsWith("download failed (HTTP status")) {
return false;
}
// Retry network errors (connection reset, timeout, body read errors)
return true;
},
});
}
2 changes: 1 addition & 1 deletion src/core/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function withRetry<T = void>(
let attempt = 0;
while (true) {
try {
return fn();
return await fn();
} catch (err) {
if (!(err instanceof Error)) throw err;
if ((attempt++ >= attempts) || (retry && !retry(err))) {
Expand Down
Loading