remove all the random .js stuff, apply fixes based on greptile comments.

This commit is contained in:
dal 2025-09-11 23:59:26 -06:00
parent af9014a30a
commit 11978e99a4
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
19 changed files with 130 additions and 64 deletions

View File

@ -7,7 +7,7 @@ import {
deleteCredentials,
loadCredentials,
saveCredentials,
} from '../utils/credentials.js';
} from '../utils/credentials';
interface AuthProps {
apiKey?: string;

View File

@ -5,8 +5,8 @@ import { Box, Text, useApp, useInput } from 'ink';
import Spinner from 'ink-spinner';
import TextInput from 'ink-text-input';
import { useEffect, useState } from 'react';
import { BusterBanner } from '../components/banner.js';
import { type Credentials, getCredentials, saveCredentials } from '../utils/credentials.js';
import { BusterBanner } from '../components/banner';
import { type Credentials, getCredentials, saveCredentials } from '../utils/credentials';
interface InitProps {
apiKey?: string;

View File

@ -3,11 +3,11 @@ import { Box, render, Text, useApp, useInput } from 'ink';
import Spinner from 'ink-spinner';
import TextInput from 'ink-text-input';
import { useEffect, useState } from 'react';
import { BusterBanner } from '../components/banner.js';
import { type Credentials, getCredentials, saveCredentials } from '../utils/credentials.js';
import { DeployCommand } from './deploy/deploy.js';
import { DeployOptionsSchema } from './deploy/schemas.js';
import { InitCommand } from './init.js';
import { BusterBanner } from '../components/banner';
import { type Credentials, getCredentials, saveCredentials } from '../utils/credentials';
import { DeployCommand } from './deploy/deploy';
import { DeployOptionsSchema } from './deploy/schemas';
import { InitCommand } from './init';
const DEFAULT_HOST = 'https://api2.buster.so';
const _LOCAL_HOST = 'http://localhost:3001';

View File

@ -6,7 +6,7 @@ import {
getDirectUpdateInstructions,
getHomebrewUpdateInstructions,
isInstalledViaHomebrew,
} from './homebrew-detection.js';
} from './homebrew-detection';
// Mock modules
vi.mock('node:child_process', () => ({

View File

@ -1,6 +1,7 @@
import { execSync } from 'node:child_process';
import { existsSync, readlinkSync } from 'node:fs';
import { platform } from 'node:os';
import chalk from 'chalk';
/**
* Check if the CLI was installed via Homebrew
@ -84,6 +85,3 @@ export function getDirectUpdateInstructions(): string {
This will download and install the latest version.`;
}
// Import chalk for colored output
import chalk from 'chalk';

View File

@ -4,12 +4,12 @@ export {
getDirectUpdateInstructions,
getHomebrewUpdateInstructions,
isInstalledViaHomebrew,
} from './homebrew-detection.js';
export { UpdateCommand } from './update.js';
} from './homebrew-detection';
export { UpdateCommand } from './update';
export {
getBinaryFileName,
getBinaryInfo,
getCurrentVersion,
updateHandler,
} from './update-handler.js';
export { type UpdateOptions, UpdateOptionsSchema, type UpdateResult } from './update-schemas.js';
} from './update-handler';
export { type UpdateOptions, UpdateOptionsSchema, type UpdateResult } from './update-schemas';

View File

@ -1,6 +1,6 @@
import { arch, platform } from 'node:os';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getBinaryFileName, getBinaryInfo, getCurrentVersion } from './update-handler.js';
import { getBinaryFileName, getBinaryInfo, getCurrentVersion } from './update-handler';
// Mock os module
vi.mock('node:os', () => ({

View File

@ -1,19 +1,20 @@
import { execSync } from 'node:child_process';
import { execSync, spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createWriteStream, existsSync } from 'node:fs';
import { chmod, mkdir, rename, unlink } from 'node:fs/promises';
import { arch, platform, tmpdir } from 'node:os';
import { join } from 'node:path';
import { pipeline } from 'node:stream/promises';
import { promisify } from 'node:util';
import chalk from 'chalk';
import { checkForUpdate, formatVersion } from '../../utils/version/index.js';
import { isInstalledViaHomebrew } from './homebrew-detection.js';
import { checkForUpdate, formatVersion } from '../../utils/version/index';
import { isInstalledViaHomebrew } from './homebrew-detection';
import {
type BinaryInfo,
type UpdateOptions,
UpdateOptionsSchema,
type UpdateResult,
} from './update-schemas.js';
} from './update-schemas';
const GITHUB_RELEASES_URL = 'https://github.com/buster-so/buster/releases/download';
@ -119,23 +120,74 @@ async function extractArchive(archivePath: string, destination: string): Promise
const os = platform();
if (os === 'win32') {
// Use PowerShell for Windows
execSync(
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destination}' -Force"`,
{
stdio: 'ignore',
}
);
// Use PowerShell for Windows with spawn to avoid shell injection
await new Promise<void>((resolve, reject) => {
const child = spawn(
'powershell',
[
'-Command',
'Expand-Archive',
'-Path',
archivePath,
'-DestinationPath',
destination,
'-Force',
],
{ stdio: 'ignore' }
);
child.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`PowerShell exited with code ${code}`));
}
});
child.on('error', (err) => {
reject(err);
});
});
return join(destination, 'buster.exe');
} else {
// Use tar for Unix-like systems
execSync(`tar -xzf "${archivePath}" -C "${destination}"`, {
stdio: 'ignore',
// Use tar for Unix-like systems with spawn to avoid shell injection
await new Promise<void>((resolve, reject) => {
const child = spawn('tar', ['-xzf', archivePath, '-C', destination], {
stdio: 'ignore',
});
child.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`tar exited with code ${code}`));
}
});
child.on('error', (err) => {
reject(err);
});
});
return join(destination, 'buster');
}
}
/**
* Check if the CLI is running as a standalone binary
*/
function isBinaryExecution(): boolean {
// Check if running via node/npm/pnpm/yarn/bun
const execPath = process.execPath.toLowerCase();
const argv0 = process.argv[0]?.toLowerCase() || '';
// Common indicators of non-binary execution
const nonBinaryIndicators = ['node', 'npm', 'pnpm', 'yarn', 'bun', 'tsx', 'ts-node'];
return !nonBinaryIndicators.some(
(indicator) => execPath.includes(indicator) || argv0.includes(indicator)
);
}
/**
* Replace the current binary with the new one
*/
@ -189,6 +241,16 @@ export async function updateHandler(options: UpdateOptions): Promise<UpdateResul
};
}
// Check if running as a binary (not via node/npm/etc)
if (!isBinaryExecution() && !validated.check && !validated.force) {
return {
success: false,
message: `Auto-update only works with standalone binary installations.\n\nYou appear to be running Buster via ${chalk.yellow(process.execPath.includes('node') ? 'Node.js' : 'a package manager')}.\n\nTo update, please reinstall Buster or use your package manager's update command.`,
currentVersion,
isHomebrew,
};
}
// Check for updates
const updateCheck = await checkForUpdate(currentVersion);
@ -252,11 +314,21 @@ export async function updateHandler(options: UpdateOptions): Promise<UpdateResul
downloadFile(binaryInfo.checksumUrl, checksumPath),
]);
// Read checksum
// Read and validate checksum
const { readFile } = await import('node:fs/promises');
const checksumContent = await readFile(checksumPath, 'utf-8');
const checksumParts = checksumContent.split(' ');
const expectedChecksum = checksumParts[0]?.trim() || '';
// SHA256 checksums are 64 hex characters
// Format can be either "checksum" or "checksum filename"
const checksumMatch = checksumContent.match(/^([a-f0-9]{64})/i);
if (!checksumMatch) {
throw new Error(
`Invalid checksum format in file. Expected SHA256 hash, got: ${checksumContent.substring(0, 100)}`
);
}
const expectedChecksum = checksumMatch[1]!.toLowerCase();
// Verify checksum
console.info(chalk.blue('Verifying download...'));

View File

@ -1,8 +1,8 @@
import { Box, Text } from 'ink';
import Spinner from 'ink-spinner';
import { useEffect, useState } from 'react';
import { getCurrentVersion, updateHandler } from './update-handler.js';
import type { UpdateOptions } from './update-schemas.js';
import { getCurrentVersion, updateHandler } from './update-handler';
import type { UpdateOptions } from './update-schemas';
interface UpdateCommandProps extends UpdateOptions {}

View File

@ -1,6 +1,6 @@
import { Box, Text, useApp } from 'ink';
import { useEffect } from 'react';
import { AnimatedLogo } from '../components/animated-logo.js';
import { AnimatedLogo } from '../components/animated-logo';
export function Welcome() {
const { exit } = useApp();

View File

@ -1,5 +1,5 @@
import { Box, Text } from 'ink';
import { SimpleBigText } from './simple-big-text.js';
import { SimpleBigText } from './simple-big-text';
interface BannerProps {
showSubtitle?: boolean;

View File

@ -1,3 +1,3 @@
export { AnimatedLogo } from './animated-logo.js';
export { BusterBanner } from './banner.js';
export { Spinner } from './spinner.js';
export { AnimatedLogo } from './animated-logo';
export { BusterBanner } from './banner';
export { Spinner } from './spinner';

View File

@ -2,13 +2,13 @@
import chalk from 'chalk';
import { program } from 'commander';
import { render } from 'ink';
import { Auth } from './commands/auth.js';
import { DeployCommand } from './commands/deploy/deploy.js';
import { DeployOptionsSchema } from './commands/deploy/schemas.js';
import { InitCommand } from './commands/init.js';
import { UpdateCommand } from './commands/update/index.js';
import { getCurrentVersion } from './commands/update/update-handler.js';
import { checkForUpdate, formatVersion } from './utils/version/index.js';
import { Auth } from './commands/auth';
import { DeployCommand } from './commands/deploy/deploy';
import { DeployOptionsSchema } from './commands/deploy/schemas';
import { InitCommand } from './commands/init';
import { UpdateCommand } from './commands/update/index';
import { getCurrentVersion } from './commands/update/update-handler';
import { checkForUpdate, formatVersion } from './utils/version/index';
// Get current version
const currentVersion = getCurrentVersion();

View File

@ -7,19 +7,19 @@ export {
isUpdateCheckDisabled,
loadVersionCache,
saveVersionCache,
} from './version-cache.js';
} from './version-cache';
export {
checkForUpdate,
checkForUpdateInBackground,
fetchLatestRelease,
getLatestVersion,
} from './version-check.js';
} from './version-check';
export {
createUpdateCheckResult,
formatVersion,
isUpdateAvailable,
parseVersion,
} from './version-compare.js';
} from './version-compare';
// Export types
export type {
@ -28,4 +28,4 @@ export type {
PlatformInfo,
UpdateCheckResult,
VersionCache,
} from './version-schemas.js';
} from './version-schemas';

View File

@ -9,8 +9,8 @@ import {
isUpdateCheckDisabled,
loadVersionCache,
saveVersionCache,
} from './version-cache.js';
import type { VersionCache } from './version-schemas.js';
} from './version-cache';
import type { VersionCache } from './version-schemas';
// Mock fs module
vi.mock('node:fs/promises');

View File

@ -1,7 +1,7 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { type VersionCache, VersionCacheSchema } from './version-schemas.js';
import { type VersionCache, VersionCacheSchema } from './version-schemas';
const CACHE_DIR = join(homedir(), '.buster');
const CACHE_FILE = join(CACHE_DIR, 'update-check.json');

View File

@ -1,10 +1,6 @@
import { getCachedVersion, isUpdateCheckDisabled, saveVersionCache } from './version-cache.js';
import { createUpdateCheckResult } from './version-compare.js';
import {
type GitHubRelease,
GitHubReleaseSchema,
type UpdateCheckResult,
} from './version-schemas.js';
import { getCachedVersion, isUpdateCheckDisabled, saveVersionCache } from './version-cache';
import { createUpdateCheckResult } from './version-compare';
import { type GitHubRelease, GitHubReleaseSchema, type UpdateCheckResult } from './version-schemas';
const GITHUB_API_URL = 'https://api.github.com/repos/buster-so/buster/releases/latest';
const USER_AGENT = 'buster-cli';

View File

@ -4,7 +4,7 @@ import {
formatVersion,
isUpdateAvailable,
parseVersion,
} from './version-compare.js';
} from './version-compare';
describe('version-compare', () => {
describe('isUpdateAvailable', () => {

View File

@ -1,5 +1,5 @@
import semver from 'semver';
import type { UpdateCheckResult } from './version-schemas.js';
import type { UpdateCheckResult } from './version-schemas';
/**
* Compare two semantic versions and determine if an update is available