@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.
Files changed (105) hide show
  1. package/bundle/arcubase-admin.mjs +1365 -375
  2. package/bundle/arcubase.mjs +1365 -375
  3. package/dist/generated/command_registry.generated.d.ts +36 -35
  4. package/dist/generated/command_registry.generated.d.ts.map +1 -1
  5. package/dist/generated/command_registry.generated.js +37 -36
  6. package/dist/generated/type_index.generated.d.ts +23 -23
  7. package/dist/generated/type_index.generated.js +23 -23
  8. package/dist/runtime/entity_save_schema.d.ts.map +1 -1
  9. package/dist/runtime/entity_save_schema.js +30 -0
  10. package/dist/runtime/env.d.ts +2 -1
  11. package/dist/runtime/env.d.ts.map +1 -1
  12. package/dist/runtime/env.js +10 -1
  13. package/dist/runtime/errors.d.ts +3 -0
  14. package/dist/runtime/errors.d.ts.map +1 -1
  15. package/dist/runtime/execute.d.ts +4 -2
  16. package/dist/runtime/execute.d.ts.map +1 -1
  17. package/dist/runtime/execute.js +1046 -134
  18. package/dist/runtime/paths.d.ts +1 -1
  19. package/dist/runtime/paths.d.ts.map +1 -1
  20. package/dist/runtime/paths.js +9 -2
  21. package/dist/runtime/upload.d.ts +1 -11
  22. package/dist/runtime/upload.d.ts.map +1 -1
  23. package/dist/runtime/upload.js +19 -133
  24. package/dist/runtime/zod_registry.d.ts +5 -3
  25. package/dist/runtime/zod_registry.d.ts.map +1 -1
  26. package/dist/runtime/zod_registry.js +108 -48
  27. package/dist/tests/bootstrap.test.js +12 -0
  28. package/dist/tests/command_registry.test.js +8 -0
  29. package/dist/tests/docs_readability.test.d.ts +2 -0
  30. package/dist/tests/docs_readability.test.d.ts.map +1 -0
  31. package/dist/tests/docs_readability.test.js +87 -0
  32. package/dist/tests/entity_save_schema.test.js +22 -0
  33. package/dist/tests/execute_validation.test.js +443 -44
  34. package/dist/tests/help.test.js +47 -8
  35. package/dist/tests/upload.test.js +30 -77
  36. package/package.json +1 -1
  37. package/sdk-dist/docs/runtime-reference/README.md +43 -29
  38. package/sdk-dist/docs/runtime-reference/access-rule.md +128 -0
  39. package/sdk-dist/docs/runtime-reference/condition.md +182 -0
  40. package/sdk-dist/docs/runtime-reference/entity-schema/README.md +6 -5
  41. package/sdk-dist/docs/runtime-reference/entity-schema/checkbox.md +1 -1
  42. package/sdk-dist/docs/runtime-reference/entity-schema/file.md +2 -2
  43. package/sdk-dist/docs/runtime-reference/entity-schema/image.md +2 -2
  44. package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +3 -3
  45. package/sdk-dist/docs/runtime-reference/entity-schema/number.md +1 -1
  46. package/sdk-dist/docs/runtime-reference/entity-schema/query.md +3 -3
  47. package/sdk-dist/docs/runtime-reference/entity-schema/relation.md +3 -3
  48. package/sdk-dist/docs/runtime-reference/entity-schema/relationfield.md +3 -3
  49. package/sdk-dist/docs/runtime-reference/entity-schema/status.md +1 -1
  50. package/sdk-dist/docs/runtime-reference/entity-schema/text.md +1 -1
  51. package/sdk-dist/docs/runtime-reference/entity-schema.md +28 -21
  52. package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
  53. package/sdk-dist/docs/runtime-reference/examples/crm-01/README.md +30 -11
  54. package/sdk-dist/docs/runtime-reference/examples/crm-01/contact.create-entity.json +1 -0
  55. package/sdk-dist/docs/runtime-reference/examples/crm-01/contact.schema.json +1 -0
  56. package/sdk-dist/docs/runtime-reference/examples/crm-01/create-app.json +1 -0
  57. package/sdk-dist/docs/runtime-reference/examples/crm-01/customer.create-entity.json +1 -0
  58. package/sdk-dist/docs/runtime-reference/examples/crm-01/customer.schema.json +1 -0
  59. package/sdk-dist/docs/runtime-reference/examples/crm-01/follow-up.create-entity.json +1 -0
  60. package/sdk-dist/docs/runtime-reference/examples/crm-01/follow-up.schema.json +1 -0
  61. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.bulk-update.json +1 -0
  62. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.create-entity.json +1 -0
  63. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.query.json +1 -0
  64. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.row.insert.json +1 -0
  65. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.schema.json +1 -0
  66. package/sdk-dist/docs/runtime-reference/examples/crm-01/lead.selection.condition.json +1 -0
  67. package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +26 -26
  68. package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.row.insert.json +1 -1
  69. package/sdk-dist/docs/runtime-reference/examples/wms-01/README.md +26 -10
  70. package/sdk-dist/docs/runtime-reference/examples/wms-01/create-app.json +1 -0
  71. package/sdk-dist/docs/runtime-reference/examples/wms-01/goods-receipt.create-entity.json +1 -0
  72. package/sdk-dist/docs/runtime-reference/examples/wms-01/goods-receipt.schema.json +1 -0
  73. package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.bulk-update.json +1 -0
  74. package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.create-entity.json +1 -0
  75. package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.query.json +1 -0
  76. package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.schema.json +1 -0
  77. package/sdk-dist/docs/runtime-reference/examples/wms-01/inventory-snapshot.selection.condition.json +1 -0
  78. package/sdk-dist/docs/runtime-reference/examples/wms-01/item-master.create-entity.json +1 -0
  79. package/sdk-dist/docs/runtime-reference/examples/wms-01/item-master.schema.json +1 -0
  80. package/sdk-dist/docs/runtime-reference/examples/wms-01/stock-issue.create-entity.json +1 -0
  81. package/sdk-dist/docs/runtime-reference/examples/wms-01/stock-issue.schema.json +1 -0
  82. package/sdk-dist/docs/runtime-reference/row-crud.md +48 -8
  83. package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +31 -95
  84. package/sdk-dist/docs/runtime-reference/selection.md +128 -0
  85. package/sdk-dist/docs/runtime-reference/table-lifecycle.md +113 -30
  86. package/sdk-dist/docs/runtime-reference/uploads.md +30 -59
  87. package/sdk-dist/docs/runtime-reference/workflow/README.md +129 -13
  88. package/sdk-dist/generated/command_registry.generated.ts +37 -36
  89. package/sdk-dist/generated/type_index.generated.ts +23 -23
  90. package/src/generated/command_registry.generated.ts +37 -36
  91. package/src/generated/type_index.generated.ts +23 -23
  92. package/src/runtime/entity_save_schema.ts +30 -0
  93. package/src/runtime/env.ts +14 -2
  94. package/src/runtime/errors.ts +3 -0
  95. package/src/runtime/execute.ts +1272 -134
  96. package/src/runtime/paths.ts +11 -2
  97. package/src/runtime/upload.ts +19 -178
  98. package/src/runtime/zod_registry.ts +113 -48
  99. package/src/tests/bootstrap.test.ts +16 -0
  100. package/src/tests/command_registry.test.ts +9 -0
  101. package/src/tests/docs_readability.test.ts +96 -0
  102. package/src/tests/entity_save_schema.test.ts +22 -0
  103. package/src/tests/execute_validation.test.ts +577 -47
  104. package/src/tests/help.test.ts +51 -8
  105. package/src/tests/upload.test.ts +34 -91
@@ -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
- return DEFAULT_ARCUBASE_SDK_ROOT
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
  }
@@ -1,196 +1,37 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
1
  import { CLIError } from './errors.js'
4
- import type { RuntimeEnv } from './env.js'
5
- import { buildRequestHeaders, buildURL } from './http.js'
2
+ import { resolveArcubaseSDKPath } from './paths.js'
6
3
 
7
- type UploadTokenResponse = {
8
- data?: {
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 <local-file> [flags]',
10
+ 'arcubase upload <command> [flags]',
82
11
  '',
83
12
  'purpose:',
84
- ' - upload one local file through Arcubase',
85
- ' - return a field value that can be used directly in file or image row payloads',
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
- 'result shape:',
92
- ' - data is a JSON array',
93
- ' - use that array directly as the value of a file or image field in insert/update payloads',
16
+ 'commands:',
17
+ ' - token',
18
+ ' - preview',
19
+ ' - mobile',
20
+ ' - check-mobile',
94
21
  '',
95
22
  'example:',
96
- ' arcubase upload ./contract.pdf',
97
- ' arcubase upload ./photo.jpg --filename lead-photo.jpg',
23
+ ' arcubase upload token --body-json \'{"global":false}\'',
98
24
  '',
99
25
  'docs:',
100
- ' - Uploads: /opt/arcubase-sdk/docs/runtime-reference/uploads.md',
101
- ' - Row CRUD: /opt/arcubase-sdk/docs/runtime-reference/row-crud.md',
102
- ' - File field: /opt/arcubase-sdk/docs/runtime-reference/entity-schema/file.md',
103
- ' - Image field: /opt/arcubase-sdk/docs/runtime-reference/entity-schema/image.md',
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
- env: RuntimeEnv,
109
- sourcePath: string,
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: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
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: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
95
+ { title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
73
96
  examplesIndexDocHint,
74
97
  ],
75
98
  AppCreateEntityReqVO: [
76
- { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
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: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
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: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
85
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
90
- { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
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
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
99
- { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
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
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
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: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
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.entity.adminGetEntityInfo': [
159
- { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
220
+ 'admin.access-rule.createEntityIngress': [
221
+ { title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
160
222
  examplesIndexDocHint,
161
223
  ],
162
- 'admin.entity.adminSaveEntity': [
163
- { title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
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.workflow.insertEntity': [
169
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
170
- { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
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.workflow.queryEntity': [
174
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
175
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
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.workflow.getEntityRow': [
179
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
239
+ 'user.row.getEntityRow': [
240
+ { title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
180
241
  examplesIndexDocHint,
181
242
  ],
182
- 'user.workflow.updateEntityRow': [
183
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
184
- { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
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.workflow.queryEntitySelection': [
188
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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.workflow.deleteEntityRow': [
192
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
254
+ 'user.row.deleteEntityRow': [
255
+ { title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
193
256
  examplesIndexDocHint,
194
257
  ],
195
- 'user.workflow.restoreEntityRow': [
196
- { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
258
+ 'user.row.restoreEntityRow': [
259
+ { title: 'Row CRUD', file: 'docs/runtime-reference/row-crud.md' },
197
260
  examplesIndexDocHint,
198
261
  ],
199
- 'user.entity.updateEntityBulk': [
200
- { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
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',