zinkee 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +46 -0
  2. package/README.md +395 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.js +7142 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +32 -0
  7. package/src/api/automations.ts +403 -0
  8. package/src/api/comments.ts +51 -0
  9. package/src/api/displays.ts +276 -0
  10. package/src/api/files.ts +50 -0
  11. package/src/api/navigation.ts +71 -0
  12. package/src/api/records.ts +167 -0
  13. package/src/api/schemas.ts +130 -0
  14. package/src/api/teamspace.ts +110 -0
  15. package/src/cli-examples.ts +175 -0
  16. package/src/cli-runner.ts +73 -0
  17. package/src/client.test.ts +128 -0
  18. package/src/client.ts +243 -0
  19. package/src/command-registry.ts +749 -0
  20. package/src/commands/automations.test.ts +728 -0
  21. package/src/commands/automations.ts +2013 -0
  22. package/src/commands/comments.test.ts +214 -0
  23. package/src/commands/comments.ts +303 -0
  24. package/src/commands/config.test.ts +81 -0
  25. package/src/commands/config.ts +150 -0
  26. package/src/commands/displays.test.ts +266 -0
  27. package/src/commands/displays.ts +755 -0
  28. package/src/commands/files.test.ts +284 -0
  29. package/src/commands/files.ts +280 -0
  30. package/src/commands/navigation.test.ts +214 -0
  31. package/src/commands/navigation.ts +348 -0
  32. package/src/commands/profiles.test.ts +82 -0
  33. package/src/commands/profiles.ts +281 -0
  34. package/src/commands/records.test.ts +367 -0
  35. package/src/commands/records.ts +726 -0
  36. package/src/commands/schemas.test.ts +748 -0
  37. package/src/commands/schemas.ts +746 -0
  38. package/src/commands/teamspace.test.ts +229 -0
  39. package/src/commands/teamspace.ts +540 -0
  40. package/src/config.test.ts +165 -0
  41. package/src/config.ts +248 -0
  42. package/src/index.test.ts +140 -0
  43. package/src/index.ts +8 -0
  44. package/src/parsers/expressions.test.ts +50 -0
  45. package/src/parsers/expressions.ts +111 -0
  46. package/src/parsers/kv.test.ts +35 -0
  47. package/src/parsers/kv.ts +49 -0
  48. package/src/parsers/selectors.test.ts +23 -0
  49. package/src/parsers/selectors.ts +29 -0
  50. package/src/program.ts +56 -0
  51. package/src/runtime-context.ts +18 -0
  52. package/src/types.ts +71 -0
  53. package/src/utils/errors.test.ts +48 -0
  54. package/src/utils/errors.ts +183 -0
  55. package/src/utils/examples.test.ts +67 -0
  56. package/src/utils/examples.ts +855 -0
  57. package/src/utils/output.test.ts +39 -0
  58. package/src/utils/output.ts +124 -0
  59. package/src/utils/schema-fields.ts +529 -0
  60. package/tsconfig.json +20 -0
  61. package/tsup.config.ts +13 -0
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { getCommandMeta } from "../command-registry.js";
4
+
5
+ import { buildJsonError, buildJsonSuccess } from "./output.js";
6
+
7
+ describe("buildJsonSuccess", () => {
8
+ it("wraps data in the standard success envelope", () => {
9
+ expect(buildJsonSuccess([{ id: "1" }], { command: "schemas list" })).toEqual({
10
+ data: [{ id: "1" }],
11
+ meta: { command: "schemas list" },
12
+ });
13
+ });
14
+ });
15
+
16
+ describe("buildJsonError", () => {
17
+ it("wraps error details in the standard error envelope", () => {
18
+ expect(
19
+ buildJsonError({ reason: "invalid_request", message: "Bad request" }, {}),
20
+ ).toMatchObject({
21
+ error: { reason: "invalid_request" },
22
+ meta: {},
23
+ });
24
+ });
25
+ });
26
+
27
+ describe("command registry", () => {
28
+ it("looks up registered command metadata cleanly", () => {
29
+ const meta = getCommandMeta("profiles add");
30
+
31
+ expect(meta).toEqual({
32
+ command: "profiles add",
33
+ accessLevel: "write",
34
+ supportsExample: true,
35
+ supportsRaw: false,
36
+ output: "json-only",
37
+ });
38
+ });
39
+ });
@@ -0,0 +1,124 @@
1
+ import Table from "cli-table3";
2
+
3
+ import type {
4
+ JsonEnvelopeMeta,
5
+ JsonErrorEnvelope,
6
+ JsonSuccessEnvelope,
7
+ OutputFormat,
8
+ } from "../types.js";
9
+
10
+ export interface TableColumn {
11
+ key: string;
12
+ label: string;
13
+ }
14
+
15
+ export interface TableCell {
16
+ key: string;
17
+ value: string;
18
+ }
19
+
20
+ export interface TableRow {
21
+ cells: TableCell[];
22
+ }
23
+
24
+ export interface OutputWriter {
25
+ write(chunk: string): unknown;
26
+ }
27
+
28
+ function stringifyCellValue(value: unknown): string {
29
+ if (value === null || value === undefined) {
30
+ return "";
31
+ }
32
+
33
+ if (typeof value === "string") {
34
+ return value;
35
+ }
36
+
37
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
38
+ return String(value);
39
+ }
40
+
41
+ return JSON.stringify(value);
42
+ }
43
+
44
+ export function buildJsonSuccess<TData, TMeta extends JsonEnvelopeMeta = JsonEnvelopeMeta>(
45
+ data: TData,
46
+ meta: TMeta = {} as TMeta,
47
+ ): JsonSuccessEnvelope<TData, TMeta> {
48
+ return { data, meta };
49
+ }
50
+
51
+ export function buildJsonError<TErr, TMeta extends JsonEnvelopeMeta = JsonEnvelopeMeta>(
52
+ error: TErr,
53
+ meta: TMeta = {} as TMeta,
54
+ ): JsonErrorEnvelope<TErr, TMeta> {
55
+ return { error, meta };
56
+ }
57
+
58
+ export function buildTableColumns(keys: readonly string[]): readonly TableColumn[] {
59
+ return keys.map((key) => ({ key, label: key }));
60
+ }
61
+
62
+ export function buildTableRows(
63
+ rows: readonly Record<string, unknown>[],
64
+ columns: readonly TableColumn[],
65
+ ): readonly TableRow[] {
66
+ return rows.map((row) => ({
67
+ cells: columns.map((column) => ({
68
+ key: column.key,
69
+ value: stringifyCellValue(row[column.key]),
70
+ })),
71
+ }));
72
+ }
73
+
74
+ function getWriter(writer?: OutputWriter): OutputWriter {
75
+ return writer ?? process.stdout;
76
+ }
77
+
78
+ export function printJson(value: unknown, writer?: OutputWriter): void {
79
+ getWriter(writer).write(`${JSON.stringify(value, null, 2)}\n`);
80
+ }
81
+
82
+ export function printTable(
83
+ rows: readonly Record<string, unknown>[],
84
+ keys?: readonly string[],
85
+ writer?: OutputWriter,
86
+ ): void {
87
+ if (rows.length === 0) {
88
+ getWriter(writer).write("No results.\n");
89
+ return;
90
+ }
91
+
92
+ const firstRow = rows[0];
93
+
94
+ if (!firstRow) {
95
+ getWriter(writer).write("No results.\n");
96
+ return;
97
+ }
98
+
99
+ const headings = [...(keys ?? Object.keys(firstRow))];
100
+ const table = new Table({
101
+ head: headings,
102
+ wordWrap: true,
103
+ });
104
+
105
+ for (const row of rows) {
106
+ table.push(headings.map((heading) => stringifyCellValue(row[heading])));
107
+ }
108
+
109
+ getWriter(writer).write(`${table.toString()}\n`);
110
+ }
111
+
112
+ export function printOutput(
113
+ format: OutputFormat,
114
+ rows: readonly Record<string, unknown>[],
115
+ keys?: readonly string[],
116
+ writer?: OutputWriter,
117
+ ): void {
118
+ if (format === "json") {
119
+ printJson(rows, writer);
120
+ return;
121
+ }
122
+
123
+ printTable(rows, keys, writer);
124
+ }
@@ -0,0 +1,529 @@
1
+ import type { JsonObject } from "../types.js";
2
+ import { CliError } from "./errors.js";
3
+
4
+ export type SchemaFieldTypeKey =
5
+ | "text"
6
+ | "number"
7
+ | "date"
8
+ | "option"
9
+ | "file"
10
+ | "reference"
11
+ | "formula"
12
+ | "member"
13
+ | "autoincremental";
14
+
15
+ export interface SchemaFieldTypeDefinition {
16
+ key: SchemaFieldTypeKey;
17
+ className: string;
18
+ aliases: readonly string[];
19
+ description: string;
20
+ rawShape: string;
21
+ examplePayload: JsonObject;
22
+ }
23
+
24
+ function isJsonObject(value: unknown): value is JsonObject {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+
28
+ function toJsonObject(value: unknown): JsonObject | undefined {
29
+ return isJsonObject(value) ? value : undefined;
30
+ }
31
+
32
+ function toStringValue(value: unknown): string | undefined {
33
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
34
+ }
35
+
36
+ function toNumberValue(value: unknown): number | undefined {
37
+ if (typeof value === "number" && Number.isFinite(value)) {
38
+ return value;
39
+ }
40
+
41
+ if (typeof value === "string" && value.trim().length > 0) {
42
+ const parsed = Number(value);
43
+ if (Number.isFinite(parsed)) {
44
+ return parsed;
45
+ }
46
+ }
47
+
48
+ return undefined;
49
+ }
50
+
51
+ const fieldTypeDefinitions = [
52
+ {
53
+ key: "text",
54
+ className: "TextField",
55
+ aliases: ["text", "TextField"],
56
+ description: "Plain text field.",
57
+ rawShape:
58
+ '{"type":"text","slug":"name","label":"Name"}',
59
+ examplePayload: {
60
+ type: "text",
61
+ slug: "name",
62
+ label: "Name",
63
+ },
64
+ },
65
+ {
66
+ key: "number",
67
+ className: "NumberField",
68
+ aliases: ["number", "NumberField"],
69
+ description: "Numeric field.",
70
+ rawShape:
71
+ '{"type":"number","slug":"price-eur","label":"Price EUR","config":{"defaultValue":"0","formatOptions":{"format":"number","decimals":2,"thousandsSeparator":true,"symbolEnabled":true,"symbolValue":" €","symbolPosition":"right"}}}',
72
+ examplePayload: {
73
+ type: "number",
74
+ slug: "price-eur",
75
+ label: "Price EUR",
76
+ config: {
77
+ defaultValue: "0",
78
+ formatOptions: {
79
+ format: "number",
80
+ decimals: 2,
81
+ thousandsSeparator: true,
82
+ symbolEnabled: true,
83
+ symbolValue: " €",
84
+ symbolPosition: "right",
85
+ },
86
+ },
87
+ },
88
+ },
89
+ {
90
+ key: "date",
91
+ className: "DateField",
92
+ aliases: ["date", "DateField"],
93
+ description: "Date or datetime field.",
94
+ rawShape:
95
+ '{"type":"date","slug":"due-date","label":"Due date"}',
96
+ examplePayload: {
97
+ type: "date",
98
+ slug: "due-date",
99
+ label: "Due date",
100
+ },
101
+ },
102
+ {
103
+ key: "option",
104
+ className: "OptionField",
105
+ aliases: ["option", "OptionField"],
106
+ description: "Dropdown/select field with options.",
107
+ rawShape:
108
+ '{"type":"option","slug":"status","label":"Status","config":{"options":[{"id":1,"name":"Open","color":"default"},{"id":2,"name":"Closed","color":"success"}]}}',
109
+ examplePayload: {
110
+ type: "option",
111
+ slug: "status",
112
+ label: "Status",
113
+ config: {
114
+ options: [
115
+ {
116
+ id: 1,
117
+ name: "Open",
118
+ color: "default",
119
+ },
120
+ {
121
+ id: 2,
122
+ name: "Closed",
123
+ color: "success",
124
+ },
125
+ ],
126
+ },
127
+ },
128
+ },
129
+ {
130
+ key: "file",
131
+ className: "FileField",
132
+ aliases: ["file", "FileField"],
133
+ description: "File attachment field.",
134
+ rawShape:
135
+ '{"type":"file","slug":"attachment","label":"Attachment"}',
136
+ examplePayload: {
137
+ type: "file",
138
+ slug: "attachment",
139
+ label: "Attachment",
140
+ },
141
+ },
142
+ {
143
+ key: "reference",
144
+ className: "ReferenceField",
145
+ aliases: ["reference", "ReferenceField"],
146
+ description: "Reference to another schema.",
147
+ rawShape:
148
+ '{"type":"reference","slug":"project","label":"Project","config":{"targetSchema":"<schema-uuid>","targetDisplayField":"<field-uuid>","deleteBehavior":"SET_NULL"}}',
149
+ examplePayload: {
150
+ type: "reference",
151
+ slug: "project",
152
+ label: "Project",
153
+ config: {
154
+ targetSchema: "<schema-uuid>",
155
+ targetDisplayField: "<field-uuid>",
156
+ deleteBehavior: "SET_NULL",
157
+ },
158
+ },
159
+ },
160
+ {
161
+ key: "formula",
162
+ className: "FormulaField",
163
+ aliases: ["formula", "FormulaField"],
164
+ description: "Calculated field from a formula.",
165
+ rawShape:
166
+ '{"type":"formula","slug":"total","label":"Total","config":{"formulaText":"{price} * {quantity}"}}',
167
+ examplePayload: {
168
+ type: "formula",
169
+ slug: "total",
170
+ label: "Total",
171
+ config: {
172
+ formulaText: "{price} * {quantity}",
173
+ },
174
+ },
175
+ },
176
+ {
177
+ key: "member",
178
+ className: "MemberField",
179
+ aliases: ["member", "MemberField"],
180
+ description: "Workspace member field.",
181
+ rawShape:
182
+ '{"type":"member","slug":"owner","label":"Owner","config":{"defaultValue":"<member-id>"}}',
183
+ examplePayload: {
184
+ type: "member",
185
+ slug: "owner",
186
+ label: "Owner",
187
+ config: {
188
+ defaultValue: "<member-id>",
189
+ },
190
+ },
191
+ },
192
+ {
193
+ key: "autoincremental",
194
+ className: "AutoIncrementalField",
195
+ aliases: [
196
+ "autoincremental",
197
+ "auto-incremental",
198
+ "auto_incremental",
199
+ "AutoIncrementalField",
200
+ ],
201
+ description: "Auto-generated incremental counter.",
202
+ rawShape:
203
+ '{"type":"autoincremental","slug":"counter","label":"Counter","config":{"segmentedByFieldId":null}}',
204
+ examplePayload: {
205
+ type: "autoincremental",
206
+ slug: "counter",
207
+ label: "Counter",
208
+ config: {
209
+ segmentedByFieldId: null,
210
+ },
211
+ },
212
+ },
213
+ ] as const satisfies readonly SchemaFieldTypeDefinition[];
214
+
215
+ const fieldTypeDefinitionByAlias = new Map(
216
+ fieldTypeDefinitions.flatMap((definition) =>
217
+ definition.aliases.map((alias) => [alias.trim().toLowerCase(), definition] as const),
218
+ ),
219
+ );
220
+
221
+ const fieldTypeDefinitionByClassName = new Map<string, SchemaFieldTypeDefinition>(
222
+ fieldTypeDefinitions.map((definition) => [definition.className, definition] as const),
223
+ );
224
+
225
+ const optionValidationMessage =
226
+ 'Option fields require an "options" array with at least one entry like {"id":1,"name":"Open","color":"default"}.';
227
+
228
+ export function getSchemaFieldTypeDefinitions(): readonly SchemaFieldTypeDefinition[] {
229
+ return fieldTypeDefinitions;
230
+ }
231
+
232
+ export function listSchemaFieldTypes(): readonly SchemaFieldTypeKey[] {
233
+ return fieldTypeDefinitions.map((definition) => definition.key);
234
+ }
235
+
236
+ export function formatSchemaFieldTypes(): string {
237
+ return listSchemaFieldTypes().join(", ");
238
+ }
239
+
240
+ export function resolveSchemaFieldType(
241
+ input: string | undefined,
242
+ ): SchemaFieldTypeDefinition | undefined {
243
+ if (!input) {
244
+ return undefined;
245
+ }
246
+
247
+ return fieldTypeDefinitionByAlias.get(input.trim().toLowerCase());
248
+ }
249
+
250
+ export function resolveSchemaFieldTypeByClassName(
251
+ className: string | undefined,
252
+ ): SchemaFieldTypeDefinition | undefined {
253
+ if (!className) {
254
+ return undefined;
255
+ }
256
+
257
+ return fieldTypeDefinitionByClassName.get(className);
258
+ }
259
+
260
+ export function normalizeSchemaFieldType(input: string): string {
261
+ const definition = resolveSchemaFieldType(input);
262
+
263
+ if (definition) {
264
+ return definition.className;
265
+ }
266
+
267
+ if (/^[a-z]/.test(input) && !input.endsWith("Field")) {
268
+ return `${input.charAt(0).toUpperCase()}${input.slice(1)}Field`;
269
+ }
270
+
271
+ return input;
272
+ }
273
+
274
+ export function assertSupportedSchemaFieldType(type: string): void {
275
+ if (!resolveSchemaFieldTypeByClassName(type)) {
276
+ throw new CliError(
277
+ "invalid_cli_usage",
278
+ `Unsupported field type "${type}". Valid types: ${formatSchemaFieldTypes()}.`,
279
+ );
280
+ }
281
+ }
282
+
283
+ interface OptionEntry {
284
+ [key: string]: string | number;
285
+ id: number;
286
+ name: string;
287
+ color: string;
288
+ }
289
+
290
+ function normalizeOptionEntries(entries: readonly unknown[]): OptionEntry[] {
291
+ return entries.flatMap((entry) => {
292
+ if (!isJsonObject(entry)) {
293
+ return [];
294
+ }
295
+
296
+ const id = toNumberValue(entry.id);
297
+ const name = toStringValue(entry.name) ?? toStringValue(entry.label);
298
+ const color = toStringValue(entry.color) ?? "default";
299
+
300
+ if (id === undefined || !name) {
301
+ return [];
302
+ }
303
+
304
+ return [{ id, name, color }];
305
+ });
306
+ }
307
+
308
+ function normalizeOptionPayload(input: JsonObject): JsonObject {
309
+ const additionalProperties = toJsonObject(input.additionalProperties) ?? {};
310
+ const config = toJsonObject(input.config);
311
+ const rootOptions = Array.isArray(input.options) ? input.options : undefined;
312
+ const nestedOptions = Array.isArray(additionalProperties.options)
313
+ ? additionalProperties.options
314
+ : Array.isArray(config?.options)
315
+ ? config.options
316
+ : undefined;
317
+ const optionObjects = normalizeOptionEntries(
318
+ nestedOptions ??
319
+ (Array.isArray(rootOptions) ? rootOptions : []),
320
+ );
321
+
322
+ if (optionObjects.length === 0) {
323
+ throw new CliError("invalid_cli_usage", optionValidationMessage);
324
+ }
325
+
326
+ const { options: _options, additionalProperties: _additionalProperties, ...rest } = input;
327
+ const defaultValue =
328
+ config?.defaultValue ?? input.defaultValue ?? additionalProperties.defaultValue;
329
+ const conditionalFormatting =
330
+ config?.conditionalFormatting ?? additionalProperties.conditionalFormatting;
331
+ const buttonConfig = config?.buttonConfig ?? additionalProperties.buttonConfig;
332
+
333
+ return {
334
+ ...rest,
335
+ config: {
336
+ ...(config ?? {}),
337
+ options: optionObjects,
338
+ ...(defaultValue !== undefined ? { defaultValue } : {}),
339
+ ...(conditionalFormatting !== undefined ? { conditionalFormatting } : {}),
340
+ ...(buttonConfig !== undefined ? { buttonConfig } : {}),
341
+ },
342
+ };
343
+ }
344
+
345
+ function normalizeReferencePayload(input: JsonObject): JsonObject {
346
+ const additionalProperties = toJsonObject(input.additionalProperties) ?? {};
347
+ const config = toJsonObject(input.config) ?? {};
348
+ const targetSchema =
349
+ config.targetSchema ?? input.targetSchema ?? additionalProperties.targetSchema;
350
+ const targetDisplayField =
351
+ config.targetDisplayField ??
352
+ input.targetDisplayField ??
353
+ additionalProperties.targetDisplayField;
354
+ const deleteBehavior =
355
+ config.deleteBehavior ?? input.deleteBehavior ?? additionalProperties.deleteBehavior;
356
+ const defaultValue =
357
+ config.defaultValue ?? input.defaultValue ?? additionalProperties.defaultValue;
358
+ const filterFields =
359
+ config.filterFields ?? input.filterFields ?? additionalProperties.filterFields;
360
+ const descriptionFields =
361
+ config.descriptionFields ??
362
+ input.descriptionFields ??
363
+ additionalProperties.descriptionFields;
364
+ const conditionalFormatting =
365
+ config.conditionalFormatting ??
366
+ input.conditionalFormatting ??
367
+ additionalProperties.conditionalFormatting;
368
+
369
+ const normalized: JsonObject = {
370
+ ...input,
371
+ };
372
+
373
+ normalized.config = {
374
+ ...config,
375
+ ...(targetSchema !== undefined ? { targetSchema } : {}),
376
+ ...(targetDisplayField !== undefined ? { targetDisplayField } : {}),
377
+ ...(deleteBehavior !== undefined ? { deleteBehavior } : {}),
378
+ ...(defaultValue !== undefined ? { defaultValue } : {}),
379
+ ...(filterFields !== undefined ? { filterFields } : {}),
380
+ ...(descriptionFields !== undefined ? { descriptionFields } : {}),
381
+ ...(conditionalFormatting !== undefined ? { conditionalFormatting } : {}),
382
+ };
383
+
384
+ const normalizedConfig = toJsonObject(normalized.config) ?? {};
385
+
386
+ if (
387
+ !toStringValue(normalizedConfig.targetSchema) ||
388
+ !toStringValue(normalizedConfig.targetDisplayField)
389
+ ) {
390
+ const definition = resolveSchemaFieldType("reference")!;
391
+ throw new CliError(
392
+ "invalid_cli_usage",
393
+ `Reference fields require "targetSchema" and "targetDisplayField". Expected raw shape: ${definition.rawShape}`,
394
+ );
395
+ }
396
+
397
+ return normalized;
398
+ }
399
+
400
+ function normalizeFormulaPayload(input: JsonObject): JsonObject {
401
+ const { status: _status, ...rest } = input;
402
+ const rawAdditionalProperties = toJsonObject(input.additionalProperties);
403
+ const rawConfig = toJsonObject(input.config);
404
+ const { status: _additionalStatus, ...additionalProperties } =
405
+ rawAdditionalProperties ?? {};
406
+ const { status: _configStatus, ...config } = rawConfig ?? {};
407
+ const formulaText =
408
+ config.formulaText ??
409
+ rest.formulaText ??
410
+ rest.expression ??
411
+ additionalProperties.formulaText ??
412
+ additionalProperties.expression;
413
+
414
+ const normalized: JsonObject = {
415
+ ...rest,
416
+ };
417
+
418
+ normalized.config = {
419
+ ...config,
420
+ ...(formulaText !== undefined ? { formulaText } : {}),
421
+ ...(config.uiFormatKind !== undefined ? { uiFormatKind: config.uiFormatKind } : {}),
422
+ ...(config.defaultValue !== undefined ? { defaultValue: config.defaultValue } : {}),
423
+ ...(config.numberFormatOptions !== undefined
424
+ ? { numberFormatOptions: config.numberFormatOptions }
425
+ : {}),
426
+ ...(config.dateFormatOptions !== undefined
427
+ ? { dateFormatOptions: config.dateFormatOptions }
428
+ : {}),
429
+ ...(config.conditionalFormatting !== undefined
430
+ ? { conditionalFormatting: config.conditionalFormatting }
431
+ : {}),
432
+ };
433
+
434
+ const normalizedConfig = toJsonObject(normalized.config) ?? {};
435
+
436
+ if (!toStringValue(normalizedConfig.formulaText)) {
437
+ const definition = resolveSchemaFieldType("formula")!;
438
+ throw new CliError(
439
+ "invalid_cli_usage",
440
+ `Formula fields require "formulaText". Expected raw shape: ${definition.rawShape}`,
441
+ );
442
+ }
443
+
444
+ return normalized;
445
+ }
446
+
447
+ export function normalizeSchemaFieldCreatePayload(input: JsonObject): JsonObject {
448
+ const rawType = toStringValue(input.type);
449
+ const normalizedType = rawType ? normalizeSchemaFieldType(rawType) : undefined;
450
+ const definition = resolveSchemaFieldTypeByClassName(normalizedType);
451
+
452
+ if (!definition) {
453
+ return input;
454
+ }
455
+
456
+ const normalizedWithType: JsonObject = {
457
+ ...input,
458
+ type: definition.className,
459
+ };
460
+
461
+ switch (definition.key) {
462
+ case "option":
463
+ return normalizeOptionPayload(normalizedWithType);
464
+ case "reference":
465
+ return normalizeReferencePayload(normalizedWithType);
466
+ case "formula":
467
+ return normalizeFormulaPayload(normalizedWithType);
468
+ case "autoincremental": {
469
+ const config = toJsonObject(normalizedWithType.config) ?? {};
470
+ const segmentedByFieldId =
471
+ config.segmentedByFieldId ??
472
+ normalizedWithType.segmentedByFieldId ??
473
+ null;
474
+ return {
475
+ ...normalizedWithType,
476
+ config: {
477
+ ...config,
478
+ segmentedByFieldId,
479
+ ...(config.conditionalFormatting !== undefined
480
+ ? { conditionalFormatting: config.conditionalFormatting }
481
+ : {}),
482
+ },
483
+ };
484
+ }
485
+ default:
486
+ return normalizedWithType;
487
+ }
488
+ }
489
+
490
+ export function normalizeSchemaFieldUpdatePayload(
491
+ type: string,
492
+ input: JsonObject,
493
+ ): JsonObject {
494
+ const definition = resolveSchemaFieldTypeByClassName(type);
495
+
496
+ if (definition?.key === "formula") {
497
+ return normalizeFormulaPayload(input);
498
+ }
499
+
500
+ return input;
501
+ }
502
+
503
+ export function buildSchemaFieldCreateHelpText(): string {
504
+ const sections = fieldTypeDefinitions.map(
505
+ (definition) =>
506
+ ` - ${definition.key}: ${definition.description}\n raw: ${definition.rawShape}`,
507
+ );
508
+
509
+ return [
510
+ "",
511
+ "Valid field types:",
512
+ ` ${formatSchemaFieldTypes()}`,
513
+ "",
514
+ "Raw payload shapes by type:",
515
+ ...sections,
516
+ "",
517
+ 'Use "zinkee --example schemas fields create --type <type>" to show a concrete example for one type.',
518
+ ].join("\n");
519
+ }
520
+
521
+ export function buildSchemaFieldCreateValidationMessage(type: string): string {
522
+ const definition = resolveSchemaFieldTypeByClassName(type);
523
+
524
+ if (!definition) {
525
+ return `Invalid payload for field type "${type}". Valid types: ${formatSchemaFieldTypes()}.`;
526
+ }
527
+
528
+ return `Invalid payload for field type "${definition.key}". Expected raw shape: ${definition.rawShape}`;
529
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "noImplicitOverride": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "skipLibCheck": true,
12
+ "esModuleInterop": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "sourceMap": true,
16
+ "outDir": "dist",
17
+ "types": ["node"]
18
+ },
19
+ "include": ["src/**/*.ts"]
20
+ }