@akanjs/devkit 2.3.9-rc.4 → 2.3.9-rc.6
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/devkitUtils.test.ts +2 -2
- package/executors.ts +17 -13
- package/getModelFileData.ts +5 -2
- package/package.json +2 -2
- package/workflow/artifacts.ts +30 -2
- package/workflow/executor.ts +137 -9
- package/workflow/index.ts +1 -0
- package/workflow/plan.ts +198 -33
- package/workflow/render.ts +92 -11
- package/workflow/source.ts +270 -18
- package/workflow/types.ts +26 -4
- package/workflow/uiPolicy.ts +45 -0
package/workflow/source.ts
CHANGED
|
@@ -18,6 +18,26 @@ export const sourceFile = (sys: Sys, path: string, action: PrimitiveChangedFile[
|
|
|
18
18
|
reason,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
export const moduleComponentName = (moduleName: string) =>
|
|
22
|
+
`${moduleName.slice(0, 1).toUpperCase()}${moduleName.slice(1)}`;
|
|
23
|
+
|
|
24
|
+
export const moduleSourcePaths = (moduleName: string) => {
|
|
25
|
+
const componentName = moduleComponentName(moduleName);
|
|
26
|
+
return {
|
|
27
|
+
abstract: `lib/${moduleName}/${moduleName}.abstract.md`,
|
|
28
|
+
constant: `lib/${moduleName}/${moduleName}.constant.ts`,
|
|
29
|
+
dictionary: `lib/${moduleName}/${moduleName}.dictionary.ts`,
|
|
30
|
+
service: `lib/${moduleName}/${moduleName}.service.ts`,
|
|
31
|
+
signal: `lib/${moduleName}/${moduleName}.signal.ts`,
|
|
32
|
+
store: `lib/${moduleName}/${moduleName}.store.ts`,
|
|
33
|
+
template: `lib/${moduleName}/${componentName}.Template.tsx`,
|
|
34
|
+
unit: `lib/${moduleName}/${componentName}.Unit.tsx`,
|
|
35
|
+
util: `lib/${moduleName}/${componentName}.Util.tsx`,
|
|
36
|
+
view: `lib/${moduleName}/${componentName}.View.tsx`,
|
|
37
|
+
zone: `lib/${moduleName}/${componentName}.Zone.tsx`,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
21
41
|
export const generatedFilesForSync = (sys: Sys, reason = "Generated files may change after sync.") =>
|
|
22
42
|
generatedFilePathsForTarget(getSysRoot(sys), reason);
|
|
23
43
|
|
|
@@ -68,6 +88,60 @@ export const titleize = (value: string) =>
|
|
|
68
88
|
|
|
69
89
|
export const lowerlize = (value: string) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
|
|
70
90
|
|
|
91
|
+
const koLabels: Record<string, string> = {
|
|
92
|
+
amount: "금액",
|
|
93
|
+
budget: "예산",
|
|
94
|
+
category: "카테고리",
|
|
95
|
+
content: "내용",
|
|
96
|
+
count: "개수",
|
|
97
|
+
createdAt: "생성일",
|
|
98
|
+
date: "날짜",
|
|
99
|
+
description: "설명",
|
|
100
|
+
due: "마감일",
|
|
101
|
+
dueAt: "마감일",
|
|
102
|
+
email: "이메일",
|
|
103
|
+
enabled: "활성화",
|
|
104
|
+
endAt: "종료일",
|
|
105
|
+
id: "ID",
|
|
106
|
+
name: "이름",
|
|
107
|
+
owner: "담당자",
|
|
108
|
+
priority: "우선순위",
|
|
109
|
+
project: "프로젝트",
|
|
110
|
+
rating: "평점",
|
|
111
|
+
startAt: "시작일",
|
|
112
|
+
status: "상태",
|
|
113
|
+
title: "제목",
|
|
114
|
+
updatedAt: "수정일",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const splitFieldWords = (fieldName: string) =>
|
|
118
|
+
fieldName
|
|
119
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
120
|
+
.replace(/[-_]+/g, " ")
|
|
121
|
+
.split(/\s+/)
|
|
122
|
+
.map((word) => word.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
|
|
125
|
+
const koLabelForField = (fieldName: string) => {
|
|
126
|
+
if (koLabels[fieldName]) return koLabels[fieldName];
|
|
127
|
+
const words = splitFieldWords(fieldName);
|
|
128
|
+
const translated = words.map((word) => koLabels[word] ?? koLabels[lowerlize(word)] ?? null);
|
|
129
|
+
return translated.every(Boolean) ? translated.join(" ") : null;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const bilingualLabelForField = (fieldName: string) => {
|
|
133
|
+
const en = titleize(fieldName);
|
|
134
|
+
return { en, ko: koLabelForField(fieldName) ?? en };
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const bilingualDescriptionForField = (fieldName: string) => {
|
|
138
|
+
const label = bilingualLabelForField(fieldName);
|
|
139
|
+
return {
|
|
140
|
+
en: `Enter ${label.en.toLowerCase()}.`,
|
|
141
|
+
ko: `${label.ko} 값을 입력합니다.`,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
71
145
|
export const normalizeFieldType = (typeName: string) => {
|
|
72
146
|
const normalizedTypes: Record<string, string> = {
|
|
73
147
|
string: "String",
|
|
@@ -96,24 +170,54 @@ export const ensureBaseTypeImport = (content: string, typeName: string) => {
|
|
|
96
170
|
return `import { ${typeName} } from "akanjs/base";\n${content}`;
|
|
97
171
|
};
|
|
98
172
|
|
|
99
|
-
|
|
173
|
+
export type FieldDefaultValue = string | number | boolean | null;
|
|
174
|
+
|
|
175
|
+
const numericDefault = (typeName: "Int" | "Float", rawDefault: FieldDefaultValue): string | null => {
|
|
176
|
+
if (typeof rawDefault === "number") {
|
|
177
|
+
if (!Number.isFinite(rawDefault)) return null;
|
|
178
|
+
if (typeName === "Int" && !Number.isInteger(rawDefault)) return null;
|
|
179
|
+
return String(rawDefault);
|
|
180
|
+
}
|
|
181
|
+
if (typeof rawDefault !== "string") return null;
|
|
100
182
|
const trimmed = rawDefault.trim();
|
|
101
183
|
if (!trimmed || !Number.isFinite(Number(trimmed))) return null;
|
|
102
184
|
if (typeName === "Int" && !/^-?\d+$/.test(trimmed)) return null;
|
|
103
185
|
return trimmed;
|
|
104
186
|
};
|
|
105
187
|
|
|
188
|
+
const booleanDefault = (rawDefault: FieldDefaultValue): string | null => {
|
|
189
|
+
if (typeof rawDefault === "boolean") return String(rawDefault);
|
|
190
|
+
if (typeof rawDefault !== "string") return null;
|
|
191
|
+
const lowered = rawDefault.trim().toLowerCase();
|
|
192
|
+
return lowered === "true" || lowered === "false" ? lowered : null;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const dateDefault = (rawDefault: FieldDefaultValue): string | null => {
|
|
196
|
+
if (typeof rawDefault === "number" && Number.isFinite(rawDefault)) return `new Date(${rawDefault})`;
|
|
197
|
+
if (typeof rawDefault !== "string") return null;
|
|
198
|
+
const trimmed = rawDefault.trim();
|
|
199
|
+
if (!trimmed) return null;
|
|
200
|
+
if (trimmed === "now") return "new Date()";
|
|
201
|
+
if (!Number.isNaN(Date.parse(trimmed))) return `new Date(${JSON.stringify(trimmed)})`;
|
|
202
|
+
return null;
|
|
203
|
+
};
|
|
204
|
+
|
|
106
205
|
export const coerceFieldDefault = (
|
|
107
206
|
typeName: string,
|
|
108
|
-
defaultValue?:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const normalizedType = normalizeFieldType(typeName);
|
|
207
|
+
defaultValue?: FieldDefaultValue,
|
|
208
|
+
options: { enumValues?: readonly string[] | null } = {},
|
|
209
|
+
): { expression: string | null; diagnostic?: WorkflowDiagnostic; normalized: boolean; normalizedType: string } => {
|
|
210
|
+
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
211
|
+
if (defaultValue === undefined || defaultValue === null || defaultValue === "") {
|
|
212
|
+
return { expression: null, normalized: false, normalizedType };
|
|
213
|
+
}
|
|
112
214
|
if (normalizedType === "Int" || normalizedType === "Float") {
|
|
113
215
|
const expression = numericDefault(normalizedType, defaultValue);
|
|
114
|
-
if (expression !== null) return { expression };
|
|
216
|
+
if (expression !== null) return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
115
217
|
return {
|
|
116
218
|
expression: null,
|
|
219
|
+
normalized: false,
|
|
220
|
+
normalizedType,
|
|
117
221
|
diagnostic: {
|
|
118
222
|
severity: "error",
|
|
119
223
|
code: "primitive-default-value-invalid",
|
|
@@ -124,10 +228,12 @@ export const coerceFieldDefault = (
|
|
|
124
228
|
};
|
|
125
229
|
}
|
|
126
230
|
if (normalizedType === "Boolean") {
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
231
|
+
const expression = booleanDefault(defaultValue);
|
|
232
|
+
if (expression !== null) return { expression, normalized: typeof defaultValue === "string", normalizedType };
|
|
129
233
|
return {
|
|
130
234
|
expression: null,
|
|
235
|
+
normalized: false,
|
|
236
|
+
normalizedType,
|
|
131
237
|
diagnostic: {
|
|
132
238
|
severity: "error",
|
|
133
239
|
code: "primitive-default-value-invalid",
|
|
@@ -137,14 +243,70 @@ export const coerceFieldDefault = (
|
|
|
137
243
|
},
|
|
138
244
|
};
|
|
139
245
|
}
|
|
140
|
-
|
|
246
|
+
if (normalizedType === "Date") {
|
|
247
|
+
const expression = dateDefault(defaultValue);
|
|
248
|
+
if (expression !== null) return { expression, normalized: true, normalizedType };
|
|
249
|
+
return {
|
|
250
|
+
expression: null,
|
|
251
|
+
normalized: false,
|
|
252
|
+
normalizedType,
|
|
253
|
+
diagnostic: {
|
|
254
|
+
severity: "error",
|
|
255
|
+
code: "primitive-default-value-invalid",
|
|
256
|
+
input: "default",
|
|
257
|
+
failureScope: "source-change",
|
|
258
|
+
message: `Default value for Date must be "now", a timestamp, or a parseable date string. Received: ${JSON.stringify(
|
|
259
|
+
defaultValue,
|
|
260
|
+
)}.`,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (normalizedType === "enum") {
|
|
265
|
+
if (typeof defaultValue === "string" && options.enumValues?.includes(defaultValue)) {
|
|
266
|
+
return { expression: JSON.stringify(defaultValue), normalized: false, normalizedType };
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
expression: null,
|
|
270
|
+
normalized: false,
|
|
271
|
+
normalizedType,
|
|
272
|
+
diagnostic: {
|
|
273
|
+
severity: "error",
|
|
274
|
+
code: "primitive-default-value-invalid",
|
|
275
|
+
input: "default",
|
|
276
|
+
failureScope: "source-change",
|
|
277
|
+
message: `Default value for enum must be one of: ${(options.enumValues ?? []).join(", ")}. Received: ${JSON.stringify(
|
|
278
|
+
defaultValue,
|
|
279
|
+
)}.`,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
expression: JSON.stringify(String(defaultValue)),
|
|
285
|
+
normalized: typeof defaultValue !== "string",
|
|
286
|
+
normalizedType,
|
|
287
|
+
};
|
|
141
288
|
};
|
|
142
289
|
|
|
143
|
-
export const fieldExpression = (
|
|
290
|
+
export const fieldExpression = (
|
|
291
|
+
typeName: string,
|
|
292
|
+
defaultValue?: FieldDefaultValue,
|
|
293
|
+
options: { enumValues?: readonly string[] | null; builderName?: string } = {},
|
|
294
|
+
) => {
|
|
144
295
|
const typeExpression = normalizeFieldType(typeName);
|
|
145
|
-
const defaultExpression = coerceFieldDefault(
|
|
296
|
+
const defaultExpression = coerceFieldDefault(
|
|
297
|
+
options.enumValues ? "enum" : typeExpression,
|
|
298
|
+
defaultValue,
|
|
299
|
+
options,
|
|
300
|
+
).expression;
|
|
146
301
|
const defaultOption = defaultExpression ? `, { default: ${defaultExpression} }` : "";
|
|
147
|
-
return
|
|
302
|
+
return `${options.builderName ?? "field"}(${typeExpression}${defaultOption})`;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const viaBuilderParameterName = (content: string, className: string) => {
|
|
306
|
+
const classIndex = content.indexOf(`export class ${className} extends via`);
|
|
307
|
+
if (classIndex < 0) return null;
|
|
308
|
+
const signature = content.slice(classIndex, content.indexOf("=>", classIndex));
|
|
309
|
+
return /via\(\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(signature)?.[1] ?? null;
|
|
148
310
|
};
|
|
149
311
|
|
|
150
312
|
export const insertIntoObject = (content: string, className: string, line: string) => {
|
|
@@ -158,6 +320,52 @@ export const insertIntoObject = (content: string, className: string, line: strin
|
|
|
158
320
|
return `${prefix}${insertion}${suffix}`;
|
|
159
321
|
};
|
|
160
322
|
|
|
323
|
+
export const insertLightProjectionField = (content: string, moduleClassName: string, fieldName: string) => {
|
|
324
|
+
const classIndex = content.indexOf(`export class Light${moduleClassName} extends via`);
|
|
325
|
+
if (classIndex < 0) return null;
|
|
326
|
+
const arrayMatch = /\[([\s\S]*?)\]\s+as const/.exec(content.slice(classIndex));
|
|
327
|
+
if (!arrayMatch || arrayMatch.index === undefined) return null;
|
|
328
|
+
const arrayStart = classIndex + arrayMatch.index;
|
|
329
|
+
const arrayEnd = arrayStart + arrayMatch[0].length;
|
|
330
|
+
const fields = [...arrayMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]).filter(Boolean);
|
|
331
|
+
if (fields.includes(fieldName)) return content;
|
|
332
|
+
const nextFields = [...fields, fieldName];
|
|
333
|
+
const nextArray =
|
|
334
|
+
nextFields.length === 0
|
|
335
|
+
? "[] as const"
|
|
336
|
+
: `[\n${nextFields.map((field) => ` ${JSON.stringify(field)},`).join("\n")}\n] as const`;
|
|
337
|
+
return `${content.slice(0, arrayStart)}${nextArray}${content.slice(arrayEnd)}`;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export const insertTemplateField = ({
|
|
341
|
+
content,
|
|
342
|
+
moduleName,
|
|
343
|
+
moduleClassName,
|
|
344
|
+
fieldName,
|
|
345
|
+
component,
|
|
346
|
+
}: {
|
|
347
|
+
content: string;
|
|
348
|
+
moduleName: string;
|
|
349
|
+
moduleClassName: string;
|
|
350
|
+
fieldName: string;
|
|
351
|
+
component: "Field.Text" | "Field.Number" | "Field.Date";
|
|
352
|
+
}) => {
|
|
353
|
+
if (content.includes(`l("${moduleName}.${fieldName}")`) || content.includes(`.${fieldName}`)) return content;
|
|
354
|
+
const layoutEndIndex = content.indexOf(" </Layout.Template>");
|
|
355
|
+
if (layoutEndIndex < 0) return null;
|
|
356
|
+
const formName = `${moduleName}Form`;
|
|
357
|
+
if (!content.includes(`const ${formName} = st.use.${moduleName}Form();`)) return null;
|
|
358
|
+
const fieldSetter = `st.do.set${moduleComponentName(fieldName)}On${moduleClassName}`;
|
|
359
|
+
const fieldBlock = ` <${component}
|
|
360
|
+
label={l("${moduleName}.${fieldName}")}
|
|
361
|
+
desc={l("${moduleName}.${fieldName}.desc")}
|
|
362
|
+
value={${formName}.${fieldName}}
|
|
363
|
+
onChange={${fieldSetter}}
|
|
364
|
+
/>
|
|
365
|
+
`;
|
|
366
|
+
return `${content.slice(0, layoutEndIndex)}${fieldBlock}${content.slice(layoutEndIndex)}`;
|
|
367
|
+
};
|
|
368
|
+
|
|
161
369
|
export const ensureEnumImport = (content: string) => {
|
|
162
370
|
if (content.includes("enumOf")) return content;
|
|
163
371
|
const baseImport = /import \{ ([^}]+) \} from "akanjs\/base";/.exec(content);
|
|
@@ -178,16 +386,60 @@ export const insertEnumClass = (content: string, enumClassName: string, enumName
|
|
|
178
386
|
return `${content.slice(0, firstClassIndex)}${enumClass}${content.slice(firstClassIndex)}`;
|
|
179
387
|
};
|
|
180
388
|
|
|
389
|
+
const findMatchingBrace = (content: string, openIndex: number) => {
|
|
390
|
+
let depth = 0;
|
|
391
|
+
let quote: '"' | "'" | "`" | null = null;
|
|
392
|
+
let escaped = false;
|
|
393
|
+
for (let index = openIndex; index < content.length; index++) {
|
|
394
|
+
const char = content[index];
|
|
395
|
+
if (quote) {
|
|
396
|
+
if (escaped) {
|
|
397
|
+
escaped = false;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (char === "\\") {
|
|
401
|
+
escaped = true;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (char === quote) quote = null;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
408
|
+
quote = char;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (char === "{") depth += 1;
|
|
412
|
+
if (char === "}") {
|
|
413
|
+
depth -= 1;
|
|
414
|
+
if (depth === 0) return index;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return -1;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const dictionaryModelFieldLine = (fieldName: string) => {
|
|
421
|
+
const label = bilingualLabelForField(fieldName);
|
|
422
|
+
const desc = bilingualDescriptionForField(fieldName);
|
|
423
|
+
return `${fieldName}: t([${JSON.stringify(label.en)}, ${JSON.stringify(label.ko)}]).desc([${JSON.stringify(
|
|
424
|
+
desc.en,
|
|
425
|
+
)}, ${JSON.stringify(desc.ko)}]),`;
|
|
426
|
+
};
|
|
427
|
+
|
|
181
428
|
export const insertDictionaryModelField = (content: string, moduleClassName: string, fieldName: string) => {
|
|
182
429
|
if (new RegExp(`\\b${fieldName}\\s*:`).test(content)) return content;
|
|
183
|
-
const
|
|
184
|
-
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => ({`);
|
|
430
|
+
const modelIndex = content.indexOf(`.model<${moduleClassName}>((t) => (`);
|
|
185
431
|
if (modelIndex < 0) return null;
|
|
186
|
-
const
|
|
432
|
+
const objectStartIndex = content.indexOf("{", modelIndex);
|
|
433
|
+
if (objectStartIndex < 0) return null;
|
|
434
|
+
const objectEndIndex = findMatchingBrace(content, objectStartIndex);
|
|
187
435
|
if (objectEndIndex < 0) return null;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
436
|
+
const fieldLine = dictionaryModelFieldLine(fieldName);
|
|
437
|
+
const body = content.slice(objectStartIndex + 1, objectEndIndex);
|
|
438
|
+
if (body.trim().length === 0) {
|
|
439
|
+
return `${content.slice(0, objectStartIndex + 1)}\n ${fieldLine}\n ${content.slice(objectEndIndex)}`;
|
|
440
|
+
}
|
|
441
|
+
const insertion = body.endsWith("\n") ? ` ${fieldLine}\n` : `\n ${fieldLine}\n`;
|
|
442
|
+
return `${content.slice(0, objectEndIndex)}${insertion}${content.slice(objectEndIndex)}`;
|
|
191
443
|
};
|
|
192
444
|
|
|
193
445
|
export const ensureConstantTypeImport = (content: string, constantPath: string, typeName: string) => {
|
package/workflow/types.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Sys, Workspace } from "../commandDecorators";
|
|
2
|
+
import type { FieldDefaultValue } from "./source";
|
|
2
3
|
|
|
3
|
-
export type WorkflowInputType = "string" | "string-list" | "surface-mode";
|
|
4
|
+
export type WorkflowInputType = "string" | "string-list" | "surface-mode" | "boolean";
|
|
4
5
|
export type WorkflowSurfaceMode = "infer" | "include" | "skip";
|
|
5
|
-
export type WorkflowInputValue = string | string[] | WorkflowSurfaceMode;
|
|
6
|
+
export type WorkflowInputValue = string | string[] | boolean | WorkflowSurfaceMode;
|
|
6
7
|
export type WorkflowFormat = "markdown" | "json";
|
|
7
8
|
export type WorkflowPlanInputs = Record<string, string | null>;
|
|
8
9
|
export type PrimitiveFormat = "markdown" | "json";
|
|
@@ -10,6 +11,12 @@ export type UiSurface = "view" | "unit" | "template";
|
|
|
10
11
|
export type WorkflowValidationKind = "sync" | "lint" | "typecheck" | "doctor" | "custom";
|
|
11
12
|
export type WorkflowFailureScope = "workspace-config" | "environment" | "source-change" | "unknown";
|
|
12
13
|
export type WorkflowValidationStatus = "passed" | "failed" | "unknown";
|
|
14
|
+
export interface WorkflowValidationSummary {
|
|
15
|
+
sourceChange: WorkflowValidationStatus;
|
|
16
|
+
generatedSync: WorkflowValidationStatus;
|
|
17
|
+
workspaceConfig: WorkflowValidationStatus;
|
|
18
|
+
environment: WorkflowValidationStatus;
|
|
19
|
+
}
|
|
13
20
|
export type WorkflowOverallStatus = "passed" | "failed" | "blocked-by-workspace-config" | "blocked-by-environment";
|
|
14
21
|
|
|
15
22
|
export interface PrimitiveTargetInput {
|
|
@@ -20,13 +27,17 @@ export interface PrimitiveTargetInput {
|
|
|
20
27
|
export interface AddFieldInput extends PrimitiveTargetInput {
|
|
21
28
|
field: string | null;
|
|
22
29
|
type: string | null;
|
|
23
|
-
defaultValue?:
|
|
30
|
+
defaultValue?: FieldDefaultValue;
|
|
31
|
+
surfaces?: string[] | null;
|
|
32
|
+
includeInLight?: boolean | null;
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
export interface AddEnumFieldInput extends PrimitiveTargetInput {
|
|
27
36
|
field: string | null;
|
|
28
37
|
values: string | null;
|
|
29
|
-
defaultValue?:
|
|
38
|
+
defaultValue?: FieldDefaultValue;
|
|
39
|
+
surfaces?: string[] | null;
|
|
40
|
+
includeInLight?: boolean | null;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
export interface WorkflowInputSpec {
|
|
@@ -150,6 +161,7 @@ export interface WorkflowKnownBlocker {
|
|
|
150
161
|
command?: string;
|
|
151
162
|
kind?: WorkflowValidationKind;
|
|
152
163
|
count: number;
|
|
164
|
+
known?: boolean;
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
export interface RepairAction {
|
|
@@ -165,6 +177,13 @@ export interface PrimitiveChangedFile {
|
|
|
165
177
|
reason: string;
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
export interface WorkflowPostApplyCheck {
|
|
181
|
+
code: string;
|
|
182
|
+
target: string;
|
|
183
|
+
status: "passed" | "failed";
|
|
184
|
+
message: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
168
187
|
export interface PrimitiveGeneratedFile {
|
|
169
188
|
path: string;
|
|
170
189
|
action: "sync";
|
|
@@ -208,6 +227,7 @@ export interface WorkflowApplyReport {
|
|
|
208
227
|
recommendedValidationCommands: WorkflowApplyCommand[];
|
|
209
228
|
commands: WorkflowApplyCommand[];
|
|
210
229
|
diagnostics: WorkflowDiagnostic[];
|
|
230
|
+
postApplyChecks?: WorkflowPostApplyCheck[];
|
|
211
231
|
recommendations: WorkflowRecommendation[];
|
|
212
232
|
nextActions: PrimitiveNextAction[];
|
|
213
233
|
plan: WorkflowPlan;
|
|
@@ -222,6 +242,7 @@ export interface WorkflowValidationRunReport {
|
|
|
222
242
|
status: "passed" | "failed";
|
|
223
243
|
sourceStatus: WorkflowValidationStatus;
|
|
224
244
|
workspaceStatus: WorkflowValidationStatus;
|
|
245
|
+
summary: WorkflowValidationSummary;
|
|
225
246
|
overallStatus: WorkflowOverallStatus;
|
|
226
247
|
knownBlockers: WorkflowKnownBlocker[];
|
|
227
248
|
commands: WorkflowValidationCommandResult[];
|
|
@@ -266,6 +287,7 @@ export interface WorkflowStepResult {
|
|
|
266
287
|
generatedFiles?: PrimitiveGeneratedFile[];
|
|
267
288
|
commands?: WorkflowApplyCommand[];
|
|
268
289
|
diagnostics?: WorkflowDiagnostic[];
|
|
290
|
+
postApplyChecks?: WorkflowPostApplyCheck[];
|
|
269
291
|
recommendations?: WorkflowRecommendation[];
|
|
270
292
|
nextActions?: PrimitiveNextAction[];
|
|
271
293
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeFieldType } from "./source";
|
|
2
|
+
|
|
3
|
+
export type AddFieldUiComponent = "Field.Text" | "Field.Number" | "Field.Date" | "Field.ToggleSelect";
|
|
4
|
+
|
|
5
|
+
export const addFieldUiPolicyForType = (typeName: string) => {
|
|
6
|
+
const normalizedType = typeName.toLowerCase() === "enum" ? "enum" : normalizeFieldType(typeName);
|
|
7
|
+
if (normalizedType === "Int" || normalizedType === "Float") {
|
|
8
|
+
return {
|
|
9
|
+
normalizedType,
|
|
10
|
+
component: "Field.Number" as const,
|
|
11
|
+
confidence: "high" as const,
|
|
12
|
+
autoTemplateSupported: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (normalizedType === "Date") {
|
|
16
|
+
return {
|
|
17
|
+
normalizedType,
|
|
18
|
+
component: "Field.Date" as const,
|
|
19
|
+
confidence: "high" as const,
|
|
20
|
+
autoTemplateSupported: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (normalizedType === "Boolean" || normalizedType === "enum") {
|
|
24
|
+
return {
|
|
25
|
+
normalizedType,
|
|
26
|
+
component: "Field.ToggleSelect" as const,
|
|
27
|
+
confidence: "medium" as const,
|
|
28
|
+
autoTemplateSupported: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (normalizedType === "String") {
|
|
32
|
+
return {
|
|
33
|
+
normalizedType,
|
|
34
|
+
component: "Field.Text" as const,
|
|
35
|
+
confidence: "high" as const,
|
|
36
|
+
autoTemplateSupported: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
normalizedType,
|
|
41
|
+
component: "Field.Text" as const,
|
|
42
|
+
confidence: "low" as const,
|
|
43
|
+
autoTemplateSupported: false,
|
|
44
|
+
};
|
|
45
|
+
};
|