diff --git a/src/utils.ts b/src/utils.ts index 9600c11..b55a718 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -67,7 +67,7 @@ export const getCurrentArch = (): string => { export const isValidISOurl = (url?: string): boolean => { return Boolean( (url?.startsWith("http://") || url?.startsWith("https://")) && - url?.endsWith(".iso"), + url?.endsWith(".iso") ); }; @@ -93,7 +93,7 @@ export const humanFileSize = (blocks: number) => }); export const validateImage = ( - image: string, + image: string ): Effect.Effect => { const regex = /^(?:[a-zA-Z0-9.-]+(?:\.[a-zA-Z0-9.-]+)*\/)?[a-z0-9]+(?:[._-][a-z0-9]+)*\/[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[a-zA-Z0-9._-]+)?$/; @@ -104,7 +104,7 @@ export const validateImage = ( image, cause: "Image name does not conform to expected format. Should be in the format 'repository/name:tag'.", - }), + }) ); } return Effect.succeed(image); @@ -113,18 +113,18 @@ export const validateImage = ( export const extractTag = (name: string) => pipe( validateImage(name), - Effect.flatMap((image) => Effect.succeed(image.split(":")[1] || "latest")), + Effect.flatMap((image) => Effect.succeed(image.split(":")[1] || "latest")) ); export const failOnMissingImage = ( - image: Image | undefined, + image: Image | undefined ): Effect.Effect => image ? Effect.succeed(image) : Effect.fail(new NoSuchImageError({ cause: "No such image" })); export const du = ( - path: string, + path: string ): Effect.Effect => Effect.tryPromise({ try: async () => { @@ -156,7 +156,7 @@ export const emptyDiskImage = (path: string) => exists ? Effect.succeed(true) : du(path).pipe(Effect.map((size) => size < EMPTY_DISK_THRESHOLD_KB)) - ), + ) ); export const downloadIso = (url: string, options: Options) => @@ -178,8 +178,8 @@ export const downloadIso = (url: string, options: Options) => if (driveSize > EMPTY_DISK_THRESHOLD_KB) { console.log( chalk.yellowBright( - `Drive image ${options.image} is not empty (size: ${driveSize} KB), skipping ISO download to avoid overwriting existing data.`, - ), + `Drive image ${options.image} is not empty (size: ${driveSize} KB), skipping ISO download to avoid overwriting existing data.` + ) ); return null; } @@ -197,8 +197,8 @@ export const downloadIso = (url: string, options: Options) => if (outputExists) { console.log( chalk.yellowBright( - `File ${outputPath} already exists, skipping download.`, - ), + `File ${outputPath} already exists, skipping download.` + ) ); return outputPath; } @@ -209,8 +209,8 @@ export const downloadIso = (url: string, options: Options) => chalk.blueBright( `Downloading ${ url.endsWith(".iso") ? "ISO" : "image" - } from ${url}...`, - ), + } from ${url}...` + ) ); const cmd = new Deno.Command("curl", { args: ["-L", "-o", outputPath, url], @@ -253,8 +253,8 @@ export const setupFirmwareFilesIfNeeded = () => if (!success) { console.error( chalk.redBright( - "Failed to get QEMU prefix from Homebrew. Ensure QEMU is installed via Homebrew.", - ), + "Failed to get QEMU prefix from Homebrew. Ensure QEMU is installed via Homebrew." + ) ); Deno.exit(1); } @@ -267,7 +267,7 @@ export const setupFirmwareFilesIfNeeded = () => try: () => Deno.copyFile( `${brewPrefix}/share/qemu/edk2-arm-vars.fd`, - edk2VarsAarch64, + edk2VarsAarch64 ), catch: (error) => new LogCommandError({ cause: error }), }); @@ -312,13 +312,13 @@ export const setupCoreOSArgs = (imagePath?: string | null) => const configOK = yield* pipe( fileExists("config.ign"), Effect.flatMap(() => Effect.succeed(true)), - Effect.catchAll(() => Effect.succeed(false)), + Effect.catchAll(() => Effect.succeed(false)) ); if (!configOK) { console.error( chalk.redBright( - "CoreOS image requires a config.ign file in the current directory.", - ), + "CoreOS image requires a config.ign file in the current directory." + ) ); Deno.exit(1); } @@ -358,7 +358,7 @@ export const setupGentooArgs = (imagePath?: string | null, seed?: string) => imagePath && imagePath.endsWith(".qcow2") && imagePath.startsWith( - `di-${Deno.build.arch === "aarch64" ? "arm64" : "amd64"}-console-`, + `di-${Deno.build.arch === "aarch64" ? "arm64" : "amd64"}-console-` ) ) { return [ @@ -373,7 +373,7 @@ export const setupGentooArgs = (imagePath?: string | null, seed?: string) => export const setupAlpineArgs = ( imagePath?: string | null, - seed: string = "seed.iso", + seed: string = "seed.iso" ) => Effect.sync(() => { if ( @@ -394,7 +394,7 @@ export const setupAlpineArgs = ( export const setupDebianArgs = ( imagePath?: string | null, - seed: string = "seed.iso", + seed: string = "seed.iso" ) => Effect.sync(() => { if ( @@ -415,7 +415,7 @@ export const setupDebianArgs = ( export const setupUbuntuArgs = ( imagePath?: string | null, - seed: string = "seed.iso", + seed: string = "seed.iso" ) => Effect.sync(() => { if ( @@ -436,7 +436,7 @@ export const setupUbuntuArgs = ( export const setupAlmaLinuxArgs = ( imagePath?: string | null, - seed: string = "seed.iso", + seed: string = "seed.iso" ) => Effect.sync(() => { if ( @@ -457,7 +457,7 @@ export const setupAlmaLinuxArgs = ( export const setupRockyLinuxArgs = ( imagePath?: string | null, - seed: string = "seed.iso", + seed: string = "seed.iso" ) => Effect.sync(() => { if ( @@ -480,39 +480,40 @@ export const runQemu = (isoPath: string | null, options: Options) => Effect.gen(function* () { const macAddress = yield* generateRandomMacAddress(); - const qemu = Deno.build.arch === "aarch64" - ? "qemu-system-aarch64" - : "qemu-system-x86_64"; + const qemu = + Deno.build.arch === "aarch64" + ? "qemu-system-aarch64" + : "qemu-system-x86_64"; const firmwareFiles = yield* setupFirmwareFilesIfNeeded(); let coreosArgs: string[] = yield* setupCoreOSArgs(isoPath || options.image); let fedoraArgs: string[] = yield* setupFedoraArgs( isoPath || options.image, - options.seed, + options.seed ); let gentooArgs: string[] = yield* setupGentooArgs( isoPath || options.image, - options.seed, + options.seed ); let alpineArgs: string[] = yield* setupAlpineArgs( isoPath || options.image, - options.seed, + options.seed ); let debianArgs: string[] = yield* setupDebianArgs( isoPath || options.image, - options.seed, + options.seed ); let ubuntuArgs: string[] = yield* setupUbuntuArgs( isoPath || options.image, - options.seed, + options.seed ); let almalinuxArgs: string[] = yield* setupAlmaLinuxArgs( isoPath || options.image, - options.seed, + options.seed ); let rockylinuxArgs: string[] = yield* setupRockyLinuxArgs( isoPath || options.image, - options.seed, + options.seed ); if (coreosArgs.length > 0 && !isoPath) { @@ -585,7 +586,7 @@ export const runQemu = (isoPath: string | null, options: Options) => options.image && [ "-drive", `file=${options.image},format=${options.diskFormat},if=virtio`, - ], + ] ), ]; @@ -600,11 +601,9 @@ export const runQemu = (isoPath: string | null, options: Options) => const logPath = `${LOGS_DIR}/${name}.log`; const fullCommand = options.bridge - ? `sudo ${qemu} ${ - qemuArgs + ? `sudo ${qemu} ${qemuArgs .slice(1) - .join(" ") - } >> "${logPath}" 2>&1 & echo $!` + .join(" ")} >> "${logPath}" 2>&1 & echo $!` : `${qemu} ${qemuArgs.join(" ")} >> "${logPath}" 2>&1 & echo $!`; const { stdout } = yield* Effect.tryPromise({ @@ -630,7 +629,8 @@ export const runQemu = (isoPath: string | null, options: Options) => cpus: options.cpus, cpu: options.cpu, diskSize: options.size || "20G", - diskFormat: (isoPath?.endsWith(".qcow2") ? "qcow2" : undefined) || + diskFormat: + (isoPath?.endsWith(".qcow2") ? "qcow2" : undefined) || options.diskFormat || "raw", portForward: options.portForward, @@ -650,7 +650,7 @@ export const runQemu = (isoPath: string | null, options: Options) => }); console.log( - `Virtual machine ${name} started in background (PID: ${qemuPid})`, + `Virtual machine ${name} started in background (PID: ${qemuPid})` ); console.log(`Logs will be written to: ${logPath}`); @@ -673,7 +673,8 @@ export const runQemu = (isoPath: string | null, options: Options) => cpus: options.cpus, cpu: options.cpu, diskSize: options.size || "20G", - diskFormat: (isoPath?.endsWith(".qcow2") ? "qcow2" : undefined) || + diskFormat: + (isoPath?.endsWith(".qcow2") ? "qcow2" : undefined) || options.diskFormat || "raw", portForward: options.portForward, @@ -782,8 +783,8 @@ export const createDriveImageIfNeeded = ({ if (pathExists) { console.log( chalk.yellowBright( - `Drive image ${path} already exists, skipping creation.`, - ), + `Drive image ${path} already exists, skipping creation.` + ) ); return; } @@ -810,7 +811,7 @@ export const createDriveImageIfNeeded = ({ }); export const fileExists = ( - path: string, + path: string ): Effect.Effect => Effect.try({ try: () => Deno.statSync(path), @@ -818,7 +819,7 @@ export const fileExists = ( }); export const constructCoreOSImageURL = ( - image: string, + image: string ): Effect.Effect => { // detect with regex if image matches coreos pattern: fedora-coreos or fedora-coreos- or coreos or coreos- const coreosRegex = /^(fedora-coreos|coreos)(-(\d+\.\d+\.\d+\.\d+))?$/; @@ -826,7 +827,7 @@ export const constructCoreOSImageURL = ( if (match) { const version = match[3] || FEDORA_COREOS_DEFAULT_VERSION; return Effect.succeed( - FEDORA_COREOS_IMG_URL.replaceAll(FEDORA_COREOS_DEFAULT_VERSION, version), + FEDORA_COREOS_IMG_URL.replaceAll(FEDORA_COREOS_DEFAULT_VERSION, version) ); } @@ -834,7 +835,7 @@ export const constructCoreOSImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match CoreOS naming conventions.", - }), + }) ); }; @@ -863,7 +864,7 @@ export const extractXz = (path: string | null) => }); export const constructNixOSImageURL = ( - image: string, + image: string ): Effect.Effect => { // detect with regex if image matches NixOS pattern: nixos or nixos- const nixosRegex = /^(nixos)(-(\d+\.\d+))?$/; @@ -871,7 +872,7 @@ export const constructNixOSImageURL = ( if (match) { const version = match[3] || NIXOS_DEFAULT_VERSION; return Effect.succeed( - NIXOS_ISO_URL.replaceAll(NIXOS_DEFAULT_VERSION, version), + NIXOS_ISO_URL.replaceAll(NIXOS_DEFAULT_VERSION, version) ); } @@ -879,13 +880,13 @@ export const constructNixOSImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match NixOS naming conventions.", - }), + }) ); }; export const constructFedoraImageURL = ( image: string, - cloud: boolean = false, + cloud: boolean = false ): Effect.Effect => { // detect with regex if image matches Fedora pattern: fedora const fedoraRegex = /^(fedora)$/; @@ -898,12 +899,12 @@ export const constructFedoraImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match Fedora naming conventions.", - }), + }) ); }; export const constructGentooImageURL = ( - image: string, + image: string ): Effect.Effect => { // detect with regex if image matches genroo pattern: gentoo-20251116T161545Z or gentoo const gentooRegex = /^(gentoo)(-(\d{8}T\d{6}Z))?$/; @@ -912,8 +913,8 @@ export const constructGentooImageURL = ( return Effect.succeed( GENTOO_IMG_URL.replaceAll("20251116T161545Z", match[3]).replaceAll( "20251116T233105Z", - match[3], - ), + match[3] + ) ); } @@ -925,13 +926,13 @@ export const constructGentooImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match Gentoo naming conventions.", - }), + }) ); }; export const constructDebianImageURL = ( image: string, - cloud: boolean = false, + cloud: boolean = false ): Effect.Effect => { if (cloud && image === "debian") { return Effect.succeed(DEBIAN_CLOUD_IMG_URL); @@ -942,7 +943,7 @@ export const constructDebianImageURL = ( const match = image.match(debianRegex); if (match?.[3]) { return Effect.succeed( - DEBIAN_ISO_URL.replaceAll(DEBIAN_DEFAULT_VERSION, match[3]), + DEBIAN_ISO_URL.replaceAll(DEBIAN_DEFAULT_VERSION, match[3]) ); } @@ -954,19 +955,19 @@ export const constructDebianImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match Debian naming conventions.", - }), + }) ); }; export const constructAlpineImageURL = ( - image: string, + image: string ): Effect.Effect => { // detect with regex if image matches alpine pattern: alpine- or alpine const alpineRegex = /^(alpine)(-(\d+\.\d+(\.\d+)?))?$/; const match = image.match(alpineRegex); if (match?.[3]) { return Effect.succeed( - ALPINE_ISO_URL.replaceAll(ALPINE_DEFAULT_VERSION, match[3]), + ALPINE_ISO_URL.replaceAll(ALPINE_DEFAULT_VERSION, match[3]) ); } @@ -978,13 +979,13 @@ export const constructAlpineImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match Alpine naming conventions.", - }), + }) ); }; export const constructUbuntuImageURL = ( image: string, - cloud: boolean = false, + cloud: boolean = false ): Effect.Effect => { // detect with regex if image matches ubuntu pattern: ubuntu const ubuntuRegex = /^(ubuntu)$/; @@ -1000,16 +1001,16 @@ export const constructUbuntuImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match Ubuntu naming conventions.", - }), + }) ); }; export const constructAlmaLinuxImageURL = ( image: string, - cloud: boolean = false, + cloud: boolean = false ): Effect.Effect => { - // detect with regex if image matches almalinux pattern: ubuntu - const almaLinuxRegex = /^(almalinux)$/; + // detect with regex if image matches almalinux pattern: almalinux, almalinux + const almaLinuxRegex = /^(almalinux|alma)$/; const match = image.match(almaLinuxRegex); if (match) { if (cloud) { @@ -1022,16 +1023,16 @@ export const constructAlmaLinuxImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match AlmaLinux naming conventions.", - }), + }) ); }; export const constructRockyLinuxImageURL = ( image: string, - cloud: boolean = false, + cloud: boolean = false ): Effect.Effect => { - // detect with regex if image matches rockylinux pattern: ubuntu - const rockyLinuxRegex = /^(rockylinux)$/; + // detect with regex if image matches rockylinux pattern: rocky. rockylinux + const rockyLinuxRegex = /^(rockylinux|rocky)$/; const match = image.match(rockyLinuxRegex); if (match) { if (cloud) { @@ -1044,6 +1045,6 @@ export const constructRockyLinuxImageURL = ( new InvalidImageNameError({ image, cause: "Image name does not match RockyLinux naming conventions.", - }), + }) ); };