From 3d82275853138704c9a02eb016a09944ed8917be Mon Sep 17 00:00:00 2001 From: Akshay kumar tyagi <79367523+akshaytyagi482@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:34:07 +0530 Subject: [PATCH] commit --- src/command-line.ts | 6 +++- src/fiddle.ts | 52 +++++++++++++++++++++++++++----- src/installer.ts | 72 +++++++++++++++++++++++++++++++++++++-------- src/paths.ts | 14 +++++---- src/runner.ts | 68 +++++++++++++++++++++++++++++++++++------- src/versions.ts | 57 ++++++++++++++++++++++++++++++----- 6 files changed, 224 insertions(+), 45 deletions(-) diff --git a/src/command-line.ts b/src/command-line.ts index 8c19205..55d0e3f 100644 --- a/src/command-line.ts +++ b/src/command-line.ts @@ -4,8 +4,12 @@ import debug from 'debug'; import { ElectronVersions } from './versions.js'; import { Fiddle, FiddleFactory } from './fiddle.js'; -import { Runner } from './runner.js'; +import { Runner } from './runner.js'; +/** + * Handles command-line arguments, initializes objects, and executes commands based on input. + * Logs debug information and exits if invalid parameters are detected. + */ export async function runFromCommandLine(argv: string[]): Promise { const d = debug('fiddle-core:runFromCommandLine'); diff --git a/src/fiddle.ts b/src/fiddle.ts index 44ec179..728e39d 100644 --- a/src/fiddle.ts +++ b/src/fiddle.ts @@ -14,13 +14,19 @@ function hashString(str: string): string { md5sum.update(str); return md5sum.digest('hex'); } - +/** + * A Fiddle instance, containing a main entry file and its source content. + */ export class Fiddle { constructor( - public readonly mainPath: string, // /path/to/main.js + /** Path to the main entry script file (e.g., `/path/to/main.js`). */ + public readonly mainPath: string, + + /** Source code for the Fiddle. */ public readonly source: string, ) {} + /** Deletes the Fiddle from the file system. */ public remove(): Promise { return fs.promises.rm(path.dirname(this.mainPath), { recursive: true, @@ -41,22 +47,31 @@ export interface FiddleFactoryCreateOptions { packAsAsar?: boolean; } +/** + * Factory class for creating Fiddle instances from different sources. + */ export class FiddleFactory { constructor(private readonly fiddles: string = DefaultPaths.fiddles) {} + /** + * Creates a Fiddle by fetching a GitHub Gist and cloning it into a temporary directory. + * @param gistId - The ID of the GitHub Gist to fetch. + */ public async fromGist(gistId: string): Promise { return this.fromRepo(`https://gist.github.com/${gistId}.git`); } + /** + * Creates a Fiddle by making a temporary copy from the specified source folder. + * @param source - The folder path containing the Fiddle source files. + */ public async fromFolder(source: string): Promise { const d = debug('fiddle-core:FiddleFactory:fromFolder'); - // make a tmp copy of this fiddle const folder = path.join(this.fiddles, hashString(source)); d({ source, folder }); await fs.promises.rm(folder, { recursive: true, force: true }); - // Disable asar in case any deps bundle Electron - ex. @electron/remote const { noAsar } = process; process.noAsar = true; await fs.promises.cp(source, folder, { recursive: true }); @@ -65,12 +80,20 @@ export class FiddleFactory { return new Fiddle(path.join(folder, 'main.js'), source); } + /** + * Creates a Fiddle instance by cloning a Git repository into a temporary directory. + * Optionally checks out a specific branch and determines the main file path + * based on the cloned content. + * + * @param url - The Git repository URL to clone. + * @param checkout - The branch to check out (default is 'master'). + * @returns A Promise that resolves to a Fiddle instance. + */ public async fromRepo(url: string, checkout = 'master'): Promise { const d = debug('fiddle-core:FiddleFactory:fromRepo'); const folder = path.join(this.fiddles, hashString(url)); d({ url, checkout, folder }); - // get the repo if (!fs.existsSync(folder)) { d(`cloning "${url}" into "${folder}"`); const git = simpleGit(); @@ -83,12 +106,18 @@ export class FiddleFactory { return new Fiddle(path.join(folder, 'main.js'), url); } - + /** + * Creates a Fiddle instance from an in-memory collection of files. + * Each entry consists of a filename and its corresponding file content. + * The files are saved to a temporary directory, and the main file path is set accordingly. + * + * @param src - An iterable of [filename, content] pairs. + * @returns A Promise that resolves to a Fiddle instance. + */ public async fromEntries(src: Iterable<[string, string]>): Promise { const d = debug('fiddle-core:FiddleFactory:fromEntries'); const map = new Map(src); - // make a name for the directory that will hold our temp copy of the fiddle const md5sum = createHash('md5'); for (const content of map.values()) md5sum.update(content); const hash = md5sum.digest('hex'); @@ -96,7 +125,6 @@ export class FiddleFactory { await fs.promises.mkdir(folder, { recursive: true }); d({ folder }); - // save content to that temp directory await Promise.all( [...map.entries()].map(([filename, content]) => util.promisify(fs.writeFile)( @@ -110,6 +138,14 @@ export class FiddleFactory { return new Fiddle(path.join(folder, 'main.js'), 'entries'); } + /** + * Determines the type of the provided source and delegates to the appropriate + * method to create a Fiddle instance. + * + * @param src - The source used to create the Fiddle. Can be an existing Fiddle instance, + * a local folder path, a Git repository URL, or a collection of file entries. + * @returns A Promise that resolves to a Fiddle instance or undefined if the source type is invalid. + */ public async create( src: FiddleSource, options?: FiddleFactoryCreateOptions, diff --git a/src/installer.ts b/src/installer.ts index ba9cef0..11a6778 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -13,7 +13,7 @@ import { DefaultPaths, Paths } from './paths.js'; function getZipName(version: string): string { return `electron-v${version}-${process.platform}-${process.arch}.zip`; } - +/** Tracks installation progress as a percentage. */ export type ProgressObject = { percent: number }; /** @@ -22,30 +22,55 @@ export type ProgressObject = { percent: number }; * See Installer.on('state-changed') to watch for state changes. */ export enum InstallState { + /** No installation detected. */ missing = 'missing', + + /** Files are being downloaded. */ downloading = 'downloading', + + /** Download completed successfully. */ downloaded = 'downloaded', + + /** Installation process is in progress. */ installing = 'installing', + + /** Installation completed successfully. */ installed = 'installed', } - +/** + * Event emitted when an installation state changes for a specific Electron version. + */ export interface InstallStateEvent { + /** Version identifier for the installation. */ version: string; + /** Current installation state. */ state: InstallState; } - +/** + * URLs for Electron mirrors used during downloads. + */ export interface Mirrors { + /** Base URL for the Electron mirror. */ electronMirror: string; + /** Base URL for the Electron nightly mirror. */ electronNightlyMirror: string; } - +/** + * Configuration details for an Electron binary. + */ export interface ElectronBinary { + /** Local path to the Electron binary. */ path: string; - alreadyExtracted: boolean; // to check if it's kept as zipped or not + /** Whether the binary has already been extracted. */ + alreadyExtracted: boolean; } - +/** + * Parameters used when running an installer. + */ export interface InstallerParams { + /** Callback invoked to report installation progress. */ progressCallback: (progress: ProgressObject) => void; + /** Mirror sources used for downloading. */ mirror: Mirrors; } @@ -70,7 +95,10 @@ export class Installer extends EventEmitter { this.paths = Object.freeze({ ...DefaultPaths, ...pathsIn }); this.rebuildStates(); } - + /** + * Returns the relative executable path based on the current platform. + * @param platform - Optional platform identifier; defaults to the host platform. + */ public static execSubpath(platform: string = process.platform): string { switch (platform) { case 'darwin': @@ -81,11 +109,17 @@ export class Installer extends EventEmitter { return 'electron'; } } - + /** + * Resolves the full path to the Electron executable within a given folder. + * @param folder - Path to the Electron version folder. + */ public static getExecPath(folder: string): string { return path.join(folder, Installer.execSubpath()); } - + /** + * Returns the current installation state for a given Electron version. + * @param version - Version identifier to check. + */ public state(version: string): InstallState { return this.stateMap.get(version) || InstallState.missing; } @@ -319,9 +353,15 @@ export class Installer extends EventEmitter { }; } - /** map of version string to currently-running active Promise */ + /** + * Tracks versions currently being downloaded, mapped to their active Promise. + */ private downloading = new Map>(); - + /** + * Ensures that the specified version of Electron is downloaded and ready for installation. + * @param version - Electron version to download. + * @param opts - Optional parameters such as progress callbacks or mirror configuration. + */ public async ensureDownloaded( version: string, opts?: Partial, @@ -337,9 +377,15 @@ export class Installer extends EventEmitter { return promise; } - /** keep a track of all currently installing versions */ + /** + * Tracks all versions currently undergoing installation. + */ private installing = new Set(); - + /** + * Installs the specified Electron version after download. + * @param version - Electron version to install. + * @param opts - Optional installer configuration. + */ public async install( version: string, opts?: Partial, diff --git a/src/paths.ts b/src/paths.ts index 829d92c..b12565a 100644 --- a/src/paths.ts +++ b/src/paths.ts @@ -2,25 +2,29 @@ import path from 'node:path'; import envPaths from 'env-paths'; +/** + * Defines standard filesystem paths used by fiddle-core. + */ export interface Paths { - // folder where electron zipfiles will be cached + /** Directory where Electron zip archives are cached. */ readonly electronDownloads: string; - // folder where an electron download will be unzipped to be run + /** Directory where Electron builds are extracted and executed. */ readonly electronInstall: string; - // folder where fiddles will be saved + /** Directory where user fiddles are stored. */ readonly fiddles: string; - // file where electron releases are cached + /** File path used to cache Electron release metadata. */ readonly versionsCache: string; } const paths = envPaths('fiddle-core', { suffix: '' }); +/** Default set of resolved paths for fiddle-core operations. */ export const DefaultPaths: Paths = { electronDownloads: path.join(paths.data, 'electron', 'zips'), electronInstall: path.join(paths.data, 'electron', 'current'), fiddles: path.join(paths.cache, 'fiddles'), versionsCache: path.join(paths.cache, 'releases.json'), -}; +}; \ No newline at end of file diff --git a/src/runner.ts b/src/runner.ts index fdfb6c0..1e5dae4 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -14,19 +14,24 @@ import { ElectronVersions, Versions } from './versions.js'; import { Fiddle, FiddleFactory, FiddleSource } from './fiddle.js'; import { DefaultPaths, Paths } from './paths.js'; +/** + * Configuration options for running an Electron instance. + */ export interface RunnerOptions { - // extra arguments to be appended to the electron invocation + /** Additional command-line arguments passed to the Electron process. */ args?: string[]; - // if true, use xvfb-run on *nix + + /** Runs the Electron process using `xvfb-run` on Unix-like systems (headless mode). */ headless?: boolean; - // where the test's output should be written + + /** Stream or file where the process output should be written. */ out?: Writable; - // whether to show config info (e.g. platform os & arch) in the log + + /** Displays system and environment configuration details in the log output. */ showConfig?: boolean; - // whether to run the fiddle from asar - runFromAsar?: boolean; } + const DefaultRunnerOpts: RunnerOptions = { args: [], headless: false, @@ -34,17 +39,33 @@ const DefaultRunnerOpts: RunnerOptions = { showConfig: true, } as const; +/** + * Combined options for spawning and configuring a test runner process. + */ export type RunnerSpawnOptions = SpawnOptions & RunnerOptions; +/** + * Represents the outcome of a single test execution. + */ export interface TestResult { + /** Final status of the test execution. */ status: 'test_passed' | 'test_failed' | 'test_error' | 'system_error'; } +/** + * Represents the outcome of a bisect operation used to isolate regressions. + */ export interface BisectResult { + /** Version range where the bisect identified a passing and failing boundary. */ range?: [string, string]; + + /** Overall status of the bisect operation. */ status: 'bisect_succeeded' | 'test_error' | 'system_error'; } +/** + * Handles the execution of Electron-related commands and tests. + */ export class Runner { private osInfo = ''; @@ -55,7 +76,11 @@ export class Runner { ) { getos((err, result) => (this.osInfo = inspect(result || err))); } - + /** + * Initializes a new Runner instance with optional dependencies. + * @param opts - Optional configuration, such as a custom Installer instance. + * @returns A fully initialized Runner ready to execute Electron tasks. + */ public static async create( opts: { installer?: Installer; @@ -135,7 +160,13 @@ export class Runner { } return { exec, args }; } - + /** + * Spawns a child process to execute a Fiddle using a specified Electron version. + * @param versionIn - The Electron version to run (string or SemVer). + * @param fiddleIn - The Fiddle source or path to execute. + * @param options - Optional runner configuration such as headless mode or output stream. + * @returns A Promise that resolves once the process completes. + */ public async spawn( versionIn: string | SemVer, fiddleIn: FiddleSource, @@ -184,7 +215,11 @@ export class Runner { return '🟢'; } } - + /** + * Formats and displays the result of a test run. + * @param result - The result object containing the test outcome. + * @returns A formatted string representation of the test result. + */ public static displayResult(result: TestResult): string { const text = Runner.displayEmoji(result); switch (result.status) { @@ -198,7 +233,12 @@ export class Runner { return text + ' passed'; } } - + /** + * Runs a Fiddle with a given Electron version and returns the test outcome. + * @param version - The Electron version to test. + * @param fiddle - The Fiddle source or configuration to run. + * @returns A Promise that resolves to the test result. + */ public async run( version: string | SemVer, fiddle: FiddleSource, @@ -218,7 +258,13 @@ export class Runner { }); }); } - + /** + * Performs a bisect between two Electron versions to identify a regression. + * @param version_a - The older Electron version. + * @param version_b - The newer Electron version. + * @param fiddle - The Fiddle to test across versions. + * @returns A Promise that resolves to the bisect result. + */ public async bisect( version_a: string | SemVer, version_b: string | SemVer, diff --git a/src/versions.ts b/src/versions.ts index 1122ef9..634403e 100644 --- a/src/versions.ts +++ b/src/versions.ts @@ -8,11 +8,16 @@ import debug from 'debug'; export { SemVer }; import { DefaultPaths, Paths } from './paths.js'; - +/** + * Represents a version identifier, either as a string or a SemVer object. + */ export type SemOrStr = SemVer | string; +/** + * Metadata describing an Electron release. + */ export interface ReleaseInfo { - /** Electron version */ + /** Version number of the Electron release. */ version: string; /** Release date */ date: string; @@ -72,17 +77,33 @@ export interface Versions { getReleaseInfo(version: SemOrStr): ReleaseInfo | undefined; } +/** + * Options for creating and managing Electron version data. + */ export interface ElectronVersionsCreateOptions { - /** Initial versions to use if there is no cache. When provided, no initial fetch is done */ + /** + * Initial versions to use if no cache exists. + * When provided, the initial fetch step is skipped. + */ initialVersions?: unknown; - /** Ignore the cache even if it exists and is fresh */ + /** + * If true, forces a cache refresh regardless of existing data. + */ ignoreCache?: boolean; - /** Paths to use for the cache and fiddles */ paths?: Partial; } +/** + * Compares two semantic version objects by their major, minor, and patch numbers. + * @param a - The first version to compare. + * @param b - The second version to compare. + * @returns A number indicating the comparison result: + * - `-1` if `a < b` + * - `0` if equal + * - `1` if `a > b` + */ export function compareVersions(a: SemVer, b: SemVer): number { const l = a.compareMain(b); if (l) return l; @@ -155,6 +176,7 @@ export class BaseVersions implements Versions { private readonly map = new Map(); private readonly releaseInfo = new Map(); + /** Updates internal maps with new version and release information. */ protected setVersions(val: unknown): void { // release info doesn't need to be in sorted order this.releaseInfo.clear(); @@ -198,6 +220,7 @@ export class BaseVersions implements Versions { this.setVersions(versions); } + /** Returns an array of major versions that include prerelease builds. */ public get prereleaseMajors(): number[] { const majors = new Set(); for (const ver of this.map.values()) { @@ -211,6 +234,7 @@ export class BaseVersions implements Versions { return [...majors]; } + /** Returns an array of major versions that only include stable releases. */ public get stableMajors(): number[] { const majors = new Set(); for (const ver of this.map.values()) { @@ -221,22 +245,27 @@ export class BaseVersions implements Versions { return [...majors]; } + /** Returns the most recently supported major versions. */ public get supportedMajors(): number[] { return this.stableMajors.slice(-NUM_SUPPORTED_MAJORS); } + /** Returns major versions that are no longer supported. */ public get obsoleteMajors(): number[] { return this.stableMajors.slice(0, -NUM_SUPPORTED_MAJORS); } + /** Returns all available semantic versions. */ public get versions(): SemVer[] { return [...this.map.values()]; } + /** Returns the latest available version. */ public get latest(): SemVer | undefined { return this.versions.pop(); } + /** Returns the latest stable release. */ public get latestStable(): SemVer | undefined { let stable: SemVer | undefined = undefined; for (const ver of this.map.values()) { @@ -247,10 +276,12 @@ export class BaseVersions implements Versions { return stable; } + /** Checks whether a given value corresponds to a known version. */ public isVersion(ver: SemOrStr): boolean { return this.map.has(typeof ver === 'string' ? ver : ver.version); } + /** Returns all versions within a specific major version. */ public inMajor(major: number): SemVer[] { const versions: SemVer[] = []; for (const ver of this.map.values()) { @@ -261,6 +292,7 @@ export class BaseVersions implements Versions { return versions; } + /** Returns all versions within a specified version range. */ public inRange(a: SemOrStr, b: SemOrStr): SemVer[] { if (typeof a !== 'string') a = a.version; if (typeof b !== 'string') b = b.version; @@ -272,6 +304,7 @@ export class BaseVersions implements Versions { return versions.slice(first, last + 1); } + /** Retrieves release metadata for a given version. */ public getReleaseInfo(ver: SemOrStr): ReleaseInfo | undefined { return this.releaseInfo.get(typeof ver === 'string' ? ver : ver.version); } @@ -315,6 +348,7 @@ export class ElectronVersions extends BaseVersions { return now <= cacheTimeMs + VERSION_CACHE_TTL_MS; } + public static async create( options: ElectronVersionsCreateOptions = {}, ): Promise { @@ -352,7 +386,7 @@ export class ElectronVersions extends BaseVersions { return new ElectronVersions(versionsCache, now, versions); } - // update the cache + /** Refreshes the version cache by fetching the latest release data. */ public async fetch(): Promise { const d = debug('fiddle-core:ElectronVersions:fetch'); const { mtimeMs, versionsCache } = this; @@ -373,43 +407,52 @@ export class ElectronVersions extends BaseVersions { await this.fetch(); } } - + /** Returns prerelease majors, ensuring the cache is refreshed. */ public override get prereleaseMajors(): number[] { void this.keepFresh(); return super.prereleaseMajors; } + /** Returns stable majors, ensuring the cache is refreshed. */ public override get stableMajors(): number[] { void this.keepFresh(); return super.stableMajors; } + /** Returns supported majors, ensuring the cache is refreshed. */ public override get supportedMajors(): number[] { void this.keepFresh(); return super.supportedMajors; } + /** Returns obsolete majors, ensuring the cache is refreshed. */ public override get obsoleteMajors(): number[] { void this.keepFresh(); return super.obsoleteMajors; } + /** Returns all versions, ensuring the cache is refreshed. */ public override get versions(): SemVer[] { void this.keepFresh(); return super.versions; } + /** Returns the latest version, ensuring the cache is refreshed. */ public override get latest(): SemVer | undefined { void this.keepFresh(); return super.latest; } + /** Returns the latest stable version, ensuring the cache is refreshed. */ public override get latestStable(): SemVer | undefined { void this.keepFresh(); return super.latestStable; } + /** Checks if a version exists, ensuring the cache is refreshed. */ public override isVersion(ver: SemOrStr): boolean { void this.keepFresh(); return super.isVersion(ver); } + /** Returns all versions within a major version, ensuring the cache is refreshed. */ public override inMajor(major: number): SemVer[] { void this.keepFresh(); return super.inMajor(major); } + /** Returns all versions in a range, ensuring the cache is refreshed. */ public override inRange(a: SemOrStr, b: SemOrStr): SemVer[] { void this.keepFresh(); return super.inRange(a, b);