mirror of
https://github.com/jdx/mise-action.git
synced 2026-06-29 00:00:44 +00:00
fix: fall back to wget when curl is unavailable
Keep streaming curl|tar downloads for speed and use wget only when curl is missing, instead of routing downloads through Node fetch.
This commit is contained in:
parent
8e185e2855
commit
0e80c99049
5 changed files with 108 additions and 207 deletions
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
npm ci
|
||||
npm run all
|
||||
git add dist
|
||||
git add -f dist
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -116,19 +116,6 @@ When installing tools hosted on GitHub (like `gh`, `node`, `bun`, etc.), mise ne
|
|||
|
||||
**Note:** The action automatically uses `${{ github.token }}` as the default, so in most cases you don't need to explicitly provide it. However, if you encounter rate limit errors, make sure the token is being passed correctly.
|
||||
|
||||
## Proxy Configuration
|
||||
|
||||
The action downloads mise using Node.js `fetch`, so it does not require `curl`
|
||||
or `wget` to be installed in the runner container.
|
||||
|
||||
If your runner uses `HTTP_PROXY`, `HTTPS_PROXY`, or `NO_PROXY`, enable Node's
|
||||
environment proxy support before the action runs:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
NODE_USE_ENV_PROXY: "1"
|
||||
```
|
||||
|
||||
## Lock Files
|
||||
|
||||
If a repo mise lock file such as `mise.lock` is present in the working
|
||||
|
|
|
|||
132
dist/index.js
generated
vendored
132
dist/index.js
generated
vendored
|
|
@ -49,7 +49,6 @@ import process$1 from 'node:process';
|
|||
import https$1 from 'node:https';
|
||||
import require$$1$5 from 'tty';
|
||||
import fs$1 from 'node:fs';
|
||||
import { pipeline } from 'stream/promises';
|
||||
|
||||
// We use any as a valid input type
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
|
@ -89247,14 +89246,7 @@ const DEFAULT_CACHE_KEY_TEMPLATE = '{{cache_key_prefix}}-{{platform}}{{#if versi
|
|||
const ROOT_MISE_LOCK_FILE_PATTERNS = [/^\.?mise(?:\.[^.]+)?\.lock$/];
|
||||
const CONFIG_DIR_MISE_LOCK_FILE_PATTERNS = [/^mise(?:\.[^.]+)?\.lock$/];
|
||||
const CONFIG_MISE_LOCK_FILE_PATTERNS = [/^config(?:\.[^.]+)?\.lock$/];
|
||||
const ACTIVE_PROXY_ENV_VARS = [
|
||||
'HTTP_PROXY',
|
||||
'HTTPS_PROXY',
|
||||
'http_proxy',
|
||||
'https_proxy'
|
||||
];
|
||||
const FETCH_TIMEOUT_MS = 300_000;
|
||||
let warnedFetchProxy = false;
|
||||
let cachedDownloadTool;
|
||||
async function run() {
|
||||
try {
|
||||
await setToolVersions();
|
||||
|
|
@ -89275,8 +89267,8 @@ async function run() {
|
|||
// etc.) don't silently send the runner's OIDC token to
|
||||
// a third-party cache without explicit consent.
|
||||
//
|
||||
// Note: `setupMise` fetches the mise binary itself with Node's
|
||||
// `fetch`, which doesn't go through mise's HTTP layer —
|
||||
// Note: `setupMise` fetches the mise binary itself with
|
||||
// `curl` or `wget`, which doesn't go through mise's HTTP layer —
|
||||
// the wings rewriter only kicks in once the resulting
|
||||
// mise binary runs `mise install` and friends. Ordering
|
||||
// here is irrelevant for binary acceleration; we just
|
||||
|
|
@ -89504,19 +89496,19 @@ async function setupMise(version, fetchFromGitHub = false) {
|
|||
case '.zip': {
|
||||
await withExtractedZip(url, 'mise.zip', async (extractDir) => {
|
||||
const extractedMiseBinDir = path$1.join(extractDir, 'mise', 'bin');
|
||||
await moveFile(path$1.join(extractedMiseBinDir, 'mise.exe'), miseBinPath);
|
||||
await mv(path$1.join(extractedMiseBinDir, 'mise.exe'), miseBinPath);
|
||||
await installWindowsMiseShim(extractedMiseBinDir, miseShimPath);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '.tar.zst':
|
||||
await installFromTar(url, 'mise.tar.zst', ['--zstd'], miseBinPath);
|
||||
await installFromTarUrl(url, '--zstd -xf -', miseBinPath);
|
||||
break;
|
||||
case '.tar.gz':
|
||||
await installFromTar(url, 'mise.tar.gz', ['-z'], miseBinPath);
|
||||
await installFromTarUrl(url, '-xzf -', miseBinPath);
|
||||
break;
|
||||
default:
|
||||
await downloadFile(url, miseBinPath);
|
||||
await downloadToFile(url, miseBinPath);
|
||||
await exec('chmod', ['+x', miseBinPath]);
|
||||
break;
|
||||
}
|
||||
|
|
@ -89554,7 +89546,7 @@ async function withExtractedZip(url, archiveName, fn) {
|
|||
try {
|
||||
const archivePath = path$1.join(tempDir, archiveName);
|
||||
const extractDir = path$1.join(tempDir, 'extract');
|
||||
await downloadFile(url, archivePath);
|
||||
await downloadToFile(url, archivePath);
|
||||
await exec('unzip', [archivePath, '-d', extractDir]);
|
||||
await fn(extractDir);
|
||||
}
|
||||
|
|
@ -89570,7 +89562,7 @@ async function installWindowsMiseShim(extractedMiseBinDir, miseShimPath) {
|
|||
info('mise-shim.exe not found in the mise archive; skipping');
|
||||
return;
|
||||
}
|
||||
await moveFile(extractedMiseShimPath, miseShimPath);
|
||||
await mv(extractedMiseShimPath, miseShimPath);
|
||||
}
|
||||
async function ensureWindowsMiseShim(miseBinPath, miseShimPath, version) {
|
||||
if (process.platform !== 'win32')
|
||||
|
|
@ -89590,33 +89582,50 @@ async function ensureWindowsMiseShim(miseBinPath, miseShimPath, version) {
|
|||
warning(`Failed to install mise-shim.exe: ${errorMessage(err)}. Continuing because mise can fall back to file shim mode on Windows.`);
|
||||
}
|
||||
}
|
||||
async function installFromTar(url, archiveName, tarArgs, miseBinPath) {
|
||||
const tempDir = await fs.promises.mkdtemp(path$1.join(os.tmpdir(), 'mise-action-'));
|
||||
try {
|
||||
const archivePath = path$1.join(tempDir, archiveName);
|
||||
await downloadFile(url, archivePath);
|
||||
await exec('tar', [...tarArgs, '-xf', archivePath, '-C', tempDir]);
|
||||
await moveFile(path$1.join(tempDir, 'mise', 'bin', 'mise'), miseBinPath);
|
||||
async function getDownloadTool() {
|
||||
if (cachedDownloadTool)
|
||||
return cachedDownloadTool;
|
||||
if (await which('curl', true)) {
|
||||
cachedDownloadTool = 'curl';
|
||||
}
|
||||
finally {
|
||||
await rmRF(tempDir);
|
||||
else if (await which('wget', true)) {
|
||||
cachedDownloadTool = 'wget';
|
||||
}
|
||||
else {
|
||||
throw new Error('Neither curl nor wget is available to download mise');
|
||||
}
|
||||
info(`Using ${cachedDownloadTool} to download mise`);
|
||||
return cachedDownloadTool;
|
||||
}
|
||||
async function downloadToStdoutShell(url) {
|
||||
const tool = await getDownloadTool();
|
||||
return tool === 'curl' ? `curl -fsSL ${url}` : `wget -qO- ${url}`;
|
||||
}
|
||||
async function downloadToFile(url, filePath) {
|
||||
const tool = await getDownloadTool();
|
||||
if (tool === 'curl') {
|
||||
await exec('curl', ['-fsSL', url, '--output', filePath]);
|
||||
}
|
||||
else {
|
||||
await exec('wget', ['-qO', filePath, url]);
|
||||
}
|
||||
}
|
||||
async function moveFile(sourcePath, targetPath) {
|
||||
try {
|
||||
await mv(sourcePath, targetPath);
|
||||
}
|
||||
catch (err) {
|
||||
if (!isCrossDeviceRename(err))
|
||||
throw err;
|
||||
await fs.promises.copyFile(sourcePath, targetPath);
|
||||
await fs.promises.unlink(sourcePath);
|
||||
async function downloadText(url) {
|
||||
const tool = await getDownloadTool();
|
||||
if (tool === 'curl') {
|
||||
const rsp = await getExecOutput('curl', ['-fsSL', url]);
|
||||
return rsp.stdout.trim();
|
||||
}
|
||||
const rsp = await getExecOutput('wget', ['-qO-', url]);
|
||||
return rsp.stdout.trim();
|
||||
}
|
||||
function isCrossDeviceRename(err) {
|
||||
return (err instanceof Error &&
|
||||
'code' in err &&
|
||||
err.code === 'EXDEV');
|
||||
async function installFromTarUrl(url, tarFlags, miseBinPath) {
|
||||
const tmpdir = os.tmpdir();
|
||||
const download = await downloadToStdoutShell(url);
|
||||
await exec('sh', [
|
||||
'-c',
|
||||
`${download} | tar ${tarFlags} -C ${tmpdir} && mv ${tmpdir}/mise/bin/mise ${miseBinPath}`
|
||||
]);
|
||||
}
|
||||
async function getInstalledMiseVersion(miseBinPath) {
|
||||
const versionOutput = await getExecOutput(miseBinPath, ['version', '--json'], { silent: true });
|
||||
|
|
@ -89636,50 +89645,7 @@ async function zstdInstalled() {
|
|||
}
|
||||
}
|
||||
async function latestMiseVersion() {
|
||||
return (await fetchText('https://mise.jdx.dev/VERSION')).trim();
|
||||
}
|
||||
async function fetchText(url) {
|
||||
const rsp = await fetchUrl(url);
|
||||
return await rsp.text();
|
||||
}
|
||||
async function downloadFile(url, filePath) {
|
||||
const rsp = await fetchUrl(url);
|
||||
if (!rsp.body) {
|
||||
throw new Error(`Failed to download ${url}: empty response body`);
|
||||
}
|
||||
const tempFilePath = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${crypto$1.randomUUID()}.tmp`);
|
||||
try {
|
||||
await pipeline(Readable.fromWeb(rsp.body), fs.createWriteStream(tempFilePath));
|
||||
await moveFile(tempFilePath, filePath);
|
||||
}
|
||||
catch (err) {
|
||||
await fs.promises.rm(tempFilePath, { force: true });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async function fetchUrl(url) {
|
||||
warnIfFetchMayIgnoreProxy();
|
||||
const rsp = await fetch(url, {
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
});
|
||||
if (!rsp.ok) {
|
||||
throw new Error(`Failed to download ${url}: ${rsp.status} ${rsp.statusText}`);
|
||||
}
|
||||
return rsp;
|
||||
}
|
||||
function warnIfFetchMayIgnoreProxy() {
|
||||
if (warnedFetchProxy)
|
||||
return;
|
||||
warnedFetchProxy = true;
|
||||
const proxyEnvVar = ACTIVE_PROXY_ENV_VARS.find(name => process.env[name]);
|
||||
if (!proxyEnvVar)
|
||||
return;
|
||||
if (process.env.NODE_USE_ENV_PROXY === '1')
|
||||
return;
|
||||
if (process.execArgv.includes('--use-env-proxy'))
|
||||
return;
|
||||
warning(`${proxyEnvVar} is set, but Node env proxy support is not enabled. ` +
|
||||
'Set NODE_USE_ENV_PROXY=1 at the workflow or job level if mise downloads need to use HTTP_PROXY, HTTPS_PROXY, or NO_PROXY.');
|
||||
return downloadText('https://mise.jdx.dev/VERSION');
|
||||
}
|
||||
async function setToolVersions() {
|
||||
const toolVersions = getInput('tool_versions');
|
||||
|
|
|
|||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
166
src/index.ts
166
src/index.ts
|
|
@ -7,8 +7,6 @@ import * as crypto from 'crypto'
|
|||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import { pipeline } from 'stream/promises'
|
||||
import * as Handlebars from 'handlebars'
|
||||
|
||||
// Configuration file patterns for cache key generation
|
||||
|
|
@ -47,14 +45,9 @@ const DEFAULT_CACHE_KEY_TEMPLATE =
|
|||
const ROOT_MISE_LOCK_FILE_PATTERNS = [/^\.?mise(?:\.[^.]+)?\.lock$/]
|
||||
const CONFIG_DIR_MISE_LOCK_FILE_PATTERNS = [/^mise(?:\.[^.]+)?\.lock$/]
|
||||
const CONFIG_MISE_LOCK_FILE_PATTERNS = [/^config(?:\.[^.]+)?\.lock$/]
|
||||
const ACTIVE_PROXY_ENV_VARS = [
|
||||
'HTTP_PROXY',
|
||||
'HTTPS_PROXY',
|
||||
'http_proxy',
|
||||
'https_proxy'
|
||||
]
|
||||
const FETCH_TIMEOUT_MS = 300_000
|
||||
let warnedFetchProxy = false
|
||||
|
||||
type DownloadTool = 'curl' | 'wget'
|
||||
let cachedDownloadTool: DownloadTool | undefined
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
|
|
@ -77,8 +70,8 @@ async function run(): Promise<void> {
|
|||
// etc.) don't silently send the runner's OIDC token to
|
||||
// a third-party cache without explicit consent.
|
||||
//
|
||||
// Note: `setupMise` fetches the mise binary itself with Node's
|
||||
// `fetch`, which doesn't go through mise's HTTP layer —
|
||||
// Note: `setupMise` fetches the mise binary itself with
|
||||
// `curl` or `wget`, which doesn't go through mise's HTTP layer —
|
||||
// the wings rewriter only kicks in once the resulting
|
||||
// mise binary runs `mise install` and friends. Ordering
|
||||
// here is irrelevant for binary acceleration; we just
|
||||
|
|
@ -345,22 +338,19 @@ async function setupMise(
|
|||
case '.zip': {
|
||||
await withExtractedZip(url, 'mise.zip', async extractDir => {
|
||||
const extractedMiseBinDir = path.join(extractDir, 'mise', 'bin')
|
||||
await moveFile(
|
||||
path.join(extractedMiseBinDir, 'mise.exe'),
|
||||
miseBinPath
|
||||
)
|
||||
await io.mv(path.join(extractedMiseBinDir, 'mise.exe'), miseBinPath)
|
||||
await installWindowsMiseShim(extractedMiseBinDir, miseShimPath)
|
||||
})
|
||||
break
|
||||
}
|
||||
case '.tar.zst':
|
||||
await installFromTar(url, 'mise.tar.zst', ['--zstd'], miseBinPath)
|
||||
await installFromTarUrl(url, '--zstd -xf -', miseBinPath)
|
||||
break
|
||||
case '.tar.gz':
|
||||
await installFromTar(url, 'mise.tar.gz', ['-z'], miseBinPath)
|
||||
await installFromTarUrl(url, '-xzf -', miseBinPath)
|
||||
break
|
||||
default:
|
||||
await downloadFile(url, miseBinPath)
|
||||
await downloadToFile(url, miseBinPath)
|
||||
await exec.exec('chmod', ['+x', miseBinPath])
|
||||
break
|
||||
}
|
||||
|
|
@ -409,7 +399,7 @@ async function withExtractedZip(
|
|||
const archivePath = path.join(tempDir, archiveName)
|
||||
const extractDir = path.join(tempDir, 'extract')
|
||||
|
||||
await downloadFile(url, archivePath)
|
||||
await downloadToFile(url, archivePath)
|
||||
await exec.exec('unzip', [archivePath, '-d', extractDir])
|
||||
await fn(extractDir)
|
||||
} finally {
|
||||
|
|
@ -429,7 +419,7 @@ async function installWindowsMiseShim(
|
|||
return
|
||||
}
|
||||
|
||||
await moveFile(extractedMiseShimPath, miseShimPath)
|
||||
await io.mv(extractedMiseShimPath, miseShimPath)
|
||||
}
|
||||
|
||||
async function ensureWindowsMiseShim(
|
||||
|
|
@ -463,41 +453,54 @@ async function ensureWindowsMiseShim(
|
|||
}
|
||||
}
|
||||
|
||||
async function installFromTar(
|
||||
async function getDownloadTool(): Promise<DownloadTool> {
|
||||
if (cachedDownloadTool) return cachedDownloadTool
|
||||
if (await io.which('curl', true)) {
|
||||
cachedDownloadTool = 'curl'
|
||||
} else if (await io.which('wget', true)) {
|
||||
cachedDownloadTool = 'wget'
|
||||
} else {
|
||||
throw new Error('Neither curl nor wget is available to download mise')
|
||||
}
|
||||
core.info(`Using ${cachedDownloadTool} to download mise`)
|
||||
return cachedDownloadTool
|
||||
}
|
||||
|
||||
async function downloadToStdoutShell(url: string): Promise<string> {
|
||||
const tool = await getDownloadTool()
|
||||
return tool === 'curl' ? `curl -fsSL ${url}` : `wget -qO- ${url}`
|
||||
}
|
||||
|
||||
async function downloadToFile(url: string, filePath: string): Promise<void> {
|
||||
const tool = await getDownloadTool()
|
||||
if (tool === 'curl') {
|
||||
await exec.exec('curl', ['-fsSL', url, '--output', filePath])
|
||||
} else {
|
||||
await exec.exec('wget', ['-qO', filePath, url])
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadText(url: string): Promise<string> {
|
||||
const tool = await getDownloadTool()
|
||||
if (tool === 'curl') {
|
||||
const rsp = await exec.getExecOutput('curl', ['-fsSL', url])
|
||||
return rsp.stdout.trim()
|
||||
}
|
||||
const rsp = await exec.getExecOutput('wget', ['-qO-', url])
|
||||
return rsp.stdout.trim()
|
||||
}
|
||||
|
||||
async function installFromTarUrl(
|
||||
url: string,
|
||||
archiveName: string,
|
||||
tarArgs: string[],
|
||||
tarFlags: string,
|
||||
miseBinPath: string
|
||||
): Promise<void> {
|
||||
const tempDir = await fs.promises.mkdtemp(
|
||||
path.join(os.tmpdir(), 'mise-action-')
|
||||
)
|
||||
try {
|
||||
const archivePath = path.join(tempDir, archiveName)
|
||||
await downloadFile(url, archivePath)
|
||||
await exec.exec('tar', [...tarArgs, '-xf', archivePath, '-C', tempDir])
|
||||
await moveFile(path.join(tempDir, 'mise', 'bin', 'mise'), miseBinPath)
|
||||
} finally {
|
||||
await io.rmRF(tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
async function moveFile(sourcePath: string, targetPath: string): Promise<void> {
|
||||
try {
|
||||
await io.mv(sourcePath, targetPath)
|
||||
} catch (err) {
|
||||
if (!isCrossDeviceRename(err)) throw err
|
||||
await fs.promises.copyFile(sourcePath, targetPath)
|
||||
await fs.promises.unlink(sourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
function isCrossDeviceRename(err: unknown): boolean {
|
||||
return (
|
||||
err instanceof Error &&
|
||||
'code' in err &&
|
||||
(err as NodeJS.ErrnoException).code === 'EXDEV'
|
||||
)
|
||||
const tmpdir = os.tmpdir()
|
||||
const download = await downloadToStdoutShell(url)
|
||||
await exec.exec('sh', [
|
||||
'-c',
|
||||
`${download} | tar ${tarFlags} -C ${tmpdir} && mv ${tmpdir}/mise/bin/mise ${miseBinPath}`
|
||||
])
|
||||
}
|
||||
|
||||
async function getInstalledMiseVersion(miseBinPath: string): Promise<string> {
|
||||
|
|
@ -524,62 +527,7 @@ async function zstdInstalled(): Promise<boolean> {
|
|||
}
|
||||
|
||||
async function latestMiseVersion(): Promise<string> {
|
||||
return (await fetchText('https://mise.jdx.dev/VERSION')).trim()
|
||||
}
|
||||
|
||||
async function fetchText(url: string): Promise<string> {
|
||||
const rsp = await fetchUrl(url)
|
||||
return await rsp.text()
|
||||
}
|
||||
|
||||
async function downloadFile(url: string, filePath: string): Promise<void> {
|
||||
const rsp = await fetchUrl(url)
|
||||
if (!rsp.body) {
|
||||
throw new Error(`Failed to download ${url}: empty response body`)
|
||||
}
|
||||
|
||||
const tempFilePath = path.join(
|
||||
path.dirname(filePath),
|
||||
`.${path.basename(filePath)}.${crypto.randomUUID()}.tmp`
|
||||
)
|
||||
try {
|
||||
await pipeline(
|
||||
Readable.fromWeb(rsp.body),
|
||||
fs.createWriteStream(tempFilePath)
|
||||
)
|
||||
await moveFile(tempFilePath, filePath)
|
||||
} catch (err) {
|
||||
await fs.promises.rm(tempFilePath, { force: true })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUrl(url: string): Promise<Response> {
|
||||
warnIfFetchMayIgnoreProxy()
|
||||
const rsp = await fetch(url, {
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
})
|
||||
if (!rsp.ok) {
|
||||
throw new Error(
|
||||
`Failed to download ${url}: ${rsp.status} ${rsp.statusText}`
|
||||
)
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
function warnIfFetchMayIgnoreProxy(): void {
|
||||
if (warnedFetchProxy) return
|
||||
warnedFetchProxy = true
|
||||
|
||||
const proxyEnvVar = ACTIVE_PROXY_ENV_VARS.find(name => process.env[name])
|
||||
if (!proxyEnvVar) return
|
||||
if (process.env.NODE_USE_ENV_PROXY === '1') return
|
||||
if (process.execArgv.includes('--use-env-proxy')) return
|
||||
|
||||
core.warning(
|
||||
`${proxyEnvVar} is set, but Node env proxy support is not enabled. ` +
|
||||
'Set NODE_USE_ENV_PROXY=1 at the workflow or job level if mise downloads need to use HTTP_PROXY, HTTPS_PROXY, or NO_PROXY.'
|
||||
)
|
||||
return downloadText('https://mise.jdx.dev/VERSION')
|
||||
}
|
||||
|
||||
async function setToolVersions(): Promise<void> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue