@carthooks/arcubase-cli 0.1.7 → 0.1.8
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 +1042 -5056
- package/bundle/arcubase.mjs +1042 -5056
- package/dist/generated/command_registry.generated.d.ts +693 -2812
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +812 -4100
- package/dist/generated/type_index.generated.d.ts +8 -397
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +8 -403
- package/dist/generated/zod_registry.generated.d.ts +33 -261
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +36 -365
- package/dist/runtime/body_loader.d.ts.map +1 -1
- package/dist/runtime/body_loader.js +12 -0
- package/dist/runtime/command_registry.d.ts +3 -21
- package/dist/runtime/command_registry.d.ts.map +1 -1
- package/dist/runtime/command_registry.js +17 -23
- package/dist/runtime/execute.d.ts +3 -3
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +53 -157
- package/dist/runtime/http.js +1 -1
- package/dist/runtime/upload.js +3 -3
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +7 -24
- package/dist/tests/command_registry.test.js +81 -28
- package/dist/tests/execute_validation.test.js +175 -510
- package/dist/tests/help.test.js +39 -62
- package/dist/tests/upload.test.js +4 -4
- package/dist/tests/zod_registry.test.js +0 -10
- package/package.json +1 -1
- package/sdk-dist/docs/runtime-reference/README.md +20 -30
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +4 -5
- package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +3 -3
- package/sdk-dist/docs/runtime-reference/entity-schema/query.md +3 -3
- package/sdk-dist/docs/runtime-reference/entity-schema/relation.md +3 -3
- package/sdk-dist/docs/runtime-reference/entity-schema/relationfield.md +3 -3
- package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema.md +4 -4
- package/sdk-dist/docs/runtime-reference/examples/README.md +2 -2
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +12 -12
- package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
- package/sdk-dist/docs/runtime-reference/row-crud.md +11 -32
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +16 -47
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +11 -27
- package/sdk-dist/docs/runtime-reference/uploads.md +1 -1
- package/sdk-dist/docs/runtime-reference/workflow/README.md +19 -0
- package/sdk-dist/generated/command_registry.generated.ts +814 -4102
- package/sdk-dist/generated/type_index.generated.ts +8 -403
- package/sdk-dist/generated/zod_registry.generated.ts +44 -500
- package/sdk-dist/types/app.ts +3 -10
- package/sdk-dist/types/common.ts +7 -0
- package/sdk-dist/types/global-action.ts +2 -1
- package/src/generated/command_registry.generated.ts +814 -4102
- package/src/generated/type_index.generated.ts +8 -403
- package/src/generated/zod_registry.generated.ts +44 -500
- package/src/runtime/body_loader.ts +11 -0
- package/src/runtime/command_registry.ts +22 -44
- package/src/runtime/execute.ts +58 -168
- package/src/runtime/http.ts +1 -1
- package/src/runtime/upload.ts +3 -3
- package/src/runtime/zod_registry.ts +7 -25
- package/src/tests/command_registry.test.ts +81 -31
- package/src/tests/execute_validation.test.ts +199 -640
- package/src/tests/help.test.ts +36 -69
- package/src/tests/upload.test.ts +4 -4
- package/src/tests/zod_registry.test.ts +0 -13
- package/sdk-dist/docs/runtime-reference/app-discovery.md +0 -68
|
@@ -11,6 +11,17 @@ function parseJSONFile(filePath: string, label: string): unknown {
|
|
|
11
11
|
|
|
12
12
|
export function loadBodyFromFlags(flags: Record<string, string | boolean>): unknown | undefined {
|
|
13
13
|
const bodyFile = typeof flags['body-file'] === 'string' ? flags['body-file'] : undefined
|
|
14
|
+
const bodyJSON = typeof flags['body-json'] === 'string' ? flags['body-json'] : undefined
|
|
15
|
+
if (bodyFile && bodyJSON) {
|
|
16
|
+
throw new CLIError('BODY_JSON_CONFLICT', 'use either --body-file or --body-json, not both', 2)
|
|
17
|
+
}
|
|
18
|
+
if (bodyJSON !== undefined) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(bodyJSON)
|
|
21
|
+
} catch {
|
|
22
|
+
throw new CLIError('INVALID_BODY_JSON', 'body json is invalid', 2)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
if (!bodyFile) return undefined
|
|
15
26
|
return parseJSONFile(bodyFile, 'body')
|
|
16
27
|
}
|
|
@@ -3,50 +3,22 @@ import { adminCommands, userCommands } from '../generated/command_registry.gener
|
|
|
3
3
|
export { adminCommands, userCommands }
|
|
4
4
|
|
|
5
5
|
export type CommandScope = 'admin' | 'user'
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
type CustomCommandDef = {
|
|
9
|
-
scope: CommandScope
|
|
10
|
-
module: string
|
|
11
|
-
functionName: string
|
|
12
|
-
commandPath: readonly [string, string]
|
|
13
|
-
method: string
|
|
14
|
-
endpoint: string
|
|
15
|
-
endpointParams: readonly string[]
|
|
16
|
-
controller: string
|
|
17
|
-
requestType: string | null
|
|
18
|
-
scalarParams: readonly { name: string; type: string; hasDefault: boolean }[]
|
|
19
|
-
responseType: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type CommandDef = GeneratedCommandDef | CustomCommandDef
|
|
23
|
-
|
|
24
|
-
const customAdminCommands: readonly CustomCommandDef[] = [
|
|
25
|
-
{
|
|
26
|
-
scope: 'admin',
|
|
27
|
-
module: 'app',
|
|
28
|
-
functionName: 'inventory',
|
|
29
|
-
commandPath: ['app', 'inventory'],
|
|
30
|
-
method: 'GET',
|
|
31
|
-
endpoint: '/apps',
|
|
32
|
-
endpointParams: [],
|
|
33
|
-
controller: 'Runtime.AppInventory',
|
|
34
|
-
requestType: null,
|
|
35
|
-
scalarParams: [],
|
|
36
|
-
responseType: 'AppInventoryRespVO',
|
|
37
|
-
},
|
|
38
|
-
] as const
|
|
39
|
-
|
|
40
|
-
const customUserCommands: readonly CustomCommandDef[] = []
|
|
6
|
+
export type CommandDef = (typeof adminCommands)[number] | (typeof userCommands)[number]
|
|
41
7
|
|
|
42
8
|
export function listCommands(scope: CommandScope): readonly CommandDef[] {
|
|
43
|
-
return scope === 'admin'
|
|
44
|
-
? [...adminCommands, ...customAdminCommands]
|
|
45
|
-
: [...userCommands, ...customUserCommands]
|
|
9
|
+
return scope === 'admin' ? adminCommands : userCommands
|
|
46
10
|
}
|
|
47
11
|
|
|
48
|
-
export function findCommand(scope: CommandScope, moduleName: string, commandName
|
|
49
|
-
return listCommands(scope).find((item) =>
|
|
12
|
+
export function findCommand(scope: CommandScope, moduleName: string, commandName?: string | null): CommandDef | null {
|
|
13
|
+
return listCommands(scope).find((item) => {
|
|
14
|
+
if (item.commandPath[0] !== moduleName) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
if (item.commandPath.length === 1) {
|
|
18
|
+
return !commandName
|
|
19
|
+
}
|
|
20
|
+
return item.commandPath[1] === commandName
|
|
21
|
+
}) ?? null
|
|
50
22
|
}
|
|
51
23
|
|
|
52
24
|
export function listModules(scope: CommandScope): string[] {
|
|
@@ -77,7 +49,7 @@ function levenshtein(a: string, b: string): number {
|
|
|
77
49
|
export type CommandSuggestion = {
|
|
78
50
|
scope: CommandScope
|
|
79
51
|
moduleName: string
|
|
80
|
-
commandName: string
|
|
52
|
+
commandName: string | null
|
|
81
53
|
binary: 'arcubase-admin' | 'arcubase'
|
|
82
54
|
score: number
|
|
83
55
|
}
|
|
@@ -86,9 +58,10 @@ export function findCommandSuggestions(moduleName: string, commandName: string,
|
|
|
86
58
|
const suggestions: CommandSuggestion[] = []
|
|
87
59
|
for (const scope of ['admin', 'user'] as const) {
|
|
88
60
|
for (const command of listCommands(scope)) {
|
|
89
|
-
const
|
|
61
|
+
const candidateModule = command.commandPath[0]
|
|
62
|
+
const candidateCommand: string | null = command.commandPath.length > 1 ? command.commandPath[1] ?? null : null
|
|
90
63
|
const moduleScore = candidateModule === moduleName ? 0 : levenshtein(candidateModule, moduleName || '')
|
|
91
|
-
const commandScore = candidateCommand === commandName ? 0 : levenshtein(candidateCommand, commandName || '')
|
|
64
|
+
const commandScore = candidateCommand === commandName ? 0 : levenshtein(candidateCommand ?? '', commandName || '')
|
|
92
65
|
const score = commandScore * 3 + moduleScore
|
|
93
66
|
suggestions.push({
|
|
94
67
|
scope,
|
|
@@ -100,6 +73,11 @@ export function findCommandSuggestions(moduleName: string, commandName: string,
|
|
|
100
73
|
}
|
|
101
74
|
}
|
|
102
75
|
return suggestions
|
|
103
|
-
.sort((a, b) =>
|
|
76
|
+
.sort((a, b) =>
|
|
77
|
+
a.score - b.score ||
|
|
78
|
+
a.binary.localeCompare(b.binary) ||
|
|
79
|
+
a.moduleName.localeCompare(b.moduleName) ||
|
|
80
|
+
(a.commandName ?? '').localeCompare(b.commandName ?? ''),
|
|
81
|
+
)
|
|
104
82
|
.slice(0, limit)
|
|
105
83
|
}
|
package/src/runtime/execute.ts
CHANGED
|
@@ -9,12 +9,12 @@ import { renderUploadHelp, uploadLocalFile } from './upload.js'
|
|
|
9
9
|
|
|
10
10
|
export type CommandExecutionSummary = {
|
|
11
11
|
scope: CommandScope
|
|
12
|
-
commandPath: readonly [
|
|
12
|
+
commandPath: readonly string[]
|
|
13
13
|
method: string
|
|
14
14
|
endpoint: string
|
|
15
15
|
requestType: string | null
|
|
16
16
|
pathFlags: readonly string[]
|
|
17
|
-
|
|
17
|
+
queryFlags: readonly string[]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type CustomUploadSummary = {
|
|
@@ -23,127 +23,19 @@ type CustomUploadSummary = {
|
|
|
23
23
|
data: ReturnType<typeof summarizeUploadResult>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
type AppInventoryItem = {
|
|
27
|
-
id: string
|
|
28
|
-
name: string
|
|
29
|
-
entities: Array<{ id: string; name: string }>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
26
|
function summarizeUploadResult(value: Awaited<ReturnType<typeof uploadLocalFile>>) {
|
|
33
27
|
return value
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
function parseJSONResponse(raw: string): unknown {
|
|
37
|
-
if (!raw) return null
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(raw)
|
|
40
|
-
} catch {
|
|
41
|
-
return raw
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function unwrapResponseData(payload: unknown): unknown {
|
|
46
|
-
return isRecord(payload) && 'data' in payload ? payload.data : payload
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function formatUpstreamError(payload: unknown): string {
|
|
50
|
-
if (typeof payload === 'string') {
|
|
51
|
-
return payload
|
|
52
|
-
}
|
|
53
|
-
if (isRecord(payload) && isRecord(payload.error) && typeof payload.error.message === 'string') {
|
|
54
|
-
return payload.error.message
|
|
55
|
-
}
|
|
56
|
-
return JSON.stringify(payload)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function extractApps(payload: unknown): Array<{ id: string; name: string }> {
|
|
60
|
-
const data = unwrapResponseData(payload)
|
|
61
|
-
if (!Array.isArray(data)) return []
|
|
62
|
-
return data
|
|
63
|
-
.filter((item): item is Record<string, unknown> => isRecord(item))
|
|
64
|
-
.filter((item) => item.id !== undefined && typeof item.name === 'string')
|
|
65
|
-
.map((item) => ({
|
|
66
|
-
id: String(item.id),
|
|
67
|
-
name: item.name as string,
|
|
68
|
-
}))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function extractEntities(payload: unknown): Array<{ id: string; name: string }> {
|
|
72
|
-
const data = unwrapResponseData(payload)
|
|
73
|
-
if (!Array.isArray(data)) return []
|
|
74
|
-
return data
|
|
75
|
-
.filter((item): item is Record<string, unknown> => isRecord(item))
|
|
76
|
-
.filter((item) => item.id !== undefined && typeof item.name === 'string')
|
|
77
|
-
.map((item) => ({
|
|
78
|
-
id: String(item.id),
|
|
79
|
-
name: item.name as string,
|
|
80
|
-
}))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function executeAppInventory(runtimeEnv: RuntimeEnv, fetchImpl: typeof fetch) {
|
|
84
|
-
const appsResponse = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, '/apps'), {
|
|
85
|
-
method: 'GET',
|
|
86
|
-
headers: buildRequestHeaders(runtimeEnv),
|
|
87
|
-
})
|
|
88
|
-
const appsRaw = await appsResponse.text()
|
|
89
|
-
const appsPayload = parseJSONResponse(appsRaw)
|
|
90
|
-
if (!appsResponse.ok) {
|
|
91
|
-
throw new CLIError('UPSTREAM_REQUEST_FAILED', formatUpstreamError(appsPayload), 1)
|
|
92
|
-
}
|
|
93
|
-
if (isRecord(appsPayload) && appsPayload.error) {
|
|
94
|
-
throw new CLIError('UPSTREAM_BODY_ERROR', formatUpstreamError(appsPayload), 1, {
|
|
95
|
-
endpoint: '/apps',
|
|
96
|
-
method: 'GET',
|
|
97
|
-
upstreamError: appsPayload.error,
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
const apps = extractApps(appsPayload)
|
|
101
|
-
const items: AppInventoryItem[] = []
|
|
102
|
-
for (const app of apps) {
|
|
103
|
-
const entitiesResponse = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, `/apps/${encodeURIComponent(app.id)}/entities`), {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: {
|
|
106
|
-
...buildRequestHeaders(runtimeEnv),
|
|
107
|
-
'Content-Type': 'application/json',
|
|
108
|
-
},
|
|
109
|
-
body: JSON.stringify({}),
|
|
110
|
-
})
|
|
111
|
-
const entitiesRaw = await entitiesResponse.text()
|
|
112
|
-
const entitiesPayload = parseJSONResponse(entitiesRaw)
|
|
113
|
-
if (!entitiesResponse.ok) {
|
|
114
|
-
throw new CLIError('UPSTREAM_REQUEST_FAILED', formatUpstreamError(entitiesPayload), 1)
|
|
115
|
-
}
|
|
116
|
-
if (isRecord(entitiesPayload) && entitiesPayload.error) {
|
|
117
|
-
throw new CLIError('UPSTREAM_BODY_ERROR', formatUpstreamError(entitiesPayload), 1, {
|
|
118
|
-
endpoint: `/apps/${app.id}/entities`,
|
|
119
|
-
method: 'POST',
|
|
120
|
-
upstreamError: entitiesPayload.error,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
items.push({
|
|
124
|
-
...app,
|
|
125
|
-
entities: extractEntities(entitiesPayload),
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
kind: 'result',
|
|
130
|
-
commandPath: ['app', 'inventory'] as const,
|
|
131
|
-
status: 200,
|
|
132
|
-
data: {
|
|
133
|
-
apps: items,
|
|
134
|
-
},
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
30
|
export function renderRootHelp(scope: CommandScope): string {
|
|
139
31
|
const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase'
|
|
140
32
|
const items = listModules(scope).map((item) => ` - ${item}`)
|
|
141
33
|
return [
|
|
142
|
-
`${binary} <
|
|
34
|
+
`${binary} <command> [subcommand] [flags]`,
|
|
143
35
|
'',
|
|
144
36
|
...(scope === 'admin'
|
|
145
|
-
? ['use arcubase-admin for app,
|
|
146
|
-
: ['use arcubase for
|
|
37
|
+
? ['use arcubase-admin for app, table, access-rule, and workflow management', '']
|
|
38
|
+
: ['use arcubase for entry, table, row, upload, profile, and workflow actions', '']),
|
|
147
39
|
'modules:',
|
|
148
40
|
...items,
|
|
149
41
|
...(scope === 'user' ? ['','special commands:',' - upload <local-file>'] : []),
|
|
@@ -153,62 +45,52 @@ export function renderRootHelp(scope: CommandScope): string {
|
|
|
153
45
|
export function renderModuleHelp(scope: CommandScope, moduleName: string): string {
|
|
154
46
|
const items = listModuleCommands(scope, moduleName)
|
|
155
47
|
if (items.length === 0) {
|
|
156
|
-
const suggestions = findCommandSuggestions(moduleName, '').map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`)
|
|
48
|
+
const suggestions = findCommandSuggestions(moduleName, '').map((item) => `${item.binary} ${item.moduleName}${item.commandName ? ` ${item.commandName}` : ''}`)
|
|
157
49
|
throw new CLIError('UNKNOWN_MODULE', `unknown module: ${moduleName}`, 2, suggestions.length > 0 ? { suggestions } : undefined)
|
|
158
50
|
}
|
|
51
|
+
if (items.length === 1 && items[0].commandPath.length === 1) {
|
|
52
|
+
return renderCommandHelp(scope, moduleName)
|
|
53
|
+
}
|
|
159
54
|
return [
|
|
160
55
|
`${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} <command> [flags]`,
|
|
161
56
|
'',
|
|
162
57
|
...(scope === 'admin'
|
|
163
|
-
? ['use arcubase-admin for app,
|
|
164
|
-
: ['use arcubase for
|
|
58
|
+
? ['use arcubase-admin for app, table, access-rule, and workflow management', '']
|
|
59
|
+
: ['use arcubase for entry, table, row, upload, profile, and workflow actions', '']),
|
|
165
60
|
'commands:',
|
|
166
|
-
...items.map((item) => ` - ${item.commandPath[1]}`),
|
|
61
|
+
...items.filter((item) => item.commandPath.length > 1).map((item) => ` - ${item.commandPath[1]}`),
|
|
167
62
|
].join('\n')
|
|
168
63
|
}
|
|
169
64
|
|
|
170
65
|
function buildUnknownCommandDetails(scope: CommandScope, moduleName: string, commandName: string): { suggestions?: string[]; reason?: string; hint?: string } | undefined {
|
|
171
|
-
const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`)
|
|
172
|
-
if (scope === 'admin' && moduleName === 'workflow' && suggestions.some((item) => item.startsWith('arcubase rows '))) {
|
|
173
|
-
return {
|
|
174
|
-
suggestions,
|
|
175
|
-
reason: `${moduleName} ${commandName} is not a valid command group; use arcubase rows for row operations`,
|
|
176
|
-
hint: `use: arcubase rows ${commandName}`,
|
|
177
|
-
}
|
|
178
|
-
}
|
|
66
|
+
const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName}${item.commandName ? ` ${item.commandName}` : ''}`)
|
|
179
67
|
return suggestions.length > 0 ? { suggestions } : undefined
|
|
180
68
|
}
|
|
181
69
|
|
|
182
|
-
export function renderCommandHelp(scope: CommandScope, moduleName: string, commandName
|
|
70
|
+
export function renderCommandHelp(scope: CommandScope, moduleName: string, commandName?: string): string {
|
|
183
71
|
const command = findCommand(scope, moduleName, commandName)
|
|
184
72
|
if (!command) {
|
|
185
|
-
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName))
|
|
73
|
+
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName}${commandName ? ` ${commandName}` : ''}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName ?? ''))
|
|
186
74
|
}
|
|
187
75
|
const docHints = [
|
|
188
76
|
...getCommandDocHints(`${scope}.${command.module}.${command.functionName}`),
|
|
189
77
|
...(command.requestType ? getDocHints(command.requestType) : []),
|
|
190
78
|
].filter((item, index, list) => list.findIndex((candidate) => candidate.file === item.file) === index)
|
|
79
|
+
const pathFlags = command.pathParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag}`)
|
|
80
|
+
const queryFlags = command.queryParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag}`)
|
|
191
81
|
return [
|
|
192
|
-
`${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${
|
|
82
|
+
`${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${command.commandPath.join(' ')}`,
|
|
193
83
|
`method: ${command.method}`,
|
|
194
84
|
`endpoint: ${normalizeExternalEndpoint(command.endpoint)}`,
|
|
195
|
-
command.requestType ? `body: ${command.requestType} via --body-file` : 'body: none',
|
|
196
|
-
'query flags: --query-file',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
...(scope === 'admin' && moduleName === 'entity' && commandName === 'admin-save-entity'
|
|
85
|
+
command.requestType ? `body: ${command.requestType} via --body-file or --body-json` : 'body: none',
|
|
86
|
+
queryFlags.length ? `query flags: ${queryFlags.join(', ')}, --query-file` : 'query flags: --query-file',
|
|
87
|
+
pathFlags.length ? `path flags: ${pathFlags.join(', ')}` : 'path flags: none',
|
|
88
|
+
...(scope === 'admin' && command.commandPath[0] === 'table' && command.commandPath[1] === 'update-schema'
|
|
200
89
|
? [
|
|
201
90
|
'next step:',
|
|
202
91
|
' - switch to arcubase for row operations',
|
|
203
|
-
' - row
|
|
204
|
-
' - row query: arcubase
|
|
205
|
-
]
|
|
206
|
-
: []),
|
|
207
|
-
...(scope === 'admin' && moduleName === 'app' && commandName === 'inventory'
|
|
208
|
-
? [
|
|
209
|
-
'purpose:',
|
|
210
|
-
' - list all accessible apps and each app entity list in one call sequence',
|
|
211
|
-
' - use this before asking the user for app_id',
|
|
92
|
+
' - row create: arcubase row create --app-id <app_id> --table-id <table_id> --body-file insert.json',
|
|
93
|
+
' - row query: arcubase row query --app-id <app_id> --table-id <table_id> --body-file query.json',
|
|
212
94
|
]
|
|
213
95
|
: []),
|
|
214
96
|
...(docHints.length > 0
|
|
@@ -234,15 +116,22 @@ function coerceScalar(value: string, typeText: string): any {
|
|
|
234
116
|
return value
|
|
235
117
|
}
|
|
236
118
|
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
119
|
+
function getFixedValue(value: object): string | undefined {
|
|
120
|
+
if (!('fixedValue' in value)) {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
const fixedValue = (value as { fixedValue?: unknown }).fixedValue
|
|
124
|
+
return typeof fixedValue === 'string' ? fixedValue : undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveEndpoint(command: NonNullable<ReturnType<typeof findCommand>>, flags: Record<string, string | boolean>): string {
|
|
128
|
+
let resolved: string = command.endpoint
|
|
129
|
+
for (const mapping of command.pathParams) {
|
|
130
|
+
const value = getFixedValue(mapping) ?? flagValue(flags, mapping.flag)
|
|
242
131
|
if (!value) {
|
|
243
|
-
throw new CLIError('MISSING_PATH_FLAG', `missing required flag --${
|
|
132
|
+
throw new CLIError('MISSING_PATH_FLAG', `missing required flag --${mapping.flag}`, 2)
|
|
244
133
|
}
|
|
245
|
-
resolved = resolved.replace(`:${
|
|
134
|
+
resolved = resolved.replace(`:${mapping.param}`, encodeURIComponent(value))
|
|
246
135
|
}
|
|
247
136
|
return resolved
|
|
248
137
|
}
|
|
@@ -252,11 +141,10 @@ function buildScalarQuery(command: ReturnType<typeof findCommand>, flags: Record
|
|
|
252
141
|
return undefined
|
|
253
142
|
}
|
|
254
143
|
const out: Record<string, any> = {}
|
|
255
|
-
for (const scalar of command.
|
|
256
|
-
const
|
|
257
|
-
const value = flagValue(flags, flagName)
|
|
144
|
+
for (const scalar of command.queryParams) {
|
|
145
|
+
const value = getFixedValue(scalar) ?? flagValue(flags, scalar.flag)
|
|
258
146
|
if (value === undefined) continue
|
|
259
|
-
out[scalar.
|
|
147
|
+
out[scalar.key] = coerceScalar(value, scalar.type)
|
|
260
148
|
}
|
|
261
149
|
return Object.keys(out).length > 0 ? out : undefined
|
|
262
150
|
}
|
|
@@ -292,7 +180,7 @@ async function fetchEntityFields(
|
|
|
292
180
|
entityId: string,
|
|
293
181
|
fetchImpl: typeof fetch,
|
|
294
182
|
): Promise<Map<number, EntityFieldInfo>> {
|
|
295
|
-
const response = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, `/entity/${encodeURIComponent(appId)}/${encodeURIComponent(entityId)}`), {
|
|
183
|
+
const response = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, `/api/entity/${encodeURIComponent(appId)}/${encodeURIComponent(entityId)}`), {
|
|
296
184
|
method: 'GET',
|
|
297
185
|
headers: buildRequestHeaders(runtimeEnv),
|
|
298
186
|
})
|
|
@@ -315,7 +203,7 @@ async function fetchEntityFields(
|
|
|
315
203
|
? upstreamError
|
|
316
204
|
: JSON.stringify(upstreamError)
|
|
317
205
|
throw new CLIError('UPSTREAM_BODY_ERROR', upstreamMessage, 1, {
|
|
318
|
-
endpoint: `/entity/${appId}/${entityId}`,
|
|
206
|
+
endpoint: `/api/entity/${appId}/${entityId}`,
|
|
319
207
|
method: 'GET',
|
|
320
208
|
upstreamError,
|
|
321
209
|
})
|
|
@@ -359,7 +247,7 @@ async function validateFileFieldPayloads(
|
|
|
359
247
|
return
|
|
360
248
|
}
|
|
361
249
|
const appId = flagValue(flags, 'app-id')
|
|
362
|
-
const entityId = flagValue(flags, '
|
|
250
|
+
const entityId = flagValue(flags, 'table-id')
|
|
363
251
|
if (!appId || !entityId) {
|
|
364
252
|
return
|
|
365
253
|
}
|
|
@@ -455,7 +343,7 @@ function validateActionSpecificBody(
|
|
|
455
343
|
return
|
|
456
344
|
}
|
|
457
345
|
|
|
458
|
-
if (command.
|
|
346
|
+
if (command.commandPath[0] === 'row' && command.commandPath[1] === 'selection-action') {
|
|
459
347
|
const action = flagValue(flags, 'action')
|
|
460
348
|
const selection = body.selection
|
|
461
349
|
if (!isRecord(selection) || typeof selection.type !== 'string') {
|
|
@@ -469,7 +357,7 @@ function validateActionSpecificBody(
|
|
|
469
357
|
if (action === 'query') {
|
|
470
358
|
if (type === 'condition') {
|
|
471
359
|
throwBodyValidationFailure(
|
|
472
|
-
'selection.type=condition is not supported for action=query; use selection.type=ids or selection.type=all, or use
|
|
360
|
+
'selection.type=condition is not supported for action=query; use selection.type=ids or selection.type=all, or use row query for search conditions',
|
|
473
361
|
command.requestType,
|
|
474
362
|
'body.selection.type',
|
|
475
363
|
scope,
|
|
@@ -510,10 +398,10 @@ export function summarizeCommand(scope: CommandScope, moduleName: string, comman
|
|
|
510
398
|
scope,
|
|
511
399
|
commandPath: command.commandPath,
|
|
512
400
|
method: command.method,
|
|
513
|
-
endpoint:
|
|
401
|
+
endpoint: command.endpoint,
|
|
514
402
|
requestType: command.requestType,
|
|
515
|
-
pathFlags: command.
|
|
516
|
-
|
|
403
|
+
pathFlags: command.pathParams.filter((item) => !getFixedValue(item)).map((item) => item.flag),
|
|
404
|
+
queryFlags: command.queryParams.filter((item) => !getFixedValue(item)).map((item) => item.flag),
|
|
517
405
|
}
|
|
518
406
|
}
|
|
519
407
|
|
|
@@ -551,7 +439,8 @@ export async function executeCLI(scope: CommandScope, argv: string[], env?: Runt
|
|
|
551
439
|
if (!moduleName) {
|
|
552
440
|
return { kind: 'help', text: renderRootHelp(scope) }
|
|
553
441
|
}
|
|
554
|
-
|
|
442
|
+
const singleTokenCommand = !commandName ? findCommand(scope, moduleName) : null
|
|
443
|
+
if (!commandName && !singleTokenCommand) {
|
|
555
444
|
return { kind: 'help', text: renderModuleHelp(scope, moduleName) }
|
|
556
445
|
}
|
|
557
446
|
if (hasFlag(parsed.flags, 'help')) {
|
|
@@ -562,22 +451,23 @@ export async function executeCLI(scope: CommandScope, argv: string[], env?: Runt
|
|
|
562
451
|
|
|
563
452
|
const command = findCommand(scope, moduleName, commandName)
|
|
564
453
|
if (!command) {
|
|
565
|
-
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName))
|
|
566
|
-
}
|
|
567
|
-
if (scope === 'admin' && moduleName === 'app' && commandName === 'inventory') {
|
|
568
|
-
return executeAppInventory(runtimeEnv, fetchImpl)
|
|
454
|
+
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName}${commandName ? ` ${commandName}` : ''}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName ?? ''))
|
|
569
455
|
}
|
|
570
456
|
|
|
571
|
-
const endpoint =
|
|
457
|
+
const endpoint = resolveEndpoint(command, parsed.flags)
|
|
572
458
|
const scalarQuery = buildScalarQuery(command, parsed.flags)
|
|
573
459
|
const fileQuery = loadQueryFromFlags(parsed.flags)
|
|
574
460
|
const query = { ...(scalarQuery ?? {}), ...(fileQuery ?? {}) }
|
|
575
461
|
const body = loadBodyFromFlags(parsed.flags)
|
|
576
462
|
let validatedBody = body
|
|
577
463
|
|
|
464
|
+
if (!command.requestType && body !== undefined) {
|
|
465
|
+
throw new CLIError('BODY_JSON_NOT_SUPPORTED', `command does not accept a request body: ${moduleName} ${commandName}`, 2)
|
|
466
|
+
}
|
|
467
|
+
|
|
578
468
|
if (command.requestType) {
|
|
579
469
|
if (body === undefined) {
|
|
580
|
-
throw new CLIError('MISSING_BODY_FILE', `command requires --body-file for ${command.requestType}`, 2)
|
|
470
|
+
throw new CLIError('MISSING_BODY_FILE', `command requires --body-file or --body-json for ${command.requestType}`, 2)
|
|
581
471
|
}
|
|
582
472
|
const schema = getBodySchema(command.requestType)
|
|
583
473
|
if (schema) {
|
package/src/runtime/http.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function normalizeExternalEndpoint(endpoint: string): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function buildURL(baseURL: string, endpoint: string, query?: Record<string, any>): string {
|
|
25
|
-
const url = new URL(
|
|
25
|
+
const url = new URL(endpoint, baseURL.endsWith('/') ? baseURL : `${baseURL}/`)
|
|
26
26
|
if (query) {
|
|
27
27
|
for (const [key, value] of Object.entries(query)) {
|
|
28
28
|
if (value === undefined || value === null) continue
|
package/src/runtime/upload.ts
CHANGED
|
@@ -64,7 +64,7 @@ function requireTokenPayload(payload: UploadTokenResponse): NonNullable<UploadTo
|
|
|
64
64
|
? upstreamError
|
|
65
65
|
: JSON.stringify(upstreamError)
|
|
66
66
|
throw new CLIError('UPSTREAM_BODY_ERROR', message, 1, {
|
|
67
|
-
endpoint: '/upload/token',
|
|
67
|
+
endpoint: '/api/upload/token',
|
|
68
68
|
method: 'POST',
|
|
69
69
|
traceId: typeof payload.trace_id === 'string' ? payload.trace_id : undefined,
|
|
70
70
|
upstreamError,
|
|
@@ -116,7 +116,7 @@ export async function uploadLocalFile(
|
|
|
116
116
|
const { absolutePath, bytes } = readUploadSource(sourcePath)
|
|
117
117
|
const filename = (options.filename && options.filename.trim()) || path.basename(absolutePath)
|
|
118
118
|
|
|
119
|
-
const tokenResponse = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, '/upload/token'), {
|
|
119
|
+
const tokenResponse = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, '/api/upload/token'), {
|
|
120
120
|
method: 'POST',
|
|
121
121
|
headers: {
|
|
122
122
|
...buildRequestHeaders(env),
|
|
@@ -131,7 +131,7 @@ export async function uploadLocalFile(
|
|
|
131
131
|
const tokenParsed = parseJSONResponse(tokenRaw)
|
|
132
132
|
if (!tokenResponse.ok) {
|
|
133
133
|
throw new CLIError('UPLOAD_TOKEN_REQUEST_FAILED', typeof tokenParsed === 'string' ? tokenParsed : JSON.stringify(tokenParsed), 1, {
|
|
134
|
-
endpoint: '/upload/token',
|
|
134
|
+
endpoint: '/api/upload/token',
|
|
135
135
|
method: 'POST',
|
|
136
136
|
})
|
|
137
137
|
}
|
|
@@ -67,19 +67,12 @@ const examplesIndexDocHint: RuntimeDocHint = {
|
|
|
67
67
|
file: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const appDiscoveryDocHint: RuntimeDocHint = {
|
|
71
|
-
title: 'App discovery',
|
|
72
|
-
file: '/opt/arcubase-sdk/docs/runtime-reference/app-discovery.md',
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
const docHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
76
71
|
AppCreateByTenantsReqVO: [
|
|
77
|
-
appDiscoveryDocHint,
|
|
78
72
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
79
73
|
examplesIndexDocHint,
|
|
80
74
|
],
|
|
81
75
|
AppCreateEntityReqVO: [
|
|
82
|
-
appDiscoveryDocHint,
|
|
83
76
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
84
77
|
examplesIndexDocHint,
|
|
85
78
|
],
|
|
@@ -155,25 +148,14 @@ export function getDocHints(typeName: string): RuntimeDocHint[] {
|
|
|
155
148
|
|
|
156
149
|
const commandDocHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
157
150
|
'admin.app.createAppByTenants': [
|
|
158
|
-
appDiscoveryDocHint,
|
|
159
151
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
160
152
|
examplesIndexDocHint,
|
|
161
153
|
],
|
|
162
|
-
'admin.app.listApps': [
|
|
163
|
-
appDiscoveryDocHint,
|
|
164
|
-
examplesIndexDocHint,
|
|
165
|
-
],
|
|
166
|
-
'admin.app.inventory': [
|
|
167
|
-
appDiscoveryDocHint,
|
|
168
|
-
examplesIndexDocHint,
|
|
169
|
-
],
|
|
170
154
|
'admin.app.createEntity': [
|
|
171
|
-
appDiscoveryDocHint,
|
|
172
155
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
173
156
|
examplesIndexDocHint,
|
|
174
157
|
],
|
|
175
158
|
'admin.entity.adminGetEntityInfo': [
|
|
176
|
-
appDiscoveryDocHint,
|
|
177
159
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
178
160
|
examplesIndexDocHint,
|
|
179
161
|
],
|
|
@@ -183,34 +165,34 @@ const commandDocHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
|
183
165
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
184
166
|
examplesIndexDocHint,
|
|
185
167
|
],
|
|
186
|
-
'user.
|
|
168
|
+
'user.workflow.insertEntity': [
|
|
187
169
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
188
170
|
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
189
171
|
examplesIndexDocHint,
|
|
190
172
|
],
|
|
191
|
-
'user.
|
|
173
|
+
'user.workflow.queryEntity': [
|
|
192
174
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
193
175
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
194
176
|
examplesIndexDocHint,
|
|
195
177
|
],
|
|
196
|
-
'user.
|
|
178
|
+
'user.workflow.getEntityRow': [
|
|
197
179
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
198
180
|
examplesIndexDocHint,
|
|
199
181
|
],
|
|
200
|
-
'user.
|
|
182
|
+
'user.workflow.updateEntityRow': [
|
|
201
183
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
202
184
|
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
203
185
|
examplesIndexDocHint,
|
|
204
186
|
],
|
|
205
|
-
'user.
|
|
187
|
+
'user.workflow.queryEntitySelection': [
|
|
206
188
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
207
189
|
examplesIndexDocHint,
|
|
208
190
|
],
|
|
209
|
-
'user.
|
|
191
|
+
'user.workflow.deleteEntityRow': [
|
|
210
192
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
211
193
|
examplesIndexDocHint,
|
|
212
194
|
],
|
|
213
|
-
'user.
|
|
195
|
+
'user.workflow.restoreEntityRow': [
|
|
214
196
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
215
197
|
examplesIndexDocHint,
|
|
216
198
|
],
|