@carthooks/arcubase-cli 0.1.10 → 0.1.13
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 +1365 -375
- package/bundle/arcubase.mjs +1365 -375
- package/dist/generated/command_registry.generated.d.ts +36 -35
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +37 -36
- package/dist/generated/type_index.generated.d.ts +23 -23
- package/dist/generated/type_index.generated.js +23 -23
- package/dist/runtime/entity_save_schema.d.ts.map +1 -1
- package/dist/runtime/entity_save_schema.js +30 -0
- package/dist/runtime/env.d.ts +2 -1
- package/dist/runtime/env.d.ts.map +1 -1
- package/dist/runtime/env.js +10 -1
- package/dist/runtime/errors.d.ts +3 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/execute.d.ts +4 -2
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +1046 -134
- package/dist/runtime/paths.d.ts +1 -1
- package/dist/runtime/paths.d.ts.map +1 -1
- package/dist/runtime/paths.js +9 -2
- package/dist/runtime/upload.d.ts +1 -11
- package/dist/runtime/upload.d.ts.map +1 -1
- package/dist/runtime/upload.js +19 -133
- package/dist/runtime/zod_registry.d.ts +5 -3
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +108 -48
- package/dist/tests/bootstrap.test.js +12 -0
- package/dist/tests/command_registry.test.js +8 -0
- package/dist/tests/docs_readability.test.d.ts +2 -0
- package/dist/tests/docs_readability.test.d.ts.map +1 -0
- package/dist/tests/docs_readability.test.js +87 -0
- package/dist/tests/entity_save_schema.test.js +22 -0
- package/dist/tests/execute_validation.test.js +443 -44
- package/dist/tests/help.test.js +47 -8
- package/dist/tests/upload.test.js +30 -77
- package/package.json +1 -1
- package/sdk-dist/docs/runtime-reference/README.md +43 -29
- package/sdk-dist/docs/runtime-reference/access-rule.md +128 -0
- package/sdk-dist/docs/runtime-reference/condition.md +182 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +6 -5
- package/sdk-dist/docs/runtime-reference/entity-schema/checkbox.md +1 -1
- package/sdk-dist/docs/runtime-reference/entity-schema/file.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema/image.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +3 -3
- package/sdk-dist/docs/runtime-reference/entity-schema/number.md +1 -1
- 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/status.md +1 -1
- package/sdk-dist/docs/runtime-reference/entity-schema/text.md +1 -1
- package/sdk-dist/docs/runtime-reference/entity-schema.md +28 -21
- package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
- package/sdk-dist/docs/runtime-reference/examples/crm-01/README.md +30 -11
- package/sdk-dist/docs/runtime-reference/examples/crm-01/contact.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/contact.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/create-app.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/customer.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/customer.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/follow-up.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/follow-up.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.bulk-update.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.query.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.row.insert.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.selection.condition.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +26 -26
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.row.insert.json +1 -1
- package/sdk-dist/docs/runtime-reference/examples/wms-01/README.md +26 -10
- package/sdk-dist/docs/runtime-reference/examples/wms-01/create-app.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/goods-receipt.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/goods-receipt.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.bulk-update.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.query.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.selection.condition.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/item-master.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/item-master.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/stock-issue.create-entity.json +1 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/stock-issue.schema.json +1 -0
- package/sdk-dist/docs/runtime-reference/row-crud.md +48 -8
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +31 -95
- package/sdk-dist/docs/runtime-reference/selection.md +128 -0
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +113 -30
- package/sdk-dist/docs/runtime-reference/uploads.md +30 -59
- package/sdk-dist/docs/runtime-reference/workflow/README.md +129 -13
- package/sdk-dist/generated/command_registry.generated.ts +37 -36
- package/sdk-dist/generated/type_index.generated.ts +23 -23
- package/src/generated/command_registry.generated.ts +37 -36
- package/src/generated/type_index.generated.ts +23 -23
- package/src/runtime/entity_save_schema.ts +30 -0
- package/src/runtime/env.ts +14 -2
- package/src/runtime/errors.ts +3 -0
- package/src/runtime/execute.ts +1272 -134
- package/src/runtime/paths.ts +11 -2
- package/src/runtime/upload.ts +19 -178
- package/src/runtime/zod_registry.ts +113 -48
- package/src/tests/bootstrap.test.ts +16 -0
- package/src/tests/command_registry.test.ts +9 -0
- package/src/tests/docs_readability.test.ts +96 -0
- package/src/tests/entity_save_schema.test.ts +22 -0
- package/src/tests/execute_validation.test.ts +577 -47
- package/src/tests/help.test.ts +51 -8
- package/src/tests/upload.test.ts +34 -91
package/src/runtime/paths.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { CLIError } from './errors.js'
|
|
2
|
+
|
|
1
3
|
export const DEFAULT_ARCUBASE_CLI_ROOT = '/opt/arcubase-cli'
|
|
2
|
-
export const DEFAULT_ARCUBASE_SDK_ROOT = '/opt/arcubase-sdk'
|
|
3
4
|
|
|
4
5
|
export function resolveArcubaseSDKRoot(env: Record<string, string | undefined> = process.env): string {
|
|
5
6
|
const value = env.ARCUBASE_SDK_ROOT?.trim()
|
|
6
7
|
if (value) return value
|
|
7
|
-
|
|
8
|
+
throw new CLIError('RUNTIME_ENV_REQUIRED', 'ARCUBASE_SDK_ROOT is required', 2)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveArcubaseSDKPath(filePath: string, env: Record<string, string | undefined> = process.env): string {
|
|
12
|
+
const cleanPath = String(filePath || '').trim().replace(/^\/+/, '')
|
|
13
|
+
if (!cleanPath) {
|
|
14
|
+
throw new CLIError('RUNTIME_ENV_REQUIRED', 'ARCUBASE_SDK_ROOT path is required', 2)
|
|
15
|
+
}
|
|
16
|
+
return `${resolveArcubaseSDKRoot(env).replace(/\/+$/, '')}/${cleanPath}`
|
|
8
17
|
}
|
package/src/runtime/upload.ts
CHANGED
|
@@ -1,196 +1,37 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
1
|
import { CLIError } from './errors.js'
|
|
4
|
-
import
|
|
5
|
-
import { buildRequestHeaders, buildURL } from './http.js'
|
|
2
|
+
import { resolveArcubaseSDKPath } from './paths.js'
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
id?: number | string
|
|
10
|
-
mode?: string
|
|
11
|
-
token_data?: {
|
|
12
|
-
dir?: string
|
|
13
|
-
policy?: string
|
|
14
|
-
host?: string
|
|
15
|
-
accessid?: string
|
|
16
|
-
signature?: string
|
|
17
|
-
x_amz_algorithm?: string
|
|
18
|
-
x_amz_credential?: string
|
|
19
|
-
x_amz_date?: string
|
|
20
|
-
x_amz_signature?: string
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
error?: unknown
|
|
24
|
-
trace_id?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type ArcubaseUploadResult = {
|
|
28
|
-
upload_id: number | string
|
|
29
|
-
file: string
|
|
30
|
-
file_name: string
|
|
31
|
-
meta: Record<string, never>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function readUploadSource(sourcePath: string): { absolutePath: string; bytes: Buffer } {
|
|
35
|
-
const absolutePath = path.resolve(sourcePath)
|
|
36
|
-
if (!fs.existsSync(absolutePath)) {
|
|
37
|
-
throw new CLIError('UPLOAD_SOURCE_NOT_FOUND', `upload source file does not exist: ${sourcePath}`, 2)
|
|
38
|
-
}
|
|
39
|
-
const stat = fs.statSync(absolutePath)
|
|
40
|
-
if (!stat.isFile()) {
|
|
41
|
-
throw new CLIError('UPLOAD_SOURCE_INVALID', `upload source path must be a file: ${sourcePath}`, 2)
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
absolutePath,
|
|
45
|
-
bytes: fs.readFileSync(absolutePath),
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parseJSONResponse(raw: string): unknown {
|
|
50
|
-
try {
|
|
51
|
-
return raw ? JSON.parse(raw) : null
|
|
52
|
-
} catch {
|
|
53
|
-
return raw
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function requireTokenPayload(payload: UploadTokenResponse): NonNullable<UploadTokenResponse['data']> {
|
|
58
|
-
if (payload && typeof payload === 'object' && payload.error) {
|
|
59
|
-
const upstreamError = payload.error
|
|
60
|
-
const message =
|
|
61
|
-
typeof upstreamError === 'object' && upstreamError && 'message' in upstreamError && typeof (upstreamError as any).message === 'string'
|
|
62
|
-
? (upstreamError as any).message
|
|
63
|
-
: typeof upstreamError === 'string'
|
|
64
|
-
? upstreamError
|
|
65
|
-
: JSON.stringify(upstreamError)
|
|
66
|
-
throw new CLIError('UPSTREAM_BODY_ERROR', message, 1, {
|
|
67
|
-
endpoint: '/api/upload/token',
|
|
68
|
-
method: 'POST',
|
|
69
|
-
traceId: typeof payload.trace_id === 'string' ? payload.trace_id : undefined,
|
|
70
|
-
upstreamError,
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
if (!payload?.data || typeof payload.data !== 'object') {
|
|
74
|
-
throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response did not include data', 1)
|
|
75
|
-
}
|
|
76
|
-
return payload.data
|
|
4
|
+
function sdkPath(file: string): string {
|
|
5
|
+
return resolveArcubaseSDKPath(file)
|
|
77
6
|
}
|
|
78
7
|
|
|
79
8
|
export function renderUploadHelp(): string {
|
|
80
9
|
return [
|
|
81
|
-
'arcubase upload <
|
|
10
|
+
'arcubase upload <command> [flags]',
|
|
82
11
|
'',
|
|
83
12
|
'purpose:',
|
|
84
|
-
' -
|
|
85
|
-
' -
|
|
86
|
-
'',
|
|
87
|
-
'flags:',
|
|
88
|
-
' --filename <name> override the uploaded filename stored in Arcubase',
|
|
89
|
-
' --global request a global upload token instead of a tenant-scoped token',
|
|
13
|
+
' - call explicit Arcubase upload APIs',
|
|
14
|
+
' - keep file transfer outside this CLI surface',
|
|
90
15
|
'',
|
|
91
|
-
'
|
|
92
|
-
' -
|
|
93
|
-
' -
|
|
16
|
+
'commands:',
|
|
17
|
+
' - token',
|
|
18
|
+
' - preview',
|
|
19
|
+
' - mobile',
|
|
20
|
+
' - check-mobile',
|
|
94
21
|
'',
|
|
95
22
|
'example:',
|
|
96
|
-
' arcubase upload
|
|
97
|
-
' arcubase upload ./photo.jpg --filename lead-photo.jpg',
|
|
23
|
+
' arcubase upload token --body-json \'{"global":false}\'',
|
|
98
24
|
'',
|
|
99
25
|
'docs:',
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
26
|
+
` - Uploads: ${sdkPath('docs/runtime-reference/uploads.md')}`,
|
|
27
|
+
` - Row CRUD: ${sdkPath('docs/runtime-reference/row-crud.md')}`,
|
|
28
|
+
` - File field: ${sdkPath('docs/runtime-reference/entity-schema/file.md')}`,
|
|
29
|
+
` - Image field: ${sdkPath('docs/runtime-reference/entity-schema/image.md')}`,
|
|
104
30
|
].join('\n')
|
|
105
31
|
}
|
|
106
32
|
|
|
107
|
-
export async function uploadLocalFile(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
options: {
|
|
111
|
-
filename?: string
|
|
112
|
-
global?: boolean
|
|
113
|
-
},
|
|
114
|
-
fetchImpl: typeof fetch = fetch,
|
|
115
|
-
): Promise<ArcubaseUploadResult[]> {
|
|
116
|
-
const { absolutePath, bytes } = readUploadSource(sourcePath)
|
|
117
|
-
const filename = (options.filename && options.filename.trim()) || path.basename(absolutePath)
|
|
118
|
-
|
|
119
|
-
const tokenResponse = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, '/api/upload/token'), {
|
|
120
|
-
method: 'POST',
|
|
121
|
-
headers: {
|
|
122
|
-
...buildRequestHeaders(env),
|
|
123
|
-
'Content-Type': 'application/json',
|
|
124
|
-
},
|
|
125
|
-
body: JSON.stringify({
|
|
126
|
-
global: Boolean(options.global),
|
|
127
|
-
}),
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const tokenRaw = await tokenResponse.text()
|
|
131
|
-
const tokenParsed = parseJSONResponse(tokenRaw)
|
|
132
|
-
if (!tokenResponse.ok) {
|
|
133
|
-
throw new CLIError('UPLOAD_TOKEN_REQUEST_FAILED', typeof tokenParsed === 'string' ? tokenParsed : JSON.stringify(tokenParsed), 1, {
|
|
134
|
-
endpoint: '/api/upload/token',
|
|
135
|
-
method: 'POST',
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const tokenData = requireTokenPayload(tokenParsed as UploadTokenResponse)
|
|
140
|
-
if (tokenData.mode !== 'aliyun-oss' && tokenData.mode !== 's3-post-policy') {
|
|
141
|
-
throw new CLIError('UPLOAD_MODE_UNSUPPORTED', `unsupported upload mode: ${String(tokenData.mode || '')}`, 1)
|
|
142
|
-
}
|
|
143
|
-
if (!tokenData.token_data?.dir || !tokenData.token_data.policy || !tokenData.token_data.host) {
|
|
144
|
-
throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required token_data fields', 1)
|
|
145
|
-
}
|
|
146
|
-
if (tokenData.id === undefined || tokenData.id === null || tokenData.id === '') {
|
|
147
|
-
throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing id', 1)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const form = new FormData()
|
|
151
|
-
form.append('key', `${tokenData.token_data.dir}${filename}`)
|
|
152
|
-
form.append('policy', tokenData.token_data.policy)
|
|
153
|
-
if (tokenData.mode === 'aliyun-oss') {
|
|
154
|
-
if (!tokenData.token_data.accessid || !tokenData.token_data.signature) {
|
|
155
|
-
throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required token_data fields', 1)
|
|
156
|
-
}
|
|
157
|
-
form.append('OSSAccessKeyId', tokenData.token_data.accessid)
|
|
158
|
-
form.append('success_action_status', '200')
|
|
159
|
-
form.append('signature', tokenData.token_data.signature)
|
|
160
|
-
} else {
|
|
161
|
-
if (
|
|
162
|
-
!tokenData.token_data.x_amz_algorithm ||
|
|
163
|
-
!tokenData.token_data.x_amz_credential ||
|
|
164
|
-
!tokenData.token_data.x_amz_date ||
|
|
165
|
-
!tokenData.token_data.x_amz_signature
|
|
166
|
-
) {
|
|
167
|
-
throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required s3 token_data fields', 1)
|
|
168
|
-
}
|
|
169
|
-
form.append('x-amz-algorithm', tokenData.token_data.x_amz_algorithm)
|
|
170
|
-
form.append('x-amz-credential', tokenData.token_data.x_amz_credential)
|
|
171
|
-
form.append('x-amz-date', tokenData.token_data.x_amz_date)
|
|
172
|
-
form.append('x-amz-signature', tokenData.token_data.x_amz_signature)
|
|
173
|
-
}
|
|
174
|
-
form.append('file', new Blob([new Uint8Array(bytes)]), filename)
|
|
175
|
-
|
|
176
|
-
const uploadResponse = await fetchImpl(tokenData.token_data.host, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
body: form,
|
|
33
|
+
export async function uploadLocalFile(): Promise<never> {
|
|
34
|
+
throw new CLIError('UNSUPPORTED_COMMAND', 'local file upload is not part of the reset CLI surface; use upload token APIs instead', 2, {
|
|
35
|
+
docHints: [{ title: 'Uploads', file: sdkPath('docs/runtime-reference/uploads.md') }],
|
|
179
36
|
})
|
|
180
|
-
const uploadRaw = await uploadResponse.text()
|
|
181
|
-
if (!uploadResponse.ok) {
|
|
182
|
-
throw new CLIError('UPLOAD_TRANSFER_FAILED', uploadRaw || `upload failed with status ${uploadResponse.status}`, 1, {
|
|
183
|
-
endpoint: tokenData.token_data.host,
|
|
184
|
-
method: 'POST',
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return [
|
|
189
|
-
{
|
|
190
|
-
upload_id: tokenData.id,
|
|
191
|
-
file: filename,
|
|
192
|
-
file_name: filename,
|
|
193
|
-
meta: {},
|
|
194
|
-
},
|
|
195
|
-
]
|
|
196
37
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ZodTypeAny } from 'zod'
|
|
2
2
|
import { bodySchemas, unsupportedBodySchemas } from '../generated/zod_registry.generated.js'
|
|
3
3
|
import { typeIndex } from '../generated/type_index.generated.js'
|
|
4
|
+
import { resolveArcubaseSDKPath } from './paths.js'
|
|
4
5
|
import {
|
|
5
6
|
AppCreateByTenantsReqVOSchemaOverride,
|
|
6
7
|
EntityBulkUpdateReqVOSchemaOverride,
|
|
@@ -62,62 +63,112 @@ export type RuntimeDocHint = {
|
|
|
62
63
|
file: string
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
type EnvInput = Record<string, string | undefined>
|
|
67
|
+
|
|
68
|
+
function sdkPath(file: string, env: EnvInput = process.env): string {
|
|
69
|
+
return resolveArcubaseSDKPath(file.replace(/^\/?opt\/arcubase-sdk\/?/, ''), env)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveTypeHintFiles(hints: RuntimeTypeHint[], env: EnvInput = process.env): RuntimeTypeHint[] {
|
|
73
|
+
return hints.map((item) => ({ ...item, file: sdkPath(item.file, env) }))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveDocHintFiles(hints: RuntimeDocHint[], env: EnvInput = process.env): RuntimeDocHint[] {
|
|
77
|
+
return hints.map((item) => ({ ...item, file: sdkPath(item.file, env) }))
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
const examplesIndexDocHint: RuntimeDocHint = {
|
|
66
81
|
title: 'Examples index',
|
|
67
|
-
file: '
|
|
82
|
+
file: 'docs/runtime-reference/examples/README.md',
|
|
83
|
+
}
|
|
84
|
+
const selectionDocHint: RuntimeDocHint = {
|
|
85
|
+
title: 'Selection',
|
|
86
|
+
file: 'docs/runtime-reference/selection.md',
|
|
87
|
+
}
|
|
88
|
+
const conditionDocHint: RuntimeDocHint = {
|
|
89
|
+
title: 'Condition',
|
|
90
|
+
file: 'docs/runtime-reference/condition.md',
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
const docHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
71
94
|
AppCreateByTenantsReqVO: [
|
|
72
|
-
{ title: 'Table lifecycle', file: '
|
|
95
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
73
96
|
examplesIndexDocHint,
|
|
74
97
|
],
|
|
75
98
|
AppCreateEntityReqVO: [
|
|
76
|
-
{ title: 'Table lifecycle', file: '
|
|
99
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
100
|
+
{ title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
|
|
101
|
+
{ title: 'Field type index', file: 'docs/runtime-reference/entity-schema/README.md' },
|
|
102
|
+
examplesIndexDocHint,
|
|
103
|
+
],
|
|
104
|
+
AppIngressCreateReqVO: [
|
|
105
|
+
{ title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
|
|
106
|
+
examplesIndexDocHint,
|
|
107
|
+
],
|
|
108
|
+
AppIngressUpdateReqVO: [
|
|
109
|
+
{ title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
|
|
77
110
|
examplesIndexDocHint,
|
|
78
111
|
],
|
|
79
112
|
EntitySaveReqVO: [
|
|
80
|
-
{ title: '
|
|
113
|
+
{ title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
|
|
114
|
+
{ title: 'Field type index', file: 'docs/runtime-reference/entity-schema/README.md' },
|
|
115
|
+
{ title: 'Text field', file: 'docs/runtime-reference/entity-schema/text.md' },
|
|
116
|
+
{ title: 'Textarea field', file: 'docs/runtime-reference/entity-schema/textarea.md' },
|
|
117
|
+
{ title: 'Number field', file: 'docs/runtime-reference/entity-schema/number.md' },
|
|
118
|
+
{ title: 'Datetime field', file: 'docs/runtime-reference/entity-schema/datetime.md' },
|
|
119
|
+
{ title: 'Boolean field', file: 'docs/runtime-reference/entity-schema/boolean.md' },
|
|
120
|
+
{ title: 'Select field', file: 'docs/runtime-reference/entity-schema/select.md' },
|
|
81
121
|
examplesIndexDocHint,
|
|
82
122
|
],
|
|
83
123
|
EntityQueryReqVO: [
|
|
84
|
-
{ title: 'Row CRUD', file: '
|
|
85
|
-
{ title: 'Search and bulk actions', file: '
|
|
124
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
125
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
126
|
+
conditionDocHint,
|
|
86
127
|
examplesIndexDocHint,
|
|
87
128
|
],
|
|
88
129
|
EntityUpdateReqVO: [
|
|
89
|
-
{ title: 'Row CRUD', file: '
|
|
90
|
-
{ title: 'Uploads', file: '
|
|
130
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
131
|
+
{ title: 'Uploads', file: 'docs/runtime-reference/uploads.md' },
|
|
91
132
|
examplesIndexDocHint,
|
|
92
133
|
],
|
|
93
134
|
EntitySelectionActionReqVO: [
|
|
94
|
-
|
|
135
|
+
selectionDocHint,
|
|
136
|
+
conditionDocHint,
|
|
137
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
95
138
|
examplesIndexDocHint,
|
|
96
139
|
],
|
|
97
140
|
InsertReqVO: [
|
|
98
|
-
{ title: 'Row CRUD', file: '
|
|
99
|
-
{ title: 'Uploads', file: '
|
|
141
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
142
|
+
{ title: 'Uploads', file: 'docs/runtime-reference/uploads.md' },
|
|
100
143
|
examplesIndexDocHint,
|
|
101
144
|
],
|
|
102
145
|
EntityBulkUpdateReqVO: [
|
|
103
|
-
|
|
146
|
+
selectionDocHint,
|
|
147
|
+
conditionDocHint,
|
|
148
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
104
149
|
examplesIndexDocHint,
|
|
105
150
|
],
|
|
106
151
|
'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
|
|
107
|
-
|
|
152
|
+
selectionDocHint,
|
|
153
|
+
conditionDocHint,
|
|
154
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
108
155
|
examplesIndexDocHint,
|
|
109
156
|
],
|
|
110
157
|
'{ policy_id: string selection: any }': [
|
|
111
|
-
|
|
158
|
+
selectionDocHint,
|
|
159
|
+
conditionDocHint,
|
|
160
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
112
161
|
examplesIndexDocHint,
|
|
113
162
|
],
|
|
114
163
|
'{ policy_id: string selection: any tag_names: string[] }': [
|
|
115
|
-
|
|
164
|
+
selectionDocHint,
|
|
165
|
+
conditionDocHint,
|
|
166
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
116
167
|
examplesIndexDocHint,
|
|
117
168
|
],
|
|
118
169
|
}
|
|
119
170
|
|
|
120
|
-
export function getTypeHints(typeName: string): RuntimeTypeHint[] {
|
|
171
|
+
export function getTypeHints(typeName: string, env: EnvInput = process.env): RuntimeTypeHint[] {
|
|
121
172
|
const name = String(typeName || '').trim()
|
|
122
173
|
if (!name) return []
|
|
123
174
|
const entry = (typeIndex as Record<string, { file: string; dependencies?: ReadonlyArray<RuntimeTypeHint> } | undefined>)[name]
|
|
@@ -137,73 +188,87 @@ export function getTypeHints(typeName: string): RuntimeTypeHint[] {
|
|
|
137
188
|
for (const dep of entry.dependencies ?? []) {
|
|
138
189
|
append(dep.symbol, dep.file)
|
|
139
190
|
}
|
|
140
|
-
return out
|
|
191
|
+
return resolveTypeHintFiles(out, env)
|
|
141
192
|
}
|
|
142
193
|
|
|
143
|
-
export function getDocHints(typeName: string): RuntimeDocHint[] {
|
|
194
|
+
export function getDocHints(typeName: string, env: EnvInput = process.env): RuntimeDocHint[] {
|
|
144
195
|
const name = String(typeName || '').trim()
|
|
145
196
|
if (!name) return []
|
|
146
|
-
return docHintIndex[name] ?? []
|
|
197
|
+
return resolveDocHintFiles(docHintIndex[name] ?? [], env)
|
|
147
198
|
}
|
|
148
199
|
|
|
149
200
|
const commandDocHintIndex: Record<string, RuntimeDocHint[]> = {
|
|
150
201
|
'admin.app.createAppByTenants': [
|
|
151
|
-
{ title: 'Table lifecycle', file: '
|
|
202
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
152
203
|
examplesIndexDocHint,
|
|
153
204
|
],
|
|
154
205
|
'admin.app.createEntity': [
|
|
155
|
-
{ title: 'Table lifecycle', file: '
|
|
206
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
207
|
+
examplesIndexDocHint,
|
|
208
|
+
],
|
|
209
|
+
'admin.table.adminGetEntityInfo': [
|
|
210
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
211
|
+
examplesIndexDocHint,
|
|
212
|
+
],
|
|
213
|
+
'admin.table.adminSaveEntity': [
|
|
214
|
+
{ title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
|
|
215
|
+
{ title: 'Field type index', file: 'docs/runtime-reference/entity-schema/README.md' },
|
|
216
|
+
{ title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
|
|
217
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
156
218
|
examplesIndexDocHint,
|
|
157
219
|
],
|
|
158
|
-
'admin.
|
|
159
|
-
{ title: '
|
|
220
|
+
'admin.access-rule.createEntityIngress': [
|
|
221
|
+
{ title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
|
|
160
222
|
examplesIndexDocHint,
|
|
161
223
|
],
|
|
162
|
-
'admin.
|
|
163
|
-
{ title: '
|
|
164
|
-
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
165
|
-
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
224
|
+
'admin.access-rule.updateEntityIngress': [
|
|
225
|
+
{ title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
|
|
166
226
|
examplesIndexDocHint,
|
|
167
227
|
],
|
|
168
|
-
'user.
|
|
169
|
-
{ title: 'Row CRUD', file: '
|
|
170
|
-
{ title: 'Uploads', file: '
|
|
228
|
+
'user.row.insertEntity': [
|
|
229
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
230
|
+
{ title: 'Uploads', file: 'docs/runtime-reference/uploads.md' },
|
|
171
231
|
examplesIndexDocHint,
|
|
172
232
|
],
|
|
173
|
-
'user.
|
|
174
|
-
{ title: 'Search and bulk actions', file: '
|
|
175
|
-
|
|
233
|
+
'user.row.queryEntity': [
|
|
234
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
235
|
+
conditionDocHint,
|
|
236
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
176
237
|
examplesIndexDocHint,
|
|
177
238
|
],
|
|
178
|
-
'user.
|
|
179
|
-
{ title: 'Row CRUD', file: '
|
|
239
|
+
'user.row.getEntityRow': [
|
|
240
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
180
241
|
examplesIndexDocHint,
|
|
181
242
|
],
|
|
182
|
-
'user.
|
|
183
|
-
{ title: 'Row CRUD', file: '
|
|
184
|
-
{ title: 'Uploads', file: '
|
|
243
|
+
'user.row.updateEntityRow': [
|
|
244
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
245
|
+
{ title: 'Uploads', file: 'docs/runtime-reference/uploads.md' },
|
|
185
246
|
examplesIndexDocHint,
|
|
186
247
|
],
|
|
187
|
-
'user.
|
|
188
|
-
|
|
248
|
+
'user.row.queryEntitySelection': [
|
|
249
|
+
selectionDocHint,
|
|
250
|
+
conditionDocHint,
|
|
251
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
189
252
|
examplesIndexDocHint,
|
|
190
253
|
],
|
|
191
|
-
'user.
|
|
192
|
-
{ title: 'Row CRUD', file: '
|
|
254
|
+
'user.row.deleteEntityRow': [
|
|
255
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
193
256
|
examplesIndexDocHint,
|
|
194
257
|
],
|
|
195
|
-
'user.
|
|
196
|
-
{ title: 'Row CRUD', file: '
|
|
258
|
+
'user.row.restoreEntityRow': [
|
|
259
|
+
{ title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
|
|
197
260
|
examplesIndexDocHint,
|
|
198
261
|
],
|
|
199
|
-
'user.
|
|
200
|
-
|
|
262
|
+
'user.row.updateEntityBulk': [
|
|
263
|
+
selectionDocHint,
|
|
264
|
+
conditionDocHint,
|
|
265
|
+
{ title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
|
|
201
266
|
examplesIndexDocHint,
|
|
202
267
|
],
|
|
203
268
|
}
|
|
204
269
|
|
|
205
|
-
export function getCommandDocHints(operation: string): RuntimeDocHint[] {
|
|
270
|
+
export function getCommandDocHints(operation: string, env: EnvInput = process.env): RuntimeDocHint[] {
|
|
206
271
|
const key = String(operation || '').trim()
|
|
207
272
|
if (!key) return []
|
|
208
|
-
return commandDocHintIndex[key] ?? []
|
|
273
|
+
return resolveDocHintFiles(commandDocHintIndex[key] ?? [], env)
|
|
209
274
|
}
|
|
@@ -7,16 +7,28 @@ test('loadRuntimeEnv requires base url and access token', () => {
|
|
|
7
7
|
assert.throws(() => loadRuntimeEnv('admin', {}), /ARCUBASE_BASE_URL is required/)
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
+
test('loadRuntimeEnv requires injected SDK root without fallback', () => {
|
|
11
|
+
assert.throws(
|
|
12
|
+
() => loadRuntimeEnv('admin', {
|
|
13
|
+
ARCUBASE_BASE_URL: 'http://example.com',
|
|
14
|
+
ARCUBASE_ACCESS_TOKEN: 'token-1',
|
|
15
|
+
}),
|
|
16
|
+
/ARCUBASE_SDK_ROOT is required/,
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
|
|
10
20
|
test('loadRuntimeEnv returns normalized runtime env', () => {
|
|
11
21
|
const env = loadRuntimeEnv('admin', {
|
|
12
22
|
ARCUBASE_BASE_URL: ' http://example.com ',
|
|
13
23
|
ARCUBASE_ACCESS_TOKEN: ' token-1 ',
|
|
24
|
+
ARCUBASE_SDK_ROOT: ' /runtime/arcubase-sdk ',
|
|
14
25
|
ARCUBASE_TRACE_ID: ' trace-1 '
|
|
15
26
|
})
|
|
16
27
|
|
|
17
28
|
assert.deepEqual(env, {
|
|
18
29
|
ARCUBASE_BASE_URL: 'http://example.com',
|
|
19
30
|
ARCUBASE_ACCESS_TOKEN: 'token-1',
|
|
31
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
20
32
|
ARCUBASE_TRACE_ID: 'trace-1',
|
|
21
33
|
ARCUBASE_SUBJECT_TYPE: undefined,
|
|
22
34
|
ARCUBASE_SUBJECT_ID: undefined,
|
|
@@ -28,6 +40,7 @@ test('loadRuntimeEnv accepts admin-prefixed runtime env', () => {
|
|
|
28
40
|
const env = loadRuntimeEnv('admin', {
|
|
29
41
|
ARCUBASE_ADMIN_BASE_URL: ' https://admin.example.com ',
|
|
30
42
|
ARCUBASE_ADMIN_ACCESS_TOKEN: ' token-admin ',
|
|
43
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
31
44
|
ARCUBASE_ADMIN_SUBJECT_TYPE: ' system_admin ',
|
|
32
45
|
ARCUBASE_ADMIN_SUBJECT_ID: ' user-admin ',
|
|
33
46
|
})
|
|
@@ -35,6 +48,7 @@ test('loadRuntimeEnv accepts admin-prefixed runtime env', () => {
|
|
|
35
48
|
assert.deepEqual(env, {
|
|
36
49
|
ARCUBASE_BASE_URL: 'https://admin.example.com',
|
|
37
50
|
ARCUBASE_ACCESS_TOKEN: 'token-admin',
|
|
51
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
38
52
|
ARCUBASE_TRACE_ID: undefined,
|
|
39
53
|
ARCUBASE_SUBJECT_TYPE: 'system_admin',
|
|
40
54
|
ARCUBASE_SUBJECT_ID: 'user-admin',
|
|
@@ -46,6 +60,7 @@ test('loadRuntimeEnv accepts user-prefixed runtime env', () => {
|
|
|
46
60
|
const env = loadRuntimeEnv('user', {
|
|
47
61
|
ARCUBASE_USER_BASE_URL: ' https://user.example.com ',
|
|
48
62
|
ARCUBASE_USER_ACCESS_TOKEN: ' token-user ',
|
|
63
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
49
64
|
ARCUBASE_USER_SUBJECT_TYPE: ' service_account ',
|
|
50
65
|
ARCUBASE_USER_SUBJECT_ID: ' sa-user ',
|
|
51
66
|
})
|
|
@@ -53,6 +68,7 @@ test('loadRuntimeEnv accepts user-prefixed runtime env', () => {
|
|
|
53
68
|
assert.deepEqual(env, {
|
|
54
69
|
ARCUBASE_BASE_URL: 'https://user.example.com',
|
|
55
70
|
ARCUBASE_ACCESS_TOKEN: 'token-user',
|
|
71
|
+
ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
|
|
56
72
|
ARCUBASE_TRACE_ID: undefined,
|
|
57
73
|
ARCUBASE_SUBJECT_TYPE: 'service_account',
|
|
58
74
|
ARCUBASE_SUBJECT_ID: 'sa-user',
|
|
@@ -68,6 +68,13 @@ test('user surface is restricted to reset white list', () => {
|
|
|
68
68
|
assert.deepEqual(actual, expected)
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
test('generated modules use CLI namespaces only', () => {
|
|
72
|
+
const adminModules = [...new Set(adminCommands.map((item) => item.module))].sort()
|
|
73
|
+
const userModules = [...new Set(userCommands.map((item) => item.module))].sort()
|
|
74
|
+
assert.deepEqual(adminModules, ['access-rule', 'app', 'table', 'workflow'])
|
|
75
|
+
assert.deepEqual(userModules, ['entry', 'profile', 'row', 'table', 'upload', 'workflow'])
|
|
76
|
+
})
|
|
77
|
+
|
|
71
78
|
test('black list commands are removed from registry', () => {
|
|
72
79
|
const all = new Set([...commandPaths(adminCommands), ...commandPaths(userCommands)])
|
|
73
80
|
const forbidden = [
|
|
@@ -79,6 +86,8 @@ test('black list commands are removed from registry', () => {
|
|
|
79
86
|
'ingress create-entity-ingress',
|
|
80
87
|
'workflow query-entity',
|
|
81
88
|
'workflow insert-entity',
|
|
89
|
+
'workflow prepare-entity-insert',
|
|
90
|
+
'workflow get-entity-row',
|
|
82
91
|
'workflow update-entity-row',
|
|
83
92
|
'workflow delete-entity-row',
|
|
84
93
|
'workflow restore-entity-row',
|