@carthooks/arcubase-cli 0.1.3 → 0.1.5
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 +546 -243
- package/bundle/arcubase.mjs +546 -243
- package/dist/bin/arcubase-admin.js +0 -0
- package/dist/bin/arcubase.js +0 -0
- package/dist/generated/command_registry.generated.d.ts +36 -112
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +40 -163
- package/dist/generated/type_index.generated.d.ts +0 -12
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +0 -12
- package/dist/generated/zod_registry.generated.d.ts +1 -19
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +0 -21
- package/dist/runtime/command_registry.d.ts +19 -1
- package/dist/runtime/command_registry.d.ts.map +1 -1
- package/dist/runtime/command_registry.js +19 -1
- package/dist/runtime/errors.d.ts +1 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +321 -8
- package/dist/runtime/upload.d.ts +13 -0
- package/dist/runtime/upload.d.ts.map +1 -0
- package/dist/runtime/upload.js +148 -0
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +50 -30
- package/dist/tests/command_registry.test.js +11 -4
- package/dist/tests/execute_validation.test.js +221 -13
- package/dist/tests/help.test.js +29 -6
- package/dist/tests/upload.test.d.ts +2 -0
- package/dist/tests/upload.test.d.ts.map +1 -0
- package/dist/tests/upload.test.js +107 -0
- package/package.json +1 -1
- package/sdk-dist/api/admin/app.ts +8 -1
- package/sdk-dist/docs/runtime-reference/README.md +33 -15
- package/sdk-dist/docs/runtime-reference/app-discovery.md +68 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema/file.md +9 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/image.md +9 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema.md +2 -2
- package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +23 -4
- package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
- package/sdk-dist/docs/runtime-reference/row-crud.md +8 -6
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +5 -5
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +34 -6
- package/sdk-dist/docs/runtime-reference/uploads.md +106 -0
- package/sdk-dist/generated/command_registry.generated.ts +40 -163
- package/sdk-dist/generated/type_index.generated.ts +0 -12
- package/sdk-dist/generated/zod_registry.generated.ts +0 -36
- package/sdk-dist/types/app.ts +7 -0
- package/src/generated/command_registry.generated.ts +40 -163
- package/src/generated/type_index.generated.ts +0 -12
- package/src/generated/zod_registry.generated.ts +0 -36
- package/src/runtime/command_registry.ts +38 -2
- package/src/runtime/errors.ts +1 -0
- package/src/runtime/execute.ts +368 -8
- package/src/runtime/upload.ts +196 -0
- package/src/runtime/zod_registry.ts +50 -30
- package/src/tests/command_registry.test.ts +13 -4
- package/src/tests/execute_validation.test.ts +257 -13
- package/src/tests/help.test.ts +35 -6
- package/src/tests/upload.test.ts +133 -0
- package/sdk-dist/docs/runtime-reference/workflow/README.md +0 -19
|
@@ -67,55 +67,60 @@ const examplesIndexDocHint: RuntimeDocHint = {
|
|
|
67
67
|
file: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
title: '
|
|
72
|
-
file: '/opt/arcubase-sdk/docs/runtime-reference/
|
|
70
|
+
const appDiscoveryDocHint: RuntimeDocHint = {
|
|
71
|
+
title: 'App discovery',
|
|
72
|
+
file: '/opt/arcubase-sdk/docs/runtime-reference/app-discovery.md',
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const docHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
76
76
|
AppCreateByTenantsReqVO: [
|
|
77
|
+
appDiscoveryDocHint,
|
|
77
78
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
78
79
|
examplesIndexDocHint,
|
|
79
|
-
omsExampleDocHint,
|
|
80
80
|
],
|
|
81
81
|
AppCreateEntityReqVO: [
|
|
82
|
+
appDiscoveryDocHint,
|
|
82
83
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
83
84
|
examplesIndexDocHint,
|
|
84
|
-
omsExampleDocHint,
|
|
85
85
|
],
|
|
86
86
|
EntitySaveReqVO: [
|
|
87
87
|
{ title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
|
|
88
|
-
|
|
88
|
+
examplesIndexDocHint,
|
|
89
89
|
],
|
|
90
90
|
EntityQueryReqVO: [
|
|
91
91
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
92
92
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
93
|
-
|
|
93
|
+
examplesIndexDocHint,
|
|
94
94
|
],
|
|
95
95
|
EntityUpdateReqVO: [
|
|
96
96
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
97
|
-
|
|
97
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
98
|
+
examplesIndexDocHint,
|
|
98
99
|
],
|
|
99
100
|
EntitySelectionActionReqVO: [
|
|
100
101
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
101
|
-
|
|
102
|
+
examplesIndexDocHint,
|
|
102
103
|
],
|
|
103
104
|
InsertReqVO: [
|
|
104
105
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
105
|
-
|
|
106
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
107
|
+
examplesIndexDocHint,
|
|
106
108
|
],
|
|
107
109
|
EntityBulkUpdateReqVO: [
|
|
108
110
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
109
|
-
|
|
111
|
+
examplesIndexDocHint,
|
|
110
112
|
],
|
|
111
113
|
'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
|
|
112
114
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
115
|
+
examplesIndexDocHint,
|
|
113
116
|
],
|
|
114
117
|
'{ policy_id: string selection: any }': [
|
|
115
118
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
119
|
+
examplesIndexDocHint,
|
|
116
120
|
],
|
|
117
121
|
'{ policy_id: string selection: any tag_names: string[] }': [
|
|
118
122
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
123
|
+
examplesIndexDocHint,
|
|
119
124
|
],
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -150,53 +155,68 @@ export function getDocHints(typeName: string): RuntimeDocHint[] {
|
|
|
150
155
|
|
|
151
156
|
const commandDocHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
152
157
|
'admin.app.createAppByTenants': [
|
|
158
|
+
appDiscoveryDocHint,
|
|
153
159
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
154
160
|
examplesIndexDocHint,
|
|
155
|
-
|
|
161
|
+
],
|
|
162
|
+
'admin.app.listApps': [
|
|
163
|
+
appDiscoveryDocHint,
|
|
164
|
+
examplesIndexDocHint,
|
|
165
|
+
],
|
|
166
|
+
'admin.app.inventory': [
|
|
167
|
+
appDiscoveryDocHint,
|
|
168
|
+
examplesIndexDocHint,
|
|
156
169
|
],
|
|
157
170
|
'admin.app.createEntity': [
|
|
171
|
+
appDiscoveryDocHint,
|
|
158
172
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
159
|
-
|
|
173
|
+
examplesIndexDocHint,
|
|
160
174
|
],
|
|
161
175
|
'admin.entity.adminGetEntityInfo': [
|
|
176
|
+
appDiscoveryDocHint,
|
|
162
177
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
163
|
-
|
|
178
|
+
examplesIndexDocHint,
|
|
164
179
|
],
|
|
165
180
|
'admin.entity.adminSaveEntity': [
|
|
166
181
|
{ title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
|
|
167
|
-
|
|
182
|
+
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
183
|
+
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
184
|
+
examplesIndexDocHint,
|
|
168
185
|
],
|
|
169
|
-
'user.
|
|
186
|
+
'user.rows.insertEntity': [
|
|
170
187
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
171
|
-
|
|
188
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
189
|
+
examplesIndexDocHint,
|
|
172
190
|
],
|
|
173
|
-
'user.
|
|
191
|
+
'user.rows.queryEntity': [
|
|
174
192
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
175
|
-
|
|
193
|
+
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
194
|
+
examplesIndexDocHint,
|
|
176
195
|
],
|
|
177
|
-
'user.
|
|
196
|
+
'user.rows.getEntityRow': [
|
|
178
197
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
179
|
-
|
|
198
|
+
examplesIndexDocHint,
|
|
180
199
|
],
|
|
181
|
-
'user.
|
|
200
|
+
'user.rows.updateEntityRow': [
|
|
182
201
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
183
|
-
|
|
202
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
203
|
+
examplesIndexDocHint,
|
|
184
204
|
],
|
|
185
|
-
'user.
|
|
205
|
+
'user.rows.queryEntitySelection': [
|
|
186
206
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
187
|
-
|
|
207
|
+
examplesIndexDocHint,
|
|
188
208
|
],
|
|
189
|
-
'user.
|
|
209
|
+
'user.rows.deleteEntityRow': [
|
|
190
210
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
191
|
-
|
|
211
|
+
examplesIndexDocHint,
|
|
192
212
|
],
|
|
193
|
-
'user.
|
|
213
|
+
'user.rows.restoreEntityRow': [
|
|
194
214
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
195
|
-
|
|
215
|
+
examplesIndexDocHint,
|
|
196
216
|
],
|
|
197
217
|
'user.entity.updateEntityBulk': [
|
|
198
218
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
199
|
-
|
|
219
|
+
examplesIndexDocHint,
|
|
200
220
|
],
|
|
201
221
|
}
|
|
202
222
|
|
|
@@ -8,8 +8,17 @@ test('admin app create-entity command exists', () => {
|
|
|
8
8
|
assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app create-entity'))
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
test('
|
|
12
|
-
assert.ok(
|
|
11
|
+
test('admin app list-apps command exists', () => {
|
|
12
|
+
assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app list-apps'))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('admin app inventory command exists', () => {
|
|
16
|
+
const suggestions = findCommandSuggestions('app', 'inventory', 20)
|
|
17
|
+
assert.ok(suggestions.some((item) => item.binary === 'arcubase-admin' && item.moduleName === 'app' && item.commandName === 'inventory'))
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('user rows query-entity command exists', () => {
|
|
21
|
+
assert.ok(userCommands.some((c) => c.commandPath.join(' ') === 'rows query-entity'))
|
|
13
22
|
})
|
|
14
23
|
|
|
15
24
|
test('inline request type keeps full object text instead of truncated brace', () => {
|
|
@@ -27,7 +36,7 @@ test('tag commands no longer emit truncated inline request type', () => {
|
|
|
27
36
|
assert.equal(typed.scalarParams.length, 0)
|
|
28
37
|
})
|
|
29
38
|
|
|
30
|
-
test('suggestions can point wrong admin delete guess to user
|
|
39
|
+
test('suggestions can point wrong admin delete guess to user rows delete command', () => {
|
|
31
40
|
const suggestions = findCommandSuggestions('entity', 'admin-delete-entity-row')
|
|
32
|
-
assert.ok(suggestions.some((item) => item.binary === 'arcubase' && item.moduleName === '
|
|
41
|
+
assert.ok(suggestions.some((item) => item.binary === 'arcubase' && item.moduleName === 'rows' && item.commandName === 'delete-entity-row'))
|
|
33
42
|
})
|
|
@@ -82,6 +82,67 @@ test('admin api-prefixed command executes against external root path', async ()
|
|
|
82
82
|
assert.equal(out.data.url, 'https://arcubase.example.com/apps/app_1/entity-shares')
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
+
test('app inventory returns apps with entity lists', async () => {
|
|
86
|
+
const calls: string[] = []
|
|
87
|
+
const out = await executeCLI(
|
|
88
|
+
'admin',
|
|
89
|
+
['app', 'inventory'],
|
|
90
|
+
env as any,
|
|
91
|
+
async (url, init) => {
|
|
92
|
+
calls.push(`${init?.method ?? 'GET'} ${String(url)}`)
|
|
93
|
+
if (String(url) === 'https://arcubase.example.com/apps') {
|
|
94
|
+
return new Response(JSON.stringify({
|
|
95
|
+
data: [
|
|
96
|
+
{ id: 101, name: 'Workspace' },
|
|
97
|
+
{ id: 102, name: 'CRM' },
|
|
98
|
+
],
|
|
99
|
+
}), { status: 200 })
|
|
100
|
+
}
|
|
101
|
+
if (String(url) === 'https://arcubase.example.com/apps/101/entities') {
|
|
102
|
+
return new Response(JSON.stringify({
|
|
103
|
+
data: [
|
|
104
|
+
{ id: 201, name: 'Orders' },
|
|
105
|
+
{ id: 202, name: 'Customers' },
|
|
106
|
+
],
|
|
107
|
+
}), { status: 200 })
|
|
108
|
+
}
|
|
109
|
+
if (String(url) === 'https://arcubase.example.com/apps/102/entities') {
|
|
110
|
+
return new Response(JSON.stringify({
|
|
111
|
+
data: [
|
|
112
|
+
{ id: 203, name: 'Leads' },
|
|
113
|
+
],
|
|
114
|
+
}), { status: 200 })
|
|
115
|
+
}
|
|
116
|
+
return new Response('{}', { status: 404 })
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
assert.equal(out.kind, 'result')
|
|
120
|
+
assert.deepEqual(out.data, {
|
|
121
|
+
apps: [
|
|
122
|
+
{
|
|
123
|
+
id: '101',
|
|
124
|
+
name: 'Workspace',
|
|
125
|
+
entities: [
|
|
126
|
+
{ id: '201', name: 'Orders' },
|
|
127
|
+
{ id: '202', name: 'Customers' },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: '102',
|
|
132
|
+
name: 'CRM',
|
|
133
|
+
entities: [
|
|
134
|
+
{ id: '203', name: 'Leads' },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})
|
|
139
|
+
assert.deepEqual(calls, [
|
|
140
|
+
'GET https://arcubase.example.com/apps',
|
|
141
|
+
'POST https://arcubase.example.com/apps/101/entities',
|
|
142
|
+
'POST https://arcubase.example.com/apps/102/entities',
|
|
143
|
+
])
|
|
144
|
+
})
|
|
145
|
+
|
|
85
146
|
test('admin save entity treats top-level error body as failure even on http 200', async () => {
|
|
86
147
|
const bodyFile = tempJSONFile({
|
|
87
148
|
id: 2188889845,
|
|
@@ -224,7 +285,7 @@ test('delete entity row rejects selection type outside ids', async () => {
|
|
|
224
285
|
await assert.rejects(async () => {
|
|
225
286
|
await executeCLI(
|
|
226
287
|
'user',
|
|
227
|
-
['
|
|
288
|
+
['rows', 'delete-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile],
|
|
228
289
|
env as any,
|
|
229
290
|
async () => new Response('{}', { status: 200 }),
|
|
230
291
|
)
|
|
@@ -283,7 +344,7 @@ test('selection action accepts condition selection with selectAll', async () =>
|
|
|
283
344
|
|
|
284
345
|
const out = await executeCLI(
|
|
285
346
|
'user',
|
|
286
|
-
['
|
|
347
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
287
348
|
env as any,
|
|
288
349
|
async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }),
|
|
289
350
|
)
|
|
@@ -306,7 +367,7 @@ test('selection action accepts condition selection with filter_* keys', async ()
|
|
|
306
367
|
|
|
307
368
|
const out = await executeCLI(
|
|
308
369
|
'user',
|
|
309
|
-
['
|
|
370
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
310
371
|
env as any,
|
|
311
372
|
async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }),
|
|
312
373
|
)
|
|
@@ -328,7 +389,7 @@ test('selection action accepts condition selection with selectAll only for batch
|
|
|
328
389
|
|
|
329
390
|
const out = await executeCLI(
|
|
330
391
|
'user',
|
|
331
|
-
['
|
|
392
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'batch_print', '--body-file', bodyFile],
|
|
332
393
|
env as any,
|
|
333
394
|
async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }),
|
|
334
395
|
)
|
|
@@ -346,7 +407,7 @@ test('selection action accepts all selection for query-style actions', async ()
|
|
|
346
407
|
|
|
347
408
|
const out = await executeCLI(
|
|
348
409
|
'user',
|
|
349
|
-
['
|
|
410
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile],
|
|
350
411
|
env as any,
|
|
351
412
|
async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }),
|
|
352
413
|
)
|
|
@@ -369,7 +430,7 @@ test('selection action rejects condition selection for query action', async () =
|
|
|
369
430
|
await assert.rejects(async () => {
|
|
370
431
|
await executeCLI(
|
|
371
432
|
'user',
|
|
372
|
-
['
|
|
433
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile],
|
|
373
434
|
env as any,
|
|
374
435
|
async () => new Response('{}', { status: 200 }),
|
|
375
436
|
)
|
|
@@ -391,7 +452,7 @@ test('selection action rejects all selection for archive action', async () => {
|
|
|
391
452
|
await assert.rejects(async () => {
|
|
392
453
|
await executeCLI(
|
|
393
454
|
'user',
|
|
394
|
-
['
|
|
455
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
395
456
|
env as any,
|
|
396
457
|
async () => new Response('{}', { status: 200 }),
|
|
397
458
|
)
|
|
@@ -417,7 +478,7 @@ test('selection action rejects selectAll-only condition for archive action', asy
|
|
|
417
478
|
await assert.rejects(async () => {
|
|
418
479
|
await executeCLI(
|
|
419
480
|
'user',
|
|
420
|
-
['
|
|
481
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
421
482
|
env as any,
|
|
422
483
|
async () => new Response('{}', { status: 200 }),
|
|
423
484
|
)
|
|
@@ -429,6 +490,189 @@ test('selection action rejects selectAll-only condition for archive action', asy
|
|
|
429
490
|
})
|
|
430
491
|
})
|
|
431
492
|
|
|
493
|
+
test('insert entity rejects local file path for file field and points to arcubase upload', async () => {
|
|
494
|
+
const bodyFile = tempJSONFile({
|
|
495
|
+
fields: {
|
|
496
|
+
1001: 'Lead A',
|
|
497
|
+
1007: './contract.pdf',
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
const calls: Array<{ url: string; method?: string }> = []
|
|
501
|
+
|
|
502
|
+
await assert.rejects(async () => {
|
|
503
|
+
await executeCLI(
|
|
504
|
+
'user',
|
|
505
|
+
['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile],
|
|
506
|
+
env as any,
|
|
507
|
+
async (url, init) => {
|
|
508
|
+
calls.push({ url: String(url), method: init?.method })
|
|
509
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
510
|
+
return new Response(JSON.stringify({
|
|
511
|
+
data: {
|
|
512
|
+
id: 1,
|
|
513
|
+
app_id: 1,
|
|
514
|
+
fields: [
|
|
515
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
516
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
517
|
+
],
|
|
518
|
+
},
|
|
519
|
+
}), { status: 200 })
|
|
520
|
+
}
|
|
521
|
+
return new Response('{}', { status: 200 })
|
|
522
|
+
},
|
|
523
|
+
)
|
|
524
|
+
}, (error: unknown) => {
|
|
525
|
+
assert.ok(error instanceof CLIError)
|
|
526
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
527
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.fields.1007'))
|
|
528
|
+
assert.match(error.message, /arcubase upload \.\/contract\.pdf/)
|
|
529
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')))
|
|
530
|
+
return true
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
assert.deepEqual(calls, [
|
|
534
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
535
|
+
])
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test('update row rejects object value for image field and requires upload array', async () => {
|
|
539
|
+
const bodyFile = tempJSONFile({
|
|
540
|
+
changed_fields: [1008],
|
|
541
|
+
data: {
|
|
542
|
+
1008: {
|
|
543
|
+
upload_id: 123456,
|
|
544
|
+
file: 'lead-photo.jpg',
|
|
545
|
+
file_name: 'lead-photo.jpg',
|
|
546
|
+
meta: {},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
await assert.rejects(async () => {
|
|
552
|
+
await executeCLI(
|
|
553
|
+
'user',
|
|
554
|
+
['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile],
|
|
555
|
+
env as any,
|
|
556
|
+
async (url, init) => {
|
|
557
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
558
|
+
return new Response(JSON.stringify({
|
|
559
|
+
data: {
|
|
560
|
+
id: 1,
|
|
561
|
+
app_id: 1,
|
|
562
|
+
fields: [
|
|
563
|
+
{ id: 1008, type: 'image', label: 'Lead Photo' },
|
|
564
|
+
],
|
|
565
|
+
},
|
|
566
|
+
}), { status: 200 })
|
|
567
|
+
}
|
|
568
|
+
return new Response('{}', { status: 200 })
|
|
569
|
+
},
|
|
570
|
+
)
|
|
571
|
+
}, (error: unknown) => {
|
|
572
|
+
assert.ok(error instanceof CLIError)
|
|
573
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
574
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.data.1008'))
|
|
575
|
+
assert.match(error.message, /must be an array/)
|
|
576
|
+
assert.match(error.message, /arcubase upload <local-file>/)
|
|
577
|
+
return true
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
test('insert entity accepts upload array for file field after schema preflight', async () => {
|
|
582
|
+
const bodyFile = tempJSONFile({
|
|
583
|
+
fields: {
|
|
584
|
+
1001: 'Lead A',
|
|
585
|
+
1007: [
|
|
586
|
+
{
|
|
587
|
+
upload_id: 123456,
|
|
588
|
+
file: 'contract.pdf',
|
|
589
|
+
file_name: 'contract.pdf',
|
|
590
|
+
meta: {},
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
},
|
|
594
|
+
})
|
|
595
|
+
const calls: Array<{ url: string; method?: string }> = []
|
|
596
|
+
|
|
597
|
+
const out = await executeCLI(
|
|
598
|
+
'user',
|
|
599
|
+
['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile],
|
|
600
|
+
env as any,
|
|
601
|
+
async (url, init) => {
|
|
602
|
+
calls.push({ url: String(url), method: init?.method })
|
|
603
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
604
|
+
return new Response(JSON.stringify({
|
|
605
|
+
data: {
|
|
606
|
+
id: 1,
|
|
607
|
+
app_id: 1,
|
|
608
|
+
fields: [
|
|
609
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
610
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
611
|
+
],
|
|
612
|
+
},
|
|
613
|
+
}), { status: 200 })
|
|
614
|
+
}
|
|
615
|
+
return new Response(String(init?.body ?? '{}'), { status: 200 })
|
|
616
|
+
},
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
assert.equal(out.kind, 'result')
|
|
620
|
+
assert.deepEqual(calls, [
|
|
621
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
622
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1/insert', method: 'POST' },
|
|
623
|
+
])
|
|
624
|
+
assert.equal(out.data.fields['1007'][0].upload_id, 123456)
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
test('insert entity decorates upload bind failures with actionable guidance', async () => {
|
|
628
|
+
const bodyFile = tempJSONFile({
|
|
629
|
+
fields: {
|
|
630
|
+
1001: 'Lead A',
|
|
631
|
+
1007: [
|
|
632
|
+
{
|
|
633
|
+
upload_id: 123456,
|
|
634
|
+
file: 'contract.pdf',
|
|
635
|
+
file_name: 'contract.pdf',
|
|
636
|
+
meta: {},
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
await assert.rejects(async () => {
|
|
643
|
+
await executeCLI(
|
|
644
|
+
'user',
|
|
645
|
+
['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile],
|
|
646
|
+
env as any,
|
|
647
|
+
async (url, init) => {
|
|
648
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
649
|
+
return new Response(JSON.stringify({
|
|
650
|
+
data: {
|
|
651
|
+
fields: [{ id: 1007, type: 'file', label: 'Contract File' }],
|
|
652
|
+
},
|
|
653
|
+
}), { status: 200 })
|
|
654
|
+
}
|
|
655
|
+
return new Response(JSON.stringify({
|
|
656
|
+
error: {
|
|
657
|
+
message: 'failed to bind upload to assets: Head \"http://test-bucket.test.aliyuncs.com/upload/123456/contract.pdf\": dial tcp: lookup test-bucket.test.aliyuncs.com: no such host',
|
|
658
|
+
type: 'system',
|
|
659
|
+
key: 'error.system',
|
|
660
|
+
},
|
|
661
|
+
trace_id: 'req_upload_bind',
|
|
662
|
+
}), { status: 200 })
|
|
663
|
+
},
|
|
664
|
+
)
|
|
665
|
+
}, (error: unknown) => {
|
|
666
|
+
assert.ok(error instanceof CLIError)
|
|
667
|
+
assert.equal(error.code, 'UPSTREAM_BODY_ERROR')
|
|
668
|
+
assert.equal(error.details?.traceId, 'req_upload_bind')
|
|
669
|
+
assert.equal(error.details?.reason, 'the row payload used a file/image upload entry, but Arcubase could not bind that upload into an asset record')
|
|
670
|
+
assert.match(error.details?.hint ?? '', /rerun arcubase upload/)
|
|
671
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')))
|
|
672
|
+
return true
|
|
673
|
+
})
|
|
674
|
+
})
|
|
675
|
+
|
|
432
676
|
test('selection action rejects condition selection without selectAll', async () => {
|
|
433
677
|
const bodyFile = tempJSONFile({
|
|
434
678
|
selection: {
|
|
@@ -443,7 +687,7 @@ test('selection action rejects condition selection without selectAll', async ()
|
|
|
443
687
|
await assert.rejects(async () => {
|
|
444
688
|
await executeCLI(
|
|
445
689
|
'user',
|
|
446
|
-
['
|
|
690
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
447
691
|
env as any,
|
|
448
692
|
async () => new Response('{}', { status: 200 }),
|
|
449
693
|
)
|
|
@@ -470,7 +714,7 @@ test('selection action rejects condition selection that uses text_search instead
|
|
|
470
714
|
await assert.rejects(async () => {
|
|
471
715
|
await executeCLI(
|
|
472
716
|
'user',
|
|
473
|
-
['
|
|
717
|
+
['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile],
|
|
474
718
|
env as any,
|
|
475
719
|
async () => new Response('{}', { status: 200 }),
|
|
476
720
|
)
|
|
@@ -597,7 +841,7 @@ test('query entity rejects camelCase viewMode and points to row crud doc', async
|
|
|
597
841
|
await assert.rejects(async () => {
|
|
598
842
|
await executeCLI(
|
|
599
843
|
'user',
|
|
600
|
-
['
|
|
844
|
+
['rows', 'query-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile],
|
|
601
845
|
env as any,
|
|
602
846
|
async () => new Response('{}', { status: 200 }),
|
|
603
847
|
)
|
|
@@ -624,7 +868,7 @@ test('update entity row rejects fields key', async () => {
|
|
|
624
868
|
await assert.rejects(async () => {
|
|
625
869
|
await executeCLI(
|
|
626
870
|
'user',
|
|
627
|
-
['
|
|
871
|
+
['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile],
|
|
628
872
|
env as any,
|
|
629
873
|
async () => new Response('{}', { status: 200 }),
|
|
630
874
|
)
|
|
@@ -648,7 +892,7 @@ test('update entity row rejects changed_fields that are missing from data', asyn
|
|
|
648
892
|
await assert.rejects(async () => {
|
|
649
893
|
await executeCLI(
|
|
650
894
|
'user',
|
|
651
|
-
['
|
|
895
|
+
['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile],
|
|
652
896
|
env as any,
|
|
653
897
|
async () => new Response('{}', { status: 200 }),
|
|
654
898
|
)
|
package/src/tests/help.test.ts
CHANGED
|
@@ -28,10 +28,11 @@ test('module help prints create-entity command', async () => {
|
|
|
28
28
|
const out = await executeCLI('admin', ['app', '--help'], env as any, async () => new Response('{}'))
|
|
29
29
|
assert.equal(out.kind, 'help')
|
|
30
30
|
assert.match(out.text, /create-entity/)
|
|
31
|
+
assert.match(out.text, /inventory/)
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
test('module help does not require runtime env', async () => {
|
|
34
|
-
const out = await executeCLI('user', ['
|
|
35
|
+
const out = await executeCLI('user', ['rows', '--help'], undefined, async () => new Response('{}'))
|
|
35
36
|
assert.equal(out.kind, 'help')
|
|
36
37
|
assert.match(out.text, /delete-entity-row/)
|
|
37
38
|
})
|
|
@@ -44,21 +45,21 @@ test('command help prints normalized external endpoint', async () => {
|
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
test('command help does not require runtime env', async () => {
|
|
47
|
-
const out = await executeCLI('user', ['
|
|
48
|
+
const out = await executeCLI('user', ['rows', 'delete-entity-row', '--help'], undefined, async () => new Response('{}'))
|
|
48
49
|
assert.equal(out.kind, 'help')
|
|
49
50
|
assert.match(out.text, /endpoint: \/entity\/:app_id\/:entity_id\/delete-item/)
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
test('command help prints runtime doc hints for request body commands', async () => {
|
|
53
|
-
const out = await executeCLI('user', ['
|
|
54
|
+
const out = await executeCLI('user', ['rows', 'update-entity-row', '--help'], undefined, async () => new Response('{}'))
|
|
54
55
|
assert.equal(out.kind, 'help')
|
|
55
56
|
assert.match(out.text, /docs:/)
|
|
56
57
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/row-crud\.md/)
|
|
57
|
-
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/
|
|
58
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/)
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
test('command help prints query-file support', async () => {
|
|
61
|
-
const out = await executeCLI('user', ['
|
|
62
|
+
const out = await executeCLI('user', ['rows', 'query-entity', '--help'], undefined, async () => new Response('{}'))
|
|
62
63
|
assert.equal(out.kind, 'help')
|
|
63
64
|
assert.match(out.text, /query flags: --query-file/)
|
|
64
65
|
})
|
|
@@ -67,5 +68,33 @@ test('command help without request body can still point to lifecycle and example
|
|
|
67
68
|
const out = await executeCLI('admin', ['entity', 'admin-get-entity-info', '--help'], undefined, async () => new Response('{}'))
|
|
68
69
|
assert.equal(out.kind, 'help')
|
|
69
70
|
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/table-lifecycle\.md/)
|
|
70
|
-
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/
|
|
71
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('app inventory help explains app discovery purpose', async () => {
|
|
75
|
+
const out = await executeCLI('admin', ['app', 'inventory', '--help'], undefined, async () => new Response('{}'))
|
|
76
|
+
assert.equal(out.kind, 'help')
|
|
77
|
+
assert.match(out.text, /list all accessible apps and each app entity list/)
|
|
78
|
+
assert.match(out.text, /use this before asking the user for app_id/)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('admin-save-entity help prints row-operation next step', async () => {
|
|
82
|
+
const out = await executeCLI('admin', ['entity', 'admin-save-entity', '--help'], undefined, async () => new Response('{}'))
|
|
83
|
+
assert.equal(out.kind, 'help')
|
|
84
|
+
assert.match(out.text, /switch to arcubase for row operations/)
|
|
85
|
+
assert.match(out.text, /arcubase rows insert-entity/)
|
|
86
|
+
assert.match(out.text, /arcubase rows query-entity/)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('wrong admin workflow command points to arcubase row command', async () => {
|
|
90
|
+
await assert.rejects(
|
|
91
|
+
() => executeCLI('admin', ['workflow', 'insert-entity'], env as any, async () => new Response('{}')),
|
|
92
|
+
(error: any) => {
|
|
93
|
+
assert.equal(error.code, 'UNKNOWN_COMMAND')
|
|
94
|
+
assert.equal(error.details?.reason, 'workflow insert-entity is not a valid command group; use arcubase rows for row operations')
|
|
95
|
+
assert.equal(error.details?.hint, 'use: arcubase rows insert-entity')
|
|
96
|
+
assert.ok((error.details?.suggestions ?? []).includes('arcubase rows insert-entity'))
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
)
|
|
71
100
|
})
|