@carthooks/arcubase-cli 0.1.9 → 0.1.12

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 +1407 -345
  2. package/bundle/arcubase.mjs +1407 -345
  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 +4 -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 +1025 -106
  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 +446 -32
  34. package/dist/tests/help.test.js +56 -4
  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 +109 -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 +35 -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 +4 -0
  95. package/src/runtime/execute.ts +1249 -129
  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 +564 -45
  104. package/src/tests/help.test.ts +51 -7
  105. package/src/tests/upload.test.ts +34 -91
@@ -1,10 +1,13 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
3
  import { executeCLI } from '../runtime/execute.js'
4
+ import { listCommands, type CommandScope } from '../runtime/command_registry.js'
5
+ import { getTypeHints } from '../runtime/zod_registry.js'
4
6
 
5
7
  const env = {
6
8
  ARCUBASE_BASE_URL: 'https://arcubase.example.com',
7
9
  ARCUBASE_ACCESS_TOKEN: 'tok',
10
+ ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
8
11
  ARCUBASE_TRACE_ID: '',
9
12
  ARCUBASE_SUBJECT_TYPE: '',
10
13
  ARCUBASE_SUBJECT_ID: '',
@@ -48,33 +51,74 @@ test('admin module help lists reset app commands', async () => {
48
51
  })
49
52
 
50
53
  test('user row help replaces workflow row commands', async () => {
51
- const out = await executeCLI('user', ['row', 'query', '--help'], undefined, async () => new Response('{}'))
54
+ const out = await executeCLI('user', ['row', 'query', '--help'], env as any, async () => new Response('{}'))
52
55
  assert.equal(out.kind, 'help')
53
- assert.match(out.text, /body: .*--body-file or --body-json/)
56
+ assert.match(out.text, /body: .*--body-json <json-string> \| --body-file <file>\.json/)
54
57
  assert.match(out.text, /query flags: --query-file/)
55
58
  assert.doesNotMatch(out.text, /workflow query-entity/)
56
59
  assert.doesNotMatch(out.text, /--entity-id/)
57
60
  })
58
61
 
62
+ test('user row help does not expose internal policy id input', async () => {
63
+ const out = await executeCLI('user', ['row', 'get', '--help'], env as any, async () => new Response('{}'))
64
+ assert.equal(out.kind, 'help')
65
+ assert.doesNotMatch(out.text, /--policy-id/)
66
+ assert.doesNotMatch(out.text, /policy_id/)
67
+ })
68
+
59
69
  test('admin table update-schema help uses table nouns and body-json', async () => {
60
- const out = await executeCLI('admin', ['table', 'update-schema', '--help'], undefined, async () => new Response('{}'))
70
+ const out = await executeCLI('admin', ['table', 'update-schema', '--help'], env as any, async () => new Response('{}'))
61
71
  assert.equal(out.kind, 'help')
62
- assert.match(out.text, /body: .*--body-file or --body-json/)
72
+ assert.match(out.text, /body: .*--body-json <json-string> \| --body-file <file>\.json/)
73
+ assert.match(out.text, /prefer --body-json/)
63
74
  assert.match(out.text, /path flags: --app-id, --table-id/)
64
75
  assert.doesNotMatch(out.text, /--entity-id/)
65
76
  assert.doesNotMatch(out.text, /admin-save-entity/)
66
77
  })
67
78
 
79
+ test('admin table create help gives one-call body-json example', async () => {
80
+ const out = await executeCLI('admin', ['table', 'create', '--help'], env as any, async () => new Response('{}'))
81
+ assert.equal(out.kind, 'help')
82
+ assert.match(out.text, /body-json example:/)
83
+ assert.match(out.text, /"field_id_seq":1003/)
84
+ assert.match(out.text, /"label":"投票描述"/)
85
+ assert.match(out.text, /"type":"textarea"/)
86
+ })
87
+
68
88
  test('admin access-rule help gives update whitelist and assign-users body-json examples', async () => {
69
- const update = await executeCLI('admin', ['access-rule', 'update', '--help'], undefined, async () => new Response('{}'))
89
+ const create = await executeCLI('admin', ['access-rule', 'create', '--help'], env as any, async () => new Response('{}'))
90
+ assert.equal(create.kind, 'help')
91
+ assert.match(create.text, /Access rule/)
92
+ assert.match(create.text, /access-rule\.md/)
93
+ assert.match(create.text, /"user_scope":\[\{"type":"user","id":2188889901\}\]/)
94
+ assert.doesNotMatch(create.text, /"user_scope":\[\{"type":"member"/)
95
+
96
+ const update = await executeCLI('admin', ['access-rule', 'update', '--help'], env as any, async () => new Response('{}'))
70
97
  assert.equal(update.kind, 'help')
71
98
  assert.match(update.text, /--app-id/)
72
99
  assert.match(update.text, /--table-id/)
73
100
  assert.match(update.text, /--rule-id/)
74
101
  assert.match(update.text, /"update":\["enabled"\]/)
102
+ assert.match(update.text, /"actions":\["view","add","edit"\]/)
75
103
 
76
- const assign = await executeCLI('admin', ['access-rule', 'assign-users', '--help'], undefined, async () => new Response('{}'))
104
+ const assign = await executeCLI('admin', ['access-rule', 'assign-users', '--help'], env as any, async () => new Response('{}'))
77
105
  assert.equal(assign.kind, 'help')
78
106
  assert.match(assign.text, /"update":\["user_scope"\]/)
79
- assert.match(assign.text, /"options":\{"user_scope":\[/)
107
+ assert.match(assign.text, /\{"update":\["user_scope"\],"options":\{"user_scope":\[\{"type":"user","id":2188889901\}\]\}\}/)
108
+ assert.doesNotMatch(assign.text, /"type":"member"/)
109
+ assert.doesNotMatch(assign.text, /"users"/)
110
+ assert.doesNotMatch(assign.text, /"allowedUsers"/)
111
+ })
112
+
113
+ test('every body command help exposes readable TypeScript type paths', async () => {
114
+ for (const scope of ['admin', 'user'] as const satisfies readonly CommandScope[]) {
115
+ for (const command of listCommands(scope).filter((item) => item.requestType)) {
116
+ const out = await executeCLI(scope, [...command.commandPath, '--help'], env as any, async () => new Response('{}'))
117
+ assert.equal(out.kind, 'help')
118
+ assert.match(out.text, /^types:$/m, `${scope} ${command.commandPath.join(' ')} must expose types section`)
119
+ for (const hint of getTypeHints(command.requestType ?? '', env)) {
120
+ assert.match(out.text, new RegExp(`${hint.symbol}: ${hint.file}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
121
+ }
122
+ }
123
+ }
80
124
  })
@@ -1,132 +1,75 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import fs from 'fs'
4
- import os from 'os'
5
- import path from 'path'
6
3
  import { executeCLI } from '../runtime/execute.js'
4
+ import { CLIError } from '../runtime/errors.js'
7
5
 
8
6
  const env = {
9
7
  ARCUBASE_BASE_URL: 'https://arcubase.example.com',
10
8
  ARCUBASE_ACCESS_TOKEN: 'tok',
9
+ ARCUBASE_SDK_ROOT: '/runtime/arcubase-sdk',
11
10
  ARCUBASE_TRACE_ID: '',
12
11
  ARCUBASE_SUBJECT_TYPE: '',
13
12
  ARCUBASE_SUBJECT_ID: '',
14
13
  ARCUBASE_TOKEN_EXPIRES_AT: '',
15
14
  }
16
15
 
17
- test('upload help works without runtime env', async () => {
18
- const out = await executeCLI('user', ['upload', '--help'], undefined, async () => new Response('{}'))
16
+ test('upload module help exposes reset upload commands with injected SDK root', async () => {
17
+ const out = await executeCLI('user', ['upload', '--help'], env as any, async () => new Response('{}'))
19
18
  assert.equal(out.kind, 'help')
20
- assert.match(out.text, /arcubase upload <local-file>/)
21
- assert.match(out.text, /uploads\.md/)
19
+ assert.match(out.text, /commands:/)
20
+ assert.match(out.text, /token/)
21
+ assert.match(out.text, /preview/)
22
+ assert.match(out.text, /mobile/)
23
+ assert.match(out.text, /check-mobile/)
24
+ assert.doesNotMatch(out.text, /<local-file>/)
22
25
  })
23
26
 
24
- test('upload returns file field value array for row payloads', async () => {
25
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-upload-'))
26
- const sourceFile = path.join(tempDir, 'contract.pdf')
27
- fs.writeFileSync(sourceFile, 'fake pdf bytes')
28
-
29
- const calls: Array<{ url: string; method?: string }> = []
30
-
31
- const result = await executeCLI(
32
- 'user',
33
- ['upload', sourceFile],
34
- env as any,
35
- async (input, init) => {
36
- const url = String(input)
37
- calls.push({ url, method: init?.method })
38
- if (url === 'https://arcubase.example.com/api/upload/token') {
39
- return new Response(
40
- JSON.stringify({
41
- data: {
42
- id: 123456,
43
- mode: 'aliyun-oss',
44
- token_data: {
45
- dir: 'upload/123456/',
46
- policy: 'policy',
47
- accessid: 'accessid',
48
- signature: 'signature',
49
- host: 'https://uploads.example.com',
50
- },
51
- },
52
- }),
53
- { status: 200, headers: { 'Content-Type': 'application/json' } },
54
- )
55
- }
56
- if (url === 'https://uploads.example.com') {
57
- return new Response('', { status: 200 })
58
- }
59
- throw new Error(`unexpected fetch url: ${url}`)
27
+ test('upload rejects local file shortcut', async () => {
28
+ await assert.rejects(
29
+ async () => {
30
+ await executeCLI('user', ['upload', './contract.pdf'], env as any, async () => new Response('{}'))
60
31
  },
61
- )
62
-
63
- assert.equal(result.kind, 'result')
64
- assert.deepEqual(calls, [
65
- { url: 'https://arcubase.example.com/api/upload/token', method: 'POST' },
66
- { url: 'https://uploads.example.com', method: 'POST' },
67
- ])
68
- assert.deepEqual(result.data, [
69
- {
70
- upload_id: 123456,
71
- file: 'contract.pdf',
72
- file_name: 'contract.pdf',
73
- meta: {},
32
+ (error: unknown) => {
33
+ assert.ok(error instanceof CLIError)
34
+ assert.equal(error.code, 'UNKNOWN_COMMAND')
35
+ assert.match(error.message, /unknown command: upload \.\/contract\.pdf/)
36
+ return true
74
37
  },
75
- ])
38
+ )
76
39
  })
77
40
 
78
- test('upload supports s3-post-policy tokens', async () => {
79
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-upload-s3-'))
80
- const sourceFile = path.join(tempDir, 'contract.pdf')
81
- fs.writeFileSync(sourceFile, 'fake pdf bytes')
82
-
83
- const calls: Array<{ url: string; method?: string }> = []
84
-
41
+ test('upload token executes the explicit API command', async () => {
42
+ const calls: Array<{ url: string; method?: string; body?: string }> = []
85
43
  const result = await executeCLI(
86
44
  'user',
87
- ['upload', sourceFile],
45
+ ['upload', 'token', '--body-json', '{"global":false}'],
88
46
  env as any,
89
47
  async (input, init) => {
90
48
  const url = String(input)
91
- calls.push({ url, method: init?.method })
49
+ calls.push({ url, method: init?.method, body: typeof init?.body === 'string' ? init.body : undefined })
92
50
  if (url === 'https://arcubase.example.com/api/upload/token') {
93
51
  return new Response(
94
52
  JSON.stringify({
95
53
  data: {
96
- id: 654321,
97
- mode: 's3-post-policy',
98
- token_data: {
99
- dir: 'upload/654321/',
100
- policy: 'policy',
101
- host: 'https://uploads.example.com/private-bucket',
102
- x_amz_algorithm: 'AWS4-HMAC-SHA256',
103
- x_amz_credential: 'test/20260504/us-east-1/s3/aws4_request',
104
- x_amz_date: '20260504T010203Z',
105
- x_amz_signature: 'abc123',
106
- },
54
+ id: 123456,
55
+ mode: 'aliyun-oss',
56
+ token_data: { host: 'https://uploads.example.com' },
107
57
  },
108
58
  }),
109
59
  { status: 200, headers: { 'Content-Type': 'application/json' } },
110
60
  )
111
61
  }
112
- if (url === 'https://uploads.example.com/private-bucket') {
113
- return new Response('', { status: 200 })
114
- }
115
62
  throw new Error(`unexpected fetch url: ${url}`)
116
63
  },
117
64
  )
118
65
 
119
66
  assert.equal(result.kind, 'result')
120
- assert.deepEqual(calls, [
121
- { url: 'https://arcubase.example.com/api/upload/token', method: 'POST' },
122
- { url: 'https://uploads.example.com/private-bucket', method: 'POST' },
123
- ])
124
- assert.deepEqual(result.data, [
125
- {
126
- upload_id: 654321,
127
- file: 'contract.pdf',
128
- file_name: 'contract.pdf',
129
- meta: {},
67
+ assert.deepEqual(calls, [{ url: 'https://arcubase.example.com/api/upload/token', method: 'POST', body: '{"global":false}' }])
68
+ assert.deepEqual(result.data, {
69
+ data: {
70
+ id: 123456,
71
+ mode: 'aliyun-oss',
72
+ token_data: { host: 'https://uploads.example.com' },
130
73
  },
131
- ])
74
+ })
132
75
  })