@vaultgradient/pipequery-cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/loader-watches.test.ts +125 -0
- package/dist/index.js +2 -1
- package/package.json +1 -1
- package/src/config/loader.ts +1 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the v0.6.0 watches-loader bug:
|
|
3
|
+
*
|
|
4
|
+
* loadConfig() built the typed PipeQueryConfig without copying raw.watches,
|
|
5
|
+
* so config.watches was always undefined. That silently broke three things:
|
|
6
|
+
* 1. pq watch list always reported "no watches"
|
|
7
|
+
* 2. pq watch add overwrote previous watches (load → empty → save → only the new one)
|
|
8
|
+
* 3. WatchManager never started in pq serve (the `if (config.watches && ...)` was always false)
|
|
9
|
+
*
|
|
10
|
+
* The fix is one line in loader.ts. These tests cover the round-trip and the
|
|
11
|
+
* additive save+reload behaviour so the regression can't sneak back.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
17
|
+
import { writeYaml } from '../src/utils/yaml.js';
|
|
18
|
+
import { loadConfig, saveConfig } from '../src/config/loader.js';
|
|
19
|
+
import type { PipeQueryConfig, WatchConfig } from '../src/config/schema.js';
|
|
20
|
+
|
|
21
|
+
describe('config loader — watches', () => {
|
|
22
|
+
let dir: string;
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
dir = await mkdtemp(join(tmpdir(), 'pq-loader-test-'));
|
|
25
|
+
});
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
await rm(dir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('preserves watches block from yaml on load', async () => {
|
|
31
|
+
const yamlPath = join(dir, 'pipequery.yaml');
|
|
32
|
+
const watch: WatchConfig = {
|
|
33
|
+
query: 'crypto | first(1)',
|
|
34
|
+
interval: '30s',
|
|
35
|
+
fireWhen: 'on_change',
|
|
36
|
+
notify: { telegram: { chatId: 123456789 } },
|
|
37
|
+
};
|
|
38
|
+
await writeYaml(yamlPath, {
|
|
39
|
+
sources: {},
|
|
40
|
+
endpoints: {},
|
|
41
|
+
watches: { foo: watch },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const { config } = await loadConfig(dir);
|
|
45
|
+
|
|
46
|
+
expect(config.watches).toBeDefined();
|
|
47
|
+
expect(Object.keys(config.watches!)).toEqual(['foo']);
|
|
48
|
+
expect(config.watches!.foo.query).toBe('crypto | first(1)');
|
|
49
|
+
expect(config.watches!.foo.fireWhen).toBe('on_change');
|
|
50
|
+
expect(config.watches!.foo.notify.telegram?.chatId).toBe(123456789);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('save+reload keeps existing watches when adding a new one (no clobber)', async () => {
|
|
54
|
+
// Mirrors the user-reported reproduction: two consecutive `pq watch add`
|
|
55
|
+
// invocations should both end up in the yaml.
|
|
56
|
+
const yamlPath = join(dir, 'pipequery.yaml');
|
|
57
|
+
await writeYaml(yamlPath, {
|
|
58
|
+
sources: {},
|
|
59
|
+
endpoints: {},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// First add — load (empty), append `foo`, save.
|
|
63
|
+
const first = await loadConfig(dir);
|
|
64
|
+
first.config.watches = first.config.watches ?? {};
|
|
65
|
+
first.config.watches.foo = {
|
|
66
|
+
query: 'a | first(1)',
|
|
67
|
+
notify: { telegram: { chatId: 1 } },
|
|
68
|
+
};
|
|
69
|
+
await saveConfig(first.config, first.path);
|
|
70
|
+
|
|
71
|
+
// Second add — load (must include foo!), append `bar`, save.
|
|
72
|
+
const second = await loadConfig(dir);
|
|
73
|
+
second.config.watches = second.config.watches ?? {};
|
|
74
|
+
second.config.watches.bar = {
|
|
75
|
+
query: 'b | first(1)',
|
|
76
|
+
notify: { telegram: { chatId: 2 } },
|
|
77
|
+
};
|
|
78
|
+
await saveConfig(second.config, second.path);
|
|
79
|
+
|
|
80
|
+
// Reload and verify both watches survived.
|
|
81
|
+
const final = await loadConfig(dir);
|
|
82
|
+
expect(Object.keys(final.config.watches ?? {}).sort()).toEqual(['bar', 'foo']);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns an empty watches object when the yaml has no watches block', async () => {
|
|
86
|
+
// Defaulting to {} (rather than undefined) keeps the call sites in
|
|
87
|
+
// watch.ts simple — they can write `config.watches[name] = ...` without
|
|
88
|
+
// a nullish-fallback dance.
|
|
89
|
+
const yamlPath = join(dir, 'pipequery.yaml');
|
|
90
|
+
await writeYaml(yamlPath, {
|
|
91
|
+
sources: {},
|
|
92
|
+
endpoints: {},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const { config } = await loadConfig(dir);
|
|
96
|
+
expect(config.watches).toEqual({});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('round-trips a watches block through save → reload unchanged', async () => {
|
|
100
|
+
const yamlPath = join(dir, 'pipequery.yaml');
|
|
101
|
+
const original: PipeQueryConfig = {
|
|
102
|
+
server: { port: 3000, host: '127.0.0.1' },
|
|
103
|
+
sources: {},
|
|
104
|
+
endpoints: {},
|
|
105
|
+
dashboards: {},
|
|
106
|
+
watches: {
|
|
107
|
+
big: {
|
|
108
|
+
query: 'events | where(severity == "high")',
|
|
109
|
+
interval: '60s',
|
|
110
|
+
fireWhen: 'when_non_empty',
|
|
111
|
+
notify: {
|
|
112
|
+
telegram: {
|
|
113
|
+
chatId: -1001234567890,
|
|
114
|
+
message: '🚨 high severity: {{ .msg }}',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
await saveConfig(original, yamlPath);
|
|
121
|
+
|
|
122
|
+
const { config } = await loadConfig(dir);
|
|
123
|
+
expect(config.watches).toEqual(original.watches);
|
|
124
|
+
});
|
|
125
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -123,7 +123,8 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
123
123
|
remote: raw.remote,
|
|
124
124
|
sources: raw.sources ?? {},
|
|
125
125
|
endpoints: raw.endpoints ?? {},
|
|
126
|
-
dashboards: raw.dashboards ?? {}
|
|
126
|
+
dashboards: raw.dashboards ?? {},
|
|
127
|
+
watches: raw.watches ?? {}
|
|
127
128
|
};
|
|
128
129
|
return { config, path: configPath };
|
|
129
130
|
}
|
package/package.json
CHANGED
package/src/config/loader.ts
CHANGED
|
@@ -27,6 +27,7 @@ export async function loadConfig(cwd = process.cwd()): Promise<{ config: PipeQue
|
|
|
27
27
|
sources: raw.sources ?? {},
|
|
28
28
|
endpoints: raw.endpoints ?? {},
|
|
29
29
|
dashboards: raw.dashboards ?? {},
|
|
30
|
+
watches: raw.watches ?? {},
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
return { config, path: configPath };
|