@carthooks/arcubase-cli 0.1.3 → 0.1.4

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 (33) hide show
  1. package/bundle/arcubase-admin.mjs +366 -41
  2. package/bundle/arcubase.mjs +366 -41
  3. package/dist/bin/arcubase-admin.js +0 -0
  4. package/dist/bin/arcubase.js +0 -0
  5. package/dist/runtime/errors.d.ts +1 -0
  6. package/dist/runtime/errors.d.ts.map +1 -1
  7. package/dist/runtime/execute.d.ts.map +1 -1
  8. package/dist/runtime/execute.js +209 -6
  9. package/dist/runtime/upload.d.ts +13 -0
  10. package/dist/runtime/upload.d.ts.map +1 -0
  11. package/dist/runtime/upload.js +148 -0
  12. package/dist/runtime/zod_registry.d.ts.map +1 -1
  13. package/dist/runtime/zod_registry.js +27 -24
  14. package/dist/tests/execute_validation.test.js +153 -0
  15. package/dist/tests/help.test.js +18 -2
  16. package/dist/tests/upload.test.d.ts +2 -0
  17. package/dist/tests/upload.test.d.ts.map +1 -0
  18. package/dist/tests/upload.test.js +107 -0
  19. package/package.json +1 -1
  20. package/sdk-dist/docs/runtime-reference/README.md +11 -1
  21. package/sdk-dist/docs/runtime-reference/entity-schema/file.md +9 -0
  22. package/sdk-dist/docs/runtime-reference/entity-schema/image.md +9 -0
  23. package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +19 -0
  24. package/sdk-dist/docs/runtime-reference/row-crud.md +2 -0
  25. package/sdk-dist/docs/runtime-reference/table-lifecycle.md +12 -0
  26. package/sdk-dist/docs/runtime-reference/uploads.md +106 -0
  27. package/src/runtime/errors.ts +1 -0
  28. package/src/runtime/execute.ts +248 -6
  29. package/src/runtime/upload.ts +196 -0
  30. package/src/runtime/zod_registry.ts +27 -25
  31. package/src/tests/execute_validation.test.ts +183 -0
  32. package/src/tests/help.test.ts +23 -2
  33. package/src/tests/upload.test.ts +133 -0
@@ -5,13 +5,22 @@ import { buildRequestHeaders, buildURL, normalizeExternalEndpoint } from './http
5
5
  import { listModules, listModuleCommands, findCommand, findCommandSuggestions } from './command_registry.js';
6
6
  import { loadBodyFromFlags, loadQueryFromFlags } from './body_loader.js';
7
7
  import { getBodySchema, getCommandDocHints, getDocHints, getTypeHints, getUnsupportedBodySchemaReason } from './zod_registry.js';
8
+ import { renderUploadHelp, uploadLocalFile } from './upload.js';
9
+ function summarizeUploadResult(value) {
10
+ return value;
11
+ }
8
12
  export function renderRootHelp(scope) {
9
13
  const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase';
14
+ const items = listModules(scope).map((item) => ` - ${item}`);
10
15
  return [
11
16
  `${binary} <module> <command> [flags]`,
12
17
  '',
18
+ ...(scope === 'admin'
19
+ ? ['use arcubase-admin for app, entity shell, and schema management', '']
20
+ : ['use arcubase for row CRUD, search, selection actions, and bulk actions', '']),
13
21
  'modules:',
14
- ...listModules(scope).map((item) => ` - ${item}`),
22
+ ...items,
23
+ ...(scope === 'user' ? ['', 'special commands:', ' - upload <local-file>'] : []),
15
24
  ].join('\n');
16
25
  }
17
26
  export function renderModuleHelp(scope, moduleName) {
@@ -23,15 +32,28 @@ export function renderModuleHelp(scope, moduleName) {
23
32
  return [
24
33
  `${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} <command> [flags]`,
25
34
  '',
35
+ ...(scope === 'admin'
36
+ ? ['use arcubase-admin for app, entity shell, and schema management', '']
37
+ : ['use arcubase for row CRUD, search, selection actions, and bulk actions', '']),
26
38
  'commands:',
27
39
  ...items.map((item) => ` - ${item.commandPath[1]}`),
28
40
  ].join('\n');
29
41
  }
42
+ function buildUnknownCommandDetails(scope, moduleName, commandName) {
43
+ const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
44
+ if (scope === 'admin' && moduleName === 'workflow' && suggestions.some((item) => item.startsWith('arcubase workflow '))) {
45
+ return {
46
+ suggestions,
47
+ reason: `${moduleName} ${commandName} is a row operation, not an arcubase-admin command`,
48
+ hint: `use: arcubase ${moduleName} ${commandName}`,
49
+ };
50
+ }
51
+ return suggestions.length > 0 ? { suggestions } : undefined;
52
+ }
30
53
  export function renderCommandHelp(scope, moduleName, commandName) {
31
54
  const command = findCommand(scope, moduleName, commandName);
32
55
  if (!command) {
33
- const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
34
- throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, suggestions.length > 0 ? { suggestions } : undefined);
56
+ throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName));
35
57
  }
36
58
  const docHints = [
37
59
  ...getCommandDocHints(`${scope}.${command.module}.${command.functionName}`),
@@ -45,6 +67,14 @@ export function renderCommandHelp(scope, moduleName, commandName) {
45
67
  'query flags: --query-file',
46
68
  command.endpointParams.length ? `path flags: ${command.endpointParams.map((item) => `--${item.replace(/_/g, '-')}`).join(', ')}` : 'path flags: none',
47
69
  command.scalarParams.length ? `scalar flags: ${command.scalarParams.map((item) => `--${item.name.replace(/([A-Z])/g, '-$1').toLowerCase()}`).join(', ')}` : 'scalar flags: none',
70
+ ...(scope === 'admin' && moduleName === 'entity' && commandName === 'admin-save-entity'
71
+ ? [
72
+ 'next step:',
73
+ ' - switch to arcubase for row operations',
74
+ ' - row insert: arcubase workflow insert-entity --app-id <app_id> --entity-id <entity_id> --body-file insert.json',
75
+ ' - row query: arcubase workflow query-entity --app-id <app_id> --entity-id <entity_id> --body-file query.json',
76
+ ]
77
+ : []),
48
78
  ...(docHints.length > 0
49
79
  ? ['docs:', ...docHints.map((item) => ` - ${item.title}: ${item.file}`)]
50
80
  : []),
@@ -97,6 +127,146 @@ function buildScalarQuery(command, flags) {
97
127
  function isRecord(value) {
98
128
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
99
129
  }
130
+ function extractEntityFields(payload) {
131
+ const candidate = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
132
+ if (!isRecord(candidate) || !Array.isArray(candidate.fields)) {
133
+ return [];
134
+ }
135
+ return candidate.fields
136
+ .filter((item) => isRecord(item))
137
+ .filter((item) => typeof item.id === 'number' && typeof item.type === 'string')
138
+ .map((item) => ({
139
+ id: item.id,
140
+ type: item.type,
141
+ label: typeof item.label === 'string' ? item.label : undefined,
142
+ }));
143
+ }
144
+ async function fetchEntityFields(runtimeEnv, appId, entityId, fetchImpl) {
145
+ const response = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, `/entity/${encodeURIComponent(appId)}/${encodeURIComponent(entityId)}`), {
146
+ method: 'GET',
147
+ headers: buildRequestHeaders(runtimeEnv),
148
+ });
149
+ const raw = await response.text();
150
+ let parsedResponse = raw;
151
+ try {
152
+ parsedResponse = raw ? JSON.parse(raw) : null;
153
+ }
154
+ catch {
155
+ parsedResponse = raw;
156
+ }
157
+ if (!response.ok) {
158
+ throw new CLIError('UPSTREAM_REQUEST_FAILED', typeof parsedResponse === 'string' ? parsedResponse : JSON.stringify(parsedResponse), 1);
159
+ }
160
+ if (isRecord(parsedResponse) && parsedResponse.error) {
161
+ const upstreamError = parsedResponse.error;
162
+ const upstreamMessage = isRecord(upstreamError) && typeof upstreamError.message === 'string'
163
+ ? upstreamError.message
164
+ : typeof upstreamError === 'string'
165
+ ? upstreamError
166
+ : JSON.stringify(upstreamError);
167
+ throw new CLIError('UPSTREAM_BODY_ERROR', upstreamMessage, 1, {
168
+ endpoint: `/entity/${appId}/${entityId}`,
169
+ method: 'GET',
170
+ upstreamError,
171
+ });
172
+ }
173
+ const fields = extractEntityFields(parsedResponse);
174
+ return new Map(fields.map((field) => [field.id, field]));
175
+ }
176
+ function looksLikeLocalFilePath(value) {
177
+ return (value.startsWith('./') ||
178
+ value.startsWith('../') ||
179
+ value.startsWith('/') ||
180
+ value.startsWith('~/') ||
181
+ /\.(pdf|png|jpe?g|gif|webp|bmp|svg|heic|txt|csv|xlsx?|docx?|pptx?|zip)$/i.test(value));
182
+ }
183
+ function isValidUploadEntry(value) {
184
+ if (!isRecord(value)) {
185
+ return false;
186
+ }
187
+ const hasUploadId = typeof value.upload_id === 'number' || typeof value.upload_id === 'string';
188
+ const hasAssetsId = typeof value.assets_id === 'number' || typeof value.assets_id === 'string';
189
+ return (hasUploadId || hasAssetsId) && typeof value.file === 'string' && typeof value.file_name === 'string';
190
+ }
191
+ async function validateFileFieldPayloads(scope, command, runtimeEnv, flags, body, fetchImpl) {
192
+ if (!isRecord(body)) {
193
+ return;
194
+ }
195
+ const container = command.functionName === 'updateEntityRow' ? body.data : body.fields;
196
+ if (!isRecord(container)) {
197
+ return;
198
+ }
199
+ const appId = flagValue(flags, 'app-id');
200
+ const entityId = flagValue(flags, 'entity-id');
201
+ if (!appId || !entityId) {
202
+ return;
203
+ }
204
+ const fieldsById = await fetchEntityFields(runtimeEnv, appId, entityId, fetchImpl);
205
+ for (const [fieldIdText, value] of Object.entries(container)) {
206
+ if (!/^\d+$/.test(fieldIdText)) {
207
+ continue;
208
+ }
209
+ const fieldId = Number(fieldIdText);
210
+ const field = fieldsById.get(fieldId);
211
+ if (!field || (field.type !== 'file' && field.type !== 'image')) {
212
+ continue;
213
+ }
214
+ const fieldPath = command.functionName === 'updateEntityRow' ? `body.data.${fieldId}` : `body.fields.${fieldId}`;
215
+ const fieldName = field.label ? `${field.label} (${field.type})` : `${field.type} field ${fieldId}`;
216
+ if (typeof value === 'string') {
217
+ const guidance = looksLikeLocalFilePath(value)
218
+ ? `do not put a local file path directly into ${fieldName}; run "arcubase upload ${value}" first and use the returned data array as the field value`
219
+ : `${fieldName} must be an array returned by "arcubase upload <local-file>", not a string`;
220
+ throw new CLIError('BODY_VALIDATION_FAILED', guidance, 2, {
221
+ operation: `${scope}.${command.module}.${command.functionName}`,
222
+ requestType: command.requestType ?? undefined,
223
+ issues: [{ path: fieldPath, message: guidance }],
224
+ docHints: [
225
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
226
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
227
+ ],
228
+ });
229
+ }
230
+ if (isRecord(value)) {
231
+ const guidance = `${fieldName} must be an array. Run "arcubase upload <local-file>" and use the returned data array directly as the field value`;
232
+ throw new CLIError('BODY_VALIDATION_FAILED', guidance, 2, {
233
+ operation: `${scope}.${command.module}.${command.functionName}`,
234
+ requestType: command.requestType ?? undefined,
235
+ issues: [{ path: fieldPath, message: guidance }],
236
+ docHints: [
237
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
238
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
239
+ ],
240
+ });
241
+ }
242
+ if (!Array.isArray(value)) {
243
+ const guidance = `${fieldName} must be an array returned by "arcubase upload <local-file>"`;
244
+ throw new CLIError('BODY_VALIDATION_FAILED', guidance, 2, {
245
+ operation: `${scope}.${command.module}.${command.functionName}`,
246
+ requestType: command.requestType ?? undefined,
247
+ issues: [{ path: fieldPath, message: guidance }],
248
+ docHints: [
249
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
250
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
251
+ ],
252
+ });
253
+ }
254
+ for (const [index, item] of value.entries()) {
255
+ if (!isValidUploadEntry(item)) {
256
+ const guidance = `${fieldName} entries must include upload_id or assets_id plus file and file_name. Use "arcubase upload <local-file>" and copy the returned array item as-is`;
257
+ throw new CLIError('BODY_VALIDATION_FAILED', guidance, 2, {
258
+ operation: `${scope}.${command.module}.${command.functionName}`,
259
+ requestType: command.requestType ?? undefined,
260
+ issues: [{ path: `${fieldPath}.${index}`, message: guidance }],
261
+ docHints: [
262
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
263
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
264
+ ],
265
+ });
266
+ }
267
+ }
268
+ }
269
+ }
100
270
  function throwBodyValidationFailure(message, requestType, path, scope, command) {
101
271
  throw new CLIError('BODY_VALIDATION_FAILED', message, 2, {
102
272
  operation: `${scope}.${command.module}.${command.functionName}`,
@@ -141,8 +311,7 @@ function validateActionSpecificBody(scope, command, flags, body) {
141
311
  export function summarizeCommand(scope, moduleName, commandName) {
142
312
  const command = findCommand(scope, moduleName, commandName);
143
313
  if (!command) {
144
- const suggestions = findCommandSuggestions(moduleName, commandName).map((item) => `${item.binary} ${item.moduleName} ${item.commandName}`);
145
- throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, suggestions.length > 0 ? { suggestions } : undefined);
314
+ throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName));
146
315
  }
147
316
  return {
148
317
  scope,
@@ -156,6 +325,26 @@ export function summarizeCommand(scope, moduleName, commandName) {
156
325
  }
157
326
  export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
158
327
  const parsed = parseCLIArgs(argv);
328
+ if (scope === 'user' && parsed.commandTokens[0] === 'upload') {
329
+ if (hasFlag(parsed.flags, 'help') || parsed.commandTokens.length === 1) {
330
+ return { kind: 'help', text: renderUploadHelp() };
331
+ }
332
+ const sourcePath = parsed.commandTokens[1];
333
+ if (!sourcePath) {
334
+ throw new CLIError('UPLOAD_SOURCE_REQUIRED', 'upload requires a local file path', 2);
335
+ }
336
+ const runtimeEnv = env ?? loadRuntimeEnv(scope);
337
+ const data = await uploadLocalFile(runtimeEnv, sourcePath, {
338
+ filename: flagValue(parsed.flags, 'filename'),
339
+ global: hasFlag(parsed.flags, 'global'),
340
+ }, fetchImpl);
341
+ return {
342
+ kind: 'result',
343
+ commandPath: ['upload', sourcePath],
344
+ status: 200,
345
+ data,
346
+ };
347
+ }
159
348
  if (parsed.commandTokens.length === 0) {
160
349
  return { kind: 'help', text: renderRootHelp(scope) };
161
350
  }
@@ -172,7 +361,7 @@ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
172
361
  const runtimeEnv = env ?? loadRuntimeEnv(scope);
173
362
  const command = findCommand(scope, moduleName, commandName);
174
363
  if (!command) {
175
- throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2);
364
+ throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2, buildUnknownCommandDetails(scope, moduleName, commandName));
176
365
  }
177
366
  const endpoint = normalizeExternalEndpoint(resolveEndpoint(command.endpoint, parsed.flags));
178
367
  const scalarQuery = buildScalarQuery(command, parsed.flags);
@@ -215,6 +404,9 @@ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
215
404
  }
216
405
  }
217
406
  }
407
+ if (scope === 'user' && (command.functionName === 'insertEntity' || command.functionName === 'updateEntityRow')) {
408
+ await validateFileFieldPayloads(scope, command, runtimeEnv, parsed.flags, validatedBody, fetchImpl);
409
+ }
218
410
  const response = await fetchImpl(buildURL(runtimeEnv.ARCUBASE_BASE_URL, endpoint, Object.keys(query).length > 0 ? query : undefined), {
219
411
  method: command.method,
220
412
  headers: {
@@ -246,11 +438,22 @@ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
246
438
  : typeof parsedResponse.request_id === 'string'
247
439
  ? parsedResponse.request_id
248
440
  : undefined;
441
+ const uploadBindFailure = upstreamMessage.includes('failed to bind upload to assets');
249
442
  throw new CLIError('UPSTREAM_BODY_ERROR', upstreamMessage, 1, {
250
443
  endpoint,
251
444
  method: command.method,
252
445
  traceId,
253
446
  upstreamError,
447
+ ...(uploadBindFailure
448
+ ? {
449
+ reason: 'the row payload used a file/image upload entry, but Arcubase could not bind that upload into an asset record',
450
+ hint: 'verify the upload storage host is reachable from the Arcubase server, then rerun arcubase upload and retry the row insert/update',
451
+ docHints: [
452
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
453
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
454
+ ],
455
+ }
456
+ : {}),
254
457
  });
255
458
  }
256
459
  return {
@@ -0,0 +1,13 @@
1
+ import type { RuntimeEnv } from './env.js';
2
+ export type ArcubaseUploadResult = {
3
+ upload_id: number | string;
4
+ file: string;
5
+ file_name: string;
6
+ meta: Record<string, never>;
7
+ };
8
+ export declare function renderUploadHelp(): string;
9
+ export declare function uploadLocalFile(env: RuntimeEnv, sourcePath: string, options: {
10
+ filename?: string;
11
+ global?: boolean;
12
+ }, fetchImpl?: typeof fetch): Promise<ArcubaseUploadResult[]>;
13
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/runtime/upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAuB1C,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;CAC5B,CAAA;AA+CD,wBAAgB,gBAAgB,IAAI,MAAM,CA0BzC;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,EACD,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAiFjC"}
@@ -0,0 +1,148 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { CLIError } from './errors.js';
4
+ import { buildRequestHeaders, buildURL } from './http.js';
5
+ function readUploadSource(sourcePath) {
6
+ const absolutePath = path.resolve(sourcePath);
7
+ if (!fs.existsSync(absolutePath)) {
8
+ throw new CLIError('UPLOAD_SOURCE_NOT_FOUND', `upload source file does not exist: ${sourcePath}`, 2);
9
+ }
10
+ const stat = fs.statSync(absolutePath);
11
+ if (!stat.isFile()) {
12
+ throw new CLIError('UPLOAD_SOURCE_INVALID', `upload source path must be a file: ${sourcePath}`, 2);
13
+ }
14
+ return {
15
+ absolutePath,
16
+ bytes: fs.readFileSync(absolutePath),
17
+ };
18
+ }
19
+ function parseJSONResponse(raw) {
20
+ try {
21
+ return raw ? JSON.parse(raw) : null;
22
+ }
23
+ catch {
24
+ return raw;
25
+ }
26
+ }
27
+ function requireTokenPayload(payload) {
28
+ if (payload && typeof payload === 'object' && payload.error) {
29
+ const upstreamError = payload.error;
30
+ const message = typeof upstreamError === 'object' && upstreamError && 'message' in upstreamError && typeof upstreamError.message === 'string'
31
+ ? upstreamError.message
32
+ : typeof upstreamError === 'string'
33
+ ? upstreamError
34
+ : JSON.stringify(upstreamError);
35
+ throw new CLIError('UPSTREAM_BODY_ERROR', message, 1, {
36
+ endpoint: '/upload/token',
37
+ method: 'POST',
38
+ traceId: typeof payload.trace_id === 'string' ? payload.trace_id : undefined,
39
+ upstreamError,
40
+ });
41
+ }
42
+ if (!payload?.data || typeof payload.data !== 'object') {
43
+ throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response did not include data', 1);
44
+ }
45
+ return payload.data;
46
+ }
47
+ export function renderUploadHelp() {
48
+ return [
49
+ 'arcubase upload <local-file> [flags]',
50
+ '',
51
+ 'purpose:',
52
+ ' - upload one local file through Arcubase',
53
+ ' - return a field value that can be used directly in file or image row payloads',
54
+ '',
55
+ 'flags:',
56
+ ' --filename <name> override the uploaded filename stored in Arcubase',
57
+ ' --global request a global upload token instead of a tenant-scoped token',
58
+ '',
59
+ 'result shape:',
60
+ ' - data is a JSON array',
61
+ ' - use that array directly as the value of a file or image field in insert/update payloads',
62
+ '',
63
+ 'example:',
64
+ ' arcubase upload ./contract.pdf',
65
+ ' arcubase upload ./photo.jpg --filename lead-photo.jpg',
66
+ '',
67
+ 'docs:',
68
+ ' - Uploads: /opt/arcubase-sdk/docs/runtime-reference/uploads.md',
69
+ ' - Row CRUD: /opt/arcubase-sdk/docs/runtime-reference/row-crud.md',
70
+ ' - File field: /opt/arcubase-sdk/docs/runtime-reference/entity-schema/file.md',
71
+ ' - Image field: /opt/arcubase-sdk/docs/runtime-reference/entity-schema/image.md',
72
+ ].join('\n');
73
+ }
74
+ export async function uploadLocalFile(env, sourcePath, options, fetchImpl = fetch) {
75
+ const { absolutePath, bytes } = readUploadSource(sourcePath);
76
+ const filename = (options.filename && options.filename.trim()) || path.basename(absolutePath);
77
+ const tokenResponse = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, '/upload/token'), {
78
+ method: 'POST',
79
+ headers: {
80
+ ...buildRequestHeaders(env),
81
+ 'Content-Type': 'application/json',
82
+ },
83
+ body: JSON.stringify({
84
+ global: Boolean(options.global),
85
+ }),
86
+ });
87
+ const tokenRaw = await tokenResponse.text();
88
+ const tokenParsed = parseJSONResponse(tokenRaw);
89
+ if (!tokenResponse.ok) {
90
+ throw new CLIError('UPLOAD_TOKEN_REQUEST_FAILED', typeof tokenParsed === 'string' ? tokenParsed : JSON.stringify(tokenParsed), 1, {
91
+ endpoint: '/upload/token',
92
+ method: 'POST',
93
+ });
94
+ }
95
+ const tokenData = requireTokenPayload(tokenParsed);
96
+ if (tokenData.mode !== 'aliyun-oss' && tokenData.mode !== 's3-post-policy') {
97
+ throw new CLIError('UPLOAD_MODE_UNSUPPORTED', `unsupported upload mode: ${String(tokenData.mode || '')}`, 1);
98
+ }
99
+ if (!tokenData.token_data?.dir || !tokenData.token_data.policy || !tokenData.token_data.host) {
100
+ throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required token_data fields', 1);
101
+ }
102
+ if (tokenData.id === undefined || tokenData.id === null || tokenData.id === '') {
103
+ throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing id', 1);
104
+ }
105
+ const form = new FormData();
106
+ form.append('key', `${tokenData.token_data.dir}${filename}`);
107
+ form.append('policy', tokenData.token_data.policy);
108
+ if (tokenData.mode === 'aliyun-oss') {
109
+ if (!tokenData.token_data.accessid || !tokenData.token_data.signature) {
110
+ throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required token_data fields', 1);
111
+ }
112
+ form.append('OSSAccessKeyId', tokenData.token_data.accessid);
113
+ form.append('success_action_status', '200');
114
+ form.append('signature', tokenData.token_data.signature);
115
+ }
116
+ else {
117
+ if (!tokenData.token_data.x_amz_algorithm ||
118
+ !tokenData.token_data.x_amz_credential ||
119
+ !tokenData.token_data.x_amz_date ||
120
+ !tokenData.token_data.x_amz_signature) {
121
+ throw new CLIError('UPLOAD_TOKEN_INVALID', 'upload token response is missing required s3 token_data fields', 1);
122
+ }
123
+ form.append('x-amz-algorithm', tokenData.token_data.x_amz_algorithm);
124
+ form.append('x-amz-credential', tokenData.token_data.x_amz_credential);
125
+ form.append('x-amz-date', tokenData.token_data.x_amz_date);
126
+ form.append('x-amz-signature', tokenData.token_data.x_amz_signature);
127
+ }
128
+ form.append('file', new Blob([new Uint8Array(bytes)]), filename);
129
+ const uploadResponse = await fetchImpl(tokenData.token_data.host, {
130
+ method: 'POST',
131
+ body: form,
132
+ });
133
+ const uploadRaw = await uploadResponse.text();
134
+ if (!uploadResponse.ok) {
135
+ throw new CLIError('UPLOAD_TRANSFER_FAILED', uploadRaw || `upload failed with status ${uploadResponse.status}`, 1, {
136
+ endpoint: tokenData.token_data.host,
137
+ method: 'POST',
138
+ });
139
+ }
140
+ return [
141
+ {
142
+ upload_id: tokenData.id,
143
+ file: filename,
144
+ file_name: filename,
145
+ meta: {},
146
+ },
147
+ ];
148
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"zod_registry.d.ts","sourceRoot":"","sources":["../../src/runtime/zod_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AA6BrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQ9D;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AA2DD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBhE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAI9D;AAsDD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAItE"}
1
+ {"version":3,"file":"zod_registry.d.ts","sourceRoot":"","sources":["../../src/runtime/zod_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AA6BrC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAOjE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQ9D;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAyDD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBhE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAI9D;AA0DD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAItE"}
@@ -42,54 +42,53 @@ const examplesIndexDocHint = {
42
42
  title: 'Examples index',
43
43
  file: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
44
44
  };
45
- const omsExampleDocHint = {
46
- title: 'OMS example 01',
47
- file: '/opt/arcubase-sdk/docs/runtime-reference/examples/oms-01/README.md',
48
- };
49
45
  const docHintIndex = {
50
46
  AppCreateByTenantsReqVO: [
51
47
  { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
52
48
  examplesIndexDocHint,
53
- omsExampleDocHint,
54
49
  ],
55
50
  AppCreateEntityReqVO: [
56
51
  { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
57
52
  examplesIndexDocHint,
58
- omsExampleDocHint,
59
53
  ],
60
54
  EntitySaveReqVO: [
61
55
  { title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
62
- omsExampleDocHint,
56
+ examplesIndexDocHint,
63
57
  ],
64
58
  EntityQueryReqVO: [
65
59
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
66
60
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
67
- omsExampleDocHint,
61
+ examplesIndexDocHint,
68
62
  ],
69
63
  EntityUpdateReqVO: [
70
64
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
71
- omsExampleDocHint,
65
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
66
+ examplesIndexDocHint,
72
67
  ],
73
68
  EntitySelectionActionReqVO: [
74
69
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
75
- omsExampleDocHint,
70
+ examplesIndexDocHint,
76
71
  ],
77
72
  InsertReqVO: [
78
73
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
79
- omsExampleDocHint,
74
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
75
+ examplesIndexDocHint,
80
76
  ],
81
77
  EntityBulkUpdateReqVO: [
82
78
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
83
- omsExampleDocHint,
79
+ examplesIndexDocHint,
84
80
  ],
85
81
  'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
86
82
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
83
+ examplesIndexDocHint,
87
84
  ],
88
85
  '{ policy_id: string selection: any }': [
89
86
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
87
+ examplesIndexDocHint,
90
88
  ],
91
89
  '{ policy_id: string selection: any tag_names: string[] }': [
92
90
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
91
+ examplesIndexDocHint,
93
92
  ],
94
93
  };
95
94
  export function getTypeHints(typeName) {
@@ -128,51 +127,55 @@ const commandDocHintIndex = {
128
127
  'admin.app.createAppByTenants': [
129
128
  { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
130
129
  examplesIndexDocHint,
131
- omsExampleDocHint,
132
130
  ],
133
131
  'admin.app.createEntity': [
134
132
  { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
135
- omsExampleDocHint,
133
+ examplesIndexDocHint,
136
134
  ],
137
135
  'admin.entity.adminGetEntityInfo': [
138
136
  { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
139
- omsExampleDocHint,
137
+ examplesIndexDocHint,
140
138
  ],
141
139
  'admin.entity.adminSaveEntity': [
142
140
  { title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
143
- omsExampleDocHint,
141
+ { title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
142
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
143
+ examplesIndexDocHint,
144
144
  ],
145
145
  'user.workflow.insertEntity': [
146
146
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
147
- omsExampleDocHint,
147
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
148
+ examplesIndexDocHint,
148
149
  ],
149
150
  'user.workflow.queryEntity': [
150
151
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
151
- omsExampleDocHint,
152
+ { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
153
+ examplesIndexDocHint,
152
154
  ],
153
155
  'user.workflow.getEntityRow': [
154
156
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
155
- omsExampleDocHint,
157
+ examplesIndexDocHint,
156
158
  ],
157
159
  'user.workflow.updateEntityRow': [
158
160
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
159
- omsExampleDocHint,
161
+ { title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
162
+ examplesIndexDocHint,
160
163
  ],
161
164
  'user.workflow.queryEntitySelection': [
162
165
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
163
- omsExampleDocHint,
166
+ examplesIndexDocHint,
164
167
  ],
165
168
  'user.workflow.deleteEntityRow': [
166
169
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
167
- omsExampleDocHint,
170
+ examplesIndexDocHint,
168
171
  ],
169
172
  'user.workflow.restoreEntityRow': [
170
173
  { title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
171
- omsExampleDocHint,
174
+ examplesIndexDocHint,
172
175
  ],
173
176
  'user.entity.updateEntityBulk': [
174
177
  { title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
175
- omsExampleDocHint,
178
+ examplesIndexDocHint,
176
179
  ],
177
180
  };
178
181
  export function getCommandDocHints(operation) {