-
Notifications
You must be signed in to change notification settings - Fork 233
nx console only daemon watcher #2851
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
base: master
Are you sure you want to change the base?
Conversation
MaxKless
commented
Oct 29, 2025
- basic start: remove old watchers & show warning
- also mention daemon not running in error
- implement alongside other listeners
- emit event when watcher gives up
- restart daemon watcher from button
- small fixes
|
View your CI Pipeline Execution ↗ for commit c062131
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nx Cloud is proposing a fix for your failed CI:
These changes fix the format:check failure by applying Prettier formatting to the newly added passive-daemon-watcher.spec.ts file. The format command automatically corrected the formatting issues in the test file to comply with the project's Prettier configuration.
We verified this fix by re-running nx-cloud record -- yarn nx format:check --verbose.
Suggested Fix changes
diff --git a/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts b/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
index 0db0b901..c7b5dbe6 100644
--- a/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
+++ b/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
@@ -110,239 +110,207 @@ describe('PassiveDaemonWatcher', () => {
});
describe('Retry on Registration Failure', () => {
- it(
- 'should retry registration if getNxDaemonClient fails',
- async () => {
- (getNxDaemonClient as jest.Mock)
- .mockRejectedValueOnce(new Error('Failed'))
- .mockRejectedValueOnce(new Error('Failed'))
- .mockResolvedValueOnce({ daemonClient: mockDaemonClient });
+ it('should retry registration if getNxDaemonClient fails', async () => {
+ (getNxDaemonClient as jest.Mock)
+ .mockRejectedValueOnce(new Error('Failed'))
+ .mockRejectedValueOnce(new Error('Failed'))
+ .mockResolvedValueOnce({ daemonClient: mockDaemonClient });
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- const listener = jest.fn();
-
- watcher.listen(listener);
- watcher.start();
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ const listener = jest.fn();
- await flushPromises();
+ watcher.listen(listener);
+ watcher.start();
- expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
+ await flushPromises();
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
- expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- await new Promise((resolve) => setTimeout(resolve, 5500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
- expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
+ await new Promise((resolve) => setTimeout(resolve, 5500));
- const mockData = {
- projectGraph: { nodes: {}, dependencies: {} },
- sourceMaps: {},
- };
- capturedDaemonCallback(null, mockData);
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
- expect(listener).toHaveBeenCalledWith(null, mockData);
+ const mockData = {
+ projectGraph: { nodes: {}, dependencies: {} },
+ sourceMaps: {},
+ };
+ capturedDaemonCallback(null, mockData);
- watcher.dispose();
- },
- 15000,
- );
+ expect(listener).toHaveBeenCalledWith(null, mockData);
- it(
- 'should retry registration if daemon.enabled() returns false',
- async () => {
- mockDaemonClient.enabled
- .mockReturnValueOnce(false)
- .mockReturnValueOnce(true);
+ watcher.dispose();
+ }, 15000);
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ it('should retry registration if daemon.enabled() returns false', async () => {
+ mockDaemonClient.enabled
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(true);
- await flushPromises();
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(0);
+ await flushPromises();
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(0);
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(1);
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- watcher.dispose();
- },
- 10000,
- );
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(1);
- it(
- 'should retry registration if registerProjectGraphRecomputationListener throws',
- async () => {
- mockDaemonClient.registerProjectGraphRecomputationListener
- .mockImplementationOnce(() => {
- throw new Error('Registration failed');
- })
- .mockImplementationOnce((callback: any) => {
- capturedDaemonCallback = callback;
- return mockUnregister;
- });
+ watcher.dispose();
+ }, 10000);
+
+ it('should retry registration if registerProjectGraphRecomputationListener throws', async () => {
+ mockDaemonClient.registerProjectGraphRecomputationListener
+ .mockImplementationOnce(() => {
+ throw new Error('Registration failed');
+ })
+ .mockImplementationOnce((callback: any) => {
+ capturedDaemonCallback = callback;
+ return mockUnregister;
+ });
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- await flushPromises();
+ await flushPromises();
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(1);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(1);
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(2);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(2);
- watcher.dispose();
- },
- 10000,
- );
+ watcher.dispose();
+ }, 10000);
});
describe('Retry on Listener Error', () => {
- it(
- 'should retry when daemon listener callback receives an error',
- async () => {
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ it('should retry when daemon listener callback receives an error', async () => {
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- await flushPromises();
+ await flushPromises();
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(1);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(1);
- capturedDaemonCallback(new Error('Daemon error'));
+ capturedDaemonCallback(new Error('Daemon error'));
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(2);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(2);
- watcher.dispose();
- },
- 10000,
- );
+ watcher.dispose();
+ }, 10000);
- it(
- 'should retry when daemon listener callback receives "closed"',
- async () => {
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ it('should retry when daemon listener callback receives "closed"', async () => {
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- await flushPromises();
+ await flushPromises();
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(1);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(1);
- capturedDaemonCallback('closed');
+ capturedDaemonCallback('closed');
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(2);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(2);
- watcher.dispose();
- },
- 10000,
- );
+ watcher.dispose();
+ }, 10000);
- it(
- 'should notify listeners of error before retrying',
- async () => {
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- const listener = jest.fn();
+ it('should notify listeners of error before retrying', async () => {
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ const listener = jest.fn();
- watcher.listen(listener);
- watcher.start();
+ watcher.listen(listener);
+ watcher.start();
- await flushPromises();
+ await flushPromises();
- const error = new Error('Daemon error');
- capturedDaemonCallback(error);
+ const error = new Error('Daemon error');
+ capturedDaemonCallback(error);
- expect(listener).toHaveBeenCalledWith(error);
+ expect(listener).toHaveBeenCalledWith(error);
- await new Promise((resolve) => setTimeout(resolve, 2500));
+ await new Promise((resolve) => setTimeout(resolve, 2500));
- expect(
- mockDaemonClient.registerProjectGraphRecomputationListener,
- ).toHaveBeenCalledTimes(2);
+ expect(
+ mockDaemonClient.registerProjectGraphRecomputationListener,
+ ).toHaveBeenCalledTimes(2);
- watcher.dispose();
- },
- 10000,
- );
+ watcher.dispose();
+ }, 10000);
});
describe('Exponential Backoff', () => {
- it(
- 'should use exponential backoff for retries (2s, 5s, 10s, 20s)',
- async () => {
- (getNxDaemonClient as jest.Mock).mockRejectedValue(
- new Error('Always fails'),
- );
+ it('should use exponential backoff for retries (2s, 5s, 10s, 20s)', async () => {
+ (getNxDaemonClient as jest.Mock).mockRejectedValue(
+ new Error('Always fails'),
+ );
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- await flushPromises();
- expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
+ await flushPromises();
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
- await new Promise((resolve) => setTimeout(resolve, 2500));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
+ await new Promise((resolve) => setTimeout(resolve, 2500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
- await new Promise((resolve) => setTimeout(resolve, 5500));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
+ await new Promise((resolve) => setTimeout(resolve, 5500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
- await new Promise((resolve) => setTimeout(resolve, 10500));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(4);
+ await new Promise((resolve) => setTimeout(resolve, 10500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(4);
- await new Promise((resolve) => setTimeout(resolve, 20500));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+ await new Promise((resolve) => setTimeout(resolve, 20500));
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
- watcher.dispose();
- },
- 50000,
- );
+ watcher.dispose();
+ }, 50000);
- it(
- 'should stop retrying after 4 failed attempts',
- async () => {
- (getNxDaemonClient as jest.Mock).mockRejectedValue(
- new Error('Always fails'),
- );
+ it('should stop retrying after 4 failed attempts', async () => {
+ (getNxDaemonClient as jest.Mock).mockRejectedValue(
+ new Error('Always fails'),
+ );
- const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
- watcher.start();
+ const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+ watcher.start();
- await flushPromises();
+ await flushPromises();
- await new Promise((resolve) => setTimeout(resolve, 40000));
+ await new Promise((resolve) => setTimeout(resolve, 40000));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
- await new Promise((resolve) => setTimeout(resolve, 5000));
+ await new Promise((resolve) => setTimeout(resolve, 5000));
- expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+ expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
- watcher.dispose();
- },
- 50000,
- );
+ watcher.dispose();
+ }, 50000);
});
describe('Cleanup', () => {
@@ -454,35 +422,31 @@ describe('PassiveDaemonWatcher', () => {
watcher.dispose();
});
- it(
- 'should call callback with false when permanently failed',
- async () => {
- const onOperationalStateChange = jest.fn();
- (getNxDaemonClient as jest.Mock).mockRejectedValue(
- new Error('Always fails'),
- );
-
- const watcher = new PassiveDaemonWatcher(
- '/workspace',
- mockLogger,
- onOperationalStateChange,
- );
-
- watcher.start();
- await flushPromises();
-
- await new Promise((resolve) => setTimeout(resolve, 45000));
- await flushPromises();
-
- const falseCalls = onOperationalStateChange.mock.calls.filter(
- (call) => call[0] === false,
- );
- expect(falseCalls.length).toBeGreaterThan(0);
-
- watcher.dispose();
- },
- 50000,
- );
+ it('should call callback with false when permanently failed', async () => {
+ const onOperationalStateChange = jest.fn();
+ (getNxDaemonClient as jest.Mock).mockRejectedValue(
+ new Error('Always fails'),
+ );
+
+ const watcher = new PassiveDaemonWatcher(
+ '/workspace',
+ mockLogger,
+ onOperationalStateChange,
+ );
+
+ watcher.start();
+ await flushPromises();
+
+ await new Promise((resolve) => setTimeout(resolve, 45000));
+ await flushPromises();
+
+ const falseCalls = onOperationalStateChange.mock.calls.filter(
+ (call) => call[0] === false,
+ );
+ expect(falseCalls.length).toBeGreaterThan(0);
+
+ watcher.dispose();
+ }, 50000);
it('should call callback with true after stop', async () => {
const onOperationalStateChange = jest.fn();
Or Apply changes locally with:
npx nx-cloud apply-locally ZpZ6-b7po
Apply fix locally with your editor ↗ View interactive diff ↗
🎓 To learn more about Self Healing CI, please visit nx.dev