import { type Reporter } from '@quenty/cli-output-helpers/reporting'; import { StudioBridge } from '@quenty/studio-bridge'; import { type BuiltPlace, type Deployment, type DeployPlaceOptions, type RunScriptOptions, type ScriptRunResult, type JobLogs, } from './job-context.js'; import { type BasePlaceResolver } from '@quenty/nevermore-deploy'; import { BaseJobContext } from './base-job-context.js'; import { type OpenCloudClient } from '../open-cloud/open-cloud-client.js'; class LocalDeployment implements Deployment { bridge: StudioBridge; builtPlace: BuiltPlace; cachedLogs = ''; constructor(bridge: StudioBridge, builtPlace: BuiltPlace) { this.bridge = bridge; this.builtPlace = builtPlace; } } export class LocalJobContext extends BaseJobContext { private _deployments = new Set(); constructor( reporter: Reporter, openCloudClient?: OpenCloudClient, basePlaceResolver?: BasePlaceResolver ) { super(reporter, openCloudClient, basePlaceResolver); } async deployBuiltPlaceAsync( options: DeployPlaceOptions ): Promise { const { builtPlace, packageName } = options; const bridge = new StudioBridge({ placePath: builtPlace.rbxlPath, onPhase: (phase) => { if (phase === 'launching' || phase === 'connecting') { this._reporter.onPackagePhaseChange(packageName, phase); } }, }); await bridge.startAsync(); const deployment = new LocalDeployment(bridge, builtPlace); this._deployments.add(deployment); return deployment; } async runScriptAsync( deployment: Deployment, options: RunScriptOptions ): Promise { const localDeployment = deployment as LocalDeployment; const { scriptContent, packageName, timeoutMs } = options; this._reporter.onPackagePhaseChange(packageName, 'executing'); try { const result = await localDeployment.bridge.executeAsync({ scriptContent, timeoutMs, }); localDeployment.cachedLogs = result.logs; return { success: result.success, returnValues: result.returnValues }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); localDeployment.cachedLogs = `[StudioBridge] Error: ${errorMessage}`; return { success: false }; } } async getLogsAsync(deployment: Deployment): Promise { const localDeployment = deployment as LocalDeployment; // No severity and no fetch: the bridge hands over text it already has. // Both stay absent rather than being invented. return { text: localDeployment.cachedLogs, messages: [] }; } async releaseAsync(deployment: Deployment): Promise { const localDeployment = deployment as LocalDeployment; await localDeployment.bridge.stopAsync(); // Release build artifacts (removes .rbxl, .rbxl.lock, and temp dir) await this.releaseBuiltPlaceAsync(localDeployment.builtPlace); this._deployments.delete(localDeployment); } async disposeAsync(): Promise { const remaining = [...this._deployments]; for (const d of remaining) { await this.releaseAsync(d); } await super.disposeAsync(); } }