@carthooks/arcubase-cli 0.1.21 → 0.1.23
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/bundle/arcubase-admin.mjs +1430 -83
- package/bundle/arcubase.mjs +1430 -83
- package/dist/generated/command_registry.generated.d.ts +300 -0
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +416 -0
- package/dist/generated/help_examples.generated.d.ts +116 -0
- package/dist/generated/help_examples.generated.d.ts.map +1 -1
- package/dist/generated/help_examples.generated.js +184 -0
- package/dist/generated/type_index.generated.d.ts +40 -1
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +42 -1
- package/dist/generated/zod_registry.generated.d.ts +29 -1
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +37 -0
- package/dist/runtime/dev_sdk_gen.d.ts +9 -0
- package/dist/runtime/dev_sdk_gen.d.ts.map +1 -0
- package/dist/runtime/dev_sdk_gen.js +319 -0
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +326 -1
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +58 -0
- package/package.json +1 -1
- package/sdk-dist/api/admin/index.ts +1 -0
- package/sdk-dist/api/admin/public-link.ts +48 -0
- package/sdk-dist/api/shared-link/form.ts +42 -1
- package/sdk-dist/api/user/index.ts +0 -1
- package/sdk-dist/docs/runtime-reference/dashboard.md +22 -0
- package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +41 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/dashboard/update-layout.json +16 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/create.json +33 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/preview-data.json +66 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/update-ingress-options.json +21 -0
- package/sdk-dist/docs/runtime-reference/widgets.md +64 -0
- package/sdk-dist/generated/command_registry.generated.ts +416 -0
- package/sdk-dist/generated/help_examples.generated.ts +184 -0
- package/sdk-dist/generated/type_index.generated.ts +42 -1
- package/sdk-dist/generated/zod_registry.generated.ts +56 -0
- package/sdk-dist/types/app.ts +37 -1
- package/sdk-dist/types/common.ts +77 -35
- package/sdk-dist/types/index.ts +1 -1
- package/sdk-dist/types/public-link.ts +10 -0
- package/sdk-dist/types/shared-link.ts +16 -1
- package/sdk-dist/types/user-action.ts +1 -43
- package/src/generated/command_registry.generated.ts +416 -0
- package/src/generated/help_examples.generated.ts +184 -0
- package/src/generated/type_index.generated.ts +42 -1
- package/src/generated/zod_registry.generated.ts +56 -0
- package/src/runtime/dev_sdk_gen.ts +360 -0
- package/src/runtime/execute.ts +371 -1
- package/src/runtime/zod_registry.ts +58 -0
- package/src/tests/command_registry.test.ts +21 -2
- package/src/tests/dev_sdk_gen.test.ts +226 -0
- package/src/tests/docs_readability.test.ts +15 -0
- package/src/tests/execute_validation.test.ts +226 -0
- package/src/tests/help.test.ts +86 -0
- package/sdk-dist/api/user/copilot.ts +0 -20
- package/sdk-dist/types/copilot.ts +0 -34
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
|
|
7
|
+
import { generateArcubaseProjectSDK } from '../runtime/dev_sdk_gen.js'
|
|
8
|
+
import { executeCLI } from '../runtime/execute.js'
|
|
9
|
+
import { CLIError } from '../runtime/errors.js'
|
|
10
|
+
|
|
11
|
+
const env = {
|
|
12
|
+
ARCUBASE_BASE_URL: 'https://arcubase.example.com',
|
|
13
|
+
ARCUBASE_ACCESS_TOKEN: 'tok',
|
|
14
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
15
|
+
ARCUBASE_TRACE_ID: '',
|
|
16
|
+
ARCUBASE_SUBJECT_TYPE: '',
|
|
17
|
+
ARCUBASE_SUBJECT_ID: '',
|
|
18
|
+
ARCUBASE_TOKEN_EXPIRES_AT: '',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function appDetail() {
|
|
22
|
+
return {
|
|
23
|
+
id: 3882856425,
|
|
24
|
+
name: '故事应用',
|
|
25
|
+
entities: {},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function entityList() {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
id: 3883547182,
|
|
33
|
+
name: '编故事',
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function appDetailWithEntityList() {
|
|
39
|
+
return {
|
|
40
|
+
id: 3882856425,
|
|
41
|
+
name: '故事应用',
|
|
42
|
+
entities: [
|
|
43
|
+
{
|
|
44
|
+
id: 3883547182,
|
|
45
|
+
name: '编故事',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function entityDetail() {
|
|
52
|
+
return {
|
|
53
|
+
id: 3883547182,
|
|
54
|
+
app_id: 3882856425,
|
|
55
|
+
name: '编故事',
|
|
56
|
+
key: 'story',
|
|
57
|
+
fields: [
|
|
58
|
+
{ id: 1006, label: '时代', key: 'era', type: 'select', required: false, options: { options: { items: [{ key: 1, value: '现代' }] } } },
|
|
59
|
+
{ id: 1013, label: '你刚刚', key: 'recentEvent', type: 'text', required: false, options: {} },
|
|
60
|
+
{ id: 1011, label: '故事', key: 'story', type: 'textarea', required: true, options: {} },
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function entityDetailWithoutFieldKeys() {
|
|
66
|
+
const detail = entityDetail()
|
|
67
|
+
detail.fields = detail.fields.map((field) => ({ ...field, key: '' }))
|
|
68
|
+
return detail
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sdkSchemaPayload() {
|
|
72
|
+
return {
|
|
73
|
+
...appDetailWithEntityList(),
|
|
74
|
+
entities: [
|
|
75
|
+
{
|
|
76
|
+
...entityDetail(),
|
|
77
|
+
key: 'story',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test('generateArcubaseProjectSDK emits typed key based entity sdk', () => {
|
|
84
|
+
const result = generateArcubaseProjectSDK(sdkSchemaPayload())
|
|
85
|
+
|
|
86
|
+
assert.deepEqual(result.files.map((file) => file.path).sort(), [
|
|
87
|
+
'client.ts',
|
|
88
|
+
'entities/story.ts',
|
|
89
|
+
'index.ts',
|
|
90
|
+
'runtime.ts',
|
|
91
|
+
'schema.ts',
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
const client = result.files.find((file) => file.path === 'client.ts')?.contents ?? ''
|
|
95
|
+
assert.match(client, /createArcubaseSdk/)
|
|
96
|
+
assert.match(client, /story: createStorySDK\(runtime\)/)
|
|
97
|
+
|
|
98
|
+
const runtime = result.files.find((file) => file.path === 'runtime.ts')?.contents ?? ''
|
|
99
|
+
assert.match(runtime, /accessToken/)
|
|
100
|
+
assert.match(runtime, /refreshToken/)
|
|
101
|
+
|
|
102
|
+
const story = result.files.find((file) => file.path === 'entities/story.ts')?.contents ?? ''
|
|
103
|
+
assert.match(story, /export type StoryFields =/)
|
|
104
|
+
assert.match(story, /era\?: number \| string/)
|
|
105
|
+
assert.match(story, /recentEvent\?: string/)
|
|
106
|
+
assert.match(story, /story: string/)
|
|
107
|
+
assert.match(story, /create\(fields: StoryCreateInput\)/)
|
|
108
|
+
assert.match(story, /update\(rowId: string \| number, fields: StoryUpdateInput\)/)
|
|
109
|
+
assert.match(story, /toArcubaseFields\(fields\)/)
|
|
110
|
+
assert.match(story, /changed_fields: Object.keys\(data\)\.map\(\(fieldId\) => Number\(fieldId\)\)/)
|
|
111
|
+
assert.match(story, /\/entity\/3882856425\/\$\{entityId\}\/insert/)
|
|
112
|
+
|
|
113
|
+
const schema = result.files.find((file) => file.path === 'schema.ts')?.contents ?? ''
|
|
114
|
+
assert.match(schema, /"story"/)
|
|
115
|
+
assert.match(schema, /"fieldId": 1011/)
|
|
116
|
+
assert.match(schema, /"entityId": 3883547182/)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('generateArcubaseProjectSDK rejects missing field keys', () => {
|
|
120
|
+
const payload = sdkSchemaPayload()
|
|
121
|
+
payload.entities[0].fields[1].key = ''
|
|
122
|
+
|
|
123
|
+
assert.throws(() => generateArcubaseProjectSDK(payload), (error: unknown) => {
|
|
124
|
+
assert.ok(error instanceof CLIError)
|
|
125
|
+
assert.equal(error.code, 'SDK_GEN_FIELD_KEY_REQUIRED')
|
|
126
|
+
assert.match(error.message, /你刚刚/)
|
|
127
|
+
return true
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('generateArcubaseProjectSDK rejects duplicate field keys within an entity', () => {
|
|
132
|
+
const payload = sdkSchemaPayload()
|
|
133
|
+
payload.entities[0].fields[1].key = 'story'
|
|
134
|
+
|
|
135
|
+
assert.throws(() => generateArcubaseProjectSDK(payload), (error: unknown) => {
|
|
136
|
+
assert.ok(error instanceof CLIError)
|
|
137
|
+
assert.equal(error.code, 'SDK_GEN_DUPLICATE_FIELD_KEY')
|
|
138
|
+
assert.match(error.message, /story/)
|
|
139
|
+
return true
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('arcubase-admin dev sdk-gen fetches app schema and writes typed sdk files', async () => {
|
|
144
|
+
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-sdk-gen-'))
|
|
145
|
+
const calls: Array<{ url: string; method?: string }> = []
|
|
146
|
+
|
|
147
|
+
const result = await executeCLI(
|
|
148
|
+
'admin',
|
|
149
|
+
['dev', 'sdk-gen', '--app-id', '3882856425', '--out', outDir],
|
|
150
|
+
env as any,
|
|
151
|
+
async (url, init) => {
|
|
152
|
+
calls.push({ url: String(url), method: init?.method })
|
|
153
|
+
if (String(url).endsWith('/api/apps/3882856425')) {
|
|
154
|
+
return new Response(JSON.stringify({ data: appDetail() }), { status: 200 })
|
|
155
|
+
}
|
|
156
|
+
if (String(url).endsWith('/api/apps/3882856425/entities')) {
|
|
157
|
+
return new Response(JSON.stringify({ data: entityList() }), { status: 200 })
|
|
158
|
+
}
|
|
159
|
+
if (String(url).endsWith('/api/apps/3882856425/entity/3883547182')) {
|
|
160
|
+
return new Response(JSON.stringify({ data: entityDetail() }), { status: 200 })
|
|
161
|
+
}
|
|
162
|
+
return new Response(JSON.stringify({ error: { message: `unexpected url ${url}` } }), { status: 404 })
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
assert.equal(result.kind, 'result')
|
|
167
|
+
assert.deepEqual(result.commandPath, ['dev', 'sdk-gen'])
|
|
168
|
+
assert.equal(calls[0].method, 'GET')
|
|
169
|
+
assert.match(calls[0].url, /\/api\/apps\/3882856425$/)
|
|
170
|
+
assert.equal(calls[1].method, 'GET')
|
|
171
|
+
assert.match(calls[1].url, /\/api\/apps\/3882856425\/entities$/)
|
|
172
|
+
assert.equal(calls[2].method, 'GET')
|
|
173
|
+
assert.match(calls[2].url, /\/api\/apps\/3882856425\/entity\/3883547182$/)
|
|
174
|
+
assert.ok(fs.existsSync(path.join(outDir, 'index.ts')))
|
|
175
|
+
assert.ok(fs.existsSync(path.join(outDir, 'client.ts')))
|
|
176
|
+
assert.ok(fs.existsSync(path.join(outDir, 'entities/story.ts')))
|
|
177
|
+
assert.match(fs.readFileSync(path.join(outDir, 'client.ts'), 'utf8'), /createArcubaseSdk/)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('arcubase-admin dev sdk-gen initializes missing field keys through admin auth before generation', async () => {
|
|
181
|
+
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-sdk-gen-'))
|
|
182
|
+
const calls: Array<{ url: string; method?: string; authorization?: string; body?: any }> = []
|
|
183
|
+
|
|
184
|
+
const result = await executeCLI(
|
|
185
|
+
'admin',
|
|
186
|
+
['dev', 'sdk-gen', '--app-id', '3882856425', '--out', outDir],
|
|
187
|
+
env as any,
|
|
188
|
+
async (url, init) => {
|
|
189
|
+
const bodyText = typeof init?.body === 'string' ? init.body : undefined
|
|
190
|
+
calls.push({
|
|
191
|
+
url: String(url),
|
|
192
|
+
method: init?.method,
|
|
193
|
+
authorization: init?.headers instanceof Headers
|
|
194
|
+
? init.headers.get('Authorization') ?? undefined
|
|
195
|
+
: (init?.headers as Record<string, string> | undefined)?.Authorization,
|
|
196
|
+
body: bodyText ? JSON.parse(bodyText) : undefined,
|
|
197
|
+
})
|
|
198
|
+
if (String(url).endsWith('/api/apps/3882856425')) {
|
|
199
|
+
return new Response(JSON.stringify({ data: appDetail() }), { status: 200 })
|
|
200
|
+
}
|
|
201
|
+
if (String(url).endsWith('/api/apps/3882856425/entities')) {
|
|
202
|
+
return new Response(JSON.stringify({ data: entityList() }), { status: 200 })
|
|
203
|
+
}
|
|
204
|
+
if (String(url).endsWith('/api/apps/3882856425/entity/3883547182')) {
|
|
205
|
+
return new Response(JSON.stringify({ data: entityDetailWithoutFieldKeys() }), { status: 200 })
|
|
206
|
+
}
|
|
207
|
+
if (String(url).endsWith('/api/apps/3882856425/entity/3883547182/custom-keys')) {
|
|
208
|
+
return new Response(JSON.stringify({ data: entityDetail() }), { status: 200 })
|
|
209
|
+
}
|
|
210
|
+
return new Response(JSON.stringify({ error: { message: `unexpected url ${url}` } }), { status: 404 })
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
assert.equal(result.kind, 'result')
|
|
215
|
+
const customKeysCall = calls.find((call) => call.url.endsWith('/api/apps/3882856425/entity/3883547182/custom-keys'))
|
|
216
|
+
assert.ok(customKeysCall)
|
|
217
|
+
assert.equal(customKeysCall.method, 'PUT')
|
|
218
|
+
assert.equal(customKeysCall.authorization, 'Bearer tok')
|
|
219
|
+
assert.deepEqual(customKeysCall.body, [
|
|
220
|
+
{ id: 1006, key: 'field1006' },
|
|
221
|
+
{ id: 1013, key: 'field1013' },
|
|
222
|
+
{ id: 1011, key: 'field1011' },
|
|
223
|
+
])
|
|
224
|
+
assert.match(fs.readFileSync(path.join(outDir, 'entities/story.ts'), 'utf8'), /field1011: string/)
|
|
225
|
+
assert.deepEqual(result.data.initializedFieldKeys, [{ entityId: 3883547182, fields: ['field1006', 'field1013', 'field1011'] }])
|
|
226
|
+
})
|
|
@@ -68,6 +68,21 @@ test('runtime docs do not use retired table create and row probe names', () => {
|
|
|
68
68
|
}
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
test('runtime docs document dashboard and widget single success paths', () => {
|
|
72
|
+
const dashboard = fs.readFileSync(path.join(root, 'dashboard.md'), 'utf8')
|
|
73
|
+
const widgets = fs.readFileSync(path.join(root, 'widgets.md'), 'utf8')
|
|
74
|
+
assert.match(dashboard, /arcubase dashboard get/)
|
|
75
|
+
assert.match(dashboard, /arcubase-admin dashboard list/)
|
|
76
|
+
assert.match(dashboard, /not exposed/)
|
|
77
|
+
assert.match(widgets, /arcubase-admin widget list/)
|
|
78
|
+
assert.match(widgets, /--widget-id/)
|
|
79
|
+
for (const aggregate of ['sum', 'avg', 'max', 'min', 'count', 'distinct_count']) {
|
|
80
|
+
assert.match(widgets, new RegExp(`\\\`${aggregate}\\\``))
|
|
81
|
+
}
|
|
82
|
+
assert.match(widgets, /unsupported aggregate names are invalid request bodies/)
|
|
83
|
+
assert.doesNotMatch(widgets, /widgets_id/)
|
|
84
|
+
})
|
|
85
|
+
|
|
71
86
|
test('scenario examples exist and are readable', () => {
|
|
72
87
|
for (const dir of ['oms-01', 'wms-01', 'crm-01']) {
|
|
73
88
|
const readme = path.join(root, 'examples', dir, 'README.md')
|
|
@@ -1094,6 +1094,232 @@ test('entry fixed app tables query cannot be disabled by query file', async () =
|
|
|
1094
1094
|
assert.equal(out.data.url, 'https://arcubase.example.com/api/entry?include_app_tables=true')
|
|
1095
1095
|
})
|
|
1096
1096
|
|
|
1097
|
+
test('dashboard and widget commands map to approved endpoints', async () => {
|
|
1098
|
+
const userAppGet = await executeCLI(
|
|
1099
|
+
'user',
|
|
1100
|
+
['app', 'get', '--app-id', 'app_1'],
|
|
1101
|
+
env as any,
|
|
1102
|
+
async (url, init) => new Response(JSON.stringify({ url, method: init?.method }), { status: 200 }),
|
|
1103
|
+
)
|
|
1104
|
+
assert.equal(userAppGet.kind, 'result')
|
|
1105
|
+
assert.equal(userAppGet.data.url, 'https://arcubase.example.com/api/apps/app_1')
|
|
1106
|
+
assert.equal(userAppGet.data.method, 'GET')
|
|
1107
|
+
|
|
1108
|
+
const adminWidgetList = await executeCLI(
|
|
1109
|
+
'admin',
|
|
1110
|
+
['widget', 'list', '--app-id', 'app_1', '--type', 'dashboard', '--dashboard-id', '100'],
|
|
1111
|
+
env as any,
|
|
1112
|
+
async (url, init) => new Response(JSON.stringify({ url, method: init?.method }), { status: 200 }),
|
|
1113
|
+
)
|
|
1114
|
+
assert.equal(adminWidgetList.kind, 'result')
|
|
1115
|
+
assert.equal(adminWidgetList.data.url, 'https://arcubase.example.com/api/apps/app_1/widgets?type=dashboard&dashboard_id=100')
|
|
1116
|
+
assert.equal(adminWidgetList.data.method, 'GET')
|
|
1117
|
+
|
|
1118
|
+
const userDashboardWidgets = await executeCLI(
|
|
1119
|
+
'user',
|
|
1120
|
+
['dashboard', 'widgets', '--app-id', 'app_1', '--dashboard-id', 'dash_1'],
|
|
1121
|
+
env as any,
|
|
1122
|
+
async (url, init) => new Response(JSON.stringify({ url, method: init?.method }), { status: 200 }),
|
|
1123
|
+
)
|
|
1124
|
+
assert.equal(userDashboardWidgets.kind, 'result')
|
|
1125
|
+
assert.equal(userDashboardWidgets.data.url, 'https://arcubase.example.com/api/dashboard/app_1/dash_1/widgets')
|
|
1126
|
+
assert.equal(userDashboardWidgets.data.method, 'GET')
|
|
1127
|
+
|
|
1128
|
+
const userWidgetData = await executeCLI(
|
|
1129
|
+
'user',
|
|
1130
|
+
['widget', 'data', '--app-id', 'app_1', '--body-json', '{"dashboard_id":100,"type":1}'],
|
|
1131
|
+
env as any,
|
|
1132
|
+
async (url, init) => new Response(JSON.stringify({
|
|
1133
|
+
url,
|
|
1134
|
+
method: init?.method,
|
|
1135
|
+
body: init?.body ? JSON.parse(String(init.body)) : null,
|
|
1136
|
+
}), { status: 200 }),
|
|
1137
|
+
)
|
|
1138
|
+
assert.equal(userWidgetData.kind, 'result')
|
|
1139
|
+
assert.equal(userWidgetData.data.url, 'https://arcubase.example.com/api/apps/app_1/widgets-data')
|
|
1140
|
+
assert.equal(userWidgetData.data.method, 'POST')
|
|
1141
|
+
assert.deepEqual(userWidgetData.data.body, { dashboard_id: 100, type: 1 })
|
|
1142
|
+
})
|
|
1143
|
+
|
|
1144
|
+
test('widget create ingress mistakes suggest ingress widget shape before request', async () => {
|
|
1145
|
+
let called = false
|
|
1146
|
+
await assert.rejects(async () => {
|
|
1147
|
+
await executeCLI(
|
|
1148
|
+
'admin',
|
|
1149
|
+
['widget', 'create', '--app-id', 'app_1', '--body-json', '{"type":"list","options":{"entity_id":2188891001}}'],
|
|
1150
|
+
env as any,
|
|
1151
|
+
async () => {
|
|
1152
|
+
called = true
|
|
1153
|
+
return new Response('{}', { status: 200 })
|
|
1154
|
+
},
|
|
1155
|
+
)
|
|
1156
|
+
}, (error: unknown) => {
|
|
1157
|
+
assert.ok(error instanceof CLIError)
|
|
1158
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1159
|
+
assert.equal(error.details?.requestType, 'AppNewWidgetsReqVO')
|
|
1160
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('ingress widgets use options.ingress_id'))
|
|
1161
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('after creating an ingress widget, use widget update-ingress-options to adjust display configuration'))
|
|
1162
|
+
return true
|
|
1163
|
+
})
|
|
1164
|
+
assert.equal(called, false)
|
|
1165
|
+
})
|
|
1166
|
+
|
|
1167
|
+
test('widget update ingress list options points to update-ingress-options before request', async () => {
|
|
1168
|
+
let called = false
|
|
1169
|
+
await assert.rejects(async () => {
|
|
1170
|
+
await executeCLI(
|
|
1171
|
+
'admin',
|
|
1172
|
+
[
|
|
1173
|
+
'widget',
|
|
1174
|
+
'update',
|
|
1175
|
+
'--app-id',
|
|
1176
|
+
'app_1',
|
|
1177
|
+
'--widget-id',
|
|
1178
|
+
'widget_1',
|
|
1179
|
+
'--body-json',
|
|
1180
|
+
'{"ingress_id":2188893001,"options":{"list":{"default_cols":[1001,1002],"page_size":10}}}',
|
|
1181
|
+
],
|
|
1182
|
+
env as any,
|
|
1183
|
+
async () => {
|
|
1184
|
+
called = true
|
|
1185
|
+
return new Response('{}', { status: 200 })
|
|
1186
|
+
},
|
|
1187
|
+
)
|
|
1188
|
+
}, (error: unknown) => {
|
|
1189
|
+
assert.ok(error instanceof CLIError)
|
|
1190
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1191
|
+
assert.equal(error.details?.requestType, 'AppUpdateWidgetsReqVO')
|
|
1192
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('ingress/list display settings use widget update-ingress-options, not widget update'))
|
|
1193
|
+
assert.ok((error.details?.suggestions ?? []).some((item) => item.includes('widget update-ingress-options')))
|
|
1194
|
+
return true
|
|
1195
|
+
})
|
|
1196
|
+
assert.equal(called, false)
|
|
1197
|
+
})
|
|
1198
|
+
|
|
1199
|
+
test('widget preview-data rejects unsupported aggregate before request', async () => {
|
|
1200
|
+
let called = false
|
|
1201
|
+
await assert.rejects(async () => {
|
|
1202
|
+
await executeCLI(
|
|
1203
|
+
'admin',
|
|
1204
|
+
[
|
|
1205
|
+
'widget',
|
|
1206
|
+
'preview-data',
|
|
1207
|
+
'--app-id',
|
|
1208
|
+
'app_1',
|
|
1209
|
+
'--body-json',
|
|
1210
|
+
'{"dataSource":2188891001,"dataSourceType":"entity","measures":[{"Name":"median_amount","FieldID":1004,"Aggregate":"median"}]}',
|
|
1211
|
+
],
|
|
1212
|
+
env as any,
|
|
1213
|
+
async () => {
|
|
1214
|
+
called = true
|
|
1215
|
+
return new Response('{}', { status: 200 })
|
|
1216
|
+
},
|
|
1217
|
+
)
|
|
1218
|
+
}, (error: unknown) => {
|
|
1219
|
+
assert.ok(error instanceof CLIError)
|
|
1220
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1221
|
+
assert.equal(error.details?.requestType, 'AppPreviewWidgetsDataReqVO')
|
|
1222
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.measures.0.Aggregate'))
|
|
1223
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file === '/runtime/arcubase-sdk/docs/runtime-reference/widgets.md'))
|
|
1224
|
+
return true
|
|
1225
|
+
})
|
|
1226
|
+
assert.equal(called, false)
|
|
1227
|
+
})
|
|
1228
|
+
|
|
1229
|
+
test('widget preview-data rejects field dimensions without IsField true before request', async () => {
|
|
1230
|
+
let called = false
|
|
1231
|
+
await assert.rejects(async () => {
|
|
1232
|
+
await executeCLI(
|
|
1233
|
+
'admin',
|
|
1234
|
+
[
|
|
1235
|
+
'widget',
|
|
1236
|
+
'preview-data',
|
|
1237
|
+
'--app-id',
|
|
1238
|
+
'app_1',
|
|
1239
|
+
'--body-json',
|
|
1240
|
+
'{"dataSource":2188891001,"dataSourceType":"entity","dimensions":[{"FieldID":1004,"Name":"Source"}],"measures":[{"Name":"Total Amount","FieldID":1005,"Aggregate":"sum"}]}',
|
|
1241
|
+
],
|
|
1242
|
+
env as any,
|
|
1243
|
+
async () => {
|
|
1244
|
+
called = true
|
|
1245
|
+
return new Response('{}', { status: 200 })
|
|
1246
|
+
},
|
|
1247
|
+
)
|
|
1248
|
+
}, (error: unknown) => {
|
|
1249
|
+
assert.ok(error instanceof CLIError)
|
|
1250
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1251
|
+
assert.equal(error.details?.requestType, 'AppPreviewWidgetsDataReqVO')
|
|
1252
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.dimensions.0.IsField'))
|
|
1253
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('field dimensions with FieldID must set IsField:true'))
|
|
1254
|
+
return true
|
|
1255
|
+
})
|
|
1256
|
+
assert.equal(called, false)
|
|
1257
|
+
})
|
|
1258
|
+
|
|
1259
|
+
test('widget preview-data filter mistakes suggest chart conditions before request', async () => {
|
|
1260
|
+
let called = false
|
|
1261
|
+
await assert.rejects(async () => {
|
|
1262
|
+
await executeCLI(
|
|
1263
|
+
'admin',
|
|
1264
|
+
[
|
|
1265
|
+
'widget',
|
|
1266
|
+
'preview-data',
|
|
1267
|
+
'--app-id',
|
|
1268
|
+
'app_1',
|
|
1269
|
+
'--body-json',
|
|
1270
|
+
'{"dataSource":2188891001,"dataSourceType":"entity","dimensions":[{"IsField":true,"FieldID":1004,"Name":"Source"}],"filters":[{"FieldID":1006,"Operator":"$eq","Value":"yes"}],"measures":[{"Name":"Distinct Customers","FieldID":1002,"Aggregate":"distinct_count"}]}',
|
|
1271
|
+
],
|
|
1272
|
+
env as any,
|
|
1273
|
+
async () => {
|
|
1274
|
+
called = true
|
|
1275
|
+
return new Response('{}', { status: 200 })
|
|
1276
|
+
},
|
|
1277
|
+
)
|
|
1278
|
+
}, (error: unknown) => {
|
|
1279
|
+
assert.ok(error instanceof CLIError)
|
|
1280
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1281
|
+
assert.equal(error.details?.requestType, 'AppPreviewWidgetsDataReqVO')
|
|
1282
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body'))
|
|
1283
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('use conditions, not filters, for chart data filtering'))
|
|
1284
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('do not retry without conditions and then hand-calculate filtered statistics'))
|
|
1285
|
+
assert.ok((error.details?.suggestions ?? []).some((item) => item.includes('"conditions":{"mode":"and","conditions":[{"column":":1006","operator":"$eq","value":"yes"}]}')))
|
|
1286
|
+
return true
|
|
1287
|
+
})
|
|
1288
|
+
assert.equal(called, false)
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1291
|
+
test('widget update rejects chart field dimensions without IsField true before request', async () => {
|
|
1292
|
+
let called = false
|
|
1293
|
+
await assert.rejects(async () => {
|
|
1294
|
+
await executeCLI(
|
|
1295
|
+
'admin',
|
|
1296
|
+
[
|
|
1297
|
+
'widget',
|
|
1298
|
+
'update',
|
|
1299
|
+
'--app-id',
|
|
1300
|
+
'app_1',
|
|
1301
|
+
'--widget-id',
|
|
1302
|
+
'widget_1',
|
|
1303
|
+
'--body-json',
|
|
1304
|
+
'{"entity_id":2188891001,"options":{"charts":{"entity_id":2188891001,"dimensions":[{"FieldID":1004,"IsField":false,"Name":"Source"}],"measures":[{"Name":"Total Amount","FieldID":1005,"Aggregate":"sum"}]}}}',
|
|
1305
|
+
],
|
|
1306
|
+
env as any,
|
|
1307
|
+
async () => {
|
|
1308
|
+
called = true
|
|
1309
|
+
return new Response('{}', { status: 200 })
|
|
1310
|
+
},
|
|
1311
|
+
)
|
|
1312
|
+
}, (error: unknown) => {
|
|
1313
|
+
assert.ok(error instanceof CLIError)
|
|
1314
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
1315
|
+
assert.equal(error.details?.requestType, 'AppUpdateWidgetsReqVO')
|
|
1316
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.options.charts.dimensions.0.IsField'))
|
|
1317
|
+
assert.ok((error.details?.commonMistakes ?? []).includes('field dimensions with FieldID must set IsField:true'))
|
|
1318
|
+
return true
|
|
1319
|
+
})
|
|
1320
|
+
assert.equal(called, false)
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1097
1323
|
test('upstream http failures include endpoint status and body details', async () => {
|
|
1098
1324
|
await assert.rejects(async () => {
|
|
1099
1325
|
await executeCLI('user', ['entry'], env as any, async () => new Response('', { status: 500 }))
|
package/src/tests/help.test.ts
CHANGED
|
@@ -21,10 +21,25 @@ test('admin root help exposes reset surface nouns only', async () => {
|
|
|
21
21
|
assert.match(out.text, /\btable\b/)
|
|
22
22
|
assert.match(out.text, /\baccess-rule\b/)
|
|
23
23
|
assert.match(out.text, /\bworkflow\b/)
|
|
24
|
+
assert.match(out.text, /\bdev\b/)
|
|
24
25
|
assert.doesNotMatch(out.text, /\bentity\b/)
|
|
25
26
|
assert.doesNotMatch(out.text, /\bingress\b/)
|
|
26
27
|
})
|
|
27
28
|
|
|
29
|
+
test('admin dev sdk-gen help documents typed project sdk generation', async () => {
|
|
30
|
+
const moduleHelp = await executeCLI('admin', ['dev', '--help'], env as any, async () => new Response('{}'))
|
|
31
|
+
assert.equal(moduleHelp.kind, 'help')
|
|
32
|
+
assert.match(moduleHelp.text, /sdk-gen/)
|
|
33
|
+
|
|
34
|
+
const commandHelp = await executeCLI('admin', ['dev', 'sdk-gen', '--help'], env as any, async () => new Response('{}'))
|
|
35
|
+
assert.equal(commandHelp.kind, 'help')
|
|
36
|
+
assert.match(commandHelp.text, /arcubase-admin dev sdk-gen/)
|
|
37
|
+
assert.match(commandHelp.text, /--app-id/)
|
|
38
|
+
assert.match(commandHelp.text, /--out/)
|
|
39
|
+
assert.match(commandHelp.text, /field\.key/)
|
|
40
|
+
assert.match(commandHelp.text, /createArcubaseSdk\(\{ baseURL, accessToken, refreshToken \}\)/)
|
|
41
|
+
})
|
|
42
|
+
|
|
28
43
|
test('user root help exposes reset surface nouns only', async () => {
|
|
29
44
|
const out = await executeCLI('user', ['--help'], env as any, async () => new Response('{}'))
|
|
30
45
|
assert.equal(out.kind, 'help')
|
|
@@ -99,6 +114,36 @@ test('admin table create help gives one-call body-json example', async () => {
|
|
|
99
114
|
assert.match(out.text, /"type":"textarea"/)
|
|
100
115
|
})
|
|
101
116
|
|
|
117
|
+
test('dashboard and widget help exposes agent-readable body docs and flags', async () => {
|
|
118
|
+
const adminDashboardCreate = await executeCLI('admin', ['dashboard', 'create', '--help'], env as any, async () => new Response('{}'))
|
|
119
|
+
assert.equal(adminDashboardCreate.kind, 'help')
|
|
120
|
+
assert.match(adminDashboardCreate.text, /arcubase-admin dashboard create/)
|
|
121
|
+
assert.match(adminDashboardCreate.text, /endpoint: \/apps\/:app_id\/dashboard/)
|
|
122
|
+
assert.match(adminDashboardCreate.text, /body: DashboardCreateReqVO via --body-json/)
|
|
123
|
+
assert.match(adminDashboardCreate.text, /^types:$/m)
|
|
124
|
+
assert.match(adminDashboardCreate.text, /^docs:$/m)
|
|
125
|
+
|
|
126
|
+
const adminWidgetList = await executeCLI('admin', ['widget', 'list', '--help'], env as any, async () => new Response('{}'))
|
|
127
|
+
assert.equal(adminWidgetList.kind, 'help')
|
|
128
|
+
assert.match(adminWidgetList.text, /arcubase-admin widget list/)
|
|
129
|
+
assert.match(adminWidgetList.text, /endpoint: \/apps\/:app_id\/widgets/)
|
|
130
|
+
assert.match(adminWidgetList.text, /query flags: --type, --dashboard-id, --ingress-id, --query-file/)
|
|
131
|
+
assert.match(adminWidgetList.text, /^docs:$/m)
|
|
132
|
+
|
|
133
|
+
const userWidgetData = await executeCLI('user', ['widget', 'data', '--help'], env as any, async () => new Response('{}'))
|
|
134
|
+
assert.equal(userWidgetData.kind, 'help')
|
|
135
|
+
assert.match(userWidgetData.text, /arcubase widget data/)
|
|
136
|
+
assert.match(userWidgetData.text, /body: FetchWidgetsDataReqVO via --body-json/)
|
|
137
|
+
assert.match(userWidgetData.text, /^types:$/m)
|
|
138
|
+
assert.match(userWidgetData.text, /^docs:$/m)
|
|
139
|
+
|
|
140
|
+
const userAppGet = await executeCLI('user', ['app', 'get', '--help'], env as any, async () => new Response('{}'))
|
|
141
|
+
assert.equal(userAppGet.kind, 'help')
|
|
142
|
+
assert.match(userAppGet.text, /arcubase app get/)
|
|
143
|
+
assert.match(userAppGet.text, /endpoint: \/apps\/:id/)
|
|
144
|
+
assert.match(userAppGet.text, /path flags: --app-id/)
|
|
145
|
+
})
|
|
146
|
+
|
|
102
147
|
test('admin access-rule help gives update whitelist and assign-users body-json examples', async () => {
|
|
103
148
|
const create = await executeCLI('admin', ['access-rule', 'create', '--help'], env as any, async () => new Response('{}'))
|
|
104
149
|
assert.equal(create.kind, 'help')
|
|
@@ -166,6 +211,47 @@ test('admin access-rule bulk-apply help-examples show transactional matrix body'
|
|
|
166
211
|
assert.doesNotMatch(out.text, /"type":"member"/)
|
|
167
212
|
})
|
|
168
213
|
|
|
214
|
+
test('admin dashboard update-layout help-examples show top-level layout array', async () => {
|
|
215
|
+
const out = await executeCLI('admin', ['dashboard', 'update-layout', '--help-examples'], env as any, async () => new Response('{}'))
|
|
216
|
+
assert.equal(out.kind, 'help')
|
|
217
|
+
assert.match(out.text, /arcubase-admin dashboard update-layout --help-examples/)
|
|
218
|
+
assert.match(out.text, /--body-json '\[\{"id":2188892001,"x":0,"y":0,"w":12,"h":6\}\]'/)
|
|
219
|
+
assert.match(out.text, /the request body is a top-level array/)
|
|
220
|
+
assert.match(out.text, /do not wrap the array/)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('admin widget preview-data help-examples show chart query body without widget id', async () => {
|
|
224
|
+
const out = await executeCLI('admin', ['widget', 'preview-data', '--help-examples'], env as any, async () => new Response('{}'))
|
|
225
|
+
assert.equal(out.kind, 'help')
|
|
226
|
+
assert.match(out.text, /arcubase-admin widget preview-data --help-examples/)
|
|
227
|
+
assert.match(out.text, /"dataSource":2188891001/)
|
|
228
|
+
assert.match(out.text, /"dataSourceType":"entity"/)
|
|
229
|
+
assert.match(out.text, /"Aggregate":"sum"/)
|
|
230
|
+
assert.match(out.text, /"conditions":\{"mode":"and","conditions":\[\{"column":":1006","operator":"\$eq","value":"yes"\}\]\}/)
|
|
231
|
+
assert.match(out.text, /use conditions, not filters/)
|
|
232
|
+
assert.match(out.text, /preview-data does not accept --widget-id/)
|
|
233
|
+
assert.match(out.text, /do not send widget_id or id/)
|
|
234
|
+
assert.doesNotMatch(out.text, /--widget-id <widget_id>/)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('admin widget create help-examples avoid manual options type', async () => {
|
|
238
|
+
const out = await executeCLI('admin', ['widget', 'create', '--help-examples'], env as any, async () => new Response('{}'))
|
|
239
|
+
assert.equal(out.kind, 'help')
|
|
240
|
+
assert.match(out.text, /arcubase-admin widget create --help-examples/)
|
|
241
|
+
assert.match(out.text, /"type":"chart","options":\{"dashboard_id":2188892001\}/)
|
|
242
|
+
assert.match(out.text, /do not set options.type by hand/)
|
|
243
|
+
assert.match(out.text, /dashboard_id selects a dashboard widget/)
|
|
244
|
+
assert.doesNotMatch(out.text, /"options":\{"dashboard_id":2188892001,"type":/)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('admin widget update-ingress-options help-examples show ingress display body', async () => {
|
|
248
|
+
const out = await executeCLI('admin', ['widget', 'update-ingress-options', '--help-examples'], env as any, async () => new Response('{}'))
|
|
249
|
+
assert.equal(out.kind, 'help')
|
|
250
|
+
assert.match(out.text, /arcubase-admin widget update-ingress-options --help-examples/)
|
|
251
|
+
assert.match(out.text, /"cond_follow":true/)
|
|
252
|
+
assert.match(out.text, /verify with widget get/)
|
|
253
|
+
})
|
|
254
|
+
|
|
169
255
|
test('user row bulk-update help-examples show ids and condition selection bodies', async () => {
|
|
170
256
|
const out = await executeCLI('user', ['row', 'bulk-update', '--help-examples'], env as any, async () => new Response('{}'))
|
|
171
257
|
assert.equal(out.kind, 'help')
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { getDefaultClient } from '../../factory'
|
|
2
|
-
import type {
|
|
3
|
-
CopilotInitConversationRespVO,
|
|
4
|
-
CopilotSendMessageReqVO,
|
|
5
|
-
CopilotSendMessageRespVO
|
|
6
|
-
} from '../../types/copilot'
|
|
7
|
-
|
|
8
|
-
// @endpoint POST /copilot/init
|
|
9
|
-
// @controller CopilotController.InitConversation
|
|
10
|
-
export async function initCopilotConversation(): Promise<CopilotInitConversationRespVO> {
|
|
11
|
-
return getDefaultClient().post('/copilot/init')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// @endpoint POST /copilot/send-message
|
|
15
|
-
// @controller CopilotController.SendMessage
|
|
16
|
-
export async function sendCopilotMessage(
|
|
17
|
-
data: CopilotSendMessageReqVO
|
|
18
|
-
): Promise<CopilotSendMessageRespVO> {
|
|
19
|
-
return getDefaultClient().post('/copilot/send-message', data)
|
|
20
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
/* tslint:disable */
|
|
3
|
-
// @ts-nocheck
|
|
4
|
-
/*
|
|
5
|
-
* ---------------------------------------------------------------
|
|
6
|
-
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
|
7
|
-
* ## ##
|
|
8
|
-
* ## AUTHOR: acacode ##
|
|
9
|
-
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
|
10
|
-
* ---------------------------------------------------------------
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ErrorResponse } from './common'
|
|
14
|
-
|
|
15
|
-
export interface CopilotMessage {
|
|
16
|
-
Content?: string;
|
|
17
|
-
Type?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface CopilotInitConversationRespVO {
|
|
21
|
-
ID?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface CopilotSendMessageReqVO {
|
|
25
|
-
conversation_id?: string;
|
|
26
|
-
current_view?: string;
|
|
27
|
-
messages?: CopilotMessage[];
|
|
28
|
-
params?: Record<string, string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CopilotSendMessageRespVO {
|
|
32
|
-
ID?: string;
|
|
33
|
-
Request?: string;
|
|
34
|
-
}
|