@carthooks/arcubase-cli 0.1.21 → 0.1.23

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 (57) hide show
  1. package/bundle/arcubase-admin.mjs +1430 -83
  2. package/bundle/arcubase.mjs +1430 -83
  3. package/dist/generated/command_registry.generated.d.ts +300 -0
  4. package/dist/generated/command_registry.generated.d.ts.map +1 -1
  5. package/dist/generated/command_registry.generated.js +416 -0
  6. package/dist/generated/help_examples.generated.d.ts +116 -0
  7. package/dist/generated/help_examples.generated.d.ts.map +1 -1
  8. package/dist/generated/help_examples.generated.js +184 -0
  9. package/dist/generated/type_index.generated.d.ts +40 -1
  10. package/dist/generated/type_index.generated.d.ts.map +1 -1
  11. package/dist/generated/type_index.generated.js +42 -1
  12. package/dist/generated/zod_registry.generated.d.ts +29 -1
  13. package/dist/generated/zod_registry.generated.d.ts.map +1 -1
  14. package/dist/generated/zod_registry.generated.js +37 -0
  15. package/dist/runtime/dev_sdk_gen.d.ts +9 -0
  16. package/dist/runtime/dev_sdk_gen.d.ts.map +1 -0
  17. package/dist/runtime/dev_sdk_gen.js +319 -0
  18. package/dist/runtime/execute.d.ts.map +1 -1
  19. package/dist/runtime/execute.js +326 -1
  20. package/dist/runtime/zod_registry.d.ts.map +1 -1
  21. package/dist/runtime/zod_registry.js +58 -0
  22. package/package.json +1 -1
  23. package/sdk-dist/api/admin/index.ts +1 -0
  24. package/sdk-dist/api/admin/public-link.ts +48 -0
  25. package/sdk-dist/api/shared-link/form.ts +42 -1
  26. package/sdk-dist/api/user/index.ts +0 -1
  27. package/sdk-dist/docs/runtime-reference/dashboard.md +22 -0
  28. package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +41 -0
  29. package/sdk-dist/docs/runtime-reference/help-examples/admin/dashboard/update-layout.json +16 -0
  30. package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/create.json +33 -0
  31. package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/preview-data.json +66 -0
  32. package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/update-ingress-options.json +21 -0
  33. package/sdk-dist/docs/runtime-reference/widgets.md +64 -0
  34. package/sdk-dist/generated/command_registry.generated.ts +416 -0
  35. package/sdk-dist/generated/help_examples.generated.ts +184 -0
  36. package/sdk-dist/generated/type_index.generated.ts +42 -1
  37. package/sdk-dist/generated/zod_registry.generated.ts +56 -0
  38. package/sdk-dist/types/app.ts +37 -1
  39. package/sdk-dist/types/common.ts +77 -35
  40. package/sdk-dist/types/index.ts +1 -1
  41. package/sdk-dist/types/public-link.ts +10 -0
  42. package/sdk-dist/types/shared-link.ts +16 -1
  43. package/sdk-dist/types/user-action.ts +1 -43
  44. package/src/generated/command_registry.generated.ts +416 -0
  45. package/src/generated/help_examples.generated.ts +184 -0
  46. package/src/generated/type_index.generated.ts +42 -1
  47. package/src/generated/zod_registry.generated.ts +56 -0
  48. package/src/runtime/dev_sdk_gen.ts +360 -0
  49. package/src/runtime/execute.ts +371 -1
  50. package/src/runtime/zod_registry.ts +58 -0
  51. package/src/tests/command_registry.test.ts +21 -2
  52. package/src/tests/dev_sdk_gen.test.ts +226 -0
  53. package/src/tests/docs_readability.test.ts +15 -0
  54. package/src/tests/execute_validation.test.ts +226 -0
  55. package/src/tests/help.test.ts +86 -0
  56. package/sdk-dist/api/user/copilot.ts +0 -20
  57. package/sdk-dist/types/copilot.ts +0 -34
@@ -11,9 +11,11 @@ import { resolveArcubaseSDKPath } from './paths.js';
11
11
  import { compileCSVImportMapping, compileExcelImportMapping } from './entity_import_mapping.js';
12
12
  import { uploadLocalFileWithToken } from './local_upload.js';
13
13
  import { renderCommandHelpExamples } from './help_examples.js';
14
+ import { generateArcubaseProjectSDK } from './dev_sdk_gen.js';
14
15
  export function renderRootHelp(scope) {
15
16
  const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase';
16
- const items = listModules(scope).map((item) => ` - ${item}`);
17
+ const modules = scope === 'admin' ? [...listModules(scope), 'dev'].sort() : listModules(scope);
18
+ const items = modules.map((item) => ` - ${item}`);
17
19
  return [
18
20
  `${binary} <command> [subcommand] [flags]`,
19
21
  '',
@@ -26,6 +28,16 @@ export function renderRootHelp(scope) {
26
28
  ...items,
27
29
  ].join('\n');
28
30
  }
31
+ function renderDevModuleHelp() {
32
+ return [
33
+ 'arcubase-admin dev <command> [flags]',
34
+ '',
35
+ 'developer commands for local project tooling',
36
+ '',
37
+ 'commands:',
38
+ ' - sdk-gen',
39
+ ].join('\n');
40
+ }
29
41
  export function renderModuleHelp(scope, moduleName, env = process.env) {
30
42
  const items = listModuleCommands(scope, moduleName);
31
43
  if (items.length === 0) {
@@ -226,6 +238,41 @@ function getFixedValue(value) {
226
238
  const fixedValue = value.fixedValue;
227
239
  return typeof fixedValue === 'string' ? fixedValue : undefined;
228
240
  }
241
+ function isDevSDKGenCommand(scope, moduleName, commandName) {
242
+ return scope === 'admin' && moduleName === 'dev' && commandName === 'sdk-gen';
243
+ }
244
+ function validateDevSDKGenFlags(flags) {
245
+ const allowed = new Set(['help', 'app-id', 'out']);
246
+ for (const flag of Object.keys(flags)) {
247
+ if (!allowed.has(flag)) {
248
+ throw new CLIError('UNKNOWN_FLAG', `unknown flag --${flag} for dev sdk-gen`, 2, {
249
+ operation: 'dev sdk-gen',
250
+ hint: 'run dev sdk-gen --help for the supported flags',
251
+ });
252
+ }
253
+ }
254
+ }
255
+ function renderDevSDKGenHelp() {
256
+ return [
257
+ 'arcubase-admin dev sdk-gen',
258
+ 'method: local generator',
259
+ 'endpoint: /apps/:app_id',
260
+ 'body: none',
261
+ 'path flags: --app-id',
262
+ 'flags: --out',
263
+ '',
264
+ 'generates typed TypeScript source for the current Web project',
265
+ '',
266
+ 'example:',
267
+ ' - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase',
268
+ '',
269
+ 'requirements:',
270
+ ' - missing field.key values are initialized through admin auth as field<id>',
271
+ ' - existing field.key values must be stable unique TypeScript identifiers',
272
+ ' - entity property names use entity.key when available, otherwise a stable entity<id> fallback',
273
+ ' - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })',
274
+ ].join('\n');
275
+ }
229
276
  function resolveEndpoint(command, flags) {
230
277
  let resolved = command.endpoint;
231
278
  for (const mapping of command.pathParams) {
@@ -592,6 +639,12 @@ function isTableSchemaCommand(scope, command) {
592
639
  function isAccessRuleCommand(scope, command) {
593
640
  return scope === 'admin' && command.commandPath[0] === 'access-rule';
594
641
  }
642
+ function isWidgetPreviewDataCommand(scope, command) {
643
+ return scope === 'admin' && command.commandPath[0] === 'widget' && command.commandPath[1] === 'preview-data';
644
+ }
645
+ function isWidgetUpdateCommand(scope, command) {
646
+ return scope === 'admin' && command.commandPath[0] === 'widget' && command.commandPath[1] === 'update';
647
+ }
595
648
  function isAccessRuleUpdateCommand(scope, command) {
596
649
  return scope === 'admin' && command.commandPath[0] === 'access-rule' && command.commandPath[1] === 'update';
597
650
  }
@@ -888,6 +941,91 @@ function buildBodyGuidance(requestType) {
888
941
  ],
889
942
  };
890
943
  }
944
+ if (requestType === 'AppPreviewWidgetsDataReqVO') {
945
+ return {
946
+ shapeHint: {
947
+ dataSource: 2188891001,
948
+ dataSourceType: 'entity',
949
+ dimensions: [{ IsField: true, FieldID: 1004, Name: 'Source' }],
950
+ measures: [{ FieldID: 1005, Aggregate: 'sum', Name: 'Total Amount' }],
951
+ conditions: { mode: 'and', conditions: [{ column: ':1006', operator: '$eq', value: 'yes' }] },
952
+ },
953
+ commonMistakes: [
954
+ 'widget preview-data body is the chart query itself',
955
+ 'do not send a saved widget object with id, entity_id, or options',
956
+ 'field dimensions with FieldID must set IsField:true',
957
+ 'use conditions, not filters, for chart data filtering',
958
+ 'do not retry without conditions and then hand-calculate filtered statistics',
959
+ 'unsupported aggregates are invalid; use sum, avg, max, min, count, or distinct_count',
960
+ ],
961
+ suggestions: [
962
+ 'retry with: arcubase-admin widget preview-data --app-id <app_id> --body-json \'{"dataSource":<table_id>,"dataSourceType":"entity","dimensions":[{"IsField":true,"FieldID":<field_id>,"Name":"Source"}],"measures":[{"FieldID":<number_field_id>,"Aggregate":"sum","Name":"Total Amount"}]}\'',
963
+ 'conditional stats: arcubase-admin widget preview-data --app-id <app_id> --body-json \'{"dataSource":<table_id>,"dataSourceType":"entity","dimensions":[{"IsField":true,"FieldID":<source_field_id>,"Name":"Source"}],"conditions":{"mode":"and","conditions":[{"column":":1006","operator":"$eq","value":"yes"}]},"measures":[{"FieldID":<customer_field_id>,"Aggregate":"distinct_count","Name":"Distinct Customers"}]}\'',
964
+ ],
965
+ };
966
+ }
967
+ if (requestType === 'AppNewWidgetsReqVO') {
968
+ return {
969
+ shapeHint: {
970
+ type: 'list',
971
+ options: { ingress_id: 2188893001 },
972
+ },
973
+ commonMistakes: [
974
+ 'dashboard widgets use options.dashboard_id',
975
+ 'ingress widgets use options.ingress_id',
976
+ 'do not put entity_id at body top level or under options when creating a widget',
977
+ 'after creating an ingress widget, use widget update-ingress-options to adjust display configuration',
978
+ ],
979
+ suggestions: [
980
+ 'dashboard chart: arcubase-admin widget create --app-id <app_id> --body-json \'{"type":"chart","options":{"dashboard_id":<dashboard_id>}}\'',
981
+ 'ingress list: arcubase-admin widget create --app-id <app_id> --body-json \'{"type":"list","options":{"ingress_id":<ingress_id>}}\'',
982
+ ],
983
+ };
984
+ }
985
+ if (requestType === 'AppUpdateWidgetsReqVO') {
986
+ return {
987
+ shapeHint: {
988
+ entity_id: 2188891001,
989
+ options: {
990
+ charts: {
991
+ entity_id: 2188891001,
992
+ dimensions: [{ IsField: true, FieldID: 1004, Name: 'Source' }],
993
+ measures: [{ FieldID: 1005, Aggregate: 'sum', Name: 'Total Amount' }],
994
+ conditions: { mode: 'and', conditions: [{ column: ':1006', operator: '$eq', value: 'yes' }] },
995
+ type: 'bar',
996
+ },
997
+ },
998
+ },
999
+ commonMistakes: [
1000
+ 'dashboard chart config belongs in body.options.charts',
1001
+ 'ingress/list display settings use widget update-ingress-options, not widget update',
1002
+ 'do not put ingress_id, list, default_cols, page_size, or entry display settings in widget update',
1003
+ 'field dimensions with FieldID must set IsField:true',
1004
+ 'use options.charts.conditions, not filters, for chart data filtering',
1005
+ 'do not retry without conditions and then hand-calculate filtered statistics',
1006
+ 'verify persistence with widget get or widget list after update',
1007
+ ],
1008
+ suggestions: [
1009
+ 'dashboard chart: arcubase-admin widget update --app-id <app_id> --widget-id <widget_id> --body-json \'{"entity_id":<table_id>,"options":{"charts":{"entity_id":<table_id>,"dimensions":[{"IsField":true,"FieldID":<field_id>,"Name":"Source"}],"measures":[{"FieldID":<number_field_id>,"Aggregate":"sum","Name":"Total Amount"}],"type":"bar"}}}\'',
1010
+ 'ingress list display: arcubase-admin widget update-ingress-options --app-id <app_id> --widget-id <widget_id> --body-json \'{"options":{"cond_follow":true,"cond_follow_tab":true,"cond_follow_cols":[":1001",":1002"]}}\'',
1011
+ ],
1012
+ };
1013
+ }
1014
+ if (requestType === 'AppUpdateWidgetsIngressOptionsReqVO') {
1015
+ return {
1016
+ shapeHint: {
1017
+ options: { cond_follow: true, cond_follow_tab: true, cond_follow_cols: [':1001', ':1002'] },
1018
+ },
1019
+ commonMistakes: [
1020
+ 'widget update-ingress-options is for ingress widgets, not dashboard chart widgets',
1021
+ 'put display settings under body.options',
1022
+ 'verify the ingress widget with widget get after update',
1023
+ ],
1024
+ suggestions: [
1025
+ 'retry with: arcubase-admin widget update-ingress-options --app-id <app_id> --widget-id <widget_id> --body-json \'{"options":{"cond_follow":true,"cond_follow_tab":true,"cond_follow_cols":[":1001",":1002"]}}\'',
1026
+ ],
1027
+ };
1028
+ }
891
1029
  return {};
892
1030
  }
893
1031
  function buildBodyValidationDetails(scope, command, requestType, issues, env = process.env) {
@@ -1038,6 +1176,18 @@ function requireUpdateContains(scope, command, body, field, env) {
1038
1176
  throwBodyValidationFailure(`body.update must include "${field}" when body.${field} is present. Example: {"update":["${field}"],"${field}":${field === 'enabled' ? 'true' : '"value"'}}`, command.requestType ?? 'AppIngressUpdateReqVO', 'body.update', scope, command, env);
1039
1177
  }
1040
1178
  }
1179
+ function validateChartFieldDimensions(scope, command, requestType, dimensions, pathPrefix, env) {
1180
+ if (!Array.isArray(dimensions))
1181
+ return;
1182
+ for (const [index, dimension] of dimensions.entries()) {
1183
+ if (!isRecord(dimension) || typeof dimension.FieldID !== 'number') {
1184
+ continue;
1185
+ }
1186
+ if (dimension.IsField !== true) {
1187
+ throwBodyValidationFailure('widget chart field dimensions require IsField:true when FieldID is present; otherwise grouped statistics collapse into total rows', requestType, `${pathPrefix}.${index}.IsField`, scope, command, env);
1188
+ }
1189
+ }
1190
+ }
1041
1191
  function validateActionSpecificRawBody(scope, command, body, env) {
1042
1192
  if (!isRecord(body) || !command.requestType) {
1043
1193
  return;
@@ -1093,6 +1243,14 @@ function validateActionSpecificRawBody(scope, command, body, env) {
1093
1243
  validateAccessRuleUserScope(scope, command, command.requestType, options?.user_scope, env);
1094
1244
  validateAccessRulePolicyShape(scope, command, command.requestType, policy, env, 'body.options.policy');
1095
1245
  }
1246
+ if (isWidgetPreviewDataCommand(scope, command)) {
1247
+ validateChartFieldDimensions(scope, command, command.requestType, body.dimensions, 'body.dimensions', env);
1248
+ }
1249
+ if (isWidgetUpdateCommand(scope, command)) {
1250
+ const options = isRecord(body.options) ? body.options : undefined;
1251
+ const charts = options && isRecord(options.charts) ? options.charts : undefined;
1252
+ validateChartFieldDimensions(scope, command, command.requestType, charts?.dimensions, 'body.options.charts.dimensions', env);
1253
+ }
1096
1254
  }
1097
1255
  function validateActionSpecificBody(scope, command, flags, body, env) {
1098
1256
  if (!isRecord(body) || !command.requestType) {
@@ -1443,6 +1601,163 @@ async function requestJSON(runtimeEnv, method, endpoint, body, fetchImpl) {
1443
1601
  }
1444
1602
  return { status: response.status, data: parsedResponse };
1445
1603
  }
1604
+ function unwrapResponseData(payload) {
1605
+ return isRecord(payload) && 'data' in payload ? payload.data : payload;
1606
+ }
1607
+ function extractSDKGenAppPayload(payload) {
1608
+ const appPayload = unwrapResponseData(payload);
1609
+ if (!isRecord(appPayload)) {
1610
+ throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected app detail response data', 2);
1611
+ }
1612
+ return appPayload;
1613
+ }
1614
+ function extractSDKGenEntityRefs(appPayload) {
1615
+ const sourceEntities = Array.isArray(appPayload.entities)
1616
+ ? appPayload.entities
1617
+ : Array.isArray(appPayload.tables)
1618
+ ? appPayload.tables
1619
+ : Array.isArray(appPayload.entitys)
1620
+ ? appPayload.entitys
1621
+ : [];
1622
+ const refs = sourceEntities
1623
+ .filter((item) => isRecord(item))
1624
+ .map((item) => item.id)
1625
+ .filter((id) => typeof id === 'string' || typeof id === 'number')
1626
+ .map((id) => ({ id: String(id) }));
1627
+ return refs;
1628
+ }
1629
+ function resolveSDKGenAppId(flagAppId, appPayload) {
1630
+ const fromPayload = typeof appPayload.id === 'number' ? appPayload.id : typeof appPayload.app_id === 'number' ? appPayload.app_id : undefined;
1631
+ const fromFlag = Number(flagAppId);
1632
+ if (Number.isFinite(fromFlag))
1633
+ return fromFlag;
1634
+ if (fromPayload)
1635
+ return fromPayload;
1636
+ throw new CLIError('SDK_GEN_APP_ID_REQUIRED', 'sdk-gen expected numeric app id', 2);
1637
+ }
1638
+ function walkSDKGenFields(fields, visit) {
1639
+ if (!Array.isArray(fields)) {
1640
+ return;
1641
+ }
1642
+ for (const field of fields) {
1643
+ if (!isRecord(field)) {
1644
+ continue;
1645
+ }
1646
+ visit(field);
1647
+ walkSDKGenFields(field.children, visit);
1648
+ }
1649
+ }
1650
+ function collectExistingSDKGenFieldKeys(entity) {
1651
+ const keys = new Set();
1652
+ walkSDKGenFields(entity.fields, (field) => {
1653
+ if (typeof field.key === 'string' && field.key.trim() !== '') {
1654
+ keys.add(field.key.trim());
1655
+ }
1656
+ });
1657
+ return keys;
1658
+ }
1659
+ function nextSDKGenFallbackFieldKey(fieldId, usedKeys) {
1660
+ const base = `field${fieldId}`;
1661
+ if (!usedKeys.has(base)) {
1662
+ usedKeys.add(base);
1663
+ return base;
1664
+ }
1665
+ let index = 2;
1666
+ while (usedKeys.has(`${base}_${index}`)) {
1667
+ index++;
1668
+ }
1669
+ const key = `${base}_${index}`;
1670
+ usedKeys.add(key);
1671
+ return key;
1672
+ }
1673
+ function initializeMissingSDKGenFieldKeys(entity) {
1674
+ const usedKeys = collectExistingSDKGenFieldKeys(entity);
1675
+ const updates = [];
1676
+ walkSDKGenFields(entity.fields, (field) => {
1677
+ if (typeof field.id !== 'number') {
1678
+ return;
1679
+ }
1680
+ if (typeof field.key === 'string' && field.key.trim() !== '') {
1681
+ field.key = field.key.trim();
1682
+ return;
1683
+ }
1684
+ const key = nextSDKGenFallbackFieldKey(field.id, usedKeys);
1685
+ field.key = key;
1686
+ updates.push({ id: field.id, key });
1687
+ });
1688
+ return updates;
1689
+ }
1690
+ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
1691
+ validateDevSDKGenFlags(flags);
1692
+ const appId = flagValue(flags, 'app-id');
1693
+ const outDir = flagValue(flags, 'out');
1694
+ if (!appId) {
1695
+ throw new CLIError('MISSING_PATH_FLAG', 'missing required flag --app-id', 2);
1696
+ }
1697
+ if (!outDir) {
1698
+ throw new CLIError('MISSING_FLAG', 'missing required flag --out', 2, {
1699
+ operation: 'dev sdk-gen',
1700
+ hint: 'retry with --out src/arcubase',
1701
+ });
1702
+ }
1703
+ const endpoint = `/api/apps/${encodeURIComponent(appId)}`;
1704
+ const appDetail = await requestJSON(runtimeEnv, 'GET', endpoint, undefined, fetchImpl);
1705
+ const appPayload = extractSDKGenAppPayload(appDetail.data);
1706
+ let entityRefs = extractSDKGenEntityRefs(appPayload);
1707
+ if (entityRefs.length === 0) {
1708
+ const entitiesEndpoint = `/api/apps/${encodeURIComponent(appId)}/entities`;
1709
+ const entityList = await requestJSON(runtimeEnv, 'GET', entitiesEndpoint, undefined, fetchImpl);
1710
+ const entityListPayload = unwrapResponseData(entityList.data);
1711
+ entityRefs = extractSDKGenEntityRefs({ entities: entityListPayload });
1712
+ }
1713
+ if (entityRefs.length === 0) {
1714
+ throw new CLIError('SDK_GEN_NO_ENTITIES', 'sdk-gen could not find entities in app detail response', 2);
1715
+ }
1716
+ const entities = [];
1717
+ const initializedFieldKeys = [];
1718
+ for (const entityRef of entityRefs) {
1719
+ const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
1720
+ const entityDetail = await requestJSON(runtimeEnv, 'GET', entityEndpoint, undefined, fetchImpl);
1721
+ const entityPayload = unwrapResponseData(entityDetail.data);
1722
+ if (!isRecord(entityPayload)) {
1723
+ throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected entity detail response data', 2);
1724
+ }
1725
+ const customKeyUpdates = initializeMissingSDKGenFieldKeys(entityPayload);
1726
+ if (customKeyUpdates.length > 0) {
1727
+ const customKeysEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/custom-keys`;
1728
+ await requestJSON(runtimeEnv, 'PUT', customKeysEndpoint, customKeyUpdates, fetchImpl);
1729
+ initializedFieldKeys.push({
1730
+ entityId: typeof entityPayload.id === 'number' ? entityPayload.id : Number(entityRef.id),
1731
+ fields: customKeyUpdates.map((item) => item.key),
1732
+ });
1733
+ }
1734
+ entities.push(entityPayload);
1735
+ }
1736
+ const generated = generateArcubaseProjectSDK({
1737
+ id: resolveSDKGenAppId(appId, appPayload),
1738
+ name: typeof appPayload.name === 'string' ? appPayload.name : undefined,
1739
+ entities,
1740
+ });
1741
+ const absoluteOutDir = path.resolve(process.cwd(), outDir);
1742
+ for (const file of generated.files) {
1743
+ const targetPath = path.resolve(absoluteOutDir, file.path);
1744
+ if (!targetPath.startsWith(`${absoluteOutDir}${path.sep}`)) {
1745
+ throw new CLIError('SDK_GEN_INVALID_OUTPUT_PATH', `invalid generated file path: ${file.path}`, 2);
1746
+ }
1747
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
1748
+ fs.writeFileSync(targetPath, file.contents);
1749
+ }
1750
+ return {
1751
+ kind: 'result',
1752
+ commandPath: ['dev', 'sdk-gen'],
1753
+ status: appDetail.status,
1754
+ data: {
1755
+ out: absoluteOutDir,
1756
+ files: generated.files.map((file) => file.path),
1757
+ initializedFieldKeys,
1758
+ },
1759
+ };
1760
+ }
1446
1761
  function requiredStringFlag(flags, flag) {
1447
1762
  const value = flagValue(flags, flag);
1448
1763
  if (!value) {
@@ -1772,6 +2087,16 @@ export async function executeCLI(scope, argv, env, fetchImpl = fetch) {
1772
2087
  }
1773
2088
  return { kind: 'help', text: renderRootHelp(scope) };
1774
2089
  }
2090
+ if (isDevSDKGenCommand(scope, moduleName, commandName)) {
2091
+ if (hasFlag(parsed.flags, 'help')) {
2092
+ return { kind: 'help', text: renderDevSDKGenHelp() };
2093
+ }
2094
+ const runtimeEnv = env ?? loadRuntimeEnv(scope);
2095
+ return executeDevSDKGen(runtimeEnv, parsed.flags, fetchImpl);
2096
+ }
2097
+ if (scope === 'admin' && moduleName === 'dev' && !commandName) {
2098
+ return { kind: 'help', text: renderDevModuleHelp() };
2099
+ }
1775
2100
  const singleTokenCommand = !commandName ? findCommand(scope, moduleName) : null;
1776
2101
  if (!commandName && !singleTokenCommand) {
1777
2102
  if (hasFlag(parsed.flags, 'help-examples')) {
@@ -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;AA8BrC,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;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AA6GlD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,eAAe,EAAE,CAqB7F;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAI3F;AA4ED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAInG"}
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;AA8BrC,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;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAiJlD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,eAAe,EAAE,CAqB7F;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAI3F;AAkGD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,cAAc,EAAE,CAInG"}
@@ -60,11 +60,31 @@ const conditionDocHint = {
60
60
  title: 'Condition',
61
61
  file: 'docs/runtime-reference/condition.md',
62
62
  };
63
+ const dashboardDocHint = {
64
+ title: 'Dashboard',
65
+ file: 'docs/runtime-reference/dashboard.md',
66
+ };
67
+ const widgetsDocHint = {
68
+ title: 'Widgets',
69
+ file: 'docs/runtime-reference/widgets.md',
70
+ };
63
71
  const docHintIndex = {
64
72
  AppCreateByTenantsReqVO: [
65
73
  { title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
66
74
  examplesIndexDocHint,
67
75
  ],
76
+ AppNewWidgetsReqVO: [
77
+ widgetsDocHint,
78
+ ],
79
+ AppPreviewWidgetsDataReqVO: [
80
+ widgetsDocHint,
81
+ ],
82
+ AppUpdateWidgetsIngressOptionsReqVO: [
83
+ widgetsDocHint,
84
+ ],
85
+ AppUpdateWidgetsReqVO: [
86
+ widgetsDocHint,
87
+ ],
68
88
  AppCreateEntityReqVO: [
69
89
  { title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
70
90
  { title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
@@ -83,6 +103,19 @@ const docHintIndex = {
83
103
  { title: 'Access rule', file: 'docs/runtime-reference/access-rule.md' },
84
104
  examplesIndexDocHint,
85
105
  ],
106
+ DashboardCreateReqVO: [
107
+ dashboardDocHint,
108
+ ],
109
+ DashboardUpdateLayoutReqVO: [
110
+ dashboardDocHint,
111
+ widgetsDocHint,
112
+ ],
113
+ DashboardUpdateOptionsReqVO: [
114
+ dashboardDocHint,
115
+ ],
116
+ DashboardUpdateTitleReqVO: [
117
+ dashboardDocHint,
118
+ ],
86
119
  EntitySaveReqVO: [
87
120
  { title: 'Table schema cheatsheet', file: 'docs/runtime-reference/entity-schema.md' },
88
121
  { title: 'Field type index', file: 'docs/runtime-reference/entity-schema/README.md' },
@@ -122,6 +155,9 @@ const docHintIndex = {
122
155
  { title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
123
156
  examplesIndexDocHint,
124
157
  ],
158
+ FetchWidgetsDataReqVO: [
159
+ widgetsDocHint,
160
+ ],
125
161
  'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
126
162
  selectionDocHint,
127
163
  conditionDocHint,
@@ -178,6 +214,21 @@ const commandDocHintIndex = {
178
214
  { title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
179
215
  examplesIndexDocHint,
180
216
  ],
217
+ 'admin.dashboard.getDashboardList': [
218
+ dashboardDocHint,
219
+ ],
220
+ 'admin.dashboard.getDashboardOptions': [
221
+ dashboardDocHint,
222
+ ],
223
+ 'admin.widget.widgetsList': [
224
+ widgetsDocHint,
225
+ ],
226
+ 'admin.widget.getWidgets': [
227
+ widgetsDocHint,
228
+ ],
229
+ 'admin.widget.deleteWidgets': [
230
+ widgetsDocHint,
231
+ ],
181
232
  'admin.app.createEntity': [
182
233
  { title: 'Table lifecycle', file: 'docs/runtime-reference/table-lifecycle.md' },
183
234
  examplesIndexDocHint,
@@ -245,6 +296,13 @@ const commandDocHintIndex = {
245
296
  { title: 'Search and bulk actions', file: 'docs/runtime-reference/search-and-bulk-actions.md' },
246
297
  examplesIndexDocHint,
247
298
  ],
299
+ 'user.dashboard.getDashboard': [
300
+ dashboardDocHint,
301
+ ],
302
+ 'user.dashboard.getDashboardWidgets': [
303
+ dashboardDocHint,
304
+ widgetsDocHint,
305
+ ],
248
306
  };
249
307
  export function getCommandDocHints(operation, env = process.env) {
250
308
  const key = String(operation || '').trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carthooks/arcubase-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Arcubase runtime CLI for admin and user command execution",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,3 +19,4 @@ export * from './print-template-background'
19
19
  export * from './ingress'
20
20
  export * from './config'
21
21
  export * from './app-entity-share'
22
+ export * from './public-link'
@@ -0,0 +1,48 @@
1
+ import { getDefaultClient } from '../../factory'
2
+ import type {
3
+ EntityPublicLinkType,
4
+ PublicLinkConfigItemVO,
5
+ PublicLinkConfigReqVO,
6
+ PublicLinkConfigRespVO,
7
+ PublicRecordLinkRespVO
8
+ } from '../../types/public-link'
9
+
10
+ // @endpoint GET /api/apps/:app_id/entity/:entity_id/public-links
11
+ // @controller PublicLink.ConfigList
12
+ export async function getPublicLinkConfigs(
13
+ appId: string,
14
+ entityId: string
15
+ ): Promise<PublicLinkConfigRespVO> {
16
+ return getDefaultClient().get(`/apps/${appId}/entity/${entityId}/public-links`)
17
+ }
18
+
19
+ // @endpoint PUT /api/apps/:app_id/entity/:entity_id/public-links/:type
20
+ // @controller PublicLink.ConfigSave
21
+ export async function savePublicLinkConfig(
22
+ appId: string,
23
+ entityId: string,
24
+ type: EntityPublicLinkType,
25
+ data: PublicLinkConfigReqVO
26
+ ): Promise<PublicLinkConfigItemVO> {
27
+ return getDefaultClient().put(`/apps/${appId}/entity/${entityId}/public-links/${type}`, data)
28
+ }
29
+
30
+ // @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/:type/regenerate
31
+ // @controller PublicLink.ConfigRegenerate
32
+ export async function regeneratePublicLinkConfig(
33
+ appId: string,
34
+ entityId: string,
35
+ type: EntityPublicLinkType
36
+ ): Promise<PublicLinkConfigItemVO> {
37
+ return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/${type}/regenerate`)
38
+ }
39
+
40
+ // @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/record/:row_id
41
+ // @controller PublicLink.RecordLinkCreate
42
+ export async function createPublicRecordLink(
43
+ appId: string,
44
+ entityId: string,
45
+ rowId: number | string
46
+ ): Promise<PublicRecordLinkRespVO> {
47
+ return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/record/${rowId}`)
48
+ }
@@ -1,16 +1,19 @@
1
1
  import { getDefaultClient } from '../../factory'
2
2
  import type {
3
3
  SharedLinkInitRespVO,
4
+ SharedLinkPublicQueryReqVO,
5
+ SharedLinkPublicQueryRespVO,
4
6
  SharedLinkPushMobileUploadReqVO,
5
7
  SharedLinkPushMobileUploadRespVO,
6
8
  SharedLinkSubmissionSubmitReqVO,
7
9
  SharedLinkSubmissionSubmitRespVO
8
- } from '../../types/shared-link'
10
+ } from '../../types'
9
11
  import type {
10
12
  EntityValidateUniqReqVO,
11
13
  EntityValidateUniqRespVO,
12
14
  EntityQueryRelationReqVO,
13
15
  EntityQueryRelationRespVO,
16
+ EntityQueryLinkToValuesReqVO,
14
17
  GetUploadTokenRespVO,
15
18
  UploadPreviewReqVO,
16
19
  UploadPreviewRespVO,
@@ -31,6 +34,36 @@ export async function querySharedLinkEntityRelation(
31
34
  return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-rel`, data)
32
35
  }
33
36
 
37
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-pick
38
+ // @controller SharedLink.EntityQueryLinkToPick
39
+ export async function querySharedLinkEntityLinkToPick(
40
+ appId: string,
41
+ entityId: string,
42
+ data: EntityQueryRelationReqVO
43
+ ): Promise<EntityQueryRelationRespVO> {
44
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-pick`, data)
45
+ }
46
+
47
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-values
48
+ // @controller SharedLink.EntityQueryLinkToValues
49
+ export async function querySharedLinkEntityLinkToValues(
50
+ appId: string,
51
+ entityId: string,
52
+ data: EntityQueryLinkToValuesReqVO
53
+ ): Promise<EntityQueryRelationRespVO> {
54
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-values`, data)
55
+ }
56
+
57
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-subform-rel
58
+ // @controller SharedLink.EntityQuerySubformRelation
59
+ export async function querySharedLinkEntitySubformRelation(
60
+ appId: string,
61
+ entityId: string,
62
+ data: any
63
+ ): Promise<any> {
64
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-subform-rel`, data)
65
+ }
66
+
34
67
  // @endpoint GET /link/api/form/dataset/:id/:node_id
35
68
  // @controller SharedLink.GetDataset
36
69
  export async function getSharedLinkDataset(
@@ -52,6 +85,14 @@ export async function initSharedLink(): Promise<SharedLinkInitRespVO> {
52
85
  return getDefaultClient().post('/init')
53
86
  }
54
87
 
88
+ // @endpoint POST /link/api/query
89
+ // @controller SharedLink.PublicQuery
90
+ export async function querySharedLinkPublic(
91
+ data: SharedLinkPublicQueryReqVO
92
+ ): Promise<SharedLinkPublicQueryRespVO> {
93
+ return getDefaultClient().post('/query', data)
94
+ }
95
+
55
96
  // @endpoint POST /link/api/form/upload/mobile-upload
56
97
  // @controller SharedLink.MobileUpload
57
98
  export async function uploadSharedLinkMobile(
@@ -6,7 +6,6 @@ export * from './profile'
6
6
  export * from './common'
7
7
  export * from './workflow'
8
8
  export * from './kiosk'
9
- export * from './copilot'
10
9
  export * from './flow'
11
10
  export * from './context'
12
11
  export * from './global-action'
@@ -0,0 +1,22 @@
1
+ # Dashboard
2
+
3
+ Use `arcubase app get`, `arcubase dashboard get`, and `arcubase dashboard widgets` to read dashboards as a tenant user.
4
+
5
+ User dashboard discovery:
6
+
7
+ - `arcubase entry` lists the current user's visible apps and tables.
8
+ - `arcubase app get --app-id <app_id>` returns app detail, including dashboards visible to the current user.
9
+ - To find a dashboard by name, run `app get` for candidate apps from `entry`, match `Dashboard[].Name`, then use that app id and dashboard id with `dashboard get`, `dashboard widgets`, and `widget data`.
10
+ - Do not use admin dashboard commands to prove a normal user's dashboard visibility.
11
+
12
+ Use `arcubase-admin dashboard list`, `arcubase-admin dashboard create`, `arcubase-admin dashboard get-options`, `arcubase-admin dashboard update-options`, `arcubase-admin dashboard update-layout`, and `arcubase-admin dashboard rename` to manage dashboards as an admin.
13
+
14
+ Dashboard title updates use body `{"name":"..."}`.
15
+
16
+ Dashboard layout updates use a top-level JSON array of layout positions, not an object wrapper. Example:
17
+
18
+ ```json
19
+ [{"id":2188892001,"x":0,"y":0,"w":12,"h":6}]
20
+ ```
21
+
22
+ Dashboard delete and clone are not exposed in the CLI surface until backend routes are registered.