zinkee 0.1.7 → 0.1.8
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/.claude/settings.local.json +3 -1
- package/README.md +36 -16
- package/dist/index.js +975 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/schemas.test.ts +93 -6
- package/src/index.test.ts +10 -5
- package/src/utils/examples.test.ts +44 -8
- package/src/utils/examples.ts +447 -66
- package/src/utils/schema-fields.ts +571 -86
package/package.json
CHANGED
|
@@ -590,6 +590,94 @@ describe("schemas commands", () => {
|
|
|
590
590
|
});
|
|
591
591
|
});
|
|
592
592
|
|
|
593
|
+
it("normalizes schedule field shorthand payloads into the backend shape", async () => {
|
|
594
|
+
const configPath = createApiConfig();
|
|
595
|
+
const output = createStdoutBuffer();
|
|
596
|
+
const fetchMock = createFetchMock();
|
|
597
|
+
mockSchemaLookup(fetchMock);
|
|
598
|
+
fetchMock.mockResolvedValueOnce(
|
|
599
|
+
jsonResponse({
|
|
600
|
+
id: "field-5",
|
|
601
|
+
type: "ScheduleField",
|
|
602
|
+
slug: "monthly-recurrence",
|
|
603
|
+
label: "Monthly Recurrence",
|
|
604
|
+
}),
|
|
605
|
+
);
|
|
606
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
607
|
+
|
|
608
|
+
await program.parseAsync([
|
|
609
|
+
"node",
|
|
610
|
+
"zinkee",
|
|
611
|
+
"--json",
|
|
612
|
+
"schemas",
|
|
613
|
+
"fields",
|
|
614
|
+
"create",
|
|
615
|
+
"contacts",
|
|
616
|
+
"--raw",
|
|
617
|
+
'{"type":"schedule","slug":"monthly-recurrence","label":"Monthly Recurrence","referenceField":"c52efe3f-2825-4c60-a7e3-34dd4c1ce043","beginningPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","endingPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","frequency":"monthly"}',
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body))).toEqual({
|
|
621
|
+
type: "ScheduleField",
|
|
622
|
+
slug: "monthly-recurrence",
|
|
623
|
+
label: "Monthly Recurrence",
|
|
624
|
+
config: {
|
|
625
|
+
referenceField: "c52efe3f-2825-4c60-a7e3-34dd4c1ce043",
|
|
626
|
+
beginningPeriodField: "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
|
|
627
|
+
endingPeriodField: "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
|
|
628
|
+
frequency: "monthly",
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("rejects schedule fields with invalid frequency before hitting the backend", async () => {
|
|
634
|
+
const configPath = createApiConfig();
|
|
635
|
+
const output = createStdoutBuffer();
|
|
636
|
+
const fetchMock = createFetchMock();
|
|
637
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
638
|
+
|
|
639
|
+
await expect(
|
|
640
|
+
program.parseAsync([
|
|
641
|
+
"node",
|
|
642
|
+
"zinkee",
|
|
643
|
+
"schemas",
|
|
644
|
+
"fields",
|
|
645
|
+
"create",
|
|
646
|
+
"contacts",
|
|
647
|
+
"--raw",
|
|
648
|
+
'{"type":"ScheduleField","slug":"weekly","label":"Weekly","config":{"referenceField":"c52efe3f-2825-4c60-a7e3-34dd4c1ce043","beginningPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","endingPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","frequency":"weekly"}}',
|
|
649
|
+
]),
|
|
650
|
+
).rejects.toThrowError(
|
|
651
|
+
'Schedule "frequency" must be "monthly" or "daily" (received "weekly").',
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it("rejects schedule fields missing required config fields", async () => {
|
|
658
|
+
const configPath = createApiConfig();
|
|
659
|
+
const output = createStdoutBuffer();
|
|
660
|
+
const fetchMock = createFetchMock();
|
|
661
|
+
const program = buildProgram({ configPath, stdout: output.stdout });
|
|
662
|
+
|
|
663
|
+
await expect(
|
|
664
|
+
program.parseAsync([
|
|
665
|
+
"node",
|
|
666
|
+
"zinkee",
|
|
667
|
+
"schemas",
|
|
668
|
+
"fields",
|
|
669
|
+
"create",
|
|
670
|
+
"contacts",
|
|
671
|
+
"--raw",
|
|
672
|
+
'{"type":"ScheduleField","slug":"recurrence","label":"Recurrence","config":{"frequency":"daily"}}',
|
|
673
|
+
]),
|
|
674
|
+
).rejects.toThrowError(
|
|
675
|
+
/Schedule fields require "referenceField", "beginningPeriodField", "endingPeriodField" and "frequency"/,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
679
|
+
});
|
|
680
|
+
|
|
593
681
|
it("rejects unsupported field types before making network requests", async () => {
|
|
594
682
|
const configPath = createApiConfig();
|
|
595
683
|
const output = createStdoutBuffer();
|
|
@@ -612,7 +700,7 @@ describe("schemas commands", () => {
|
|
|
612
700
|
"Bad",
|
|
613
701
|
]),
|
|
614
702
|
).rejects.toThrowError(
|
|
615
|
-
'Unsupported field type "InvalidField". Valid types: text, number, date, option, file, reference, formula, member, autoincremental.',
|
|
703
|
+
'Unsupported field type "InvalidField". Valid types: text, number, date, option, file, reference, formula, member, autoincremental, schedule.',
|
|
616
704
|
);
|
|
617
705
|
|
|
618
706
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
@@ -629,13 +717,12 @@ describe("schemas commands", () => {
|
|
|
629
717
|
expect(help).toContain("member");
|
|
630
718
|
expect(help).toContain("autoincremental");
|
|
631
719
|
expect(help).toContain("Raw payload shapes by type:");
|
|
632
|
-
expect(help).toContain('"type":"
|
|
720
|
+
expect(help).toContain('"type":"NumberField","slug":"quantity"');
|
|
633
721
|
expect(help).toContain('"defaultValue":"0"');
|
|
634
|
-
expect(help).toContain('"symbolEnabled":true');
|
|
635
722
|
expect(help).toContain('- member: Workspace member field.');
|
|
636
|
-
expect(help).toContain('"type":"
|
|
723
|
+
expect(help).toContain('"type":"MemberField","slug":"owner","label":"Owner"');
|
|
637
724
|
expect(help).toContain('- option: Dropdown/select field with options.');
|
|
638
|
-
expect(help).toContain('"type":"
|
|
725
|
+
expect(help).toContain('"type":"OptionField","slug":"status","label":"Status"');
|
|
639
726
|
expect(help).toContain('Use "zinkee --example schemas fields create --type <type>"');
|
|
640
727
|
});
|
|
641
728
|
|
|
@@ -692,7 +779,7 @@ describe("schemas commands", () => {
|
|
|
692
779
|
'{"type":"option","slug":"status","label":"Status","options":[{"id":1,"name":"Open","color":"default"}]}',
|
|
693
780
|
]),
|
|
694
781
|
).rejects.toThrowError(
|
|
695
|
-
'Invalid payload for field type "option". Expected raw shape: {"type":"
|
|
782
|
+
'Invalid payload for field type "option". Expected raw shape: {"type":"OptionField","slug":"status","label":"Status","config":{"options":[{"id":1,"name":"Open","color":"default"},{"id":2,"name":"Closed","color":"success"}]}}',
|
|
696
783
|
);
|
|
697
784
|
});
|
|
698
785
|
|
package/src/index.test.ts
CHANGED
|
@@ -85,12 +85,12 @@ describe("project bootstrap", () => {
|
|
|
85
85
|
);
|
|
86
86
|
|
|
87
87
|
expect(output).toContain('zinkee --json schemas fields create contacts --raw');
|
|
88
|
-
expect(output).toContain('"type":"
|
|
89
|
-
expect(output).not.toContain('"type":"
|
|
90
|
-
expect(output).toContain("
|
|
88
|
+
expect(output).toContain('"type":"OptionField"');
|
|
89
|
+
expect(output).not.toContain('"type":"TextField"');
|
|
90
|
+
expect(output).toContain("Option field (select dropdown)");
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
it("shows
|
|
93
|
+
it("shows every backend number variant, including currency formatting", async () => {
|
|
94
94
|
let output = "";
|
|
95
95
|
|
|
96
96
|
await showExamples(
|
|
@@ -105,9 +105,14 @@ describe("project bootstrap", () => {
|
|
|
105
105
|
{ type: "number" },
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
-
expect(output).toContain('"slug":"
|
|
108
|
+
expect(output).toContain('"slug":"quantity"');
|
|
109
|
+
expect(output).toContain('"slug":"amount-eur"');
|
|
110
|
+
expect(output).toContain('"slug":"amount-usd"');
|
|
109
111
|
expect(output).toContain('"symbolEnabled":true');
|
|
112
|
+
expect(output).toContain('"symbolPosition":"before"');
|
|
110
113
|
expect(output).toContain('"symbolPosition":"right"');
|
|
114
|
+
expect(output).toContain('"format":"percentage"');
|
|
115
|
+
expect(output).toContain('"durationFormat":"HH:MM"');
|
|
111
116
|
});
|
|
112
117
|
|
|
113
118
|
it("shows automation examples with descriptions and equivalent payloads", async () => {
|
|
@@ -21,18 +21,54 @@ describe("getCommandExamples", () => {
|
|
|
21
21
|
const examples = getCommandExamples("schemas fields create", { type: "member" });
|
|
22
22
|
|
|
23
23
|
expect(examples).toHaveLength(1);
|
|
24
|
-
expect(examples[0]?.command).toContain('"type":"
|
|
25
|
-
expect(examples[0]?.description).
|
|
24
|
+
expect(examples[0]?.command).toContain('"type":"MemberField"');
|
|
25
|
+
expect(examples[0]?.description).toBe("Member field");
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it("returns
|
|
29
|
-
const examples = getCommandExamples("schemas fields create", { type: "
|
|
28
|
+
it("returns monthly and daily variants for schedule fields", () => {
|
|
29
|
+
const examples = getCommandExamples("schemas fields create", { type: "schedule" });
|
|
30
30
|
|
|
31
31
|
expect(examples).toHaveLength(2);
|
|
32
|
-
expect(examples
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
expect(examples.map((example) => example.description)).toEqual([
|
|
33
|
+
"Schedule field (monthly)",
|
|
34
|
+
"Schedule field (daily)",
|
|
35
|
+
]);
|
|
36
|
+
expect(examples[0]?.command).toContain('"type":"ScheduleField"');
|
|
37
|
+
expect(examples[0]?.command).toContain('"frequency":"monthly"');
|
|
38
|
+
expect(examples[1]?.command).toContain('"frequency":"daily"');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("covers every backend number field variant", () => {
|
|
42
|
+
const examples = getCommandExamples("schemas fields create", { type: "number" });
|
|
43
|
+
|
|
44
|
+
expect(examples).toHaveLength(6);
|
|
45
|
+
|
|
46
|
+
const descriptions = examples.map((example) => example.description);
|
|
47
|
+
expect(descriptions).toEqual([
|
|
48
|
+
"Number field (plain integer)",
|
|
49
|
+
"Number field (decimal with thousands)",
|
|
50
|
+
"Number field (currency € prefix)",
|
|
51
|
+
"Number field (currency $ suffix)",
|
|
52
|
+
"Number field (percentage)",
|
|
53
|
+
"Number field (duration HH:MM)",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
expect(examples[0]?.command).toContain('"slug":"quantity"');
|
|
57
|
+
expect(examples[0]?.command).toContain('"format":"number"');
|
|
58
|
+
|
|
59
|
+
const eurCurrency = examples[2]?.command ?? "";
|
|
60
|
+
expect(eurCurrency).toContain('"format":"currency"');
|
|
61
|
+
expect(eurCurrency).toContain('"symbolEnabled":true');
|
|
62
|
+
expect(eurCurrency).toContain('"symbolPosition":"before"');
|
|
63
|
+
expect(eurCurrency).toContain('"symbolValue":"€"');
|
|
64
|
+
|
|
65
|
+
const usdCurrency = examples[3]?.command ?? "";
|
|
66
|
+
expect(usdCurrency).toContain('"symbolPosition":"right"');
|
|
67
|
+
expect(usdCurrency).toContain('"symbolValue":" $"');
|
|
68
|
+
|
|
69
|
+
expect(examples[4]?.command).toContain('"format":"percentage"');
|
|
70
|
+
expect(examples[5]?.command).toContain('"format":"duration"');
|
|
71
|
+
expect(examples[5]?.command).toContain('"durationFormat":"HH:MM"');
|
|
36
72
|
});
|
|
37
73
|
|
|
38
74
|
it("returns canonical automation creation examples", () => {
|