Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recursive directory watch #488

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,52 @@ for event in events: # $HighlightLine
print(f"wrote to file {event.name}") # $HighlightLine
```
</CodeGroup>


## Recursive Watching

You can enable recursive watching using the parameter `recursive`.

<Note>
When rapidly creating new folders (e.g., deeply nested path of folders), events other than `CREATE` might not be emitted. To avoid this behavior, create the required folder structure in advance.
</Note>

<CodeGroup>
```js
import { Sandbox, FilesystemEventType } from '@e2b/code-interpreter'

const sandbox = await Sandbox.create()
const dirname = '/home/user'

// Start watching directory for changes
const handle = await sandbox.files.watchDir(dirname, async (event) => {
console.log(event)
if (event.type === FilesystemEventType.WRITE) {
console.log(`wrote to file ${event.name}`)
}
}, {
recursive: true // $HighlightLine
})

// Trigger file write event
await sandbox.files.write(`${dirname}/my-folder/my-file`, 'hello') // $HighlightLine
```
dobrac marked this conversation as resolved.
Show resolved Hide resolved
```python
from e2b_code_interpreter import Sandbox

sandbox = Sandbox()
dirname = '/home/user'

# Watch directory for changes
handle = sandbox.files.watch_dir(dirname, recursive=True) # $HighlightLine
# Trigger file write event
sandbox.files.write(f"{dirname}/my-folder/my-file", "hello") # $HighlightLine

# Retrieve the latest new events since the last `get_new_events()` call
events = handle.get_new_events()
for event in events:
print(event)
if event.type == FilesystemEventType.Write:
print(f"wrote to file {event.name}")
```
</CodeGroup>
5 changes: 4 additions & 1 deletion packages/js-sdk/src/envd/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ export async function handleWatchDirStartEvent(
class EnvdApiClient {
readonly api: ReturnType<typeof createClient<paths>>

constructor(config: Pick<ConnectionConfig, 'apiUrl' | 'logger'>) {
constructor(
config: Pick<ConnectionConfig, 'apiUrl' | 'logger'>,
readonly version?: string
) {
this.api = createClient({
baseUrl: config.apiUrl,
// keepalive: true, // TODO: Return keepalive
Expand Down
12 changes: 11 additions & 1 deletion packages/js-sdk/src/envd/filesystem/filesystem_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file filesystem/filesystem.proto.
*/
export const file_filesystem_filesystem: GenFile = /*@__PURE__*/
fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHwoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiRAoPRmlsZXN5c3RlbUV2ZW50EgwKBG5hbWUYASABKAkSIwoEdHlwZRgCIAEoDjIVLmZpbGVzeXN0ZW0uRXZlbnRUeXBlIuABChBXYXRjaERpclJlc3BvbnNlEjgKBXN0YXJ0GAEgASgLMicuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLlN0YXJ0RXZlbnRIABIxCgpmaWxlc3lzdGVtGAIgASgLMhsuZmlsZXN5c3RlbS5GaWxlc3lzdGVtRXZlbnRIABI7CglrZWVwYWxpdmUYAyABKAsyJi5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UuS2VlcEFsaXZlSAAaDAoKU3RhcnRFdmVudBoLCglLZWVwQWxpdmVCBwoFZXZlbnQiJAoUQ3JlYXRlV2F0Y2hlclJlcXVlc3QSDAoEcGF0aBgBIAEoCSIrChVDcmVhdGVXYXRjaGVyUmVzcG9uc2USEgoKd2F0Y2hlcl9pZBgBIAEoCSItChdHZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIkcKGEdldFdhdGNoZXJFdmVudHNSZXNwb25zZRIrCgZldmVudHMYASADKAsyGy5maWxlc3lzdGVtLkZpbGVzeXN0ZW1FdmVudCIqChRSZW1vdmVXYXRjaGVyUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIhcKFVJlbW92ZVdhdGNoZXJSZXNwb25zZSpSCghGaWxlVHlwZRIZChVGSUxFX1RZUEVfVU5TUEVDSUZJRUQQABISCg5GSUxFX1RZUEVfRklMRRABEhcKE0ZJTEVfVFlQRV9ESVJFQ1RPUlkQAiqYAQoJRXZlbnRUeXBlEhoKFkVWRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIVChFFVkVOVF9UWVBFX0NSRUFURRABEhQKEEVWRU5UX1RZUEVfV1JJVEUQAhIVChFFVkVOVF9UWVBFX1JFTU9WRRADEhUKEUVWRU5UX1RZUEVfUkVOQU1FEAQSFAoQRVZFTlRfVFlQRV9DSE1PRBAFMp8FCgpGaWxlc3lzdGVtEjkKBFN0YXQSFy5maWxlc3lzdGVtLlN0YXRSZXF1ZXN0GhguZmlsZXN5c3RlbS5TdGF0UmVzcG9uc2USQgoHTWFrZURpchIaLmZpbGVzeXN0ZW0uTWFrZURpclJlcXVlc3QaGy5maWxlc3lzdGVtLk1ha2VEaXJSZXNwb25zZRI5CgRNb3ZlEhcuZmlsZXN5c3RlbS5Nb3ZlUmVxdWVzdBoYLmZpbGVzeXN0ZW0uTW92ZVJlc3BvbnNlEkIKB0xpc3REaXISGi5maWxlc3lzdGVtLkxpc3REaXJSZXF1ZXN0GhsuZmlsZXN5c3RlbS5MaXN0RGlyUmVzcG9uc2USPwoGUmVtb3ZlEhkuZmlsZXN5c3RlbS5SZW1vdmVSZXF1ZXN0GhouZmlsZXN5c3RlbS5SZW1vdmVSZXNwb25zZRJHCghXYXRjaERpchIbLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXF1ZXN0GhwuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlMAESVAoNQ3JlYXRlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLkNyZWF0ZVdhdGNoZXJSZXNwb25zZRJdChBHZXRXYXRjaGVyRXZlbnRzEiMuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBokLmZpbGVzeXN0ZW0uR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlElQKDVJlbW92ZVdhdGNoZXISIC5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXF1ZXN0GiEuZmlsZXN5c3RlbS5SZW1vdmVXYXRjaGVyUmVzcG9uc2VCaQoOY29tLmZpbGVzeXN0ZW1CD0ZpbGVzeXN0ZW1Qcm90b1ABogIDRlhYqgIKRmlsZXN5c3RlbcoCCkZpbGVzeXN0ZW3iAhZGaWxlc3lzdGVtXEdQQk1ldGFkYXRh6gIKRmlsZXN5c3RlbWIGcHJvdG8z");
fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM");

/**
* @generated from message filesystem.MoveRequest
Expand Down Expand Up @@ -218,6 +218,11 @@ export type WatchDirRequest = Message<"filesystem.WatchDirRequest"> & {
* @generated from field: string path = 1;
*/
path: string;

/**
* @generated from field: bool recursive = 2;
*/
recursive: boolean;
};

/**
Expand Down Expand Up @@ -318,6 +323,11 @@ export type CreateWatcherRequest = Message<"filesystem.CreateWatcherRequest"> &
* @generated from field: string path = 1;
*/
path: string;

/**
* @generated from field: bool recursive = 2;
*/
recursive: boolean;
};

/**
Expand Down
20 changes: 19 additions & 1 deletion packages/js-sdk/src/sandbox/filesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import { FileType as FsFileType, Filesystem as FilesystemService } from '../../e

import { WatchHandle, FilesystemEvent } from './watchHandle'

import { compareVersions } from 'compare-versions'
import { TemplateError } from '../../errors'

/**
* Sandbox filesystem object information.
*/
Expand Down Expand Up @@ -90,6 +93,10 @@ export interface WatchOpts extends FilesystemRequestOpts {
* Callback to call when the watch operation stops.
*/
onExit?: (err?: Error) => void | Promise<void>
/**
* Watch the directory recursively
*/
recursive?: boolean
}

/**
Expand All @@ -99,6 +106,7 @@ export class Filesystem {
private readonly rpc: Client<typeof FilesystemService>

private readonly defaultWatchTimeout = 60_000 // 60 seconds
private readonly defaultWatchRecursive = false

constructor(
transport: Transport,
Expand Down Expand Up @@ -434,6 +442,13 @@ export class Filesystem {
onEvent: (event: FilesystemEvent) => void | Promise<void>,
opts?: WatchOpts
): Promise<WatchHandle> {
if (opts?.recursive && this.envdApi.version && compareVersions(this.envdApi.version, '0.1.3') < 0) {
throw new TemplateError(
'You need to update the template to use recursive watching. ' +
'You can do this by running `e2b template build` in the directory with the template.'
)
}

const requestTimeoutMs =
opts?.requestTimeoutMs ?? this.connectionConfig.requestTimeoutMs

Expand All @@ -446,7 +461,10 @@ export class Filesystem {
: undefined

const events = this.rpc.watchDir(
{ path },
{
path,
recursive: opts?.recursive ?? this.defaultWatchRecursive,
},
{
headers: {
...authenticationHeader(opts?.user),
Expand Down
18 changes: 11 additions & 7 deletions packages/js-sdk/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class Sandbox extends SandboxApi {
constructor(
opts: Omit<SandboxOpts, 'timeoutMs' | 'envs' | 'metadata'> & {
sandboxId: string
envdVersion?: string
}
) {
super()
Expand All @@ -115,10 +116,13 @@ export class Sandbox extends SandboxApi {
interceptors: opts?.logger ? [createRpcLogger(opts.logger)] : undefined,
})

this.envdApi = new EnvdApiClient({
apiUrl: this.envdApiUrl,
logger: opts?.logger,
})
this.envdApi = new EnvdApiClient(
{
apiUrl: this.envdApiUrl,
logger: opts?.logger,
},
opts?.envdVersion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not like this?

Suggested change
apiUrl: this.envdApiUrl,
logger: opts?.logger,
},
opts?.envdVersion
apiUrl: this.envdApiUrl,
logger: opts?.logger,
envdVersion: opts?.envdVersion
},

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking it may be different scope than "ConnectionConfig" (maybe in the future part of some Metadata interface?), but If you want, I can move it there

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by wrapping envdVersion to the metadata object

)
this.files = new Filesystem(
rpcTransport,
this.envdApi,
Expand Down Expand Up @@ -177,15 +181,15 @@ export class Sandbox extends SandboxApi {

const config = new ConnectionConfig(sandboxOpts)

const sandboxId = config.debug
? 'debug_sandbox_id'
const sandbox = config.debug
? { sandboxId: 'debug_sandbox_id' }
: await this.createSandbox(
template,
sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
sandboxOpts
)

const sbx = new this({ sandboxId, ...config }) as InstanceType<S>
const sbx = new this({ ...sandbox, ...config }) as InstanceType<S>
return sbx
}

Expand Down
16 changes: 11 additions & 5 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ export class SandboxApi {
metadata?: Record<string, string>
envs?: Record<string, string>
}
): Promise<string> {
): Promise<{
sandboxId: string
envdVersion: string
}> {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)

Expand Down Expand Up @@ -191,10 +194,13 @@ export class SandboxApi {
'You can do this by running `e2b template build` in the directory with the template.'
)
}
return this.getSandboxId({
sandboxId: res.data!.sandboxID,
clientId: res.data!.clientID,
})
return {
sandboxId: this.getSandboxId({
sandboxId: res.data!.sandboxID,
clientId: res.data!.clientID,
}),
envdVersion: res.data!.envdVersion
}
}

private static timeoutToSeconds(timeout: number): number {
Expand Down
75 changes: 74 additions & 1 deletion packages/js-sdk/tests/sandbox/files/watch.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from 'vitest'
import { expect, onTestFinished } from 'vitest'

import { sandboxTest } from '../../setup.js'
import { FilesystemEventType, NotFoundError, SandboxError } from '../../../src'
Expand Down Expand Up @@ -31,6 +31,79 @@ sandboxTest('watch directory changes', async ({ sandbox }) => {
await handle.stop()
})

sandboxTest('watch recursive directory changes', async ({ sandbox }) => {
const dirname = 'test_recursive_watch_dir'
const nestedDirname = 'test_nested_watch_dir'
const filename = 'test_watch.txt'
const content = 'This file will be watched.'
const newContent = 'This file has been modified.'

await sandbox.files.makeDir(`${dirname}/${nestedDirname}`)
onTestFinished(() => sandbox.files.remove(dirname))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically correct, but not really necessary, the sandbox is killed after the test ends. This will only add one call to sandbox, but you could do this for local testing. Locally the same sandbox is reused

Suggested change
onTestFinished(() => sandbox.files.remove(dirname))
if (isDebug) {
onTestFinished(() => sandbox.files.remove(dirname))
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content)

let trigger: () => void

const eventPromise = new Promise<void>((resolve) => {
trigger = resolve
})

const expectedFileName = `${nestedDirname}/${filename}`
const handle = await sandbox.files.watchDir(dirname, async (event) => {
if (event.type === FilesystemEventType.WRITE && event.name === expectedFileName) {
trigger()
}
}, {
recursive: true
})

await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, newContent)

await eventPromise

await handle.stop()
})

sandboxTest('watch recursive directory after nested folder addition', async ({ sandbox }) => {
const dirname = 'test_recursive_watch_dir_add'
const nestedDirname = 'test_nested_watch_dir'
const filename = 'test_watch.txt'
const content = 'This file will be watched.'

await sandbox.files.makeDir(dirname)
onTestFinished(() => sandbox.files.remove(dirname))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

let triggerFile: () => void
let triggerFolder: () => void

const eventFilePromise = new Promise<void>((resolve) => {
triggerFile = resolve
})
const eventFolderPromise = new Promise<void>((resolve) => {
triggerFolder = resolve
})

const expectedFileName = `${nestedDirname}/${filename}`
const handle = await sandbox.files.watchDir(dirname, async (event) => {
if (event.type === FilesystemEventType.WRITE && event.name === expectedFileName) {
triggerFile()
} else if (event.type === FilesystemEventType.CREATE && event.name === nestedDirname) {
triggerFolder()
}
}, {
recursive: true
})

await sandbox.files.makeDir(`${dirname}/${nestedDirname}`)
await eventFolderPromise

await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content)
await eventFilePromise

await handle.stop()
})

dobrac marked this conversation as resolved.
Show resolved Hide resolved
sandboxTest('watch non-existing directory', async ({ sandbox }) => {
const dirname = 'non_existing_watch_dir'

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading