@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.
- package/bundle/arcubase-admin.mjs +813 -448
- package/bundle/arcubase.mjs +813 -448
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +3 -3
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +118 -11
- package/package.json +2 -2
- package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +14 -10
- package/sdk-dist/generated/zod_registry.generated.ts +3 -3
- package/sdk-dist/types/common.ts +1 -0
- package/sdk-dist/types/ingress.ts +15 -0
- package/sdk-dist/types/user-action.ts +1 -0
- package/src/generated/zod_registry.generated.ts +3 -3
- package/src/runtime/execute.ts +126 -11
- package/src/tests/dev_sdk_gen.test.ts +128 -39
- package/src/tests/help.test.ts +2 -1
- package/src/tests/zod_registry.test.ts +1 -0
- package/dist/runtime/dev_sdk_gen.d.ts +0 -4
- package/dist/runtime/dev_sdk_gen.d.ts.map +0 -1
- package/dist/runtime/dev_sdk_gen.js +0 -13
- package/src/runtime/dev_sdk_gen.ts +0 -20
package/bundle/arcubase.mjs
CHANGED
|
@@ -9,6 +9,592 @@ var __export = (target, all) => {
|
|
|
9
9
|
import fs3 from "fs";
|
|
10
10
|
import path2 from "path";
|
|
11
11
|
|
|
12
|
+
// node_modules/@arcubase/code-gen/dist/index.js
|
|
13
|
+
var ArcubaseCodeGenError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "ArcubaseCodeGenError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var identifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function unwrapData(payload) {
|
|
26
|
+
return isRecord(payload) && "data" in payload ? payload.data : payload;
|
|
27
|
+
}
|
|
28
|
+
function toPascalCase(value) {
|
|
29
|
+
const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
30
|
+
if (words.length === 0)
|
|
31
|
+
return "Ingress";
|
|
32
|
+
return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join("");
|
|
33
|
+
}
|
|
34
|
+
function normalizeFieldType(type) {
|
|
35
|
+
switch (type) {
|
|
36
|
+
case "number":
|
|
37
|
+
return "number";
|
|
38
|
+
case "boolean":
|
|
39
|
+
return "boolean";
|
|
40
|
+
case "select":
|
|
41
|
+
case "radio":
|
|
42
|
+
case "checkbox":
|
|
43
|
+
case "status":
|
|
44
|
+
return "number | string";
|
|
45
|
+
case "file":
|
|
46
|
+
case "image":
|
|
47
|
+
case "member":
|
|
48
|
+
case "members":
|
|
49
|
+
case "department":
|
|
50
|
+
case "departments":
|
|
51
|
+
case "linkto":
|
|
52
|
+
case "relation":
|
|
53
|
+
case "relationfield":
|
|
54
|
+
case "subform":
|
|
55
|
+
return "unknown";
|
|
56
|
+
default:
|
|
57
|
+
return "string";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function normalizeField(field) {
|
|
61
|
+
if (typeof field.id !== "number" || typeof field.label !== "string" || typeof field.type !== "string") {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
const children = Array.isArray(field.children) ? field.children.filter((child) => isRecord(child)).map(normalizeField).filter((child) => Boolean(child)) : void 0;
|
|
65
|
+
return {
|
|
66
|
+
id: field.id,
|
|
67
|
+
label: field.label,
|
|
68
|
+
key: typeof field.key === "string" ? field.key.trim() : "",
|
|
69
|
+
type: field.type,
|
|
70
|
+
required: field.required === true,
|
|
71
|
+
options: isRecord(field.options) ? field.options : void 0,
|
|
72
|
+
children
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function normalizeEntity(payload, appId) {
|
|
76
|
+
if (!isRecord(payload) || typeof payload.id !== "number" || typeof payload.name !== "string") {
|
|
77
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected ingress.entity with id and name");
|
|
78
|
+
}
|
|
79
|
+
const fields = Array.isArray(payload.fields) ? payload.fields.filter((field) => isRecord(field)).map(normalizeField).filter((field) => Boolean(field)) : [];
|
|
80
|
+
return {
|
|
81
|
+
appId,
|
|
82
|
+
id: payload.id,
|
|
83
|
+
name: payload.name,
|
|
84
|
+
fields
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function normalizeActions(ingress) {
|
|
88
|
+
const policy = isRecord(ingress.policy) ? ingress.policy : isRecord(ingress.options) && isRecord(ingress.options.policy) ? ingress.options.policy : void 0;
|
|
89
|
+
const source = Array.isArray(ingress.actions) ? ingress.actions : Array.isArray(policy?.actions) ? policy.actions : [];
|
|
90
|
+
return source.filter((action) => typeof action === "string" && action.trim() !== "").map((action) => action.trim());
|
|
91
|
+
}
|
|
92
|
+
function normalizeIngresses(payload) {
|
|
93
|
+
const root = unwrapData(payload);
|
|
94
|
+
if (!isRecord(root)) {
|
|
95
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app ingress schema object");
|
|
96
|
+
}
|
|
97
|
+
const appId = typeof root.id === "number" ? root.id : typeof root.app_id === "number" ? root.app_id : void 0;
|
|
98
|
+
if (!appId) {
|
|
99
|
+
throw new ArcubaseCodeGenError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in schema payload");
|
|
100
|
+
}
|
|
101
|
+
const sourceIngresses = Array.isArray(root.ingresses) ? root.ingresses : [];
|
|
102
|
+
if (sourceIngresses.length === 0) {
|
|
103
|
+
throw new ArcubaseCodeGenError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingresses in schema payload");
|
|
104
|
+
}
|
|
105
|
+
const ingresses = sourceIngresses.filter((item) => isRecord(item)).map((item) => {
|
|
106
|
+
const entity = normalizeEntity(item.entity, appId);
|
|
107
|
+
const options = isRecord(item.options) ? item.options : {};
|
|
108
|
+
const hashId = typeof item.hash_id === "string" ? item.hash_id.trim() : typeof item.hashId === "string" ? item.hashId.trim() : "";
|
|
109
|
+
return {
|
|
110
|
+
appId,
|
|
111
|
+
id: typeof item.id === "number" ? item.id : void 0,
|
|
112
|
+
hashId,
|
|
113
|
+
key: typeof item.key === "string" ? item.key.trim() : "",
|
|
114
|
+
name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : String(item.key ?? ""),
|
|
115
|
+
actions: normalizeActions(item),
|
|
116
|
+
entity,
|
|
117
|
+
listOptions: isRecord(options.list_options) ? options.list_options : isRecord(item.list_options) ? item.list_options : void 0
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
validateIngresses(ingresses);
|
|
121
|
+
return ingresses;
|
|
122
|
+
}
|
|
123
|
+
function walkFields(fields, visit) {
|
|
124
|
+
for (const field of fields) {
|
|
125
|
+
visit(field);
|
|
126
|
+
if (field.children && field.children.length > 0) {
|
|
127
|
+
walkFields(field.children, visit);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function allFields(entity) {
|
|
132
|
+
const out = [];
|
|
133
|
+
walkFields(entity.fields, (field) => out.push(field));
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
function validateIngresses(ingresses) {
|
|
137
|
+
const ingressKeys = /* @__PURE__ */ new Set();
|
|
138
|
+
for (const ingress of ingresses) {
|
|
139
|
+
if (!ingress.key) {
|
|
140
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INGRESS_KEY_REQUIRED", `ingress.key is required for ${ingress.name || "unnamed ingress"}`);
|
|
141
|
+
}
|
|
142
|
+
if (!identifierPattern.test(ingress.key)) {
|
|
143
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_INGRESS_KEY", `ingress.key must be a TypeScript identifier: ${ingress.key}`);
|
|
144
|
+
}
|
|
145
|
+
if (ingressKeys.has(ingress.key)) {
|
|
146
|
+
throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key: ${ingress.key}`);
|
|
147
|
+
}
|
|
148
|
+
ingressKeys.add(ingress.key);
|
|
149
|
+
if (!ingress.hashId) {
|
|
150
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", `ingress.hash_id is required for ${ingress.key}`);
|
|
151
|
+
}
|
|
152
|
+
const fieldKeys = /* @__PURE__ */ new Set();
|
|
153
|
+
for (const field of allFields(ingress.entity)) {
|
|
154
|
+
if (!field.key) {
|
|
155
|
+
throw new ArcubaseCodeGenError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${ingress.entity.name}.${field.label} (${field.id})`);
|
|
156
|
+
}
|
|
157
|
+
if (!identifierPattern.test(field.key)) {
|
|
158
|
+
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${ingress.entity.name}.${field.key}`);
|
|
159
|
+
}
|
|
160
|
+
if (fieldKeys.has(field.key)) {
|
|
161
|
+
throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${ingress.entity.name}: ${field.key}`);
|
|
162
|
+
}
|
|
163
|
+
fieldKeys.add(field.key);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function hasAction(ingress, action) {
|
|
168
|
+
return ingress.actions.includes(action);
|
|
169
|
+
}
|
|
170
|
+
function methodLines(lines) {
|
|
171
|
+
return lines.length > 0 ? `${lines.join("\n\n")}
|
|
172
|
+
` : "";
|
|
173
|
+
}
|
|
174
|
+
function emitRuntime() {
|
|
175
|
+
return `export type ArcubaseClientConfig = {
|
|
176
|
+
baseURL: string
|
|
177
|
+
accessToken?: string
|
|
178
|
+
refreshToken?: string
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type ArcubaseRow<TFields> = {
|
|
182
|
+
id: number
|
|
183
|
+
title?: string
|
|
184
|
+
fields: TFields
|
|
185
|
+
raw: Record<string, any>
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type ArcubaseRuntime = {
|
|
189
|
+
request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
type ArcubaseEnvelope<T> = {
|
|
193
|
+
data?: T
|
|
194
|
+
error?: { message?: string; key?: string; type?: string }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createRuntime(config: ArcubaseClientConfig): ArcubaseRuntime {
|
|
198
|
+
let accessToken = config.accessToken ?? ''
|
|
199
|
+
let refreshToken = config.refreshToken ?? ''
|
|
200
|
+
|
|
201
|
+
async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
|
|
202
|
+
const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
|
|
203
|
+
const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
|
|
204
|
+
method,
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'application/json',
|
|
207
|
+
...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
|
|
208
|
+
...(refreshToken ? { 'X-Arcubase-Refresh-Token': refreshToken } : {}),
|
|
209
|
+
},
|
|
210
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
211
|
+
})
|
|
212
|
+
const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
|
|
213
|
+
if (!response.ok || payload?.error) {
|
|
214
|
+
throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
|
|
215
|
+
}
|
|
216
|
+
return payload?.data as T
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { request }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function toArcubaseFields<T extends Record<string, any>>(fields: Partial<T>, fieldIds: Record<string, number>): Record<string, any> {
|
|
223
|
+
const out: Record<string, any> = {}
|
|
224
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
225
|
+
if (value === undefined) continue
|
|
226
|
+
const fieldId = fieldIds[key]
|
|
227
|
+
if (!fieldId) {
|
|
228
|
+
throw new Error(\`Unknown Arcubase field: \${key}\`)
|
|
229
|
+
}
|
|
230
|
+
out[String(fieldId)] = value
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function fromArcubaseFields<T>(raw: Record<string, any> | undefined, fieldIds: Record<string, number>): T {
|
|
236
|
+
const out: Record<string, any> = {}
|
|
237
|
+
const source = raw ?? {}
|
|
238
|
+
for (const [key, fieldId] of Object.entries(fieldIds)) {
|
|
239
|
+
if (String(fieldId) in source) {
|
|
240
|
+
out[key] = source[String(fieldId)]
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return out as T
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function toArcubaseColumn(column: string, fieldIds: Record<string, number>): string {
|
|
247
|
+
return fieldIds[column] ? \`:\${fieldIds[column]}\` : column
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function mapArcubaseCondition(value: unknown, fieldIds: Record<string, number>): unknown {
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
return value.map((item) => mapArcubaseCondition(item, fieldIds))
|
|
253
|
+
}
|
|
254
|
+
if (!value || typeof value !== 'object') {
|
|
255
|
+
return value
|
|
256
|
+
}
|
|
257
|
+
const out: Record<string, unknown> = {}
|
|
258
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
259
|
+
if (key === 'column' && typeof child === 'string') {
|
|
260
|
+
out[key] = toArcubaseColumn(child, fieldIds)
|
|
261
|
+
} else {
|
|
262
|
+
const mappedKey = fieldIds[key] ? \`:\${fieldIds[key]}\` : key
|
|
263
|
+
out[mappedKey] = mapArcubaseCondition(child, fieldIds)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return out
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function mapArcubaseSorts<T extends { column: string; order?: string }>(sorts: T[] | undefined, fieldIds: Record<string, number>): T[] | undefined {
|
|
270
|
+
return sorts?.map((sort) => ({ ...sort, column: toArcubaseColumn(sort.column, fieldIds) }))
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
function emitSchema(ingresses) {
|
|
275
|
+
const schema = {
|
|
276
|
+
ingresses: Object.fromEntries(ingresses.map((ingress) => [
|
|
277
|
+
ingress.key,
|
|
278
|
+
{
|
|
279
|
+
appId: ingress.appId,
|
|
280
|
+
ingressId: ingress.hashId,
|
|
281
|
+
entityId: ingress.entity.id,
|
|
282
|
+
name: ingress.name,
|
|
283
|
+
actions: ingress.actions,
|
|
284
|
+
fields: Object.fromEntries(allFields(ingress.entity).map((field) => [
|
|
285
|
+
field.key,
|
|
286
|
+
{
|
|
287
|
+
fieldId: field.id,
|
|
288
|
+
label: field.label,
|
|
289
|
+
type: field.type,
|
|
290
|
+
required: field.required
|
|
291
|
+
}
|
|
292
|
+
]))
|
|
293
|
+
}
|
|
294
|
+
]))
|
|
295
|
+
};
|
|
296
|
+
return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
|
|
297
|
+
|
|
298
|
+
export type ArcubaseIngressKey = keyof typeof arcubaseSchema.ingresses
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
function emitIngress(ingress) {
|
|
302
|
+
const typeName = toPascalCase(ingress.key);
|
|
303
|
+
const fields = allFields(ingress.entity);
|
|
304
|
+
const fieldKeyUnion = fields.length > 0 ? fields.map((field) => `'${field.key}'`).join(" | ") : "never";
|
|
305
|
+
const fieldLines = fields.map((field) => {
|
|
306
|
+
const optional2 = field.required ? "" : "?";
|
|
307
|
+
return ` ${field.key}${optional2}: ${normalizeFieldType(field.type)}`;
|
|
308
|
+
});
|
|
309
|
+
const fieldMapLines = fields.map((field) => ` ${field.key}: ${field.id},`);
|
|
310
|
+
const methodChunks = [];
|
|
311
|
+
if (hasAction(ingress, "view")) {
|
|
312
|
+
methodChunks.push(` async query(params: ${typeName}QueryParams = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number; offset?: number; limit?: number }> {
|
|
313
|
+
const data = await runtime.request<{ items?: Record<string, any>[]; total?: number; offset?: number; limit?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/data-query\`, {
|
|
314
|
+
...params,
|
|
315
|
+
policy_id: ingressId,
|
|
316
|
+
search: mapArcubaseCondition(params.search, fieldIds),
|
|
317
|
+
sorts: mapArcubaseSorts(params.sorts, fieldIds),
|
|
318
|
+
})
|
|
319
|
+
return {
|
|
320
|
+
items: (data.items ?? []).map(mapRow),
|
|
321
|
+
total: data.total,
|
|
322
|
+
offset: data.offset,
|
|
323
|
+
limit: data.limit,
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
|
|
328
|
+
const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}?policy_id=\${encodeURIComponent(ingressId)}\`)
|
|
329
|
+
return mapRow(data.record ?? {})
|
|
330
|
+
},`);
|
|
331
|
+
}
|
|
332
|
+
if (hasAction(ingress, "add")) {
|
|
333
|
+
methodChunks.push(` async prepareCreate(): Promise<Record<string, any>> {
|
|
334
|
+
return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/prepare\`, {
|
|
335
|
+
policy_id: ingressId,
|
|
336
|
+
})
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
async create(fields: ${typeName}CreateInput): Promise<number> {
|
|
340
|
+
return runtime.request<number>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/insert\`, {
|
|
341
|
+
fields: toArcubaseFields(fields, fieldIds),
|
|
342
|
+
policy_id: ingressId,
|
|
343
|
+
})
|
|
344
|
+
},`);
|
|
345
|
+
}
|
|
346
|
+
if (hasAction(ingress, "edit")) {
|
|
347
|
+
methodChunks.push(` async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
|
|
348
|
+
const data = toArcubaseFields(fields, fieldIds)
|
|
349
|
+
return runtime.request<boolean>('PUT', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}\`, {
|
|
350
|
+
data,
|
|
351
|
+
changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
|
|
352
|
+
policy_id: ingressId,
|
|
353
|
+
})
|
|
354
|
+
},`);
|
|
355
|
+
}
|
|
356
|
+
if (hasAction(ingress, "delete")) {
|
|
357
|
+
methodChunks.push(` async delete(selection: ${typeName}Selection): Promise<{ deleted?: number } | boolean> {
|
|
358
|
+
return runtime.request<{ deleted?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/delete-item\`, {
|
|
359
|
+
policy_id: ingressId,
|
|
360
|
+
selection: toArcubaseSelection(selection),
|
|
361
|
+
})
|
|
362
|
+
},`);
|
|
363
|
+
}
|
|
364
|
+
if (hasAction(ingress, "bulk_update")) {
|
|
365
|
+
methodChunks.push(` async bulkUpdate(selection: ${typeName}Selection, fields: ${typeName}BulkUpdateField[]): Promise<{ updated: number }> {
|
|
366
|
+
return runtime.request<{ updated: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update\`, {
|
|
367
|
+
policy_id: ingressId,
|
|
368
|
+
selection: toArcubaseSelection(selection),
|
|
369
|
+
fields: fields.map((field) => ({
|
|
370
|
+
id: fieldIds[field.key],
|
|
371
|
+
action: field.action,
|
|
372
|
+
})),
|
|
373
|
+
})
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
async saveBulkUpdate(input: { name: string; subject: string; fields: ${typeName}BulkUpdateField[] }): Promise<string> {
|
|
377
|
+
return runtime.request<string>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update-save-as\`, {
|
|
378
|
+
name: input.name,
|
|
379
|
+
subject: input.subject,
|
|
380
|
+
fields: input.fields.map((field) => ({
|
|
381
|
+
id: fieldIds[field.key],
|
|
382
|
+
action: field.action,
|
|
383
|
+
})),
|
|
384
|
+
})
|
|
385
|
+
},`);
|
|
386
|
+
}
|
|
387
|
+
if (hasAction(ingress, "export")) {
|
|
388
|
+
methodChunks.push(` async exportRows(options: ${typeName}ExportOptions = {}): Promise<{ task_id: string }> {
|
|
389
|
+
const fieldKeys = options.fields ?? Object.keys(fieldIds) as ${typeName}FieldKey[]
|
|
390
|
+
return runtime.request<{ task_id: string }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/export\`, {
|
|
391
|
+
policy_id: ingressId,
|
|
392
|
+
props: options.props ?? [],
|
|
393
|
+
fields: fieldKeys.map((key) => fieldIds[key]),
|
|
394
|
+
propnames: options.propNames ?? {},
|
|
395
|
+
options: { has_id: options.hasIdField ?? false },
|
|
396
|
+
})
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
async exportTask(taskId: string): Promise<Record<string, any>> {
|
|
400
|
+
return runtime.request<Record<string, any>>('GET', \`/export-task/\${taskId}\`)
|
|
401
|
+
},`);
|
|
402
|
+
}
|
|
403
|
+
if (hasAction(ingress, "batch_print")) {
|
|
404
|
+
methodChunks.push(` async batchPrint(selection: ${typeName}Selection, params: { limit?: number; offset?: number } = {}): Promise<{ items?: Record<string, any>[]; total?: number }> {
|
|
405
|
+
return runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/batch_print\`, {
|
|
406
|
+
...params,
|
|
407
|
+
policy_id: ingressId,
|
|
408
|
+
selection: toArcubaseSelection(selection),
|
|
409
|
+
})
|
|
410
|
+
},`);
|
|
411
|
+
}
|
|
412
|
+
if (hasAction(ingress, "archive")) {
|
|
413
|
+
methodChunks.push(` async archive(selection: ${typeName}Selection): Promise<{ archived?: number } | boolean> {
|
|
414
|
+
return runtime.request<{ archived?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/archive\`, {
|
|
415
|
+
policy_id: ingressId,
|
|
416
|
+
selection: toArcubaseSelection(selection),
|
|
417
|
+
})
|
|
418
|
+
},`);
|
|
419
|
+
}
|
|
420
|
+
if (hasAction(ingress, "tag_manage")) {
|
|
421
|
+
methodChunks.push(` tags: {
|
|
422
|
+
async list(): Promise<string[]> {
|
|
423
|
+
return runtime.request<string[]>('GET', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/tag-names\`)
|
|
424
|
+
},
|
|
425
|
+
async add(selection: ${typeName}Selection, tagNames: string[]): Promise<Record<string, any>> {
|
|
426
|
+
return runtime.request<Record<string, any>>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-add-tags\`, {
|
|
427
|
+
policy_id: ingressId,
|
|
428
|
+
selection: toArcubaseSelection(selection),
|
|
429
|
+
tag_names: tagNames,
|
|
430
|
+
})
|
|
431
|
+
},
|
|
432
|
+
async remove(selection: ${typeName}Selection, tagNames: string[]): Promise<boolean> {
|
|
433
|
+
return runtime.request<boolean>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-remove-tags\`, {
|
|
434
|
+
policy_id: ingressId,
|
|
435
|
+
selection: toArcubaseSelection(selection),
|
|
436
|
+
tag_names: tagNames,
|
|
437
|
+
})
|
|
438
|
+
},
|
|
439
|
+
async selection(selection: ${typeName}Selection): Promise<string[]> {
|
|
440
|
+
return runtime.request<string[]>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/get-selection-tags\`, {
|
|
441
|
+
policy_id: ingressId,
|
|
442
|
+
selection: toArcubaseSelection(selection),
|
|
443
|
+
})
|
|
444
|
+
},
|
|
445
|
+
},`);
|
|
446
|
+
}
|
|
447
|
+
const customActions = ingress.actions.filter((action) => !["view", "add", "edit", "delete", "bulk_update", "export", "batch_print", "archive", "tag_manage"].includes(action));
|
|
448
|
+
if (customActions.length > 0) {
|
|
449
|
+
methodChunks.push(` async action(action: ${typeName}CustomAction, payload: Record<string, any> = {}): Promise<Record<string, any>> {
|
|
450
|
+
if (!allowedActions.includes(action)) {
|
|
451
|
+
throw new Error(\`Action is not allowed for ${ingress.key}: \${action}\`)
|
|
452
|
+
}
|
|
453
|
+
return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/\${encodeURIComponent(action)}\`, {
|
|
454
|
+
...payload,
|
|
455
|
+
policy_id: ingressId,
|
|
456
|
+
selection: payload.selection ? toArcubaseSelection(payload.selection) : undefined,
|
|
457
|
+
})
|
|
458
|
+
},`);
|
|
459
|
+
}
|
|
460
|
+
return `import {
|
|
461
|
+
fromArcubaseFields,
|
|
462
|
+
mapArcubaseCondition,
|
|
463
|
+
mapArcubaseSorts,
|
|
464
|
+
toArcubaseFields,
|
|
465
|
+
type ArcubaseRow,
|
|
466
|
+
type ArcubaseRuntime,
|
|
467
|
+
} from '../runtime.js'
|
|
468
|
+
|
|
469
|
+
export type ${typeName}FieldKey = ${fieldKeyUnion}
|
|
470
|
+
|
|
471
|
+
export type ${typeName}Fields = {
|
|
472
|
+
${fieldLines.join("\n")}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export type ${typeName}CreateInput = ${typeName}Fields
|
|
476
|
+
export type ${typeName}UpdateInput = Partial<${typeName}Fields>
|
|
477
|
+
export type ${typeName}Action = ${ingress.actions.map((action) => `'${action}'`).join(" | ") || "never"}
|
|
478
|
+
export type ${typeName}CustomAction = ${customActions.map((action) => `'${action}'`).join(" | ") || "never"}
|
|
479
|
+
|
|
480
|
+
export type ${typeName}Sort = {
|
|
481
|
+
column: ${typeName}FieldKey | 'id' | 'created_at' | 'updated_at' | 'creator' | string
|
|
482
|
+
order: 'asc' | 'desc'
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export type ${typeName}QueryParams = {
|
|
486
|
+
limit?: number
|
|
487
|
+
offset?: number
|
|
488
|
+
search?: Record<string, any>
|
|
489
|
+
sorts?: ${typeName}Sort[]
|
|
490
|
+
view_mode?: 'normal' | 'deleted' | 'archived'
|
|
491
|
+
withSubForm?: boolean
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export type ${typeName}Selection = {
|
|
495
|
+
type: 'ids' | 'condition'
|
|
496
|
+
length?: number
|
|
497
|
+
ids?: Array<number | string>
|
|
498
|
+
condition?: Record<string, any>
|
|
499
|
+
sorts?: ${typeName}Sort[]
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export type ${typeName}BulkUpdateField = {
|
|
503
|
+
key: ${typeName}FieldKey
|
|
504
|
+
action: Record<string, any>
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export type ${typeName}ExportOptions = {
|
|
508
|
+
fields?: ${typeName}FieldKey[]
|
|
509
|
+
props?: string[]
|
|
510
|
+
propNames?: Record<string, string>
|
|
511
|
+
hasIdField?: boolean
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const ingressId = ${JSON.stringify(ingress.hashId)}
|
|
515
|
+
const fieldIds = {
|
|
516
|
+
${fieldMapLines.join("\n")}
|
|
517
|
+
} as const
|
|
518
|
+
const allowedActions = ${JSON.stringify(customActions)} as const
|
|
519
|
+
|
|
520
|
+
function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
|
|
521
|
+
return {
|
|
522
|
+
id: Number(raw.id),
|
|
523
|
+
title: typeof raw.title === 'string' ? raw.title : undefined,
|
|
524
|
+
fields: fromArcubaseFields<${typeName}Fields>(raw.fields, fieldIds),
|
|
525
|
+
raw,
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function toArcubaseSelection(selection: ${typeName}Selection): Record<string, any> {
|
|
530
|
+
return {
|
|
531
|
+
...selection,
|
|
532
|
+
condition: mapArcubaseCondition(selection.condition ?? {}, fieldIds),
|
|
533
|
+
sorts: mapArcubaseSorts(selection.sorts, fieldIds),
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function create${typeName}Ingress(runtime: ArcubaseRuntime) {
|
|
538
|
+
return {
|
|
539
|
+
${methodLines(methodChunks)} }
|
|
540
|
+
}
|
|
541
|
+
`;
|
|
542
|
+
}
|
|
543
|
+
function emitClient(ingresses) {
|
|
544
|
+
const imports = ingresses.map((ingress) => {
|
|
545
|
+
const typeName = toPascalCase(ingress.key);
|
|
546
|
+
return `import { create${typeName}Ingress } from './ingresses/${ingress.key}.js'`;
|
|
547
|
+
});
|
|
548
|
+
const mapLines = ingresses.map((ingress) => ` ${JSON.stringify(ingress.key)}: ReturnType<typeof create${toPascalCase(ingress.key)}Ingress>`);
|
|
549
|
+
const cases = ingresses.map((ingress) => ` case ${JSON.stringify(ingress.key)}:
|
|
550
|
+
return create${toPascalCase(ingress.key)}Ingress(runtime) as ArcubaseIngressMap[K]`);
|
|
551
|
+
return `import { createRuntime, type ArcubaseClientConfig } from './runtime.js'
|
|
552
|
+
${imports.join("\n")}
|
|
553
|
+
|
|
554
|
+
export type ArcubaseIngressMap = {
|
|
555
|
+
${mapLines.join("\n")}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export type ArcubaseIngressKey = keyof ArcubaseIngressMap
|
|
559
|
+
|
|
560
|
+
export function createArcubaseClient(config: ArcubaseClientConfig) {
|
|
561
|
+
const runtime = createRuntime(config)
|
|
562
|
+
return {
|
|
563
|
+
ingress<K extends ArcubaseIngressKey>(key: K): ArcubaseIngressMap[K] {
|
|
564
|
+
switch (key) {
|
|
565
|
+
${cases.join("\n")}
|
|
566
|
+
default:
|
|
567
|
+
throw new Error(\`Unknown Arcubase ingress: \${String(key)}\`)
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
`;
|
|
573
|
+
}
|
|
574
|
+
function emitIndex(ingresses) {
|
|
575
|
+
return `export { createArcubaseClient } from './client.js'
|
|
576
|
+
export type { ArcubaseClientConfig, ArcubaseRow } from './runtime.js'
|
|
577
|
+
export { arcubaseSchema } from './schema.js'
|
|
578
|
+
export type { ArcubaseIngressKey, ArcubaseIngressMap } from './client.js'
|
|
579
|
+
${ingresses.map((ingress) => {
|
|
580
|
+
const typeName = toPascalCase(ingress.key);
|
|
581
|
+
return `export type { ${typeName}Fields, ${typeName}CreateInput, ${typeName}UpdateInput, ${typeName}FieldKey, ${typeName}Selection } from './ingresses/${ingress.key}.js'`;
|
|
582
|
+
}).join("\n")}
|
|
583
|
+
`;
|
|
584
|
+
}
|
|
585
|
+
function generateArcubaseProjectSDK(payload) {
|
|
586
|
+
const ingresses = normalizeIngresses(payload);
|
|
587
|
+
return {
|
|
588
|
+
files: [
|
|
589
|
+
{ path: "runtime.ts", contents: emitRuntime() },
|
|
590
|
+
{ path: "schema.ts", contents: emitSchema(ingresses) },
|
|
591
|
+
...ingresses.map((ingress) => ({ path: `ingresses/${ingress.key}.ts`, contents: emitIngress(ingress) })),
|
|
592
|
+
{ path: "client.ts", contents: emitClient(ingresses) },
|
|
593
|
+
{ path: "index.ts", contents: emitIndex(ingresses) }
|
|
594
|
+
]
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
12
598
|
// src/runtime/errors.ts
|
|
13
599
|
var CLIError = class extends Error {
|
|
14
600
|
constructor(code, message, exitCode = 1, details) {
|
|
@@ -16529,10 +17115,10 @@ var PermitFieldPermitSchema = external_exports.lazy(() => external_exports.objec
|
|
|
16529
17115
|
var PermitPolicySchema = external_exports.lazy(() => external_exports.object({ "actions": external_exports.array(external_exports.string()).optional(), "all_fields_read": external_exports.boolean().optional(), "all_fields_write": external_exports.boolean().optional(), "condition": PermitConditionSetSchema.optional(), "description": external_exports.string().optional(), "fields": external_exports.array(PermitFieldPermitSchema).optional(), "id": external_exports.string().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional() }).strict());
|
|
16530
17116
|
var PermitUserScopeSchema = external_exports.lazy(() => external_exports.object({ "data": external_exports.object({ "name": external_exports.string().optional() }).strict().optional(), "id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
|
|
16531
17117
|
var EntityIngressOptionsSchema = external_exports.lazy(() => external_exports.object({ "i18n_name": I18NTextSchema.optional(), "list_options": PermitEntityListOptionsSchema.optional(), "policy": PermitPolicySchema.optional(), "user_scope": external_exports.array(PermitUserScopeSchema).optional() }).strict());
|
|
16532
|
-
var AppIngressBulkApplyItemVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "rule_id": external_exports.number().optional(), "table_id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
|
|
17118
|
+
var AppIngressBulkApplyItemVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "rule_id": external_exports.number().optional(), "table_id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
|
|
16533
17119
|
var AppIngressBulkApplyReqVOSchema = external_exports.lazy(() => external_exports.object({ "dry_run": external_exports.boolean().optional(), "items": external_exports.array(AppIngressBulkApplyItemVOSchema).optional(), "mode": external_exports.string().optional() }).strict());
|
|
16534
|
-
var AppIngressCreateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "entity_id": external_exports.number().optional(), "icon_name": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "type": external_exports.string().optional(), "url": external_exports.string().optional() }).strict());
|
|
16535
|
-
var AppIngressUpdateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "update": external_exports.array(external_exports.string()).optional() }).strict());
|
|
17120
|
+
var AppIngressCreateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "entity_id": external_exports.number().optional(), "icon_name": external_exports.string().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "type": external_exports.string().optional(), "url": external_exports.string().optional() }).strict());
|
|
17121
|
+
var AppIngressUpdateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "update": external_exports.array(external_exports.string()).optional() }).strict());
|
|
16536
17122
|
var WidgetsTypeCodeSchema = external_exports.lazy(() => external_exports.union([external_exports.literal(1), external_exports.literal(2)]));
|
|
16537
17123
|
var WidgetsTypeSchema = external_exports.lazy(() => external_exports.object({ "dashboard_id": external_exports.number().optional(), "ingress_id": external_exports.number().optional(), "type": WidgetsTypeCodeSchema.optional() }).strict());
|
|
16538
17124
|
var AppNewWidgetsReqVOSchema = external_exports.lazy(() => external_exports.object({ "options": WidgetsTypeSchema.optional(), "type": external_exports.string().optional() }).strict());
|
|
@@ -16943,14 +17529,14 @@ var fieldCommonShape = {
|
|
|
16943
17529
|
code_index: external_exports.boolean(),
|
|
16944
17530
|
transfers: external_exports.array(external_exports.any()).nullable()
|
|
16945
17531
|
};
|
|
16946
|
-
function
|
|
17532
|
+
function isRecord2(value) {
|
|
16947
17533
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16948
17534
|
}
|
|
16949
17535
|
function cloneJSON(value) {
|
|
16950
17536
|
return JSON.parse(JSON.stringify(value));
|
|
16951
17537
|
}
|
|
16952
17538
|
function normalizeFieldVO(value) {
|
|
16953
|
-
if (!
|
|
17539
|
+
if (!isRecord2(value)) {
|
|
16954
17540
|
return value;
|
|
16955
17541
|
}
|
|
16956
17542
|
const field = { ...value };
|
|
@@ -17103,7 +17689,7 @@ var FieldVOSchema = external_exports.lazy(
|
|
|
17103
17689
|
])
|
|
17104
17690
|
);
|
|
17105
17691
|
function normalizeEntitySaveInput(value) {
|
|
17106
|
-
if (!
|
|
17692
|
+
if (!isRecord2(value)) {
|
|
17107
17693
|
return value;
|
|
17108
17694
|
}
|
|
17109
17695
|
const entity = cloneJSON(value);
|
|
@@ -17113,7 +17699,7 @@ function normalizeEntitySaveInput(value) {
|
|
|
17113
17699
|
if (entity.current_serial === null) {
|
|
17114
17700
|
delete entity.current_serial;
|
|
17115
17701
|
}
|
|
17116
|
-
if (
|
|
17702
|
+
if (isRecord2(entity.options)) {
|
|
17117
17703
|
if (entity.options.Buttons === null) {
|
|
17118
17704
|
entity.options.Buttons = [];
|
|
17119
17705
|
}
|
|
@@ -17126,7 +17712,7 @@ function normalizeEntitySaveInput(value) {
|
|
|
17126
17712
|
if (entity.options.misc === null) {
|
|
17127
17713
|
entity.options.misc = {};
|
|
17128
17714
|
}
|
|
17129
|
-
if (
|
|
17715
|
+
if (isRecord2(entity.options.TitleFormat)) {
|
|
17130
17716
|
if (entity.options.TitleFormat.Fields === null) {
|
|
17131
17717
|
entity.options.TitleFormat.Fields = [];
|
|
17132
17718
|
}
|
|
@@ -17927,7 +18513,7 @@ function compileCSVImportMapping(input, headers) {
|
|
|
17927
18513
|
// src/runtime/local_upload.ts
|
|
17928
18514
|
import fs2 from "fs/promises";
|
|
17929
18515
|
import path from "path";
|
|
17930
|
-
function
|
|
18516
|
+
function isRecord3(value) {
|
|
17931
18517
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17932
18518
|
}
|
|
17933
18519
|
async function ensureLocalFile(filePath) {
|
|
@@ -17943,7 +18529,7 @@ async function ensureLocalFile(filePath) {
|
|
|
17943
18529
|
}
|
|
17944
18530
|
function tokenData(token) {
|
|
17945
18531
|
const data = token.token_data ?? token.tokenData;
|
|
17946
|
-
if (!
|
|
18532
|
+
if (!isRecord3(data)) {
|
|
17947
18533
|
throw new CLIError("UPLOAD_TOKEN_UNSUPPORTED", "upload token data is not an object", 1);
|
|
17948
18534
|
}
|
|
17949
18535
|
return data;
|
|
@@ -18569,447 +19155,124 @@ var helpExamplesIndex = {
|
|
|
18569
19155
|
"id": 1003,
|
|
18570
19156
|
"action": {
|
|
18571
19157
|
"type": "set",
|
|
18572
|
-
"value": 2
|
|
18573
|
-
}
|
|
18574
|
-
}
|
|
18575
|
-
]
|
|
18576
|
-
}
|
|
18577
|
-
},
|
|
18578
|
-
{
|
|
18579
|
-
"title": "bulk update rows selected by a search condition",
|
|
18580
|
-
"body": {
|
|
18581
|
-
"selection": {
|
|
18582
|
-
"type": "condition",
|
|
18583
|
-
"condition": {
|
|
18584
|
-
"selectAll": true,
|
|
18585
|
-
"q": "SO-1001"
|
|
18586
|
-
},
|
|
18587
|
-
"length": 0
|
|
18588
|
-
},
|
|
18589
|
-
"fields": [
|
|
18590
|
-
{
|
|
18591
|
-
"id": 1003,
|
|
18592
|
-
"action": {
|
|
18593
|
-
"type": "set",
|
|
18594
|
-
"value": 2
|
|
18595
|
-
}
|
|
18596
|
-
}
|
|
18597
|
-
]
|
|
18598
|
-
},
|
|
18599
|
-
"notes": [
|
|
18600
|
-
"condition selection uses row-query style keys such as q, tab, and filter_:1002"
|
|
18601
|
-
]
|
|
18602
|
-
}
|
|
18603
|
-
]
|
|
18604
|
-
},
|
|
18605
|
-
"user.row.selection-action": {
|
|
18606
|
-
"command": [
|
|
18607
|
-
"row",
|
|
18608
|
-
"selection-action"
|
|
18609
|
-
],
|
|
18610
|
-
"examples": [
|
|
18611
|
-
{
|
|
18612
|
-
"title": "run a custom action against explicit row ids",
|
|
18613
|
-
"body": {
|
|
18614
|
-
"selection": {
|
|
18615
|
-
"type": "ids",
|
|
18616
|
-
"ids": [
|
|
18617
|
-
2188890411
|
|
18618
|
-
],
|
|
18619
|
-
"length": 1
|
|
18620
|
-
}
|
|
18621
|
-
},
|
|
18622
|
-
"notes": [
|
|
18623
|
-
"pass the backend action code with --action",
|
|
18624
|
-
"for query action, use ids or all selection instead of condition selection"
|
|
18625
|
-
]
|
|
18626
|
-
},
|
|
18627
|
-
{
|
|
18628
|
-
"title": "run a custom action against a filtered selection",
|
|
18629
|
-
"body": {
|
|
18630
|
-
"selection": {
|
|
18631
|
-
"type": "condition",
|
|
18632
|
-
"condition": {
|
|
18633
|
-
"selectAll": true,
|
|
18634
|
-
"q": "SO-1001"
|
|
18635
|
-
},
|
|
18636
|
-
"length": 0
|
|
18637
|
-
}
|
|
18638
|
-
},
|
|
18639
|
-
"notes": [
|
|
18640
|
-
"condition selection uses row-query style keys such as q, tab, and filter_:1002"
|
|
18641
|
-
]
|
|
18642
|
-
}
|
|
18643
|
-
]
|
|
18644
|
-
}
|
|
18645
|
-
};
|
|
18646
|
-
|
|
18647
|
-
// src/runtime/help_examples.ts
|
|
18648
|
-
function binaryForScope(scope) {
|
|
18649
|
-
return scope === "admin" ? "arcubase-admin" : "arcubase";
|
|
18650
|
-
}
|
|
18651
|
-
function getFixedValue(value) {
|
|
18652
|
-
if (!("fixedValue" in value)) {
|
|
18653
|
-
return void 0;
|
|
18654
|
-
}
|
|
18655
|
-
const fixedValue = value.fixedValue;
|
|
18656
|
-
return typeof fixedValue === "string" ? fixedValue : void 0;
|
|
18657
|
-
}
|
|
18658
|
-
function pathFlagPlaceholders(command) {
|
|
18659
|
-
const parts = command.pathParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag} <${item.flag.replace(/-/g, "_")}>`);
|
|
18660
|
-
return parts.length > 0 ? `${parts.join(" ")} ` : "";
|
|
18661
|
-
}
|
|
18662
|
-
function helpExampleKey(scope, command) {
|
|
18663
|
-
return `${scope}.${command.commandPath.join(".")}`;
|
|
18664
|
-
}
|
|
18665
|
-
function getCommandHelpExamples(scope, command) {
|
|
18666
|
-
return helpExamplesIndex[helpExampleKey(scope, command)]?.examples ?? [];
|
|
18667
|
-
}
|
|
18668
|
-
function renderCommandHelpExamples(scope, command) {
|
|
18669
|
-
const binary = binaryForScope(scope);
|
|
18670
|
-
const examples = getCommandHelpExamples(scope, command);
|
|
18671
|
-
const header = `${binary} ${command.commandPath.join(" ")} --help-examples`;
|
|
18672
|
-
if (examples.length === 0) {
|
|
18673
|
-
return [
|
|
18674
|
-
header,
|
|
18675
|
-
"",
|
|
18676
|
-
"No dedicated examples for this final command yet.",
|
|
18677
|
-
`Use ${binary} ${command.commandPath.join(" ")} --help for flags, docs, and type paths.`
|
|
18678
|
-
].join("\n");
|
|
18679
|
-
}
|
|
18680
|
-
return [
|
|
18681
|
-
header,
|
|
18682
|
-
"",
|
|
18683
|
-
...examples.flatMap((example) => [
|
|
18684
|
-
`${example.title}:`,
|
|
18685
|
-
...example.body === void 0 ? [] : [` ${binary} ${command.commandPath.join(" ")} ${pathFlagPlaceholders(command)}--body-json '${JSON.stringify(example.body)}'`.replace(/\s+/g, " ").trim()],
|
|
18686
|
-
...(example.notes ?? []).map((note) => ` - ${note}`),
|
|
18687
|
-
""
|
|
18688
|
-
])
|
|
18689
|
-
].join("\n").trimEnd();
|
|
18690
|
-
}
|
|
18691
|
-
|
|
18692
|
-
// node_modules/@arcubase/code-gen/dist/index.js
|
|
18693
|
-
var ArcubaseCodeGenError = class extends Error {
|
|
18694
|
-
code;
|
|
18695
|
-
constructor(code, message) {
|
|
18696
|
-
super(message);
|
|
18697
|
-
this.code = code;
|
|
18698
|
-
this.name = "ArcubaseCodeGenError";
|
|
18699
|
-
}
|
|
18700
|
-
};
|
|
18701
|
-
function isRecord3(value) {
|
|
18702
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18703
|
-
}
|
|
18704
|
-
function unwrapData(payload) {
|
|
18705
|
-
return isRecord3(payload) && "data" in payload ? payload.data : payload;
|
|
18706
|
-
}
|
|
18707
|
-
function toPascalCase(value) {
|
|
18708
|
-
const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
18709
|
-
if (words.length === 0)
|
|
18710
|
-
return "Entity";
|
|
18711
|
-
return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join("");
|
|
18712
|
-
}
|
|
18713
|
-
function toCamelCase(value) {
|
|
18714
|
-
const pascal = toPascalCase(value);
|
|
18715
|
-
return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
|
|
18716
|
-
}
|
|
18717
|
-
function sanitizeEntityKey(entity) {
|
|
18718
|
-
const fallback = entity.sdkName || `entity_${entity.id}`;
|
|
18719
|
-
return toCamelCase(fallback) === "entity" ? `entity${entity.id}` : toCamelCase(fallback);
|
|
18720
|
-
}
|
|
18721
|
-
function normalizeFieldType(type) {
|
|
18722
|
-
switch (type) {
|
|
18723
|
-
case "number":
|
|
18724
|
-
return "number";
|
|
18725
|
-
case "boolean":
|
|
18726
|
-
return "boolean";
|
|
18727
|
-
case "select":
|
|
18728
|
-
case "radio":
|
|
18729
|
-
case "checkbox":
|
|
18730
|
-
case "status":
|
|
18731
|
-
return "number | string";
|
|
18732
|
-
case "file":
|
|
18733
|
-
case "image":
|
|
18734
|
-
case "member":
|
|
18735
|
-
case "members":
|
|
18736
|
-
case "department":
|
|
18737
|
-
case "departments":
|
|
18738
|
-
case "linkto":
|
|
18739
|
-
case "relation":
|
|
18740
|
-
case "relationfield":
|
|
18741
|
-
case "subform":
|
|
18742
|
-
return "unknown";
|
|
18743
|
-
default:
|
|
18744
|
-
return "string";
|
|
18745
|
-
}
|
|
18746
|
-
}
|
|
18747
|
-
function normalizeEntities(payload) {
|
|
18748
|
-
const root = unwrapData(payload);
|
|
18749
|
-
if (!isRecord3(root)) {
|
|
18750
|
-
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app detail object");
|
|
18751
|
-
}
|
|
18752
|
-
const appId = typeof root.id === "number" ? root.id : typeof root.app_id === "number" ? root.app_id : void 0;
|
|
18753
|
-
if (!appId) {
|
|
18754
|
-
throw new ArcubaseCodeGenError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in app detail response");
|
|
18755
|
-
}
|
|
18756
|
-
const sourceEntities = Array.isArray(root.entities) ? root.entities : Array.isArray(root.tables) ? root.tables : Array.isArray(root.entitys) ? root.entitys : [];
|
|
18757
|
-
const entities = sourceEntities.filter((item) => isRecord3(item)).filter((item) => typeof item.id === "number" && typeof item.name === "string").map((item) => {
|
|
18758
|
-
const fields = Array.isArray(item.fields) ? item.fields.filter((field) => isRecord3(field)).filter((field) => typeof field.id === "number" && typeof field.label === "string" && typeof field.type === "string").map((field) => ({
|
|
18759
|
-
id: field.id,
|
|
18760
|
-
label: field.label,
|
|
18761
|
-
key: typeof field.key === "string" ? field.key.trim() : "",
|
|
18762
|
-
type: field.type,
|
|
18763
|
-
required: field.required === true,
|
|
18764
|
-
options: isRecord3(field.options) ? field.options : void 0
|
|
18765
|
-
})) : [];
|
|
18766
|
-
const entity = {
|
|
18767
|
-
appId,
|
|
18768
|
-
id: item.id,
|
|
18769
|
-
name: item.name,
|
|
18770
|
-
sdkName: typeof item.key === "string" && item.key.trim() ? item.key.trim() : "",
|
|
18771
|
-
fields
|
|
18772
|
-
};
|
|
18773
|
-
entity.sdkName = sanitizeEntityKey(entity);
|
|
18774
|
-
return entity;
|
|
18775
|
-
});
|
|
18776
|
-
if (entities.length === 0) {
|
|
18777
|
-
throw new ArcubaseCodeGenError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response");
|
|
18778
|
-
}
|
|
18779
|
-
validateEntities(entities);
|
|
18780
|
-
return entities;
|
|
18781
|
-
}
|
|
18782
|
-
function validateEntities(entities) {
|
|
18783
|
-
const entityKeys = /* @__PURE__ */ new Set();
|
|
18784
|
-
for (const entity of entities) {
|
|
18785
|
-
if (entityKeys.has(entity.sdkName)) {
|
|
18786
|
-
throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_ENTITY_KEY", `duplicate generated entity key: ${entity.sdkName}`);
|
|
18787
|
-
}
|
|
18788
|
-
entityKeys.add(entity.sdkName);
|
|
18789
|
-
const fieldKeys = /* @__PURE__ */ new Set();
|
|
18790
|
-
for (const field of entity.fields) {
|
|
18791
|
-
if (!field.key) {
|
|
18792
|
-
throw new ArcubaseCodeGenError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${entity.name}.${field.label} (${field.id})`);
|
|
18793
|
-
}
|
|
18794
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(field.key)) {
|
|
18795
|
-
throw new ArcubaseCodeGenError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${entity.name}.${field.key}`);
|
|
18796
|
-
}
|
|
18797
|
-
if (fieldKeys.has(field.key)) {
|
|
18798
|
-
throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${entity.name}: ${field.key}`);
|
|
18799
|
-
}
|
|
18800
|
-
fieldKeys.add(field.key);
|
|
18801
|
-
}
|
|
18802
|
-
}
|
|
18803
|
-
}
|
|
18804
|
-
function emitRuntime() {
|
|
18805
|
-
return `export type ArcubaseSdkConfig = {
|
|
18806
|
-
baseURL: string
|
|
18807
|
-
accessToken?: string
|
|
18808
|
-
refreshToken?: string
|
|
18809
|
-
}
|
|
18810
|
-
|
|
18811
|
-
export type ArcubaseRow<TFields> = {
|
|
18812
|
-
id: number
|
|
18813
|
-
title?: string
|
|
18814
|
-
fields: TFields
|
|
18815
|
-
raw: Record<string, any>
|
|
18816
|
-
}
|
|
18817
|
-
|
|
18818
|
-
export type ArcubaseRuntime = {
|
|
18819
|
-
config: ArcubaseSdkConfig
|
|
18820
|
-
request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
|
|
18821
|
-
}
|
|
18822
|
-
|
|
18823
|
-
type ArcubaseEnvelope<T> = {
|
|
18824
|
-
data?: T
|
|
18825
|
-
error?: { message?: string; key?: string; type?: string }
|
|
18826
|
-
}
|
|
18827
|
-
|
|
18828
|
-
export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
|
|
18829
|
-
let accessToken = config.accessToken ?? ''
|
|
18830
|
-
let refreshToken = config.refreshToken ?? ''
|
|
18831
|
-
|
|
18832
|
-
async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
|
|
18833
|
-
const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
|
|
18834
|
-
const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
|
|
18835
|
-
method,
|
|
18836
|
-
headers: {
|
|
18837
|
-
'Content-Type': 'application/json',
|
|
18838
|
-
...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
|
|
19158
|
+
"value": 2
|
|
19159
|
+
}
|
|
19160
|
+
}
|
|
19161
|
+
]
|
|
19162
|
+
}
|
|
18839
19163
|
},
|
|
18840
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
18841
|
-
})
|
|
18842
|
-
const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
|
|
18843
|
-
if (!response.ok || payload?.error) {
|
|
18844
|
-
throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
|
|
18845
|
-
}
|
|
18846
|
-
return payload?.data as T
|
|
18847
|
-
}
|
|
18848
|
-
|
|
18849
|
-
return {
|
|
18850
|
-
config: { ...config, accessToken, refreshToken },
|
|
18851
|
-
request,
|
|
18852
|
-
}
|
|
18853
|
-
}
|
|
18854
|
-
`;
|
|
18855
|
-
}
|
|
18856
|
-
function emitSchema(entities) {
|
|
18857
|
-
const schema = {
|
|
18858
|
-
entities: Object.fromEntries(entities.map((entity) => [
|
|
18859
|
-
entity.sdkName,
|
|
18860
19164
|
{
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
|
|
18868
|
-
|
|
18869
|
-
|
|
19165
|
+
"title": "bulk update rows selected by a search condition",
|
|
19166
|
+
"body": {
|
|
19167
|
+
"selection": {
|
|
19168
|
+
"type": "condition",
|
|
19169
|
+
"condition": {
|
|
19170
|
+
"selectAll": true,
|
|
19171
|
+
"q": "SO-1001"
|
|
19172
|
+
},
|
|
19173
|
+
"length": 0
|
|
19174
|
+
},
|
|
19175
|
+
"fields": [
|
|
19176
|
+
{
|
|
19177
|
+
"id": 1003,
|
|
19178
|
+
"action": {
|
|
19179
|
+
"type": "set",
|
|
19180
|
+
"value": 2
|
|
19181
|
+
}
|
|
19182
|
+
}
|
|
19183
|
+
]
|
|
19184
|
+
},
|
|
19185
|
+
"notes": [
|
|
19186
|
+
"condition selection uses row-query style keys such as q, tab, and filter_:1002"
|
|
19187
|
+
]
|
|
19188
|
+
}
|
|
19189
|
+
]
|
|
19190
|
+
},
|
|
19191
|
+
"user.row.selection-action": {
|
|
19192
|
+
"command": [
|
|
19193
|
+
"row",
|
|
19194
|
+
"selection-action"
|
|
19195
|
+
],
|
|
19196
|
+
"examples": [
|
|
19197
|
+
{
|
|
19198
|
+
"title": "run a custom action against explicit row ids",
|
|
19199
|
+
"body": {
|
|
19200
|
+
"selection": {
|
|
19201
|
+
"type": "ids",
|
|
19202
|
+
"ids": [
|
|
19203
|
+
2188890411
|
|
19204
|
+
],
|
|
19205
|
+
"length": 1
|
|
18870
19206
|
}
|
|
18871
|
-
|
|
19207
|
+
},
|
|
19208
|
+
"notes": [
|
|
19209
|
+
"pass the backend action code with --action",
|
|
19210
|
+
"for query action, use ids or all selection instead of condition selection"
|
|
19211
|
+
]
|
|
19212
|
+
},
|
|
19213
|
+
{
|
|
19214
|
+
"title": "run a custom action against a filtered selection",
|
|
19215
|
+
"body": {
|
|
19216
|
+
"selection": {
|
|
19217
|
+
"type": "condition",
|
|
19218
|
+
"condition": {
|
|
19219
|
+
"selectAll": true,
|
|
19220
|
+
"q": "SO-1001"
|
|
19221
|
+
},
|
|
19222
|
+
"length": 0
|
|
19223
|
+
}
|
|
19224
|
+
},
|
|
19225
|
+
"notes": [
|
|
19226
|
+
"condition selection uses row-query style keys such as q, tab, and filter_:1002"
|
|
19227
|
+
]
|
|
18872
19228
|
}
|
|
18873
|
-
]
|
|
18874
|
-
};
|
|
18875
|
-
return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
|
|
18876
|
-
|
|
18877
|
-
export type ArcubaseEntityKey = keyof typeof arcubaseSchema.entities
|
|
18878
|
-
`;
|
|
18879
|
-
}
|
|
18880
|
-
function emitEntity(entity) {
|
|
18881
|
-
const typeName = toPascalCase(entity.sdkName);
|
|
18882
|
-
const fieldLines = entity.fields.map((field) => {
|
|
18883
|
-
const optional2 = field.required ? "" : "?";
|
|
18884
|
-
return ` ${field.key}${optional2}: ${normalizeFieldType(field.type)}`;
|
|
18885
|
-
});
|
|
18886
|
-
const fieldMapLines = entity.fields.map((field) => ` ${field.key}: ${field.id},`);
|
|
18887
|
-
return `import type { ArcubaseRuntime, ArcubaseRow } from '../runtime.js'
|
|
18888
|
-
|
|
18889
|
-
export type ${typeName}Fields = {
|
|
18890
|
-
${fieldLines.join("\n")}
|
|
18891
|
-
}
|
|
18892
|
-
|
|
18893
|
-
export type ${typeName}CreateInput = ${typeName}Fields
|
|
18894
|
-
export type ${typeName}UpdateInput = Partial<${typeName}Fields>
|
|
18895
|
-
|
|
18896
|
-
const entityId = ${entity.id}
|
|
18897
|
-
const fieldIds = {
|
|
18898
|
-
${fieldMapLines.join("\n")}
|
|
18899
|
-
} as const
|
|
18900
|
-
|
|
18901
|
-
function toArcubaseFields(fields: Partial<${typeName}Fields>): Record<string, any> {
|
|
18902
|
-
const out: Record<string, any> = {}
|
|
18903
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
18904
|
-
if (value === undefined) continue
|
|
18905
|
-
const fieldId = fieldIds[key as keyof typeof fieldIds]
|
|
18906
|
-
if (!fieldId) {
|
|
18907
|
-
throw new Error(\`Unknown ${entity.sdkName} field: \${key}\`)
|
|
18908
|
-
}
|
|
18909
|
-
out[String(fieldId)] = value
|
|
18910
|
-
}
|
|
18911
|
-
return out
|
|
18912
|
-
}
|
|
18913
|
-
|
|
18914
|
-
function fromArcubaseFields(raw: Record<string, any> | undefined): ${typeName}Fields {
|
|
18915
|
-
const out: Record<string, any> = {}
|
|
18916
|
-
const source = raw ?? {}
|
|
18917
|
-
for (const [key, fieldId] of Object.entries(fieldIds)) {
|
|
18918
|
-
if (String(fieldId) in source) {
|
|
18919
|
-
out[key] = source[String(fieldId)]
|
|
18920
|
-
}
|
|
18921
|
-
}
|
|
18922
|
-
return out as ${typeName}Fields
|
|
18923
|
-
}
|
|
18924
|
-
|
|
18925
|
-
function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
|
|
18926
|
-
return {
|
|
18927
|
-
id: Number(raw.id),
|
|
18928
|
-
title: typeof raw.title === 'string' ? raw.title : undefined,
|
|
18929
|
-
fields: fromArcubaseFields(raw.fields),
|
|
18930
|
-
raw,
|
|
19229
|
+
]
|
|
18931
19230
|
}
|
|
18932
|
-
}
|
|
18933
|
-
|
|
18934
|
-
export function create${typeName}SDK(runtime: ArcubaseRuntime) {
|
|
18935
|
-
return {
|
|
18936
|
-
async create(fields: ${typeName}CreateInput): Promise<number> {
|
|
18937
|
-
return runtime.request<number>('POST', \`/entity/${entity.appId}/\${entityId}/insert\`, {
|
|
18938
|
-
fields: toArcubaseFields(fields),
|
|
18939
|
-
})
|
|
18940
|
-
},
|
|
18941
|
-
|
|
18942
|
-
async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
|
|
18943
|
-
const data = toArcubaseFields(fields)
|
|
18944
|
-
return runtime.request<boolean>('PUT', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`, {
|
|
18945
|
-
data,
|
|
18946
|
-
changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
|
|
18947
|
-
})
|
|
18948
|
-
},
|
|
18949
|
-
|
|
18950
|
-
async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
|
|
18951
|
-
const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`)
|
|
18952
|
-
return mapRow(data.record ?? {})
|
|
18953
|
-
},
|
|
19231
|
+
};
|
|
18954
19232
|
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
items: (data.items ?? []).map(mapRow),
|
|
18959
|
-
total: data.total,
|
|
18960
|
-
}
|
|
18961
|
-
},
|
|
18962
|
-
}
|
|
18963
|
-
}
|
|
18964
|
-
`;
|
|
19233
|
+
// src/runtime/help_examples.ts
|
|
19234
|
+
function binaryForScope(scope) {
|
|
19235
|
+
return scope === "admin" ? "arcubase-admin" : "arcubase";
|
|
18965
19236
|
}
|
|
18966
|
-
function
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
return `import { create${typeName}SDK } from './entities/${entity.sdkName}.js'`;
|
|
18970
|
-
});
|
|
18971
|
-
const properties = entities.map((entity) => ` ${entity.sdkName}: create${toPascalCase(entity.sdkName)}SDK(runtime),`);
|
|
18972
|
-
return `import { createRuntime, type ArcubaseSdkConfig } from './runtime.js'
|
|
18973
|
-
${imports.join("\n")}
|
|
18974
|
-
|
|
18975
|
-
export function createArcubaseSdk(config: ArcubaseSdkConfig) {
|
|
18976
|
-
const runtime = createRuntime(config)
|
|
18977
|
-
return {
|
|
18978
|
-
${properties.join("\n")}
|
|
19237
|
+
function getFixedValue(value) {
|
|
19238
|
+
if (!("fixedValue" in value)) {
|
|
19239
|
+
return void 0;
|
|
18979
19240
|
}
|
|
19241
|
+
const fixedValue = value.fixedValue;
|
|
19242
|
+
return typeof fixedValue === "string" ? fixedValue : void 0;
|
|
18980
19243
|
}
|
|
18981
|
-
|
|
19244
|
+
function pathFlagPlaceholders(command) {
|
|
19245
|
+
const parts = command.pathParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag} <${item.flag.replace(/-/g, "_")}>`);
|
|
19246
|
+
return parts.length > 0 ? `${parts.join(" ")} ` : "";
|
|
18982
19247
|
}
|
|
18983
|
-
function
|
|
18984
|
-
return
|
|
18985
|
-
export type { ArcubaseSdkConfig, ArcubaseRow } from './runtime.js'
|
|
18986
|
-
export { arcubaseSchema } from './schema.js'
|
|
18987
|
-
${entities.map((entity) => `export type { ${toPascalCase(entity.sdkName)}Fields, ${toPascalCase(entity.sdkName)}CreateInput, ${toPascalCase(entity.sdkName)}UpdateInput } from './entities/${entity.sdkName}.js'`).join("\n")}
|
|
18988
|
-
`;
|
|
19248
|
+
function helpExampleKey(scope, command) {
|
|
19249
|
+
return `${scope}.${command.commandPath.join(".")}`;
|
|
18989
19250
|
}
|
|
18990
|
-
function
|
|
18991
|
-
|
|
18992
|
-
return {
|
|
18993
|
-
files: [
|
|
18994
|
-
{ path: "runtime.ts", contents: emitRuntime() },
|
|
18995
|
-
{ path: "schema.ts", contents: emitSchema(entities) },
|
|
18996
|
-
...entities.map((entity) => ({ path: `entities/${entity.sdkName}.ts`, contents: emitEntity(entity) })),
|
|
18997
|
-
{ path: "client.ts", contents: emitClient(entities) },
|
|
18998
|
-
{ path: "index.ts", contents: emitIndex(entities) }
|
|
18999
|
-
]
|
|
19000
|
-
};
|
|
19251
|
+
function getCommandHelpExamples(scope, command) {
|
|
19252
|
+
return helpExamplesIndex[helpExampleKey(scope, command)]?.examples ?? [];
|
|
19001
19253
|
}
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19011
|
-
|
|
19254
|
+
function renderCommandHelpExamples(scope, command) {
|
|
19255
|
+
const binary = binaryForScope(scope);
|
|
19256
|
+
const examples = getCommandHelpExamples(scope, command);
|
|
19257
|
+
const header = `${binary} ${command.commandPath.join(" ")} --help-examples`;
|
|
19258
|
+
if (examples.length === 0) {
|
|
19259
|
+
return [
|
|
19260
|
+
header,
|
|
19261
|
+
"",
|
|
19262
|
+
"No dedicated examples for this final command yet.",
|
|
19263
|
+
`Use ${binary} ${command.commandPath.join(" ")} --help for flags, docs, and type paths.`
|
|
19264
|
+
].join("\n");
|
|
19012
19265
|
}
|
|
19266
|
+
return [
|
|
19267
|
+
header,
|
|
19268
|
+
"",
|
|
19269
|
+
...examples.flatMap((example) => [
|
|
19270
|
+
`${example.title}:`,
|
|
19271
|
+
...example.body === void 0 ? [] : [` ${binary} ${command.commandPath.join(" ")} ${pathFlagPlaceholders(command)}--body-json '${JSON.stringify(example.body)}'`.replace(/\s+/g, " ").trim()],
|
|
19272
|
+
...(example.notes ?? []).map((note) => ` - ${note}`),
|
|
19273
|
+
""
|
|
19274
|
+
])
|
|
19275
|
+
].join("\n").trimEnd();
|
|
19013
19276
|
}
|
|
19014
19277
|
|
|
19015
19278
|
// src/runtime/execute.ts
|
|
@@ -19243,10 +19506,10 @@ function renderDevSDKGenHelp() {
|
|
|
19243
19506
|
" - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase",
|
|
19244
19507
|
"",
|
|
19245
19508
|
"requirements:",
|
|
19509
|
+
" - every generated ingress must have a configured ingress.key; sdk-gen fails fast when keys are missing",
|
|
19246
19510
|
" - every generated field must have a configured field.key; sdk-gen fails fast when keys are missing",
|
|
19247
|
-
" -
|
|
19248
|
-
|
|
19249
|
-
" - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })"
|
|
19511
|
+
" - ingress.key and field.key values must be stable unique TypeScript identifiers",
|
|
19512
|
+
' - generated code accepts createArcubaseClient({ baseURL, accessToken, refreshToken }).ingress("<IngressKey>")'
|
|
19250
19513
|
].join("\n");
|
|
19251
19514
|
}
|
|
19252
19515
|
function resolveEndpoint(command, flags) {
|
|
@@ -20788,6 +21051,85 @@ function assertSDKGenFieldKeysPresent(entity) {
|
|
|
20788
21051
|
]
|
|
20789
21052
|
});
|
|
20790
21053
|
}
|
|
21054
|
+
function extractSDKGenIngressRefs(payload) {
|
|
21055
|
+
const data = unwrapResponseData(payload);
|
|
21056
|
+
const source = Array.isArray(data) ? data : isRecord4(data) && Array.isArray(data.list) ? data.list : [];
|
|
21057
|
+
return source.filter((item) => isRecord4(item));
|
|
21058
|
+
}
|
|
21059
|
+
function ingressKeyOf(ingress) {
|
|
21060
|
+
return typeof ingress.key === "string" ? ingress.key.trim() : "";
|
|
21061
|
+
}
|
|
21062
|
+
function ingressHashIDOf(ingress) {
|
|
21063
|
+
if (typeof ingress.hash_id === "string" && ingress.hash_id.trim()) {
|
|
21064
|
+
return ingress.hash_id.trim();
|
|
21065
|
+
}
|
|
21066
|
+
if (typeof ingress.hashId === "string" && ingress.hashId.trim()) {
|
|
21067
|
+
return ingress.hashId.trim();
|
|
21068
|
+
}
|
|
21069
|
+
return "";
|
|
21070
|
+
}
|
|
21071
|
+
function assertSDKGenIngressesReady(ingresses) {
|
|
21072
|
+
if (ingresses.length === 0) {
|
|
21073
|
+
throw new CLIError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingress definitions for this app", 2, {
|
|
21074
|
+
operation: "dev sdk-gen",
|
|
21075
|
+
suggestions: ["create at least one access rule with a stable ingress.key before running sdk-gen"]
|
|
21076
|
+
});
|
|
21077
|
+
}
|
|
21078
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21079
|
+
const missingKey = [];
|
|
21080
|
+
const missingHash = [];
|
|
21081
|
+
const invalidKey = [];
|
|
21082
|
+
const duplicateKey = [];
|
|
21083
|
+
for (const ingress of ingresses) {
|
|
21084
|
+
const key = ingressKeyOf(ingress);
|
|
21085
|
+
if (!key) {
|
|
21086
|
+
missingKey.push(ingress);
|
|
21087
|
+
continue;
|
|
21088
|
+
}
|
|
21089
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) {
|
|
21090
|
+
invalidKey.push(key);
|
|
21091
|
+
}
|
|
21092
|
+
if (seen.has(key)) {
|
|
21093
|
+
duplicateKey.push(key);
|
|
21094
|
+
}
|
|
21095
|
+
seen.add(key);
|
|
21096
|
+
if (!ingressHashIDOf(ingress)) {
|
|
21097
|
+
missingHash.push(ingress);
|
|
21098
|
+
}
|
|
21099
|
+
}
|
|
21100
|
+
if (missingKey.length > 0) {
|
|
21101
|
+
const preview = missingKey.slice(0, 8).map((ingress) => String(ingress.name ?? ingress.id ?? "unnamed ingress"));
|
|
21102
|
+
const suffix = missingKey.length > preview.length ? `, and ${missingKey.length - preview.length} more` : "";
|
|
21103
|
+
throw new CLIError("SDK_GEN_INGRESS_KEY_REQUIRED", `missing ingress.key values: ${preview.join(", ")}${suffix}`, 2, {
|
|
21104
|
+
operation: "dev sdk-gen",
|
|
21105
|
+
issues: missingKey.map((ingress) => ({
|
|
21106
|
+
path: `ingress.${String(ingress.id ?? "unknown")}.key`,
|
|
21107
|
+
message: `ingress.key is required for ${String(ingress.name ?? ingress.id ?? "unnamed ingress")}`
|
|
21108
|
+
})),
|
|
21109
|
+
suggestions: [
|
|
21110
|
+
"set stable ingress.key values in Arcubase admin before running sdk-gen",
|
|
21111
|
+
"rerun arcubase-admin dev sdk-gen after ingress keys are complete"
|
|
21112
|
+
]
|
|
21113
|
+
});
|
|
21114
|
+
}
|
|
21115
|
+
if (invalidKey.length > 0) {
|
|
21116
|
+
throw new CLIError("SDK_GEN_INVALID_INGRESS_KEY", `invalid ingress.key values: ${invalidKey.join(", ")}`, 2, {
|
|
21117
|
+
operation: "dev sdk-gen",
|
|
21118
|
+
suggestions: ["use TypeScript identifier keys such as publicEntry or customerPortal"]
|
|
21119
|
+
});
|
|
21120
|
+
}
|
|
21121
|
+
if (duplicateKey.length > 0) {
|
|
21122
|
+
throw new CLIError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key values: ${duplicateKey.join(", ")}`, 2, {
|
|
21123
|
+
operation: "dev sdk-gen"
|
|
21124
|
+
});
|
|
21125
|
+
}
|
|
21126
|
+
if (missingHash.length > 0) {
|
|
21127
|
+
throw new CLIError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", "sdk-gen expected hash_id for every ingress definition", 2, {
|
|
21128
|
+
operation: "dev sdk-gen",
|
|
21129
|
+
suggestions: ["upgrade Arcubase server so admin access-rule list returns hash_id"]
|
|
21130
|
+
});
|
|
21131
|
+
}
|
|
21132
|
+
}
|
|
20791
21133
|
async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
|
|
20792
21134
|
validateDevSDKGenFlags(flags);
|
|
20793
21135
|
const appId = flagValue(flags, "app-id");
|
|
@@ -20814,7 +21156,7 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
|
|
|
20814
21156
|
if (entityRefs.length === 0) {
|
|
20815
21157
|
throw new CLIError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response", 2);
|
|
20816
21158
|
}
|
|
20817
|
-
const
|
|
21159
|
+
const ingresses = [];
|
|
20818
21160
|
for (const entityRef of entityRefs) {
|
|
20819
21161
|
const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
|
|
20820
21162
|
const entityDetail = await requestJSON(runtimeEnv, "GET", entityEndpoint, void 0, fetchImpl);
|
|
@@ -20823,13 +21165,36 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
|
|
|
20823
21165
|
throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected entity detail response data", 2);
|
|
20824
21166
|
}
|
|
20825
21167
|
assertSDKGenFieldKeysPresent(entityPayload);
|
|
20826
|
-
|
|
21168
|
+
const ingressEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/ingress`;
|
|
21169
|
+
const ingressList = await requestJSON(runtimeEnv, "GET", ingressEndpoint, void 0, fetchImpl);
|
|
21170
|
+
const entityIngresses = extractSDKGenIngressRefs(ingressList.data).map((ingress) => ({
|
|
21171
|
+
...ingress,
|
|
21172
|
+
hash_id: ingressHashIDOf(ingress),
|
|
21173
|
+
key: ingressKeyOf(ingress),
|
|
21174
|
+
entity: entityPayload
|
|
21175
|
+
}));
|
|
21176
|
+
ingresses.push(...entityIngresses);
|
|
21177
|
+
}
|
|
21178
|
+
assertSDKGenIngressesReady(ingresses);
|
|
21179
|
+
let generated;
|
|
21180
|
+
try {
|
|
21181
|
+
generated = generateArcubaseProjectSDK({
|
|
21182
|
+
id: resolveSDKGenAppId(appId, appPayload),
|
|
21183
|
+
name: typeof appPayload.name === "string" ? appPayload.name : void 0,
|
|
21184
|
+
ingresses
|
|
21185
|
+
});
|
|
21186
|
+
} catch (error51) {
|
|
21187
|
+
if (error51 instanceof ArcubaseCodeGenError) {
|
|
21188
|
+
throw new CLIError(error51.code, error51.message, 2, {
|
|
21189
|
+
operation: "dev sdk-gen",
|
|
21190
|
+
suggestions: [
|
|
21191
|
+
"set stable ingress.key and field.key values in Arcubase admin before running sdk-gen",
|
|
21192
|
+
"rerun arcubase-admin dev sdk-gen after schema keys are complete"
|
|
21193
|
+
]
|
|
21194
|
+
});
|
|
21195
|
+
}
|
|
21196
|
+
throw error51;
|
|
20827
21197
|
}
|
|
20828
|
-
const generated = generateArcubaseProjectSDK2({
|
|
20829
|
-
id: resolveSDKGenAppId(appId, appPayload),
|
|
20830
|
-
name: typeof appPayload.name === "string" ? appPayload.name : void 0,
|
|
20831
|
-
entities
|
|
20832
|
-
});
|
|
20833
21198
|
const absoluteOutDir = path2.resolve(process.cwd(), outDir);
|
|
20834
21199
|
for (const file2 of generated.files) {
|
|
20835
21200
|
const targetPath = path2.resolve(absoluteOutDir, file2.path);
|