@@ -35,6 +35,7 @@ import type { AssemblyData, StackDetails, ToolkitAction } from '../api/shared-pu
35
35
import { DiffFormatter , RequireApproval , ToolkitError , removeNonImportResources } from '../api/shared-public' ;
36
36
import { obscureTemplate , serializeStructure , validateSnsTopicArn , formatTime , formatErrorMessage , deserializeStructure } from '../private/util' ;
37
37
import { pLimit } from '../util/concurrency' ;
38
+ import { promiseWithResolvers } from '../util/promises' ;
38
39
39
40
export interface ToolkitOptions {
40
41
/**
@@ -720,10 +721,12 @@ export class Toolkit extends CloudAssemblySourceBuilder {
720
721
/**
721
722
* Watch Action
722
723
*
723
- * Continuously observe project files and deploy the selected stacks automatically when changes are detected.
724
- * Implies hotswap deployments.
724
+ * Continuously observe project files and deploy the selected stacks
725
+ * automatically when changes are detected. Implies hotswap deployments.
726
+ *
727
+ * This function returns immediately, starting a watcher in the background.
725
728
*/
726
- public async watch ( cx : ICloudAssemblySource , options : WatchOptions ) : Promise < void > {
729
+ public async watch ( cx : ICloudAssemblySource , options : WatchOptions ) : Promise < IWatcher > {
727
730
const ioHelper = asIoHelper ( this . ioHost , 'watch' ) ;
728
731
const assembly = await assemblyFromSource ( ioHelper , cx , false ) ;
729
732
const rootDir = options . watchDir ?? process . cwd ( ) ;
@@ -810,7 +813,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {
810
813
await cloudWatchLogMonitor ?. activate ( ) ;
811
814
} ;
812
815
813
- chokidar
816
+ const watcher = chokidar
814
817
. watch ( watchIncludes , {
815
818
ignored : watchExcludes ,
816
819
cwd : rootDir ,
@@ -840,6 +843,26 @@ export class Toolkit extends CloudAssemblySourceBuilder {
840
843
) ) ;
841
844
}
842
845
} ) ;
846
+
847
+ const stoppedPromise = promiseWithResolvers < void > ( ) ;
848
+
849
+ return {
850
+ async dispose ( ) {
851
+ await watcher . close ( ) ;
852
+ // Prevents Node from staying alive. There is no 'end' event that the watcher emits
853
+ // that we can know it's definitely done, so best we can do is tell it to stop watching,
854
+ // stop keeping Node alive, and then pretend that's everything we needed to do.
855
+ watcher . unref ( ) ;
856
+ stoppedPromise . resolve ( ) ;
857
+ return stoppedPromise . promise ;
858
+ } ,
859
+ async waitForEnd ( ) {
860
+ return stoppedPromise . promise ;
861
+ } ,
862
+ async [ Symbol . asyncDispose ] ( ) {
863
+ return this . dispose ( ) ;
864
+ } ,
865
+ } satisfies IWatcher ;
843
866
}
844
867
845
868
/**
@@ -1007,3 +1030,25 @@ export class Toolkit extends CloudAssemblySourceBuilder {
1007
1030
}
1008
1031
}
1009
1032
}
1033
+
1034
+ /**
1035
+ * The result of a `cdk.watch()` operation.
1036
+ */
1037
+ export interface IWatcher extends AsyncDisposable {
1038
+ /**
1039
+ * Stop the watcher and wait for the current watch iteration to complete.
1040
+ *
1041
+ * An alias for `[Symbol.asyncDispose]`, as a more readable alternative for
1042
+ * environments that don't support the Disposable APIs yet.
1043
+ */
1044
+ dispose ( ) : Promise < void > ;
1045
+
1046
+ /**
1047
+ * Wait for the watcher to stop.
1048
+ *
1049
+ * The watcher will only stop if `dispose()` or `[Symbol.asyncDispose]()` are called.
1050
+ *
1051
+ * If neither of those is called, awaiting this promise will wait forever.
1052
+ */
1053
+ waitForEnd ( ) : Promise < void > ;
1054
+ }
0 commit comments