UNPKG

screenkitten

Version:

A cross-platform Node.js library for taking screenshots on iOS simulators and Android devices/emulators

8 lines (7 loc) 16.2 kB
{ "version": 3, "sources": ["../src/ios.ts", "../src/errors.ts", "../src/utils.ts", "../src/android.ts", "../src/index.ts"], "sourcesContent": ["import execa from 'execa';\n\nimport type { ScreenkittenOptionsIOS, ScreenkittenOptionsBase, Screenkitten } from './types';\nimport {\n ScreenkittenOperationAbortedError,\n ScreenkittenXcrunNotFoundError,\n ScreenkittenIOSSimulatorError,\n ScreenkittenInvalidTypeError,\n ScreenkittenScreenshotFailedError\n} from './errors';\nimport { doHandleError, createTempScreenshotPath } from './utils';\n\n/**\n * iOS screenshot implementation using xcrun simctl\n */\nexport class ScreenkittenIOS implements Screenkitten {\n private xcrunPath: string;\n private options: ScreenkittenOptionsIOS;\n\n constructor(options: ScreenkittenOptionsIOS) {\n this.options = options;\n this.xcrunPath = options.xcrunPath || '/usr/bin/xcrun';\n }\n\n async takeScreenshot(overrideOptions: Partial<ScreenkittenOptionsBase> = {}): Promise<string> {\n const options = { ...this.options, ...overrideOptions };\n\n const deviceId = options.deviceId || 'booted';\n const type = (options as ScreenkittenOptionsIOS).type || 'png';\n const display = (options as ScreenkittenOptionsIOS).display || 'internal';\n const mask = (options as ScreenkittenOptionsIOS).mask || 'ignored';\n const outputPath = options.outputPath || createTempScreenshotPath('ios', type);\n const onError = options.onError || 'throw';\n\n // Validate screenshot type\n if (type !== 'png' && type !== 'jpeg') {\n const error = new ScreenkittenInvalidTypeError(type);\n return doHandleError(onError, error, Promise.resolve(outputPath));\n }\n\n const args = [\n 'simctl', 'io', deviceId, 'screenshot',\n '--type', type,\n '--display', display,\n '--mask', mask,\n outputPath\n ];\n\n try {\n // Check for abort signal before starting\n if (options.abortSignal?.aborted) {\n throw new ScreenkittenOperationAbortedError('iOS screenshot');\n }\n\n await execa(this.xcrunPath, args, {\n signal: options.abortSignal\n } as any); // Type assertion to bypass outdated definitions\n return outputPath;\n } catch (error) {\n const screenshotError = this._classifyError(error, deviceId);\n return doHandleError(onError, screenshotError, Promise.resolve(outputPath));\n }\n }\n\n private _classifyError(error: unknown, deviceId: string): Error {\n if (error instanceof Error) {\n if (error.message.includes('ENOENT') || error.message.includes('command not found')) {\n return new ScreenkittenXcrunNotFoundError(this.xcrunPath, error);\n } else if (error.message.includes('Invalid device') || error.message.includes('device not found')) {\n return new ScreenkittenIOSSimulatorError(deviceId, error);\n } else if (error.message.includes('aborted') || error.name === 'AbortError') {\n return new ScreenkittenOperationAbortedError('iOS screenshot');\n } else {\n return new ScreenkittenScreenshotFailedError('ios', error);\n }\n } else {\n return new ScreenkittenScreenshotFailedError('ios');\n }\n }\n}\n", "/**\n * Base error class for Screenkitten errors\n */\nexport class ScreenkittenError extends Error {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, options);\n this.name = 'ScreenkittenError';\n }\n}\n\n/**\n * Error thrown when a device is not found\n */\nexport class ScreenkittenDeviceNotFoundError extends ScreenkittenError {\n constructor(deviceId: string, cause?: Error) {\n super(`Device not found: ${deviceId}`, { cause });\n this.name = 'ScreenkittenDeviceNotFoundError';\n }\n}\n\n/**\n * Error thrown when xcrun tool is not found or not executable\n */\nexport class ScreenkittenXcrunNotFoundError extends ScreenkittenError {\n constructor(xcrunPath: string, cause?: Error) {\n super(`xcrun not found at path: ${xcrunPath}`, { cause });\n this.name = 'ScreenkittenXcrunNotFoundError';\n }\n}\n\n/**\n * Error thrown when adb tool is not found or not executable\n */\nexport class ScreenkittenAdbNotFoundError extends ScreenkittenError {\n constructor(adbPath: string, cause?: Error) {\n super(`adb not found at path: ${adbPath}`, { cause });\n this.name = 'ScreenkittenAdbNotFoundError';\n }\n}\n\n/**\n * Error thrown when iOS simulator is not available\n */\nexport class ScreenkittenIOSSimulatorError extends ScreenkittenError {\n constructor(deviceId: string, cause?: Error) {\n super(`iOS Simulator not available or not booted: ${deviceId}`, { cause });\n this.name = 'ScreenkittenIOSSimulatorError';\n }\n}\n\n/**\n * Error thrown when Android device/emulator is not available\n */\nexport class ScreenkittenAndroidDeviceError extends ScreenkittenError {\n constructor(deviceId: string, cause?: Error) {\n super(`Android device/emulator not available: ${deviceId}`, { cause });\n this.name = 'ScreenkittenAndroidDeviceError';\n }\n}\n\n/**\n * Error thrown when screenshot file cannot be written\n */\nexport class ScreenkittenFileWriteError extends ScreenkittenError {\n constructor(outputPath: string, cause?: Error) {\n super(`Failed to write screenshot file: ${outputPath}`, { cause });\n this.name = 'ScreenkittenFileWriteError';\n }\n}\n\n/**\n * Error thrown when operation is aborted\n */\nexport class ScreenkittenOperationAbortedError extends ScreenkittenError {\n constructor(operation: string = 'operation') {\n super(`${operation} was aborted`);\n this.name = 'ScreenkittenOperationAbortedError';\n }\n}\n\n/**\n * Error thrown when screenshot command fails with unknown error\n */\nexport class ScreenkittenScreenshotFailedError extends ScreenkittenError {\n constructor(platform: 'ios' | 'android', cause?: Error) {\n super(`${platform.toUpperCase()} screenshot command failed`, { cause });\n this.name = 'ScreenkittenScreenshotFailedError';\n }\n}\n\n/**\n * Error thrown when invalid screenshot type is specified (iOS only)\n */\nexport class ScreenkittenInvalidTypeError extends ScreenkittenError {\n constructor(type: string) {\n super(`Invalid screenshot type: ${type}. Valid types are: png, jpeg`);\n this.name = 'ScreenkittenInvalidTypeError';\n }\n}\n", "import crypto from 'node:crypto';\nimport path from 'node:path';\nimport os from 'node:os';\n\nimport type { OnErrorHandler } from './types';\n\n/**\n * Handles errors based on the provided error handler strategy\n * @param handler - The error handling strategy ('throw', 'ignore', or a function)\n * @param error - The error to handle\n * @param result - The result to return when not throwing\n * @returns The result if not throwing, otherwise throws the error\n */\nexport function doHandleError<R>(handler: OnErrorHandler | undefined, error: Error, result: R): R {\n if (handler) {\n if (handler === 'throw') {\n throw error;\n } else if (handler === 'ignore') {\n return result; // Return result even though operation might have failed\n } else {\n handler(error);\n return result;\n }\n }\n\n throw error;\n}\n\n/**\n * Generates a unique temporary file path for screenshots\n * @param platform - The platform ('android' or 'ios')\n * @param extension - The file extension (e.g., 'png', 'jpeg')\n * @returns A unique temporary file path\n */\nexport function createTempScreenshotPath(platform: 'android' | 'ios', extension: string): string {\n const timestamp = Date.now();\n const uuid = crypto.randomUUID().slice(0, 8); // Use first 8 chars of UUID for brevity\n const filename = `${platform}-screenshot-${timestamp}-${uuid}.${extension}`;\n return path.join(os.tmpdir(), filename);\n}\n", "import fs from 'node:fs/promises';\n\nimport execa from 'execa';\n\nimport type { ScreenkittenOptionsAndroid, ScreenkittenOptionsBase, Screenkitten } from './types';\nimport {\n ScreenkittenOperationAbortedError,\n ScreenkittenAdbNotFoundError,\n ScreenkittenAndroidDeviceError,\n ScreenkittenFileWriteError,\n ScreenkittenScreenshotFailedError\n} from './errors';\nimport { doHandleError, createTempScreenshotPath } from './utils';\n\n/**\n * Android screenshot implementation using adb\n */\nexport class ScreenkittenAndroid implements Screenkitten {\n private adbPath: string;\n private options: ScreenkittenOptionsAndroid;\n\n constructor(options: ScreenkittenOptionsAndroid) {\n this.options = options;\n this.adbPath = options.adbPath || 'adb';\n }\n\n async takeScreenshot(overrideOptions: Partial<ScreenkittenOptionsBase> = {}): Promise<string> {\n const options = { ...this.options, ...overrideOptions };\n\n const deviceId = options.deviceId || 'booted';\n const outputPath = options.outputPath || createTempScreenshotPath('android', 'png');\n const onError = options.onError || 'throw';\n\n try {\n // Check for abort signal before starting\n if (options.abortSignal?.aborted) {\n throw new ScreenkittenOperationAbortedError('Android screenshot');\n }\n\n const args = deviceId === 'booted'\n ? ['exec-out', 'screencap', '-p']\n : ['-s', deviceId, 'exec-out', 'screencap', '-p'];\n\n const result = await execa(this.adbPath, args, {\n encoding: null, // Return buffer for binary data\n signal: options.abortSignal\n } as any); // Type assertion to bypass outdated definitions\n\n await fs.writeFile(outputPath, result.stdout);\n return outputPath;\n } catch (error) {\n const screenshotError = this._classifyError(error, deviceId, outputPath);\n return doHandleError(onError, screenshotError, Promise.resolve(outputPath));\n }\n }\n\n private _classifyError(error: unknown, deviceId: string, outputPath: string): Error {\n if (error instanceof Error) {\n if (error.message.includes('ENOENT') || error.message.includes('command not found')) {\n return new ScreenkittenAdbNotFoundError(this.adbPath, error);\n } else if (error.message.includes('device not found') || error.message.includes('device offline')) {\n return new ScreenkittenAndroidDeviceError(deviceId, error);\n } else if (error.message.includes('aborted') || error.name === 'AbortError') {\n return new ScreenkittenOperationAbortedError('Android screenshot');\n } else if ((error as NodeJS.ErrnoException).code === 'ENOENT' || (error as NodeJS.ErrnoException).code === 'EACCES') {\n return new ScreenkittenFileWriteError(outputPath, error);\n } else {\n return new ScreenkittenScreenshotFailedError('android', error);\n }\n } else {\n return new ScreenkittenScreenshotFailedError('android');\n }\n }\n\n}\n", "import type { ScreenkittenOptions } from './types';\nimport { ScreenkittenIOS } from './ios';\nimport { ScreenkittenAndroid } from './android';\n\n/**\n * Create a Screenkitten instance based on the provided options.\n */\nexport function screenkitten(options: ScreenkittenOptions) {\n switch (options.platform) {\n case 'ios': {\n return new ScreenkittenIOS(options);\n }\n case 'android': {\n return new ScreenkittenAndroid(options);\n }\n default: {\n throw new Error(`Unsupported platform: ${(options as any).platform}`);\n }\n }\n}\n\n// Export types\nexport type * from './types';\n\n// Export error classes\nexport * from './errors';"], "mappings": "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", "names": ["execa", "execa"] }