@liquidmetal-ai/raindrop 0.9.6 → 0.10.0
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/README.md +86 -86
- package/bundle/{chunk-AWVSG5HH.js → chunk-2GD7Q3YS.js} +1 -1
- package/bundle/{chunk-FYMUM7KM.js → chunk-2QH4PO44.js} +1 -1
- package/bundle/{chunk-2KEFV5U5.js → chunk-3DZWEZ2R.js} +1 -1
- package/bundle/{chunk-B4MQISI4.js → chunk-45V47YNS.js} +4 -4
- package/bundle/{chunk-ILG4QJPW.js → chunk-4FXE4I6S.js} +2 -2
- package/bundle/{chunk-43UGYZQA.js → chunk-4RN4TIJI.js} +1 -1
- package/bundle/{chunk-OHP3SQGY.js → chunk-4U2CSHCN.js} +1 -1
- package/bundle/{chunk-T3VHOMDJ.js → chunk-4UUTGMDZ.js} +1 -1
- package/bundle/{chunk-Z2VVSBIX.js → chunk-4WNBHWJP.js} +4 -4
- package/bundle/{chunk-CEFC4JXX.js → chunk-5KMI72AS.js} +1 -1
- package/bundle/{chunk-GBFY2PMX.js → chunk-5REFCZTG.js} +2 -2
- package/bundle/chunk-6BCRCAR4.js +22452 -0
- package/bundle/{chunk-YWKX37S2.js → chunk-7GDFE7HE.js} +3 -1
- package/bundle/{chunk-4NTUYQ5R.js → chunk-AI5PKHR7.js} +1 -1
- package/bundle/chunk-D5MXH3QV.js +133 -0
- package/bundle/chunk-ETDAUGBG.js +74 -0
- package/bundle/chunk-GBUNP7OT.js +4539 -0
- package/bundle/chunk-GRUPCK5H.js +384 -0
- package/bundle/chunk-JENN4EVA.js +75 -0
- package/bundle/chunk-JZYHHRGL.js +292 -0
- package/bundle/chunk-LJKAU7FY.js +4486 -0
- package/bundle/{chunk-E7J2LWQ5.js → chunk-MJZG4ABT.js} +1 -1
- package/bundle/chunk-MZZHXHB4.js +805 -0
- package/bundle/{chunk-B6JLI47W.js → chunk-ORUMKXQZ.js} +2 -2
- package/bundle/chunk-PMHLHYMI.js +231 -0
- package/bundle/chunk-QRJUX37T.js +48 -0
- package/bundle/chunk-QTCJ6YYG.js +147 -0
- package/bundle/chunk-RAPSKVRO.js +12148 -0
- package/bundle/chunk-U7NHRBYD.js +502 -0
- package/bundle/chunk-WDR5M2SS.js +238746 -0
- package/bundle/{chunk-SPBJ5BNI.js → chunk-YXJU5KSB.js} +1 -1
- package/bundle/chunk-ZQIPU6IX.js +44 -0
- package/bundle/commands/annotation/get.js +3 -3
- package/bundle/commands/annotation/list.js +3 -3
- package/bundle/commands/annotation/put.js +3 -3
- package/bundle/commands/auth/list.js +2 -2
- package/bundle/commands/auth/login.js +3 -3
- package/bundle/commands/auth/logout.js +2 -2
- package/bundle/commands/auth/select.js +3 -3
- package/bundle/commands/bucket/create-credential.js +2 -2
- package/bundle/commands/bucket/delete-credential.js +2 -2
- package/bundle/commands/bucket/get-credential.js +2 -2
- package/bundle/commands/bucket/list-credentials.js +2 -2
- package/bundle/commands/build/branch.js +9 -9
- package/bundle/commands/build/checkout.js +6 -6
- package/bundle/commands/build/clone.js +7 -7
- package/bundle/commands/build/delete.js +6 -6
- package/bundle/commands/build/deploy.js +9 -9
- package/bundle/commands/build/env/get.js +2 -2
- package/bundle/commands/build/env/set.js +2 -2
- package/bundle/commands/build/find.js +4 -4
- package/bundle/commands/build/generate.js +3 -3
- package/bundle/commands/build/init-workspace.js +3 -3
- package/bundle/commands/build/init.js +3 -3
- package/bundle/commands/build/list.js +5 -5
- package/bundle/commands/build/sandbox.js +7 -7
- package/bundle/commands/build/start.js +2 -2
- package/bundle/commands/build/status.js +5 -5
- package/bundle/commands/build/stop.js +2 -2
- package/bundle/commands/build/tools/check.js +2 -2
- package/bundle/commands/build/tools/fmt.js +2 -2
- package/bundle/commands/build/unsandbox.js +7 -7
- package/bundle/commands/build/upload.js +5 -5
- package/bundle/commands/build/validate.js +4 -4
- package/bundle/commands/build/workos/delete.js +4 -4
- package/bundle/commands/build/workos/env/attach.js +3 -3
- package/bundle/commands/build/workos/env/create.js +3 -3
- package/bundle/commands/build/workos/env/delete.js +3 -3
- package/bundle/commands/build/workos/env/detach.js +3 -3
- package/bundle/commands/build/workos/env/get.js +3 -3
- package/bundle/commands/build/workos/env/list.js +3 -3
- package/bundle/commands/build/workos/env/set.js +3 -3
- package/bundle/commands/build/workos/invite.js +3 -3
- package/bundle/commands/build/workos/setup.js +3 -3
- package/bundle/commands/build/workos/status.js +3 -3
- package/bundle/commands/dns/create.js +2 -2
- package/bundle/commands/dns/delete.js +4 -4
- package/bundle/commands/dns/get.js +4 -4
- package/bundle/commands/dns/list.js +3 -3
- package/bundle/commands/dns/records/create.js +2 -2
- package/bundle/commands/dns/records/delete.js +3 -3
- package/bundle/commands/dns/records/get.js +2 -2
- package/bundle/commands/dns/records/list.js +2 -2
- package/bundle/commands/dns/records/update.js +2 -2
- package/bundle/commands/logs/query.js +3 -3
- package/bundle/commands/logs/tail.js +3 -3
- package/bundle/commands/mcp/install-claude.js +2 -2
- package/bundle/commands/mcp/install-gemini.js +2 -2
- package/bundle/commands/mcp/install-goose.js +2 -2
- package/bundle/commands/mcp/status.js +2 -2
- package/bundle/commands/object/delete.js +2 -2
- package/bundle/commands/object/get.js +2 -2
- package/bundle/commands/object/list.js +73 -7
- package/bundle/commands/object/put.js +2 -2
- package/bundle/commands/query/chunk-search.js +3 -3
- package/bundle/commands/query/document.js +3 -3
- package/bundle/commands/query/events.js +2 -2
- package/bundle/commands/query/reindex.js +2 -2
- package/bundle/commands/query/search.js +3 -3
- package/bundle/commands/tail.js +2 -2
- package/bundle/index.js +1 -1
- package/dist/build.test.d.ts +2 -0
- package/dist/build.test.d.ts.map +1 -0
- package/dist/build.test.js +46 -0
- package/dist/codegen.test.d.ts +2 -0
- package/dist/codegen.test.d.ts.map +1 -0
- package/dist/codegen.test.js +223 -0
- package/dist/commands/logs/tail.test.d.ts +2 -0
- package/dist/commands/logs/tail.test.d.ts.map +1 -0
- package/dist/commands/logs/tail.test.js +366 -0
- package/dist/commands/object/list.d.ts.map +1 -1
- package/dist/commands/object/list.js +91 -4
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +27 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +56 -0
- package/oclif.manifest.json +2569 -2569
- package/package.json +3 -3
- package/templates/db/node_modules/.bin/prisma +2 -2
- package/templates/db/node_modules/.bin/prisma-kysely +2 -2
- package/templates/db/node_modules/.bin/tsc +3 -7
- package/templates/db/node_modules/.bin/tsserver +3 -7
- package/templates/db/node_modules/.bin/zx +2 -2
- package/templates/init/RAINDROP.md.hbs +3 -3
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { create } from '@bufbuild/protobuf';
|
|
2
|
+
import { QueryLogsResponseSchema, StreamLogsResponseSchema } from '@liquidmetal-ai/drizzle/liquidmetal/v1alpha1/riverjack_pb';
|
|
3
|
+
import { expect, test, vi, describe, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import Tail from './tail.js';
|
|
5
|
+
// Mock valueOf function
|
|
6
|
+
vi.mock('@liquidmetal-ai/drizzle/appify/build', () => ({
|
|
7
|
+
valueOf: (obj) => obj?.value || obj,
|
|
8
|
+
}));
|
|
9
|
+
// Mock the base command dependencies
|
|
10
|
+
vi.mock('../../base-command.js', () => {
|
|
11
|
+
return {
|
|
12
|
+
BaseCommand: class {
|
|
13
|
+
static HIDDEN_FLAGS = {};
|
|
14
|
+
flags = {};
|
|
15
|
+
log = vi.fn();
|
|
16
|
+
error = vi.fn((msg, options) => {
|
|
17
|
+
if (options?.exit) {
|
|
18
|
+
throw new Error(msg);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
async loadManifest() {
|
|
22
|
+
return [{ name: { value: 'test-app' } }];
|
|
23
|
+
}
|
|
24
|
+
async loadConfig() {
|
|
25
|
+
return { versionId: 'test-version-123' };
|
|
26
|
+
}
|
|
27
|
+
async tenantRiverjackService() {
|
|
28
|
+
return {
|
|
29
|
+
userId: 'test-user',
|
|
30
|
+
organizationId: 'test-org',
|
|
31
|
+
client: this.mockRiverjackClient,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
mockRiverjackClient = {
|
|
35
|
+
queryLogs: vi.fn(),
|
|
36
|
+
streamLogs: vi.fn(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
// Mock the log helpers
|
|
42
|
+
vi.mock('../../log-helpers.js', () => ({
|
|
43
|
+
displayTraceGroupedEvents: vi.fn(),
|
|
44
|
+
}));
|
|
45
|
+
describe('Tail command', () => {
|
|
46
|
+
let tail;
|
|
47
|
+
let mockQueryLogs;
|
|
48
|
+
let mockStreamLogs;
|
|
49
|
+
let logSpy;
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
tail = new Tail([], {});
|
|
52
|
+
logSpy = vi.spyOn(tail, 'log');
|
|
53
|
+
// Access the mock client through the instance
|
|
54
|
+
const mockClient = tail.mockRiverjackClient;
|
|
55
|
+
mockQueryLogs = mockClient.queryLogs;
|
|
56
|
+
mockStreamLogs = mockClient.streamLogs;
|
|
57
|
+
// Reset mocks
|
|
58
|
+
vi.clearAllMocks();
|
|
59
|
+
});
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
vi.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
describe('GNU tail behavior', () => {
|
|
64
|
+
test('default behavior: shows 10 logs and exits (no streaming)', async () => {
|
|
65
|
+
// Setup mock response
|
|
66
|
+
const mockEvents = [
|
|
67
|
+
{ traceId: '1', message: 'log 1' },
|
|
68
|
+
{ traceId: '2', message: 'log 2' },
|
|
69
|
+
];
|
|
70
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
71
|
+
events: mockEvents,
|
|
72
|
+
totalCount: BigInt(2),
|
|
73
|
+
hasMore: false,
|
|
74
|
+
}));
|
|
75
|
+
// Set no flags (default behavior)
|
|
76
|
+
tail.flags = {
|
|
77
|
+
application: 'test-app',
|
|
78
|
+
output: 'text',
|
|
79
|
+
};
|
|
80
|
+
await tail.run();
|
|
81
|
+
// Should query for 10 logs (default)
|
|
82
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
83
|
+
limit: 10,
|
|
84
|
+
organizationId: 'test-org',
|
|
85
|
+
userId: 'test-user',
|
|
86
|
+
applicationName: 'test-app',
|
|
87
|
+
applicationVersionId: 'test-version-123',
|
|
88
|
+
}));
|
|
89
|
+
// Should NOT start streaming
|
|
90
|
+
expect(mockStreamLogs).not.toHaveBeenCalled();
|
|
91
|
+
// Should show appropriate messages
|
|
92
|
+
expect(logSpy).toHaveBeenCalledWith('Using organization: test-org');
|
|
93
|
+
expect(logSpy).toHaveBeenCalledWith('Using user: test-user');
|
|
94
|
+
expect(logSpy).toHaveBeenCalledWith('Showing last 10 logs for test-app@test-version-123...');
|
|
95
|
+
expect(logSpy).toHaveBeenCalledWith('📜 Showing last 2 log events:');
|
|
96
|
+
// Should NOT show streaming messages
|
|
97
|
+
expect(logSpy).not.toHaveBeenCalledWith(expect.stringContaining('Press Ctrl+C'));
|
|
98
|
+
expect(logSpy).not.toHaveBeenCalledWith(expect.stringContaining('Live stream starting'));
|
|
99
|
+
});
|
|
100
|
+
test('with -n flag: shows N logs and exits', async () => {
|
|
101
|
+
const mockEvents = Array(5).fill(null).map((_, i) => ({
|
|
102
|
+
traceId: `${i}`,
|
|
103
|
+
message: `log ${i}`,
|
|
104
|
+
}));
|
|
105
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
106
|
+
events: mockEvents,
|
|
107
|
+
totalCount: BigInt(5),
|
|
108
|
+
hasMore: false,
|
|
109
|
+
}));
|
|
110
|
+
tail.flags = {
|
|
111
|
+
application: 'test-app',
|
|
112
|
+
output: 'text',
|
|
113
|
+
lines: 25,
|
|
114
|
+
};
|
|
115
|
+
await tail.run();
|
|
116
|
+
// Should query for 25 logs
|
|
117
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
118
|
+
limit: 25,
|
|
119
|
+
}));
|
|
120
|
+
// Should NOT stream
|
|
121
|
+
expect(mockStreamLogs).not.toHaveBeenCalled();
|
|
122
|
+
// Should show correct message
|
|
123
|
+
expect(logSpy).toHaveBeenCalledWith('Showing last 25 logs for test-app@test-version-123...');
|
|
124
|
+
});
|
|
125
|
+
test('with -f flag: shows 10 logs then streams', async () => {
|
|
126
|
+
const mockEvents = [{ traceId: '1', message: 'log 1' }];
|
|
127
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
128
|
+
events: mockEvents,
|
|
129
|
+
totalCount: BigInt(1),
|
|
130
|
+
hasMore: false,
|
|
131
|
+
}));
|
|
132
|
+
// Mock async generator for streaming
|
|
133
|
+
const mockStream = async function* () {
|
|
134
|
+
yield create(StreamLogsResponseSchema, {
|
|
135
|
+
responseType: 'stream_started',
|
|
136
|
+
message: 'Stream started',
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
mockStreamLogs.mockReturnValue(mockStream());
|
|
140
|
+
tail.flags = {
|
|
141
|
+
application: 'test-app',
|
|
142
|
+
output: 'text',
|
|
143
|
+
follow: true,
|
|
144
|
+
};
|
|
145
|
+
await tail.run();
|
|
146
|
+
// Should query for 10 logs (default)
|
|
147
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
148
|
+
limit: 10,
|
|
149
|
+
}));
|
|
150
|
+
// Should start streaming
|
|
151
|
+
expect(mockStreamLogs).toHaveBeenCalled();
|
|
152
|
+
// Should show appropriate messages
|
|
153
|
+
expect(logSpy).toHaveBeenCalledWith('Following logs for test-app@test-version-123...');
|
|
154
|
+
expect(logSpy).toHaveBeenCalledWith('Press Ctrl+C to stop following\n');
|
|
155
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Live stream starting'));
|
|
156
|
+
});
|
|
157
|
+
test('with -n and -f flags: shows N logs then streams', async () => {
|
|
158
|
+
const mockEvents = Array(30).fill(null).map((_, i) => ({
|
|
159
|
+
traceId: `${i}`,
|
|
160
|
+
message: `log ${i}`,
|
|
161
|
+
}));
|
|
162
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
163
|
+
events: mockEvents,
|
|
164
|
+
totalCount: BigInt(30),
|
|
165
|
+
hasMore: false,
|
|
166
|
+
}));
|
|
167
|
+
const mockStream = async function* () {
|
|
168
|
+
yield create(StreamLogsResponseSchema, {
|
|
169
|
+
responseType: 'stream_started',
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
mockStreamLogs.mockReturnValue(mockStream());
|
|
173
|
+
tail.flags = {
|
|
174
|
+
application: 'test-app',
|
|
175
|
+
output: 'text',
|
|
176
|
+
lines: 30,
|
|
177
|
+
follow: true,
|
|
178
|
+
};
|
|
179
|
+
await tail.run();
|
|
180
|
+
// Should query for 30 logs
|
|
181
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
182
|
+
limit: 30,
|
|
183
|
+
}));
|
|
184
|
+
// Should start streaming
|
|
185
|
+
expect(mockStreamLogs).toHaveBeenCalled();
|
|
186
|
+
// Should show correct messages
|
|
187
|
+
expect(logSpy).toHaveBeenCalledWith('Following logs for test-app@test-version-123...');
|
|
188
|
+
expect(logSpy).toHaveBeenCalledWith('📜 Showing last 30 log events:');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe('edge cases', () => {
|
|
192
|
+
test('handles no logs found gracefully', async () => {
|
|
193
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
194
|
+
events: [],
|
|
195
|
+
totalCount: BigInt(0),
|
|
196
|
+
hasMore: false,
|
|
197
|
+
}));
|
|
198
|
+
tail.flags = {
|
|
199
|
+
application: 'test-app',
|
|
200
|
+
output: 'text',
|
|
201
|
+
lines: 10,
|
|
202
|
+
};
|
|
203
|
+
await tail.run();
|
|
204
|
+
expect(logSpy).toHaveBeenCalledWith('ℹ️ No recent logs found in the last hour');
|
|
205
|
+
expect(mockStreamLogs).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
test('handles query error gracefully when following', async () => {
|
|
208
|
+
mockQueryLogs.mockRejectedValue(new Error('Query failed'));
|
|
209
|
+
const mockStream = async function* () {
|
|
210
|
+
yield create(StreamLogsResponseSchema, {
|
|
211
|
+
responseType: 'stream_started',
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
mockStreamLogs.mockReturnValue(mockStream());
|
|
215
|
+
tail.flags = {
|
|
216
|
+
application: 'test-app',
|
|
217
|
+
output: 'text',
|
|
218
|
+
follow: true,
|
|
219
|
+
};
|
|
220
|
+
await tail.run();
|
|
221
|
+
// Should show error but continue to stream
|
|
222
|
+
expect(logSpy).toHaveBeenCalledWith('⚠️ Could not fetch recent logs: Query failed');
|
|
223
|
+
expect(logSpy).toHaveBeenCalledWith('🔴 Starting live stream anyway...');
|
|
224
|
+
expect(mockStreamLogs).toHaveBeenCalled();
|
|
225
|
+
});
|
|
226
|
+
test('handles query error gracefully when not following', async () => {
|
|
227
|
+
mockQueryLogs.mockRejectedValue(new Error('Query failed'));
|
|
228
|
+
tail.flags = {
|
|
229
|
+
application: 'test-app',
|
|
230
|
+
output: 'text',
|
|
231
|
+
lines: 10,
|
|
232
|
+
};
|
|
233
|
+
await tail.run();
|
|
234
|
+
// Should show error and exit
|
|
235
|
+
expect(logSpy).toHaveBeenCalledWith('⚠️ Could not fetch recent logs: Query failed');
|
|
236
|
+
expect(mockStreamLogs).not.toHaveBeenCalled();
|
|
237
|
+
});
|
|
238
|
+
test('handles -n 0 correctly (no recent logs)', async () => {
|
|
239
|
+
tail.flags = {
|
|
240
|
+
application: 'test-app',
|
|
241
|
+
output: 'text',
|
|
242
|
+
lines: 0,
|
|
243
|
+
};
|
|
244
|
+
await tail.run();
|
|
245
|
+
// Should not query for logs when lines is 0
|
|
246
|
+
expect(mockQueryLogs).not.toHaveBeenCalled();
|
|
247
|
+
expect(mockStreamLogs).not.toHaveBeenCalled();
|
|
248
|
+
expect(logSpy).toHaveBeenCalledWith('Showing last 0 logs for test-app@test-version-123...');
|
|
249
|
+
});
|
|
250
|
+
test('handles JSON output format', async () => {
|
|
251
|
+
const mockEvents = [
|
|
252
|
+
{ traceId: '1', message: 'log 1' },
|
|
253
|
+
];
|
|
254
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
255
|
+
events: mockEvents,
|
|
256
|
+
totalCount: BigInt(1),
|
|
257
|
+
hasMore: false,
|
|
258
|
+
}));
|
|
259
|
+
tail.flags = {
|
|
260
|
+
application: 'test-app',
|
|
261
|
+
output: 'json',
|
|
262
|
+
lines: 1,
|
|
263
|
+
};
|
|
264
|
+
await tail.run();
|
|
265
|
+
// Should output JSON format
|
|
266
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('['));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('streaming behavior', () => {
|
|
270
|
+
test('handles different stream response types', async () => {
|
|
271
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
272
|
+
events: [],
|
|
273
|
+
totalCount: BigInt(0),
|
|
274
|
+
hasMore: false,
|
|
275
|
+
}));
|
|
276
|
+
const mockStream = async function* () {
|
|
277
|
+
yield create(StreamLogsResponseSchema, {
|
|
278
|
+
responseType: 'stream_started',
|
|
279
|
+
message: 'Started',
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
});
|
|
282
|
+
yield create(StreamLogsResponseSchema, {
|
|
283
|
+
responseType: 'heartbeat',
|
|
284
|
+
});
|
|
285
|
+
yield create(StreamLogsResponseSchema, {
|
|
286
|
+
responseType: 'events',
|
|
287
|
+
events: [{ traceId: '1', message: 'streamed log' }],
|
|
288
|
+
});
|
|
289
|
+
yield create(StreamLogsResponseSchema, {
|
|
290
|
+
responseType: 'stream_ended',
|
|
291
|
+
message: 'Ended',
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
mockStreamLogs.mockReturnValue(mockStream());
|
|
295
|
+
tail.flags = {
|
|
296
|
+
application: 'test-app',
|
|
297
|
+
output: 'text',
|
|
298
|
+
follow: true,
|
|
299
|
+
};
|
|
300
|
+
await tail.run();
|
|
301
|
+
// Check that different response types are handled
|
|
302
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Started streaming logs'));
|
|
303
|
+
expect(logSpy).toHaveBeenCalledWith('✅ Stream ended');
|
|
304
|
+
});
|
|
305
|
+
test('handles stream errors', async () => {
|
|
306
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
307
|
+
events: [],
|
|
308
|
+
totalCount: BigInt(0),
|
|
309
|
+
hasMore: false,
|
|
310
|
+
}));
|
|
311
|
+
// Mock error method to capture the error
|
|
312
|
+
const errorSpy = vi.spyOn(tail, 'error').mockImplementation((msg) => {
|
|
313
|
+
throw new Error(msg);
|
|
314
|
+
});
|
|
315
|
+
const mockStream = async function* () {
|
|
316
|
+
yield create(StreamLogsResponseSchema, {
|
|
317
|
+
responseType: 'service_error',
|
|
318
|
+
message: 'Stream connection failed',
|
|
319
|
+
});
|
|
320
|
+
throw new Error('Stream connection failed');
|
|
321
|
+
};
|
|
322
|
+
mockStreamLogs.mockReturnValue(mockStream());
|
|
323
|
+
tail.flags = {
|
|
324
|
+
application: 'test-app',
|
|
325
|
+
output: 'text',
|
|
326
|
+
follow: true,
|
|
327
|
+
};
|
|
328
|
+
// Should handle the error
|
|
329
|
+
await expect(tail.run()).rejects.toThrow('Failed to tail logs');
|
|
330
|
+
// Restore the mock
|
|
331
|
+
errorSpy.mockRestore();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
describe('configuration', () => {
|
|
335
|
+
test('uses application from manifest when not provided', async () => {
|
|
336
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
337
|
+
events: [],
|
|
338
|
+
totalCount: BigInt(0),
|
|
339
|
+
hasMore: false,
|
|
340
|
+
}));
|
|
341
|
+
tail.flags = {
|
|
342
|
+
output: 'text',
|
|
343
|
+
// No application flag set
|
|
344
|
+
};
|
|
345
|
+
await tail.run();
|
|
346
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
347
|
+
applicationName: 'test-app', // From manifest
|
|
348
|
+
}));
|
|
349
|
+
});
|
|
350
|
+
test('uses provided application over manifest', async () => {
|
|
351
|
+
mockQueryLogs.mockResolvedValue(create(QueryLogsResponseSchema, {
|
|
352
|
+
events: [],
|
|
353
|
+
totalCount: BigInt(0),
|
|
354
|
+
hasMore: false,
|
|
355
|
+
}));
|
|
356
|
+
tail.flags = {
|
|
357
|
+
output: 'text',
|
|
358
|
+
application: 'custom-app',
|
|
359
|
+
};
|
|
360
|
+
await tail.run();
|
|
361
|
+
expect(mockQueryLogs).toHaveBeenCalledWith(expect.objectContaining({
|
|
362
|
+
applicationName: 'custom-app',
|
|
363
|
+
}));
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/object/list.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW,CAAC,OAAO,UAAU,CAAC;IACpE,MAAM,CAAC,WAAW,SAA8B;IAEhD,MAAM,CAAC,QAAQ,WAUb;IAEF,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;MAgDV;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/object/list.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW,CAAC,OAAO,UAAU,CAAC;IACpE,MAAM,CAAC,WAAW,SAA8B;IAEhD,MAAM,CAAC,QAAQ,WAUb;IAEF,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;MAgDV;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA0P3B"}
|
|
@@ -193,25 +193,112 @@ List objects in my-bucket within my-app
|
|
|
193
193
|
console.log('No objects found in bucket');
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
|
+
// Get search agent service for status queries (used by all output formats)
|
|
197
|
+
const { client: searchAgent } = await this.searchAgentService();
|
|
198
|
+
// Query status for all objects using bulk API (significantly more efficient)
|
|
199
|
+
const statusMap = new Map();
|
|
200
|
+
try {
|
|
201
|
+
const bulkStatusResponse = await searchAgent.getDocumentStatusBulk({
|
|
202
|
+
userId,
|
|
203
|
+
organizationId,
|
|
204
|
+
bucketLocation,
|
|
205
|
+
objectIds: response.objects.map(obj => obj.key),
|
|
206
|
+
});
|
|
207
|
+
// Build a map of objectId -> formatted status
|
|
208
|
+
for (const docStatus of bulkStatusResponse.documents) {
|
|
209
|
+
let indexingStatus = 'unknown';
|
|
210
|
+
// Format status based on document state
|
|
211
|
+
if (docStatus.status === 'not_found') {
|
|
212
|
+
indexingStatus = 'not started';
|
|
213
|
+
}
|
|
214
|
+
else if (docStatus.status === 'completed') {
|
|
215
|
+
indexingStatus = 'completed';
|
|
216
|
+
}
|
|
217
|
+
else if (docStatus.status === 'failed') {
|
|
218
|
+
const errorMsg = docStatus.errors.length > 0 ? ` (${docStatus.errors[0]})` : '';
|
|
219
|
+
indexingStatus = `failed${errorMsg}`;
|
|
220
|
+
}
|
|
221
|
+
else if (docStatus.status === 'partial') {
|
|
222
|
+
const errorMsg = docStatus.errors.length > 0 ? ` (${docStatus.errors[0]})` : '';
|
|
223
|
+
indexingStatus = `partial${errorMsg}`;
|
|
224
|
+
}
|
|
225
|
+
else if (docStatus.status === 'ingesting') {
|
|
226
|
+
const chunks = docStatus.ingest?.totalChunksCreated || 0;
|
|
227
|
+
indexingStatus = `ingesting (${chunks} chunks)`;
|
|
228
|
+
}
|
|
229
|
+
else if (docStatus.status === 'processing') {
|
|
230
|
+
// Show progress for most active stage
|
|
231
|
+
const stages = [
|
|
232
|
+
{ name: 'embedding', info: docStatus.embedding },
|
|
233
|
+
{ name: 'vector index', info: docStatus.vectorIndex },
|
|
234
|
+
{ name: 'pii', info: docStatus.pii },
|
|
235
|
+
{ name: 'relationships', info: docStatus.relationships },
|
|
236
|
+
];
|
|
237
|
+
// Find first stage with items remaining
|
|
238
|
+
const activeStage = stages.find(s => s.info && s.info.itemsRemaining > 0);
|
|
239
|
+
if (activeStage && activeStage.info) {
|
|
240
|
+
const progress = activeStage.info.totalExpected - activeStage.info.itemsRemaining;
|
|
241
|
+
const total = activeStage.info.totalExpected;
|
|
242
|
+
indexingStatus = `processing ${activeStage.name} (${progress}/${total})`;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// No active stage found - check if stages have been initialized
|
|
246
|
+
const hasInitializedStages = stages.some(s => s.info && s.info.totalExpected > 0);
|
|
247
|
+
if (!hasInitializedStages) {
|
|
248
|
+
// Stages not initialized yet - show ingest info
|
|
249
|
+
const chunks = docStatus.ingest?.totalChunksCreated || 0;
|
|
250
|
+
const queued = docStatus.ingest?.chunksQueued || 0;
|
|
251
|
+
indexingStatus = `processing (${chunks} chunks created, ${queued} queued)`;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Stages initialized but all complete - shouldn't happen in 'processing' state
|
|
255
|
+
indexingStatus = 'processing';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
indexingStatus = docStatus.status;
|
|
261
|
+
}
|
|
262
|
+
statusMap.set(docStatus.objectId, indexingStatus);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
// If bulk query fails, fall back to marking all as unknown
|
|
267
|
+
console.error('Failed to fetch bulk status:', error);
|
|
268
|
+
}
|
|
269
|
+
// Attach status to each object
|
|
270
|
+
const objectsWithStatus = response.objects.map(obj => ({
|
|
271
|
+
...obj,
|
|
272
|
+
indexingStatus: statusMap.get(obj.key) || 'unknown',
|
|
273
|
+
}));
|
|
196
274
|
switch (this.flags.output) {
|
|
197
|
-
case 'json':
|
|
198
|
-
|
|
275
|
+
case 'json': {
|
|
276
|
+
// Create enhanced JSON response with indexing status
|
|
277
|
+
const jsonResponse = JSON.parse(toJsonString(ListObjectsResponseSchema, response));
|
|
278
|
+
jsonResponse.objects = objectsWithStatus.map((obj, index) => ({
|
|
279
|
+
...jsonResponse.objects[index],
|
|
280
|
+
indexingStatus: obj.indexingStatus,
|
|
281
|
+
}));
|
|
282
|
+
console.log(JSON.stringify(jsonResponse, null, 2));
|
|
199
283
|
break;
|
|
284
|
+
}
|
|
200
285
|
case 'table':
|
|
201
|
-
console.table(
|
|
286
|
+
console.table(objectsWithStatus.map((obj) => ({
|
|
202
287
|
key: obj.key,
|
|
203
288
|
size: obj.size,
|
|
204
289
|
contentType: obj.contentType,
|
|
205
290
|
lastModified: obj.lastModified ? timestampDate(obj.lastModified).toUTCString() : 'N/A',
|
|
291
|
+
indexing: obj.indexingStatus,
|
|
206
292
|
})));
|
|
207
293
|
break;
|
|
208
294
|
default:
|
|
209
|
-
for (const obj of
|
|
295
|
+
for (const obj of objectsWithStatus) {
|
|
210
296
|
const lastModified = obj.lastModified ? timestampDate(obj.lastModified).toUTCString() : 'N/A';
|
|
211
297
|
console.log(`Key: ${obj.key}`);
|
|
212
298
|
console.log(` Size: ${obj.size} bytes`);
|
|
213
299
|
console.log(` Content-Type: ${obj.contentType || 'N/A'}`);
|
|
214
300
|
console.log(` Last Modified: ${lastModified}`);
|
|
301
|
+
console.log(` Indexing: ${obj.indexingStatus}`);
|
|
215
302
|
console.log('---');
|
|
216
303
|
}
|
|
217
304
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { expect, test } from 'vitest';
|
|
5
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
+
test('loading missing config returns default/base config', async () => {
|
|
7
|
+
const configPath = '/tmp/this-never-exists-config.json';
|
|
8
|
+
const config = await loadConfig(configPath);
|
|
9
|
+
expect(config).toEqual({
|
|
10
|
+
versionId: undefined,
|
|
11
|
+
sandbox: false,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
test('save/load config', async () => {
|
|
15
|
+
// Make a temp directory and write a config file to it.
|
|
16
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'config-test'));
|
|
17
|
+
const configPath = path.join(tempDir, 'config.json');
|
|
18
|
+
await saveConfig(configPath, {
|
|
19
|
+
versionId: 'test-version-id',
|
|
20
|
+
sandbox: false,
|
|
21
|
+
});
|
|
22
|
+
const config = await loadConfig(configPath);
|
|
23
|
+
expect(config).toEqual({
|
|
24
|
+
versionId: 'test-version-id',
|
|
25
|
+
sandbox: false,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
// Import the function for testing (we need to export it first)
|
|
4
|
+
// For now, we'll test the logic by creating a local version
|
|
5
|
+
function shouldUpdateCli(cliVersion, serverVersion) {
|
|
6
|
+
try {
|
|
7
|
+
// Use tilde range (~) - allows patch-level changes but requires same major.minor
|
|
8
|
+
const clientSatisfies = semver.satisfies(cliVersion, `~${serverVersion}`);
|
|
9
|
+
return !clientSatisfies;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// If version parsing fails, fall back to simple comparison
|
|
13
|
+
try {
|
|
14
|
+
return semver.lt(cliVersion, serverVersion);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// If all else fails, assume update is needed
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
test('shouldUpdateCli - same major.minor, CLI patch higher', () => {
|
|
23
|
+
expect(shouldUpdateCli('1.2.5', '1.2.3')).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
test('shouldUpdateCli - same major.minor, server patch higher', () => {
|
|
26
|
+
expect(shouldUpdateCli('1.2.3', '1.2.5')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
test('shouldUpdateCli - same major.minor.patch', () => {
|
|
29
|
+
expect(shouldUpdateCli('1.2.3', '1.2.3')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
test('shouldUpdateCli - different major versions, server higher', () => {
|
|
32
|
+
expect(shouldUpdateCli('1.2.3', '2.0.0')).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
test('shouldUpdateCli - different major versions, CLI higher', () => {
|
|
35
|
+
expect(shouldUpdateCli('2.0.0', '1.5.10')).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
test('shouldUpdateCli - different minor versions, server higher', () => {
|
|
38
|
+
expect(shouldUpdateCli('1.2.3', '1.5.0')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
test('shouldUpdateCli - different minor versions, CLI higher', () => {
|
|
41
|
+
expect(shouldUpdateCli('1.5.0', '1.2.10')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
test('shouldUpdateCli - tilde range compatibility', () => {
|
|
44
|
+
// Within tilde range - should not require update
|
|
45
|
+
expect(shouldUpdateCli('1.2.5', '1.2.3')).toBe(false); // CLI 1.2.5 satisfies ~1.2.3 (>=1.2.3 <1.3.0)
|
|
46
|
+
expect(shouldUpdateCli('1.2.3', '1.2.5')).toBe(true); // CLI 1.2.3 does NOT satisfy ~1.2.5 (>=1.2.5 <1.3.0)
|
|
47
|
+
expect(shouldUpdateCli('1.2.3', '1.2.3')).toBe(false); // Same version
|
|
48
|
+
// Outside tilde range - should require update
|
|
49
|
+
expect(shouldUpdateCli('1.2.3', '1.3.0')).toBe(true); // Different minor
|
|
50
|
+
expect(shouldUpdateCli('1.2.3', '2.0.0')).toBe(true); // Different major
|
|
51
|
+
});
|
|
52
|
+
test('shouldUpdateCli - invalid version strings', () => {
|
|
53
|
+
expect(shouldUpdateCli('invalid', '1.2.3')).toBe(true);
|
|
54
|
+
expect(shouldUpdateCli('1.2.3', 'invalid')).toBe(true);
|
|
55
|
+
expect(shouldUpdateCli('invalid', 'invalid')).toBe(true);
|
|
56
|
+
});
|