@carthooks/arcubase-cli 0.1.25 → 0.1.27

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.
@@ -4,7 +4,7 @@ import fs from 'fs'
4
4
  import os from 'os'
5
5
  import path from 'path'
6
6
 
7
- import { generateArcubaseProjectSDK } from '../runtime/dev_sdk_gen.js'
7
+ import { ArcubaseCodeGenError, generateArcubaseProjectSDK } from '@arcubase/code-gen'
8
8
  import { executeCLI } from '../runtime/execute.js'
9
9
  import { CLIError } from '../runtime/errors.js'
10
10
 
@@ -21,7 +21,7 @@ const env = {
21
21
  function appDetail() {
22
22
  return {
23
23
  id: 3882856425,
24
- name: '故事应用',
24
+ name: 'Demo App',
25
25
  entities: {},
26
26
  }
27
27
  }
@@ -30,7 +30,7 @@ function entityList() {
30
30
  return [
31
31
  {
32
32
  id: 3883547182,
33
- name: '编故事',
33
+ name: 'Records',
34
34
  },
35
35
  ]
36
36
  }
@@ -42,7 +42,7 @@ function appDetailWithEntityList() {
42
42
  entities: [
43
43
  {
44
44
  id: 3883547182,
45
- name: '编故事',
45
+ name: 'Records',
46
46
  },
47
47
  ],
48
48
  }
@@ -52,12 +52,11 @@ function entityDetail() {
52
52
  return {
53
53
  id: 3883547182,
54
54
  app_id: 3882856425,
55
- name: '编故事',
56
- key: 'story',
55
+ name: 'Records',
57
56
  fields: [
58
- { id: 1006, label: '时代', key: 'era', type: 'select', required: false, options: { options: { items: [{ key: 1, value: '现代' }] } } },
59
- { id: 1013, label: '你刚刚', key: 'recentEvent', type: 'text', required: false, options: {} },
60
- { id: 1011, label: '故事', key: 'story', type: 'textarea', required: true, options: {} },
57
+ { id: 1006, label: 'Title', key: 'title', type: 'text', required: true, options: {} },
58
+ { id: 1013, label: 'Status', key: 'status', type: 'select', required: false, options: { options: { items: [{ key: 1, value: 'Open' }] } } },
59
+ { id: 1011, label: 'Notes', key: 'notes', type: 'textarea', required: false, options: {} },
61
60
  ],
62
61
  }
63
62
  }
@@ -70,76 +69,125 @@ function entityDetailWithoutFieldKeys() {
70
69
 
71
70
  function sdkSchemaPayload() {
72
71
  return {
73
- ...appDetailWithEntityList(),
74
- entities: [
75
- {
76
- ...entityDetail(),
77
- key: 'story',
72
+ id: 3882856425,
73
+ name: 'Demo App',
74
+ ingresses: [ingressDefinition()],
75
+ }
76
+ }
77
+
78
+ function ingressDefinition(actions = ['view', 'add', 'edit', 'delete', 'bulk_update', 'export', 'batch_print', 'tag_manage', 'archive']) {
79
+ return {
80
+ id: 9001,
81
+ hash_id: '8WR5Kypbw9KQK20',
82
+ key: 'publicEntry',
83
+ name: 'Public entry',
84
+ entity: entityDetail(),
85
+ options: {
86
+ policy: {
87
+ actions,
88
+ all_fields_read: true,
89
+ all_fields_write: true,
90
+ fields: [],
78
91
  },
79
- ],
92
+ list_options: {},
93
+ },
80
94
  }
81
95
  }
82
96
 
83
- test('generateArcubaseProjectSDK emits typed key based entity sdk', () => {
97
+ test('generateArcubaseProjectSDK emits typed ingress sdk selected from client.ingress', () => {
84
98
  const result = generateArcubaseProjectSDK(sdkSchemaPayload())
85
99
 
86
100
  assert.deepEqual(result.files.map((file) => file.path).sort(), [
87
101
  'client.ts',
88
- 'entities/story.ts',
89
102
  'index.ts',
103
+ 'ingresses/publicEntry.ts',
90
104
  'runtime.ts',
91
105
  'schema.ts',
92
106
  ])
93
107
 
94
108
  const client = result.files.find((file) => file.path === 'client.ts')?.contents ?? ''
95
- assert.match(client, /createArcubaseSdk/)
96
- assert.match(client, /story: createStorySDK\(runtime\)/)
109
+ assert.match(client, /createArcubaseClient/)
110
+ assert.match(client, /ingress<K extends ArcubaseIngressKey>\(key: K\)/)
111
+ assert.match(client, /case ["']publicEntry["']/)
97
112
 
98
113
  const runtime = result.files.find((file) => file.path === 'runtime.ts')?.contents ?? ''
99
114
  assert.match(runtime, /accessToken/)
100
115
  assert.match(runtime, /refreshToken/)
101
116
 
102
- const story = result.files.find((file) => file.path === 'entities/story.ts')?.contents ?? ''
103
- assert.match(story, /export type StoryFields =/)
104
- assert.match(story, /era\?: number \| string/)
105
- assert.match(story, /recentEvent\?: string/)
106
- assert.match(story, /story: string/)
107
- assert.match(story, /create\(fields: StoryCreateInput\)/)
108
- assert.match(story, /update\(rowId: string \| number, fields: StoryUpdateInput\)/)
109
- assert.match(story, /toArcubaseFields\(fields\)/)
110
- assert.match(story, /changed_fields: Object.keys\(data\)\.map\(\(fieldId\) => Number\(fieldId\)\)/)
111
- assert.match(story, /\/entity\/3882856425\/\$\{entityId\}\/insert/)
117
+ const ingress = result.files.find((file) => file.path === 'ingresses/publicEntry.ts')?.contents ?? ''
118
+ assert.match(ingress, /export type PublicEntryFields =/)
119
+ assert.match(ingress, /title: string/)
120
+ assert.match(ingress, /status\?: number \| string/)
121
+ assert.match(ingress, /notes\?: string/)
122
+ assert.match(ingress, /create\(fields: PublicEntryCreateInput\)/)
123
+ assert.match(ingress, /update\(rowId: string \| number, fields: PublicEntryUpdateInput\)/)
124
+ assert.match(ingress, /query\(params: PublicEntryQueryParams = {}\)/)
125
+ assert.match(ingress, /delete\(selection: PublicEntrySelection\)/)
126
+ assert.match(ingress, /bulkUpdate\(selection: PublicEntrySelection, fields: PublicEntryBulkUpdateField\[\]\)/)
127
+ assert.match(ingress, /exportRows\(options: PublicEntryExportOptions = {}\)/)
128
+ assert.match(ingress, /batchPrint\(selection: PublicEntrySelection/)
129
+ assert.match(ingress, /archive\(selection: PublicEntrySelection\)/)
130
+ assert.match(ingress, /tags:/)
131
+ assert.match(ingress, /policy_id: ingressId/)
112
132
 
113
133
  const schema = result.files.find((file) => file.path === 'schema.ts')?.contents ?? ''
114
- assert.match(schema, /"story"/)
134
+ assert.match(schema, /"publicEntry"/)
115
135
  assert.match(schema, /"fieldId": 1011/)
116
136
  assert.match(schema, /"entityId": 3883547182/)
137
+ assert.match(schema, /"ingressId": "8WR5Kypbw9KQK20"/)
138
+ assert.doesNotMatch(schema, /entity3883547182/)
117
139
  })
118
140
 
119
141
  test('generateArcubaseProjectSDK rejects missing field keys', () => {
120
142
  const payload = sdkSchemaPayload()
121
- payload.entities[0].fields[1].key = ''
143
+ payload.ingresses[0].entity.fields[1].key = ''
122
144
 
123
145
  assert.throws(() => generateArcubaseProjectSDK(payload), (error: unknown) => {
124
- assert.ok(error instanceof CLIError)
146
+ assert.ok(error instanceof ArcubaseCodeGenError)
125
147
  assert.equal(error.code, 'SDK_GEN_FIELD_KEY_REQUIRED')
126
- assert.match(error.message, /你刚刚/)
148
+ assert.match(error.message, /Status/)
127
149
  return true
128
150
  })
129
151
  })
130
152
 
131
153
  test('generateArcubaseProjectSDK rejects duplicate field keys within an entity', () => {
132
154
  const payload = sdkSchemaPayload()
133
- payload.entities[0].fields[1].key = 'story'
155
+ payload.ingresses[0].entity.fields[1].key = 'notes'
134
156
 
135
157
  assert.throws(() => generateArcubaseProjectSDK(payload), (error: unknown) => {
136
- assert.ok(error instanceof CLIError)
158
+ assert.ok(error instanceof ArcubaseCodeGenError)
137
159
  assert.equal(error.code, 'SDK_GEN_DUPLICATE_FIELD_KEY')
138
- assert.match(error.message, /story/)
160
+ assert.match(error.message, /notes/)
161
+ return true
162
+ })
163
+ })
164
+
165
+ test('generateArcubaseProjectSDK rejects missing ingress keys', () => {
166
+ const payload = sdkSchemaPayload()
167
+ payload.ingresses[0].key = ''
168
+
169
+ assert.throws(() => generateArcubaseProjectSDK(payload), (error: unknown) => {
170
+ assert.ok(error instanceof ArcubaseCodeGenError)
171
+ assert.equal(error.code, 'SDK_GEN_INGRESS_KEY_REQUIRED')
172
+ assert.match(error.message, /Public entry/)
139
173
  return true
140
174
  })
141
175
  })
142
176
 
177
+ test('generateArcubaseProjectSDK only emits methods allowed by ingress actions', () => {
178
+ const payload = {
179
+ id: 3882856425,
180
+ ingresses: [ingressDefinition(['view'])],
181
+ }
182
+
183
+ const result = generateArcubaseProjectSDK(payload)
184
+ const ingress = result.files.find((file) => file.path === 'ingresses/publicEntry.ts')?.contents ?? ''
185
+ assert.match(ingress, /query\(params: PublicEntryQueryParams = {}\)/)
186
+ assert.doesNotMatch(ingress, /create\(fields:/)
187
+ assert.doesNotMatch(ingress, /update\(rowId:/)
188
+ assert.doesNotMatch(ingress, /bulkUpdate\(/)
189
+ })
190
+
143
191
  test('arcubase-admin dev sdk-gen fetches app schema and writes typed sdk files', async () => {
144
192
  const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-sdk-gen-'))
145
193
  const calls: Array<{ url: string; method?: string }> = []
@@ -159,6 +207,9 @@ test('arcubase-admin dev sdk-gen fetches app schema and writes typed sdk files',
159
207
  if (String(url).endsWith('/api/apps/3882856425/entity/3883547182')) {
160
208
  return new Response(JSON.stringify({ data: entityDetail() }), { status: 200 })
161
209
  }
210
+ if (String(url).endsWith('/api/apps/3882856425/entity/3883547182/ingress')) {
211
+ return new Response(JSON.stringify({ data: { list: [ingressDefinition()] } }), { status: 200 })
212
+ }
162
213
  return new Response(JSON.stringify({ error: { message: `unexpected url ${url}` } }), { status: 404 })
163
214
  },
164
215
  )
@@ -171,10 +222,13 @@ test('arcubase-admin dev sdk-gen fetches app schema and writes typed sdk files',
171
222
  assert.match(calls[1].url, /\/api\/apps\/3882856425\/entities$/)
172
223
  assert.equal(calls[2].method, 'GET')
173
224
  assert.match(calls[2].url, /\/api\/apps\/3882856425\/entity\/3883547182$/)
225
+ assert.equal(calls[3].method, 'GET')
226
+ assert.match(calls[3].url, /\/api\/apps\/3882856425\/entity\/3883547182\/ingress$/)
174
227
  assert.ok(fs.existsSync(path.join(outDir, 'index.ts')))
175
228
  assert.ok(fs.existsSync(path.join(outDir, 'client.ts')))
176
- assert.ok(fs.existsSync(path.join(outDir, 'entities/story.ts')))
177
- assert.match(fs.readFileSync(path.join(outDir, 'client.ts'), 'utf8'), /createArcubaseSdk/)
229
+ assert.ok(fs.existsSync(path.join(outDir, 'ingresses/publicEntry.ts')))
230
+ assert.match(fs.readFileSync(path.join(outDir, 'client.ts'), 'utf8'), /createArcubaseClient/)
231
+ assert.notEqual(fs.existsSync(path.join(outDir, 'entities')), true)
178
232
  })
179
233
 
180
234
  test('arcubase-admin dev sdk-gen fails before generation when field keys are missing', async () => {
@@ -196,15 +250,50 @@ test('arcubase-admin dev sdk-gen fails before generation when field keys are mis
196
250
  if (String(url).endsWith('/api/apps/3882856425/entity/3883547182')) {
197
251
  return new Response(JSON.stringify({ data: entityDetailWithoutFieldKeys() }), { status: 200 })
198
252
  }
253
+ if (String(url).endsWith('/api/apps/3882856425/entity/3883547182/ingress')) {
254
+ return new Response(JSON.stringify({ data: { list: [ingressDefinition()] } }), { status: 200 })
255
+ }
199
256
  return new Response(JSON.stringify({ error: { message: `unexpected url ${url}` } }), { status: 404 })
200
257
  },
201
258
  ), (error: unknown) => {
202
259
  assert.ok(error instanceof CLIError)
203
260
  assert.equal(error.code, 'SDK_GEN_FIELD_KEY_REQUIRED')
204
- assert.match(error.message, /编故事\.时代 \(1006\)/)
261
+ assert.match(error.message, /Records\.Title \(1006\)/)
205
262
  return true
206
263
  })
207
264
 
208
265
  assert.equal(calls.some((call) => call.url.endsWith('/custom-keys')), false)
209
266
  assert.equal(fs.existsSync(path.join(outDir, 'index.ts')), false)
210
267
  })
268
+
269
+ test('arcubase-admin dev sdk-gen fails before generation when ingress keys are missing', async () => {
270
+ const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-sdk-gen-'))
271
+
272
+ await assert.rejects(() => executeCLI(
273
+ 'admin',
274
+ ['dev', 'sdk-gen', '--app-id', '3882856425', '--out', outDir],
275
+ env as any,
276
+ async (url) => {
277
+ if (String(url).endsWith('/api/apps/3882856425')) {
278
+ return new Response(JSON.stringify({ data: appDetail() }), { status: 200 })
279
+ }
280
+ if (String(url).endsWith('/api/apps/3882856425/entities')) {
281
+ return new Response(JSON.stringify({ data: entityList() }), { status: 200 })
282
+ }
283
+ if (String(url).endsWith('/api/apps/3882856425/entity/3883547182')) {
284
+ return new Response(JSON.stringify({ data: entityDetail() }), { status: 200 })
285
+ }
286
+ if (String(url).endsWith('/api/apps/3882856425/entity/3883547182/ingress')) {
287
+ return new Response(JSON.stringify({ data: { list: [{ ...ingressDefinition(), key: '' }] } }), { status: 200 })
288
+ }
289
+ return new Response(JSON.stringify({ error: { message: `unexpected url ${url}` } }), { status: 404 })
290
+ },
291
+ ), (error: unknown) => {
292
+ assert.ok(error instanceof CLIError)
293
+ assert.equal(error.code, 'SDK_GEN_INGRESS_KEY_REQUIRED')
294
+ assert.match(error.message, /Public entry/)
295
+ return true
296
+ })
297
+
298
+ assert.equal(fs.existsSync(path.join(outDir, 'index.ts')), false)
299
+ })
@@ -36,8 +36,9 @@ test('admin dev sdk-gen help documents typed project sdk generation', async () =
36
36
  assert.match(commandHelp.text, /arcubase-admin dev sdk-gen/)
37
37
  assert.match(commandHelp.text, /--app-id/)
38
38
  assert.match(commandHelp.text, /--out/)
39
+ assert.match(commandHelp.text, /ingress\.key/)
39
40
  assert.match(commandHelp.text, /field\.key/)
40
- assert.match(commandHelp.text, /createArcubaseSdk\(\{ baseURL, accessToken, refreshToken \}\)/)
41
+ assert.match(commandHelp.text, /createArcubaseClient\(\{ baseURL, accessToken, refreshToken \}\)\.ingress\("<IngressKey>"\)/)
41
42
  })
42
43
 
43
44
  test('user root help exposes reset surface nouns only', async () => {
@@ -46,6 +46,7 @@ test('AppIngressCreateReqVO accepts nested ConditionSet with field EntityProp co
46
46
  assert.ok(schema)
47
47
 
48
48
  const result = schema.safeParse({
49
+ key: 'salesOwnOrWestRegion',
49
50
  name: 'Sales own or west region',
50
51
  enabled: true,
51
52
  type: 'form',
@@ -1,4 +0,0 @@
1
- import { type GeneratedSDK } from '@arcubase/code-gen';
2
- export type { GeneratedSDK, GeneratedSDKFile } from '@arcubase/code-gen';
3
- export declare function generateArcubaseProjectSDK(payload: unknown): GeneratedSDK;
4
- //# sourceMappingURL=dev_sdk_gen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dev_sdk_gen.d.ts","sourceRoot":"","sources":["../../src/runtime/dev_sdk_gen.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAA;AAI3B,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAExE,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CASzE"}
@@ -1,13 +0,0 @@
1
- import { ArcubaseCodeGenError, generateArcubaseProjectSDK as generateArcubaseProjectSDKCore, } from '@arcubase/code-gen';
2
- import { CLIError } from './errors.js';
3
- export function generateArcubaseProjectSDK(payload) {
4
- try {
5
- return generateArcubaseProjectSDKCore(payload);
6
- }
7
- catch (error) {
8
- if (error instanceof ArcubaseCodeGenError) {
9
- throw new CLIError(error.code, error.message, 2);
10
- }
11
- throw error;
12
- }
13
- }
@@ -1,20 +0,0 @@
1
- import {
2
- ArcubaseCodeGenError,
3
- generateArcubaseProjectSDK as generateArcubaseProjectSDKCore,
4
- type GeneratedSDK,
5
- } from '@arcubase/code-gen'
6
-
7
- import { CLIError } from './errors.js'
8
-
9
- export type { GeneratedSDK, GeneratedSDKFile } from '@arcubase/code-gen'
10
-
11
- export function generateArcubaseProjectSDK(payload: unknown): GeneratedSDK {
12
- try {
13
- return generateArcubaseProjectSDKCore(payload)
14
- } catch (error) {
15
- if (error instanceof ArcubaseCodeGenError) {
16
- throw new CLIError(error.code, error.message, 2)
17
- }
18
- throw error
19
- }
20
- }