1- import type { ImageDeployer } from '../deployer/image-deployer'
2- import type { DeployedImageConfigWithProductName } from '../deployer/list'
1+ import type { Device } from '../deployer/image-deployer'
2+ import type { DeployedImageConfigWithProductName , FullDeployedImageOptions , ProductNameable } from '../deployer/list'
33import type { ProductConfigItem } from '../product-config'
44import type { DeviceType , Stringifiable } from '../types'
55import type { BaseImage } from './image'
@@ -8,13 +8,14 @@ import { ImageBase } from './image'
88
99export interface LocalImage extends BaseImage , Stringifiable < LocalImage . Stringifiable > {
1010 imageType : 'local'
11- createDeployer ( name : string , config : DeployedImageConfigWithProductName ) : ImageDeployer
11+ createDevice ( name : string , config : DeployedImageConfigWithProductName ) : Device
1212 getProductConfig ( ) : Promise < ProductConfigItem [ ] >
1313 delete ( ) : Promise < void | Error >
14- buildStartCommand ( deployer : ImageDeployer ) : Promise < string >
15- start ( deployer : ImageDeployer ) : Promise < import ( 'node:child_process' ) . ChildProcess >
16- buildStopCommand ( deployer : ImageDeployer ) : Promise < string >
17- stop ( deployer : ImageDeployer ) : Promise < import ( 'node:child_process' ) . ChildProcess >
14+ buildStartCommand ( deployer : Device ) : Promise < string >
15+ start ( deployer : Device ) : Promise < import ( 'node:child_process' ) . ChildProcess >
16+ buildStopCommand ( deployer : Device ) : Promise < string >
17+ stop ( deployer : Device ) : Promise < import ( 'node:child_process' ) . ChildProcess >
18+ getDevices ( ) : Promise < Device [ ] >
1819}
1920
2021export namespace LocalImage {
@@ -27,7 +28,7 @@ export namespace LocalImage {
2728export class LocalImageImpl extends ImageBase < LocalImage . Stringifiable > implements LocalImage {
2829 imageType = 'local' as const
2930
30- createDeployer ( name : string , config : DeployedImageConfigWithProductName ) : ImageDeployer {
31+ createDevice ( name : string , config : ProductNameable < FullDeployedImageOptions > ) : Device {
3132 return createImageDeployer (
3233 this ,
3334 this . getImageManager ( ) . getOptions ( ) . crypto . randomUUID ( ) ,
@@ -52,11 +53,16 @@ export class LocalImageImpl extends ImageBase<LocalImage.Stringifiable> implemen
5253 }
5354
5455 async delete ( ) : Promise < void | Error > {
55- const fs = this . getImageManager ( ) . getOptions ( ) . fs
56+ const { fs } = this . getImageManager ( ) . getOptions ( )
5657 const path = this . getFsPath ( )
5758 if ( ! fs . existsSync ( path ) || ! fs . statSync ( path ) . isDirectory ( ) )
5859 return new Error ( 'Image path does not exist' )
5960 fs . rmSync ( path , { recursive : true } )
61+ const devices = await this . getDevices ( )
62+ const error = await Promise . allSettled ( devices . map ( device => device . delete ( ) ) )
63+ . then ( results => results . find ( result => result . status === 'rejected' ) )
64+ if ( error )
65+ return error . reason
6066 return undefined
6167 }
6268
@@ -65,7 +71,7 @@ export class LocalImageImpl extends ImageBase<LocalImage.Stringifiable> implemen
6571 return process . platform === 'win32' ? path . join ( emulatorPath , 'Emulator.exe' ) : path . join ( emulatorPath , 'Emulator' )
6672 }
6773
68- async buildStartCommand ( deployer : ImageDeployer ) : Promise < string > {
74+ async buildStartCommand ( deployer : Device ) : Promise < string > {
6975 const config = await deployer . buildList ( )
7076 const executablePath = this . getExecutablePath ( )
7177 const args = [
@@ -79,12 +85,12 @@ export class LocalImageImpl extends ImageBase<LocalImage.Stringifiable> implemen
7985 return `${ executablePath } ${ args } `
8086 }
8187
82- async start ( deployer : ImageDeployer ) : Promise < import ( 'node:child_process' ) . ChildProcess > {
88+ async start ( deployer : Device ) : Promise < import ( 'node:child_process' ) . ChildProcess > {
8389 const { child_process, emulatorPath } = this . getImageManager ( ) . getOptions ( )
8490 return child_process . exec ( await this . buildStartCommand ( deployer ) , { cwd : emulatorPath } )
8591 }
8692
87- async buildStopCommand ( deployer : ImageDeployer ) : Promise < string > {
93+ async buildStopCommand ( deployer : Device ) : Promise < string > {
8894 const config = await deployer . buildList ( )
8995 const executablePath = this . getExecutablePath ( )
9096 const args = [
@@ -94,11 +100,38 @@ export class LocalImageImpl extends ImageBase<LocalImage.Stringifiable> implemen
94100 return `${ executablePath } ${ args } `
95101 }
96102
97- async stop ( deployer : ImageDeployer ) : Promise < import ( 'node:child_process' ) . ChildProcess > {
103+ async stop ( deployer : Device ) : Promise < import ( 'node:child_process' ) . ChildProcess > {
98104 const { child_process, emulatorPath } = this . getImageManager ( ) . getOptions ( )
99105 return child_process . exec ( await this . buildStopCommand ( deployer ) , { cwd : emulatorPath } )
100106 }
101107
108+ private typeAssert < T > ( value : unknown ) : asserts value is T { }
109+
110+ async getDevices ( ) : Promise < Device [ ] > {
111+ const { path, fs, imageBasePath } = this . getImageManager ( ) . getOptions ( )
112+ const listsJsonPath = path . resolve ( this . getImageManager ( ) . getOptions ( ) . deployedPath , 'lists.json' )
113+ if ( ! fs . existsSync ( listsJsonPath ) || ! fs . statSync ( listsJsonPath ) . isFile ( ) )
114+ return [ ]
115+ const listsJson : unknown = JSON . parse ( fs . readFileSync ( listsJsonPath , 'utf-8' ) )
116+ if ( ! Array . isArray ( listsJson ) || this . imageType !== 'local' )
117+ return [ ]
118+
119+ this . typeAssert < LocalImage > ( this )
120+ const devices : Device [ ] = [ ]
121+ for ( const listsJsonItem of listsJson as unknown [ ] ) {
122+ if ( typeof listsJsonItem !== 'object' || listsJsonItem === null )
123+ continue
124+ if ( ! ( 'imageDir' in listsJsonItem ) || typeof listsJsonItem . imageDir !== 'string' )
125+ continue
126+ if ( ! ( 'name' in listsJsonItem ) || typeof listsJsonItem . name !== 'string' )
127+ continue
128+ if ( path . resolve ( this . getFsPath ( ) ) !== path . resolve ( imageBasePath , listsJsonItem . imageDir ) )
129+ continue
130+ devices . push ( this . createDevice ( listsJsonItem . name , listsJsonItem as ProductNameable < FullDeployedImageOptions > ) )
131+ }
132+ return devices
133+ }
134+
102135 toJSON ( ) : LocalImage . Stringifiable {
103136 return {
104137 ...super . toJSON ( ) ,
0 commit comments