@liquidmetal-ai/raindrop 0.9.7 → 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-FFKINGT5.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-6VCATADU.js → chunk-4WNBHWJP.js} +4 -4
- package/bundle/{chunk-CEFC4JXX.js → chunk-5KMI72AS.js} +1 -1
- package/bundle/{chunk-2HWWLWLM.js → chunk-5REFCZTG.js} +2 -2
- package/bundle/{chunk-ZEKMKKMJ.js → chunk-6BCRCAR4.js} +1 -1
- package/bundle/{chunk-YWKX37S2.js → chunk-7GDFE7HE.js} +3 -1
- package/bundle/{chunk-FV4QERYA.js → chunk-AI5PKHR7.js} +1 -1
- package/bundle/{chunk-WTTFYUHL.js → chunk-D5MXH3QV.js} +1 -1
- package/bundle/{chunk-GBFY2PMX.js → chunk-ETDAUGBG.js} +2 -2
- package/bundle/{chunk-O75RZNCU.js → chunk-GBUNP7OT.js} +2 -2
- package/bundle/{chunk-Z2VVSBIX.js → chunk-GRUPCK5H.js} +4 -4
- package/bundle/{chunk-XN52RPCA.js → chunk-JENN4EVA.js} +2 -2
- package/bundle/{chunk-KYP4ZWG7.js → chunk-JZYHHRGL.js} +2 -2
- package/bundle/{chunk-VW5YPIKL.js → chunk-LJKAU7FY.js} +30 -2
- package/bundle/{chunk-DSP542FN.js → chunk-MJZG4ABT.js} +1 -1
- package/bundle/{chunk-T7WFDN3I.js → chunk-MZZHXHB4.js} +4 -4
- package/bundle/{chunk-RUORC7A6.js → chunk-ORUMKXQZ.js} +2 -2
- package/bundle/{chunk-B4MQISI4.js → chunk-PMHLHYMI.js} +4 -4
- package/bundle/{chunk-4NTUYQ5R.js → chunk-QRJUX37T.js} +1 -1
- package/bundle/chunk-QTCJ6YYG.js +147 -0
- package/bundle/{chunk-E7J2LWQ5.js → chunk-RAPSKVRO.js} +1 -1
- package/bundle/{chunk-BC3Z5E5X.js → chunk-U7NHRBYD.js} +1 -1
- package/bundle/{chunk-QN2OJEAL.js → chunk-WDR5M2SS.js} +25 -25
- package/bundle/{chunk-SPBJ5BNI.js → chunk-YXJU5KSB.js} +1 -1
- package/bundle/{chunk-6AU7I5YZ.js → chunk-ZQIPU6IX.js} +2 -2
- 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 +2058 -2058
- package/package.json +4 -4
- 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 +26 -22
- package/templates/init/src/_app/cors.ts +5 -4
- package/bundle/chunk-B6JLI47W.js +0 -315
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mustManifestFromString } from '@liquidmetal-ai/drizzle/appify/index';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { expect, test } from 'vitest';
|
|
6
|
+
import { codegenPlan, gatherEnvForHandler, kebabCaseToCamelCase, kebabCaseToConstantCase, kebabCaseToUpperCamelCase, renderTemplatesFromDirectory, shouldWriteTemplate, trimTemplateSuffix, } from './codegen.js';
|
|
7
|
+
test('trims handlebars suffix', async () => {
|
|
8
|
+
expect(trimTemplateSuffix('index.ts.hbs')).toEqual('index.ts');
|
|
9
|
+
});
|
|
10
|
+
test('codegens from blank app', async () => {
|
|
11
|
+
const apps = await mustManifestFromString(`
|
|
12
|
+
application "ingest" {}
|
|
13
|
+
`);
|
|
14
|
+
const plan = codegenPlan(apps);
|
|
15
|
+
expect(Object.keys(plan)).toHaveLength(1);
|
|
16
|
+
expect(plan[0]?.templateName).toMatch('templates/init');
|
|
17
|
+
expect(plan[0]?.outPath).toEqual('');
|
|
18
|
+
});
|
|
19
|
+
test('codegens from app with observer', async () => {
|
|
20
|
+
const apps = await mustManifestFromString(`
|
|
21
|
+
application "ingest" {
|
|
22
|
+
bucket "bucket" {}
|
|
23
|
+
|
|
24
|
+
observer "process" {
|
|
25
|
+
source {
|
|
26
|
+
bucket = "bucket"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`);
|
|
31
|
+
const plan = codegenPlan(apps);
|
|
32
|
+
expect(Object.keys(plan)).toHaveLength(2);
|
|
33
|
+
expect(plan[1]?.templateName).toMatch('templates/handlers/bucket-event-notification');
|
|
34
|
+
expect(plan[1]?.outPath).toEqual('src/process');
|
|
35
|
+
});
|
|
36
|
+
test('codegens from app with service', async () => {
|
|
37
|
+
const apps = await mustManifestFromString(`
|
|
38
|
+
application "ingest" {
|
|
39
|
+
bucket "bucket" {}
|
|
40
|
+
|
|
41
|
+
service "ingester" {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`);
|
|
45
|
+
const plan = codegenPlan(apps);
|
|
46
|
+
expect(Object.keys(plan)).toHaveLength(2);
|
|
47
|
+
expect(plan[1]?.templateName).toMatch('templates/handlers/http-service');
|
|
48
|
+
expect(plan[1]?.outPath).toEqual('src/ingester');
|
|
49
|
+
});
|
|
50
|
+
test('codegens from app with durable object', async () => {
|
|
51
|
+
const apps = await mustManifestFromString(`
|
|
52
|
+
application "ingest" {
|
|
53
|
+
actor "ingest-object" {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
const plan = codegenPlan(apps);
|
|
58
|
+
expect(Object.keys(plan)).toHaveLength(2);
|
|
59
|
+
expect(plan[1]?.templateName).toMatch('templates/handlers/actor');
|
|
60
|
+
expect(plan[1]?.outPath).toEqual('src/ingest-object');
|
|
61
|
+
});
|
|
62
|
+
test('codegens from app with sql database', async () => {
|
|
63
|
+
const apps = await mustManifestFromString(`
|
|
64
|
+
application "ingest" {
|
|
65
|
+
sql_database "demosql" {}
|
|
66
|
+
}
|
|
67
|
+
`);
|
|
68
|
+
const plan = codegenPlan(apps);
|
|
69
|
+
expect(Object.keys(plan)).toHaveLength(6);
|
|
70
|
+
expect(plan[0]?.templateName).toMatch('templates/init');
|
|
71
|
+
expect(plan[0]?.outPath).toEqual('');
|
|
72
|
+
expect(plan[1]?.templateName).toMatch('templates/db/package.json');
|
|
73
|
+
expect(plan[1]?.outPath).toEqual('./');
|
|
74
|
+
expect(plan[2]?.templateName).toMatch('templates/db/db');
|
|
75
|
+
expect(plan[2]?.outPath).toEqual('db/demosql');
|
|
76
|
+
expect(plan[3]?.templateName).toMatch('templates/db/common');
|
|
77
|
+
expect(plan[3]?.outPath).toEqual('src/common');
|
|
78
|
+
expect(plan[4]?.templateName).toMatch('templates/db/prisma');
|
|
79
|
+
expect(plan[4]?.outPath).toEqual('prisma/demosql');
|
|
80
|
+
expect(plan[5]?.templateName).toMatch('templates/db/scripts');
|
|
81
|
+
expect(plan[5]?.outPath).toEqual('scripts');
|
|
82
|
+
});
|
|
83
|
+
test('only codegens scaffolding when enabled', () => {
|
|
84
|
+
expect(shouldWriteTemplate({
|
|
85
|
+
renderScaffoldingCode: true,
|
|
86
|
+
}, 'src/handlers/something/__index.ts')).toBe(true);
|
|
87
|
+
expect(shouldWriteTemplate({
|
|
88
|
+
renderScaffoldingCode: false,
|
|
89
|
+
}, 'src/handlers/something/__index.ts')).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
test('only codegens user modifiable code when enabled', () => {
|
|
92
|
+
expect(shouldWriteTemplate({
|
|
93
|
+
renderUserModifiableCode: true,
|
|
94
|
+
}, 'src/handlers/something/index.ts')).toBe(true);
|
|
95
|
+
expect(shouldWriteTemplate({
|
|
96
|
+
renderUserModifiableCode: false,
|
|
97
|
+
}, 'src/handlers/something/index.ts')).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
test('only codegens non-user modifiable code when enabled', () => {
|
|
100
|
+
expect(shouldWriteTemplate({
|
|
101
|
+
renderNonUserModifiableCode: true,
|
|
102
|
+
}, 'src/handlers/something/env.gen.d.ts')).toBe(true);
|
|
103
|
+
expect(shouldWriteTemplate({
|
|
104
|
+
renderNonUserModifiableCode: false,
|
|
105
|
+
}, 'src/handlers/something/env.gen.d.ts')).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
test('codegens template files', async () => {
|
|
108
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'init');
|
|
109
|
+
const outPath = await fs.mkdtemp(path.join(os.tmpdir(), 'codegen-test'));
|
|
110
|
+
await renderTemplatesFromDirectory({
|
|
111
|
+
templatePath,
|
|
112
|
+
outPath,
|
|
113
|
+
context: {
|
|
114
|
+
toolName: 'raindrop',
|
|
115
|
+
applicationName: 'test-app',
|
|
116
|
+
raindropFrameworkVersion: '0.4.9',
|
|
117
|
+
},
|
|
118
|
+
opts: {
|
|
119
|
+
renderScaffoldingCode: true,
|
|
120
|
+
renderUserModifiableCode: true,
|
|
121
|
+
renderNonUserModifiableCode: true,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
// Verify nested directory structure is preserved
|
|
125
|
+
const authFilePath = path.join(outPath, 'src', '_app', 'auth.ts');
|
|
126
|
+
const authFileExists = await fs.access(authFilePath).then(() => true, () => false);
|
|
127
|
+
expect(authFileExists).toBe(true);
|
|
128
|
+
// Verify the file content was copied correctly
|
|
129
|
+
const authFileContent = await fs.readFile(authFilePath, 'utf-8');
|
|
130
|
+
expect(authFileContent).toContain('verifyIssuer');
|
|
131
|
+
expect(authFileContent).toContain('requireAuthenticated');
|
|
132
|
+
});
|
|
133
|
+
test('kebabCaseToConstantCase', () => {
|
|
134
|
+
expect(kebabCaseToConstantCase('my-variable')).toEqual('MY_VARIABLE');
|
|
135
|
+
});
|
|
136
|
+
test('kebabCaseToCamelCase', () => {
|
|
137
|
+
expect(kebabCaseToCamelCase('my-variable')).toEqual('myVariable');
|
|
138
|
+
});
|
|
139
|
+
test('kebabCaseToUpperCamelCase', () => {
|
|
140
|
+
expect(kebabCaseToUpperCamelCase('my-variable')).toEqual('MyVariable');
|
|
141
|
+
});
|
|
142
|
+
test('generates SmartMemory type bindings', async () => {
|
|
143
|
+
const apps = await mustManifestFromString(`
|
|
144
|
+
application "test-app" {
|
|
145
|
+
smartmemory "user-memory" {}
|
|
146
|
+
|
|
147
|
+
service "test-service" {}
|
|
148
|
+
}
|
|
149
|
+
`);
|
|
150
|
+
const app = apps[0];
|
|
151
|
+
const service = app.service[0];
|
|
152
|
+
const generated = gatherEnvForHandler(service, app);
|
|
153
|
+
// Should include SmartMemory import
|
|
154
|
+
expect(generated).toMatch(/import\s+{[^}]*SmartMemory[^}]*}\s+from\s+'@liquidmetal-ai\/raindrop-framework'/);
|
|
155
|
+
// Should include SmartMemory type binding with constant case naming
|
|
156
|
+
expect(generated).toMatch(/USER_MEMORY:\s*SmartMemory/);
|
|
157
|
+
});
|
|
158
|
+
test('generates SmartBucket type bindings', async () => {
|
|
159
|
+
const apps = await mustManifestFromString(`
|
|
160
|
+
application "test-app" {
|
|
161
|
+
smartbucket "data-bucket" {}
|
|
162
|
+
|
|
163
|
+
service "test-service" {}
|
|
164
|
+
}
|
|
165
|
+
`);
|
|
166
|
+
const app = apps[0];
|
|
167
|
+
const service = app.service[0];
|
|
168
|
+
const generated = gatherEnvForHandler(service, app);
|
|
169
|
+
// Should include SmartBucket import
|
|
170
|
+
expect(generated).toMatch(/import\s+{[^}]*SmartBucket[^}]*}\s+from\s+'@liquidmetal-ai\/raindrop-framework'/);
|
|
171
|
+
// Should include SmartBucket type binding with constant case naming
|
|
172
|
+
expect(generated).toMatch(/DATA_BUCKET:\s*SmartBucket/);
|
|
173
|
+
});
|
|
174
|
+
test('always includes mem KvCache binding', async () => {
|
|
175
|
+
const apps = await mustManifestFromString(`
|
|
176
|
+
application "test-app" {
|
|
177
|
+
service "test-service" {}
|
|
178
|
+
}
|
|
179
|
+
`);
|
|
180
|
+
const app = apps[0];
|
|
181
|
+
const service = app.service[0];
|
|
182
|
+
const generated = gatherEnvForHandler(service, app);
|
|
183
|
+
// Should include KvCache import
|
|
184
|
+
expect(generated).toMatch(/import\s+{[^}]*KvCache[^}]*}\s+from\s+'@liquidmetal-ai\/raindrop-framework'/);
|
|
185
|
+
// Should include mem type binding
|
|
186
|
+
expect(generated).toMatch(/mem:\s*KvCache/);
|
|
187
|
+
});
|
|
188
|
+
test('includes auth env vars for protected service', async () => {
|
|
189
|
+
const apps = await mustManifestFromString(`
|
|
190
|
+
application "test-app" {
|
|
191
|
+
service "protected-service" {
|
|
192
|
+
visibility = "protected"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
`);
|
|
196
|
+
const app = apps[0];
|
|
197
|
+
const service = app.service[0];
|
|
198
|
+
const generated = gatherEnvForHandler(service, app);
|
|
199
|
+
// Should include core auth bindings and WorkOS provider bindings for protected services
|
|
200
|
+
expect(generated).toMatch(/LM_AUTH_ALLOWED_ORIGINS:\s*string/);
|
|
201
|
+
expect(generated).toMatch(/LM_AUTH_ALLOWED_ISSUERS:\s*string/);
|
|
202
|
+
expect(generated).toMatch(/LM_PROVIDER_WORKOS_ORG_ID\?:\s*string/);
|
|
203
|
+
expect(generated).toMatch(/LM_PROVIDER_WORKOS_CLIENT_ID\?:\s*string/);
|
|
204
|
+
expect(generated).toMatch(/LM_PROVIDER_WORKOS_API_KEY\?:\s*string/);
|
|
205
|
+
});
|
|
206
|
+
test('does not include auth env vars for public service', async () => {
|
|
207
|
+
const apps = await mustManifestFromString(`
|
|
208
|
+
application "test-app" {
|
|
209
|
+
service "public-service" {
|
|
210
|
+
visibility = "public"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`);
|
|
214
|
+
const app = apps[0];
|
|
215
|
+
const service = app.service[0];
|
|
216
|
+
const generated = gatherEnvForHandler(service, app);
|
|
217
|
+
// Should NOT include auth env vars for non-protected services
|
|
218
|
+
expect(generated).not.toMatch(/LM_AUTH_ALLOWED_ORIGINS/);
|
|
219
|
+
expect(generated).not.toMatch(/LM_AUTH_ALLOWED_ISSUERS/);
|
|
220
|
+
expect(generated).not.toMatch(/LM_PROVIDER_WORKOS_ORG_ID/);
|
|
221
|
+
expect(generated).not.toMatch(/LM_PROVIDER_WORKOS_CLIENT_ID/);
|
|
222
|
+
expect(generated).not.toMatch(/LM_PROVIDER_WORKOS_API_KEY/);
|
|
223
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tail.test.d.ts","sourceRoot":"","sources":["../../../src/commands/logs/tail.test.ts"],"names":[],"mappings":""}
|
|
@@ -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"}
|