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
26 changes: 20 additions & 6 deletions src/checkVersion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execSync } from "node:child_process";
import { spawnSync } from "node:child_process";
import * as console from "node:console";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
Expand All @@ -8,6 +8,7 @@ import boxen from "boxen";
import chalk from "chalk";
import semver from "semver";
import pkg from "../package.json" with { type: "json" };
import { handleUpgrade } from "./lib/upgrade.ts";

const CACHE_FILE = join(homedir(), ".sfcompute", "version-cache");
const CACHE_TTL = 1 * 60 * 60 * 1000; // 1 hour in milliseconds
Expand Down Expand Up @@ -125,13 +126,26 @@ export async function checkVersion() {
chalk.cyan(`Automatically upgrading ${version} → ${latestVersion}`),
);
try {
execSync("sf upgrade", { stdio: "inherit" });
const success = await handleUpgrade(version, latestVersion);
if (!success) throw new Error("Upgrade failed");
console.log(chalk.gray("\n☁️☁️☁️\n"));

// Re-run the original command
const args = process.argv.slice(2);
execSync(`sf ${args.join(" ")}`, { stdio: "inherit" });
process.exit(0);
// Re-run the original command with the newly installed binary.
// process.execPath is the binary's own path in a pkg build; the
// upgrade just replaced that file on disk, so re-invoking it runs
// the new version. We use `env -u PKG_EXECPATH` because pkg's
// patched child_process re-adds PKG_EXECPATH even if we delete it
// from the env object, causing the bootstrap to treat argv[1] as a
// script path. spawnSync with an argv array avoids shell injection.
const reRun = spawnSync(
"env",
["-u", "PKG_EXECPATH", process.execPath, ...process.argv.slice(2)],
{
stdio: "inherit",
env: { ...process.env, SF_CLI_DISABLE_AUTO_UPGRADE: "1" },
},
);
process.exit(reRun.status ?? 0);
} catch {
// Silent error, just run the command the user wanted to run
}
Expand Down
140 changes: 72 additions & 68 deletions src/lib/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,85 +28,89 @@ const getIsOnLatestVersion = async (currentVersion: string | undefined) => {
return false;
};

export function registerUpgrade(program: Command) {
return program
.command("upgrade")
.argument("[version]", "The version to upgrade to")
.description("Upgrade to the latest version or a specific version")
.action(async (version) => {
const spinner = ora();
const currentVersion = program.version();

if (version) {
spinner.start(`Checking if version ${version} exists`);
const url = `https://github.com/sfcompute/cli/archive/refs/tags/${version}.zip`;
const response = await fetch(url, { method: "HEAD" });

if (response.status === 404) {
spinner.fail(`Version ${version} does not exist.`);
process.exit(1);
}
spinner.succeed();
} else {
const isOnLatestVersion = await getIsOnLatestVersion(currentVersion);
if (isOnLatestVersion) {
spinner.succeed(
`You are already on the latest version (${currentVersion}).`,
);
process.exit(0);
}
}

// Fetch the install script
spinner.start("Downloading install script");
const scriptResponse = await fetch(
"https://www.sfcompute.com/cli/install",
export async function handleUpgrade(
currentVersion?: string,
version?: string,
): Promise<boolean> {
const spinner = ora();

if (version) {
spinner.start(`Checking if version ${version} exists`);
const url =
`https://github.com/sfcompute/cli/archive/refs/tags/${version}.zip` as const;
const response = await fetch(url, { method: "HEAD" });

if (response.status === 404) {
spinner.fail(`Version ${version} does not exist.`);
return false;
}
spinner.succeed();
} else {
const isOnLatestVersion = await getIsOnLatestVersion(currentVersion);
if (isOnLatestVersion) {
spinner.succeed(
`You are already on the latest version (${currentVersion}).`,
);
return true;
}
}

// Fetch the install script
spinner.start("Downloading install script");
const scriptResponse = await fetch("https://www.sfcompute.com/cli/install");

if (!scriptResponse.ok) {
spinner.fail("Failed to download install script.");
return false;
}

if (!scriptResponse.ok) {
spinner.fail("Failed to download install script.");
process.exit(1);
}
const script = await scriptResponse.text();
spinner.succeed();

const script = await scriptResponse.text();
spinner.succeed();
// Execute the script with bash
spinner.start("Installing upgrade");

// Execute the script with bash
spinner.start("Installing upgrade");
const bashProcess = spawn("bash", [], {
stdio: ["pipe", "pipe", "pipe"],
env: version ? { ...process.env, SF_CLI_VERSION: version } : process.env,
});

const bashProcess = spawn("bash", [], {
stdio: ["pipe", "pipe", "pipe"],
env: version
? { ...process.env, SF_CLI_VERSION: version }
: process.env,
});
let stdout = "";
let stderr = "";

let stdout = "";
let stderr = "";
bashProcess.stdout.on("data", (data) => {
stdout += data.toString();
});

bashProcess.stdout.on("data", (data) => {
stdout += data.toString();
});
bashProcess.stderr.on("data", (data) => {
stderr += data.toString();
});

bashProcess.stderr.on("data", (data) => {
stderr += data.toString();
});
bashProcess.stdin.write(script);
bashProcess.stdin.end();

bashProcess.stdin.write(script);
bashProcess.stdin.end();
const code = await new Promise<number | null>((resolve) => {
bashProcess.on("close", resolve);
});

const code = await new Promise<number | null>((resolve) => {
bashProcess.on("close", resolve);
});
if (code !== 0) {
spinner.fail("Upgrade failed");
console.error(stderr);
console.log(stdout);
return false;
}

if (code !== 0) {
spinner.fail("Upgrade failed");
console.error(stderr);
console.log(stdout);
process.exit(1);
}
spinner.succeed("Upgrade completed successfully");
return true;
}

spinner.succeed("Upgrade completed successfully");
process.exit(0);
export function registerUpgrade(program: Command) {
return program
.command("upgrade")
.argument("[version]", "The version to upgrade to")
.description("Upgrade to the latest version or a specific version")
.action(async (version) => {
const success = await handleUpgrade(program.version(), version);
process.exit(success ? 0 : 1);
});
}
Loading