@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.
Files changed (66) hide show
  1. package/bundle/arcubase-admin.mjs +1042 -5056
  2. package/bundle/arcubase.mjs +1042 -5056
  3. package/dist/generated/command_registry.generated.d.ts +693 -2812
  4. package/dist/generated/command_registry.generated.d.ts.map +1 -1
  5. package/dist/generated/command_registry.generated.js +812 -4100
  6. package/dist/generated/type_index.generated.d.ts +8 -397
  7. package/dist/generated/type_index.generated.d.ts.map +1 -1
  8. package/dist/generated/type_index.generated.js +8 -403
  9. package/dist/generated/zod_registry.generated.d.ts +33 -261
  10. package/dist/generated/zod_registry.generated.d.ts.map +1 -1
  11. package/dist/generated/zod_registry.generated.js +36 -365
  12. package/dist/runtime/body_loader.d.ts.map +1 -1
  13. package/dist/runtime/body_loader.js +12 -0
  14. package/dist/runtime/command_registry.d.ts +3 -21
  15. package/dist/runtime/command_registry.d.ts.map +1 -1
  16. package/dist/runtime/command_registry.js +17 -23
  17. package/dist/runtime/execute.d.ts +3 -3
  18. package/dist/runtime/execute.d.ts.map +1 -1
  19. package/dist/runtime/execute.js +53 -157
  20. package/dist/runtime/http.js +1 -1
  21. package/dist/runtime/upload.js +3 -3
  22. package/dist/runtime/zod_registry.d.ts.map +1 -1
  23. package/dist/runtime/zod_registry.js +7 -24
  24. package/dist/tests/command_registry.test.js +81 -28
  25. package/dist/tests/execute_validation.test.js +175 -510
  26. package/dist/tests/help.test.js +39 -62
  27. package/dist/tests/upload.test.js +4 -4
  28. package/dist/tests/zod_registry.test.js +0 -10
  29. package/package.json +1 -1
  30. package/sdk-dist/docs/runtime-reference/README.md +20 -30
  31. package/sdk-dist/docs/runtime-reference/entity-schema/README.md +4 -5
  32. package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +3 -3
  33. package/sdk-dist/docs/runtime-reference/entity-schema/query.md +3 -3
  34. package/sdk-dist/docs/runtime-reference/entity-schema/relation.md +3 -3
  35. package/sdk-dist/docs/runtime-reference/entity-schema/relationfield.md +3 -3
  36. package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
  37. package/sdk-dist/docs/runtime-reference/entity-schema.md +4 -4
  38. package/sdk-dist/docs/runtime-reference/examples/README.md +2 -2
  39. package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +12 -12
  40. package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
  41. package/sdk-dist/docs/runtime-reference/row-crud.md +11 -32
  42. package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +16 -47
  43. package/sdk-dist/docs/runtime-reference/table-lifecycle.md +11 -27
  44. package/sdk-dist/docs/runtime-reference/uploads.md +1 -1
  45. package/sdk-dist/docs/runtime-reference/workflow/README.md +19 -0
  46. package/sdk-dist/generated/command_registry.generated.ts +814 -4102
  47. package/sdk-dist/generated/type_index.generated.ts +8 -403
  48. package/sdk-dist/generated/zod_registry.generated.ts +44 -500
  49. package/sdk-dist/types/app.ts +3 -10
  50. package/sdk-dist/types/common.ts +7 -0
  51. package/sdk-dist/types/global-action.ts +2 -1
  52. package/src/generated/command_registry.generated.ts +814 -4102
  53. package/src/generated/type_index.generated.ts +8 -403
  54. package/src/generated/zod_registry.generated.ts +44 -500
  55. package/src/runtime/body_loader.ts +11 -0
  56. package/src/runtime/command_registry.ts +22 -44
  57. package/src/runtime/execute.ts +58 -168
  58. package/src/runtime/http.ts +1 -1
  59. package/src/runtime/upload.ts +3 -3
  60. package/src/runtime/zod_registry.ts +7 -25
  61. package/src/tests/command_registry.test.ts +81 -31
  62. package/src/tests/execute_validation.test.ts +199 -640
  63. package/src/tests/help.test.ts +36 -69
  64. package/src/tests/upload.test.ts +4 -4
  65. package/src/tests/zod_registry.test.ts +0 -13
  66. 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 GeneratedCommandDef = (typeof adminCommands)[number] | (typeof userCommands)[number]
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: string): CommandDef | null {
49
- return listCommands(scope).find((item) => item.commandPath[0] === moduleName && item.commandPath[1] === commandName) ?? null
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 [candidateModule, candidateCommand] = command.commandPath
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) => a.score - b.score || a.binary.localeCompare(b.binary) || a.moduleName.localeCompare(b.moduleName) || a.commandName.localeCompare(b.commandName))
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
  }
@@ -9,12 +9,12 @@ import { renderUploadHelp, uploadLocalFile } from './upload.js'
9
9
 
10
10
  export type CommandExecutionSummary = {
11
11
  scope: CommandScope
12
- commandPath: readonly [string, string]
12
+ commandPath: readonly string[]
13
13
  method: string
14
14
  endpoint: string
15
15
  requestType: string | null
16
16
  pathFlags: readonly string[]
17
- scalarFlags: readonly string[]
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} <module> <command> [flags]`,
34
+ `${binary} <command> [subcommand] [flags]`,
143
35
  '',
144
36
  ...(scope === 'admin'
145
- ? ['use arcubase-admin for app, entity shell, and schema management', '']
146
- : ['use arcubase for rows, search, selection actions, and bulk actions', '']),
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, entity shell, and schema management', '']
164
- : ['use arcubase for rows, search, selection actions, and bulk actions', '']),
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: string): string {
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'} ${moduleName} ${commandName}`,
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
- command.endpointParams.length ? `path flags: ${command.endpointParams.map((item) => `--${item.replace(/_/g, '-')}`).join(', ')}` : 'path flags: none',
198
- command.scalarParams.length ? `scalar flags: ${command.scalarParams.map((item) => `--${item.name.replace(/([A-Z])/g, '-$1').toLowerCase()}`).join(', ')}` : 'scalar flags: none',
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 insert: arcubase rows insert-entity --app-id <app_id> --entity-id <entity_id> --body-file insert.json',
204
- ' - row query: arcubase rows query-entity --app-id <app_id> --entity-id <entity_id> --body-file query.json',
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 resolveEndpoint(endpoint: string, flags: Record<string, string | boolean>): string {
238
- let resolved = endpoint
239
- for (const placeholder of [...endpoint.matchAll(/:([A-Za-z0-9_]+)/g)].map((match) => match[1])) {
240
- const flagName = placeholder.replace(/_/g, '-')
241
- const value = flagValue(flags, flagName)
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 --${flagName}`, 2)
132
+ throw new CLIError('MISSING_PATH_FLAG', `missing required flag --${mapping.flag}`, 2)
244
133
  }
245
- resolved = resolved.replace(`:${placeholder}`, encodeURIComponent(value))
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.scalarParams) {
256
- const flagName = scalar.name.replace(/([A-Z])/g, '-$1').toLowerCase()
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.name] = coerceScalar(value, scalar.type)
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, 'entity-id')
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.module === 'rows' && command.functionName === 'queryEntitySelection') {
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 rows query-entity for search conditions',
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: normalizeExternalEndpoint(command.endpoint),
401
+ endpoint: command.endpoint,
514
402
  requestType: command.requestType,
515
- pathFlags: command.endpointParams,
516
- scalarFlags: command.scalarParams.map((item) => item.name.replace(/([A-Z])/g, '-$1').toLowerCase()),
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
- if (!commandName) {
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 = normalizeExternalEndpoint(resolveEndpoint(command.endpoint, parsed.flags))
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) {
@@ -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(normalizeExternalEndpoint(endpoint), baseURL.endsWith('/') ? baseURL : `${baseURL}/`)
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
@@ -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.rows.insertEntity': [
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.rows.queryEntity': [
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.rows.getEntityRow': [
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.rows.updateEntityRow': [
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.rows.queryEntitySelection': [
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.rows.deleteEntityRow': [
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.rows.restoreEntityRow': [
195
+ 'user.workflow.restoreEntityRow': [
214
196
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
215
197
  examplesIndexDocHint,
216
198
  ],