mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 11:59:29 +00:00
fix: reset stale cache flag, guard gcloud null, validate DO config (#2073)
- manifest.ts: Reset _staleCache on successful fetch/cache load so isStaleCache() doesn't falsely report stale data after reconnecting - gcp.ts: Replace getGcloudCmd()! with requireGcloudCmd() that throws a descriptive error instead of crashing with null dereference - digitalocean.ts: Replace unvalidated JSON.parse return with parseJsonObj() + isString()/isNumber() guards for type safety Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b066b3a1ac
commit
bb4deaf24c
4 changed files with 42 additions and 23 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.11.21",
|
||||
"version": "0.11.22",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -156,36 +156,28 @@ function getConfigPath(): string {
|
|||
return join(process.env.HOME || homedir(), ".config", "spawn", "digitalocean.json");
|
||||
}
|
||||
|
||||
interface DoConfig {
|
||||
api_key?: string;
|
||||
token?: string;
|
||||
refresh_token?: string;
|
||||
expires_at?: number;
|
||||
auth_method?: "oauth" | "manual";
|
||||
}
|
||||
|
||||
function loadConfig(): DoConfig | null {
|
||||
function loadConfig(): Record<string, unknown> | null {
|
||||
try {
|
||||
return JSON.parse(readFileSync(getConfigPath(), "utf-8"));
|
||||
return parseJsonObj(readFileSync(getConfigPath(), "utf-8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfig(config: DoConfig): Promise<void> {
|
||||
async function saveConfig(values: Record<string, unknown>): Promise<void> {
|
||||
const configPath = getConfigPath();
|
||||
const dir = configPath.replace(/\/[^/]+$/, "");
|
||||
mkdirSync(dir, {
|
||||
recursive: true,
|
||||
mode: 0o700,
|
||||
});
|
||||
await Bun.write(configPath, JSON.stringify(config, null, 2) + "\n", {
|
||||
await Bun.write(configPath, JSON.stringify(values, null, 2) + "\n", {
|
||||
mode: 0o600,
|
||||
});
|
||||
}
|
||||
|
||||
async function saveTokenToConfig(token: string, refreshToken?: string, expiresIn?: number): Promise<void> {
|
||||
const config: DoConfig = {
|
||||
const config: Record<string, unknown> = {
|
||||
api_key: token,
|
||||
token,
|
||||
};
|
||||
|
|
@ -204,7 +196,9 @@ function loadTokenFromConfig(): string | null {
|
|||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
const token = data.api_key || data.token || "";
|
||||
const apiKey = isString(data.api_key) ? data.api_key : "";
|
||||
const tok = isString(data.token) ? data.token : "";
|
||||
const token = apiKey || tok;
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -216,22 +210,30 @@ function loadTokenFromConfig(): string | null {
|
|||
|
||||
function loadRefreshToken(): string | null {
|
||||
const data = loadConfig();
|
||||
if (!data?.refresh_token) {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9._/@:+=, -]+$/.test(data.refresh_token)) {
|
||||
const refreshToken = isString(data.refresh_token) ? data.refresh_token : "";
|
||||
if (!refreshToken) {
|
||||
return null;
|
||||
}
|
||||
return data.refresh_token;
|
||||
if (!/^[a-zA-Z0-9._/@:+=, -]+$/.test(refreshToken)) {
|
||||
return null;
|
||||
}
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
function isTokenExpired(): boolean {
|
||||
const data = loadConfig();
|
||||
if (!data?.expires_at) {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
const expiresAt = isNumber(data.expires_at) ? data.expires_at : 0;
|
||||
if (!expiresAt) {
|
||||
return false;
|
||||
}
|
||||
// Consider expired 5 minutes before actual expiry
|
||||
return Math.floor(Date.now() / 1000) >= data.expires_at - 300;
|
||||
return Math.floor(Date.now() / 1000) >= expiresAt - 300;
|
||||
}
|
||||
|
||||
// ─── Token Validation ────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -187,13 +187,27 @@ function getGcloudCmd(): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Get gcloud path or throw a descriptive error. */
|
||||
function requireGcloudCmd(): string {
|
||||
const cmd = getGcloudCmd();
|
||||
if (!cmd) {
|
||||
throw new Error(
|
||||
"gcloud CLI not found. Install it first:\n" +
|
||||
" macOS: brew install --cask google-cloud-sdk\n" +
|
||||
" Linux: curl https://sdk.cloud.google.com | bash\n" +
|
||||
" Or run: spawn <agent> gcp (auto-installs gcloud)",
|
||||
);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/** Run a gcloud command and return stdout. */
|
||||
function gcloudSync(args: string[]): {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
exitCode: number;
|
||||
} {
|
||||
const cmd = getGcloudCmd()!;
|
||||
const cmd = requireGcloudCmd();
|
||||
const proc = Bun.spawnSync(
|
||||
[
|
||||
cmd,
|
||||
|
|
@ -221,7 +235,7 @@ async function gcloud(args: string[]): Promise<{
|
|||
stderr: string;
|
||||
exitCode: number;
|
||||
}> {
|
||||
const cmd = getGcloudCmd()!;
|
||||
const cmd = requireGcloudCmd();
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
cmd,
|
||||
|
|
@ -250,7 +264,7 @@ async function gcloud(args: string[]): Promise<{
|
|||
|
||||
/** Run a gcloud command interactively (inheriting stdio). */
|
||||
async function gcloudInteractive(args: string[]): Promise<number> {
|
||||
const cmd = getGcloudCmd()!;
|
||||
const cmd = requireGcloudCmd();
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
cmd,
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ function tryLoadFromDiskCache(): Manifest | null {
|
|||
function updateCache(manifest: Manifest): Manifest {
|
||||
writeCache(manifest);
|
||||
_cached = manifest;
|
||||
_staleCache = false;
|
||||
return manifest;
|
||||
}
|
||||
|
||||
|
|
@ -233,6 +234,7 @@ export async function loadManifest(forceRefresh = false): Promise<Manifest> {
|
|||
const local = tryLoadLocalManifest();
|
||||
if (local) {
|
||||
_cached = local;
|
||||
_staleCache = false;
|
||||
return local;
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +243,7 @@ export async function loadManifest(forceRefresh = false): Promise<Manifest> {
|
|||
const cached = tryLoadFromDiskCache();
|
||||
if (cached) {
|
||||
_cached = cached;
|
||||
_staleCache = false;
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue