zinkee 0.1.42 → 0.1.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zinkee",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,6 +5,7 @@ export interface ApiDisplay {
5
5
  [key: string]: unknown;
6
6
  id?: string;
7
7
  name?: string;
8
+ description?: string;
8
9
  layoutTemplate?: string;
9
10
  layout?: {
10
11
  template?: string;
@@ -62,6 +63,17 @@ export interface MoveDisplayInput {
62
63
  export type DisplaySummary = ApiDisplay;
63
64
  export type DisplayResource = ApiDisplay;
64
65
 
66
+ export interface CreateDisplayInput {
67
+ name: string;
68
+ description?: string;
69
+ layout: Record<string, unknown>;
70
+ }
71
+
72
+ export interface UpdateDisplayInput {
73
+ name?: string;
74
+ description?: string;
75
+ }
76
+
65
77
  function jsonOptions(body: unknown, method: string): RequestInit {
66
78
  return {
67
79
  method,
@@ -91,13 +103,13 @@ export class DisplaysApi {
91
103
  return this.client.getJson<ApiDisplay>(`/api/v2/displays/${selectorValue(display)}`);
92
104
  }
93
105
 
94
- createDisplay(body: Record<string, unknown>): Promise<ApiDisplay> {
106
+ createDisplay(body: CreateDisplayInput): Promise<ApiDisplay> {
95
107
  return this.client.requestJson<ApiDisplay>("/api/v2/displays", jsonOptions(body, "POST"));
96
108
  }
97
109
 
98
110
  updateDisplay(
99
111
  display: NormalizedUuidOrSlug,
100
- body: Record<string, unknown>,
112
+ body: UpdateDisplayInput,
101
113
  ): Promise<ApiDisplay> {
102
114
  return this.client.requestJson<ApiDisplay>(
103
115
  `/api/v2/displays/${selectorValue(display)}`,
@@ -7,6 +7,7 @@ export interface ApiSchemaField {
7
7
  type?: string;
8
8
  slug?: string;
9
9
  label?: string;
10
+ description?: string;
10
11
  required?: boolean;
11
12
  }
12
13
 
@@ -47,12 +48,14 @@ export interface CreateSchemaFieldInput {
47
48
  type: string;
48
49
  slug: string;
49
50
  label: string;
51
+ description?: string;
50
52
  required?: boolean;
51
53
  }
52
54
 
53
55
  export interface UpdateSchemaFieldInput {
54
56
  [key: string]: unknown;
55
57
  label?: string;
58
+ description?: string;
56
59
  required?: boolean;
57
60
  }
58
61
 
@@ -94,6 +94,80 @@ describe("displays commands", () => {
94
94
  });
95
95
  });
96
96
 
97
+ it("creates displays with a description", async () => {
98
+ const configPath = createApiConfig();
99
+ const output = createStdoutBuffer();
100
+ const fetchMock = createFetchMock();
101
+ fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1" }));
102
+ const program = buildProgram({ configPath, stdout: output.stdout });
103
+
104
+ await program.parseAsync([
105
+ "node",
106
+ "zinkee",
107
+ "--json",
108
+ "displays",
109
+ "create",
110
+ "--name",
111
+ "Pipeline",
112
+ "--description",
113
+ "Sales pipeline overview",
114
+ "--template",
115
+ "widget_row_tabs",
116
+ ]);
117
+
118
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
119
+ name: "Pipeline",
120
+ description: "Sales pipeline overview",
121
+ layout: { template: "widget_row_tabs" },
122
+ });
123
+ });
124
+
125
+ it.each([
126
+ ["sets", "Sales pipeline overview"],
127
+ ["clears", ""],
128
+ ])("%s a display description without renaming it", async (_action, description) => {
129
+ const configPath = createApiConfig();
130
+ const output = createStdoutBuffer();
131
+ const fetchMock = createFetchMock();
132
+ fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1" }));
133
+ const program = buildProgram({ configPath, stdout: output.stdout });
134
+
135
+ await program.parseAsync([
136
+ "node",
137
+ "zinkee",
138
+ "--json",
139
+ "displays",
140
+ "update",
141
+ "display-1",
142
+ "--description",
143
+ description,
144
+ ]);
145
+
146
+ expect(fetchMock.mock.calls[0]?.[1]?.method).toBe("PATCH");
147
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({ description });
148
+ });
149
+
150
+ it.each(["", " "])("rejects an empty display name", async (name) => {
151
+ const configPath = createApiConfig();
152
+ const output = createStdoutBuffer();
153
+ const fetchMock = createFetchMock();
154
+ const program = buildProgram({ configPath, stdout: output.stdout });
155
+
156
+ await expect(
157
+ program.parseAsync([
158
+ "node",
159
+ "zinkee",
160
+ "--json",
161
+ "displays",
162
+ "update",
163
+ "display-1",
164
+ "--name",
165
+ name,
166
+ ]),
167
+ ).rejects.toThrow("Display name cannot be empty.");
168
+ expect(fetchMock).not.toHaveBeenCalled();
169
+ });
170
+
97
171
  it("creates freeform displays with nested freeform layouts", async () => {
98
172
  const configPath = createApiConfig();
99
173
  const output = createStdoutBuffer();
@@ -167,6 +241,31 @@ describe("displays commands", () => {
167
241
  });
168
242
  });
169
243
 
244
+ it("keeps the widget description flag in presentation and sends an empty clear value", async () => {
245
+ const configPath = createApiConfig();
246
+ const output = createStdoutBuffer();
247
+ const fetchMock = createFetchMock();
248
+ fetchMock.mockResolvedValueOnce(jsonResponse({ displayId: "display-1", widgetId: "widget-1" }));
249
+ const program = buildProgram({ configPath, stdout: output.stdout });
250
+
251
+ await program.parseAsync([
252
+ "node",
253
+ "zinkee",
254
+ "--json",
255
+ "displays",
256
+ "widgets",
257
+ "update",
258
+ "display-1",
259
+ "widget-1",
260
+ "--description",
261
+ "",
262
+ ]);
263
+
264
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
265
+ presentation: { description: "" },
266
+ });
267
+ });
268
+
170
269
  it("deep merges chart flags with chart data from --raw", async () => {
171
270
  const configPath = createApiConfig();
172
271
  const output = createStdoutBuffer();
@@ -38,6 +38,7 @@ interface GlobalOptions extends RuntimeOverrides {}
38
38
 
39
39
  interface DisplayCreateOptions {
40
40
  name: string;
41
+ description?: string;
41
42
  template: string;
42
43
  freeformLayouts?: string;
43
44
  freeformLayoutsFile?: string;
@@ -45,6 +46,7 @@ interface DisplayCreateOptions {
45
46
 
46
47
  interface DisplayUpdateOptions {
47
48
  name?: string;
49
+ description?: string;
48
50
  }
49
51
 
50
52
  interface LayoutUpdateOptions {
@@ -979,6 +981,7 @@ export function registerDisplaysCommands(
979
981
  .command("create")
980
982
  .description("Create a display")
981
983
  .requiredOption("--name <name>", "display name")
984
+ .option("--description <text>", "display description")
982
985
  .requiredOption("--template <template>", "layout template")
983
986
  .option("--freeform-layouts <json>", "freeform layouts JSON")
984
987
  .option("--freeform-layouts-file <path>", "freeform layouts JSON file")
@@ -993,7 +996,11 @@ export function registerDisplaysCommands(
993
996
  "--freeform-layouts-file",
994
997
  );
995
998
  if (freeformLayouts !== undefined) layout.freeformLayouts = freeformLayouts;
996
- const data = await api.createDisplay({ name: commandOptions.name, layout });
999
+ const data = await api.createDisplay({
1000
+ name: commandOptions.name,
1001
+ description: commandOptions.description,
1002
+ layout,
1003
+ });
997
1004
  if (json) {
998
1005
  emitJson(stdout, "displays create", data);
999
1006
  return;
@@ -1006,11 +1013,20 @@ export function registerDisplaysCommands(
1006
1013
  .description("Update a display")
1007
1014
  .argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
1008
1015
  .option("--name <name>", "display name")
1016
+ .option("--description <text>", 'display description (use "" to clear)')
1009
1017
  .action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: DisplayUpdateOptions) {
1010
1018
  const { api, json, readOnly } = createDisplaysApi(this, filePath);
1011
1019
  ensureWritable("displays update", readOnly, "write");
1012
- if (!commandOptions.name) throw new CliError("invalid_cli_usage", "Nothing to update.");
1013
- const data = await api.updateDisplay(display, { name: commandOptions.name });
1020
+ if (commandOptions.name === undefined && commandOptions.description === undefined) {
1021
+ throw new CliError("invalid_cli_usage", "Nothing to update.");
1022
+ }
1023
+ if (commandOptions.name !== undefined && commandOptions.name.trim().length === 0) {
1024
+ throw new CliError("invalid_cli_usage", "Display name cannot be empty.");
1025
+ }
1026
+ const data = await api.updateDisplay(display, {
1027
+ name: commandOptions.name,
1028
+ description: commandOptions.description,
1029
+ });
1014
1030
  if (json) {
1015
1031
  emitJson(stdout, "displays update", data);
1016
1032
  return;
@@ -381,6 +381,48 @@ describe("schemas commands", () => {
381
381
  });
382
382
  });
383
383
 
384
+ it("creates fields with a description from the dedicated flag", async () => {
385
+ const configPath = createApiConfig();
386
+ const output = createStdoutBuffer();
387
+ const fetchMock = createFetchMock();
388
+ mockSchemaLookup(fetchMock);
389
+ fetchMock.mockResolvedValueOnce(
390
+ jsonResponse({
391
+ id: "field-1",
392
+ type: "TextField",
393
+ slug: "email",
394
+ label: "Email",
395
+ description: "Primary contact email",
396
+ }),
397
+ );
398
+ const program = buildProgram({ configPath, stdout: output.stdout });
399
+
400
+ await program.parseAsync([
401
+ "node",
402
+ "zinkee",
403
+ "--json",
404
+ "schemas",
405
+ "fields",
406
+ "create",
407
+ "contacts",
408
+ "--type",
409
+ "text",
410
+ "--slug",
411
+ "email",
412
+ "--label",
413
+ "Email",
414
+ "--description",
415
+ "Primary contact email",
416
+ ]);
417
+
418
+ expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body))).toEqual({
419
+ type: "TextField",
420
+ slug: "email",
421
+ label: "Email",
422
+ description: "Primary contact email",
423
+ });
424
+ });
425
+
384
426
  it("creates fields with a default value flag", async () => {
385
427
  const configPath = createApiConfig();
386
428
  const output = createStdoutBuffer();
@@ -589,6 +631,53 @@ describe("schemas commands", () => {
589
631
  });
590
632
  });
591
633
 
634
+ it.each([
635
+ ["sets", "Shown in customer notifications"],
636
+ ["clears", ""],
637
+ ])("%s a field description without another metadata change", async (_action, description) => {
638
+ const configPath = createApiConfig();
639
+ const output = createStdoutBuffer();
640
+ const fetchMock = createFetchMock();
641
+ mockSchemaLookup(fetchMock);
642
+ fetchMock.mockResolvedValueOnce(
643
+ jsonResponse({
644
+ id: "550e8400-e29b-41d4-a716-446655440003",
645
+ type: "TextField",
646
+ slug: "notes",
647
+ label: "Notes",
648
+ required: false,
649
+ }),
650
+ );
651
+ fetchMock.mockResolvedValueOnce(
652
+ jsonResponse({
653
+ id: "550e8400-e29b-41d4-a716-446655440003",
654
+ type: "TextField",
655
+ slug: "notes",
656
+ label: "Notes",
657
+ description,
658
+ }),
659
+ );
660
+ const program = buildProgram({ configPath, stdout: output.stdout });
661
+
662
+ await program.parseAsync([
663
+ "node",
664
+ "zinkee",
665
+ "--json",
666
+ "schemas",
667
+ "fields",
668
+ "update",
669
+ "contacts",
670
+ "550E8400-E29B-41D4-A716-446655440003",
671
+ "--description",
672
+ description,
673
+ ]);
674
+
675
+ expect(JSON.parse(String(fetchMock.mock.calls[2]?.[1]?.body))).toEqual({
676
+ type: "TextField",
677
+ description,
678
+ });
679
+ });
680
+
592
681
  it("strips formula status from update payloads", async () => {
593
682
  const configPath = createApiConfig();
594
683
  const output = createStdoutBuffer();
@@ -78,6 +78,7 @@ interface FieldCreateCommandOptions {
78
78
  type?: string;
79
79
  slug?: string;
80
80
  label?: string;
81
+ description?: string;
81
82
  required?: boolean;
82
83
  default?: string;
83
84
  raw?: string;
@@ -86,6 +87,7 @@ interface FieldCreateCommandOptions {
86
87
 
87
88
  interface FieldUpdateCommandOptions {
88
89
  label?: string;
90
+ description?: string;
89
91
  required?: boolean;
90
92
  default?: string;
91
93
  raw?: string;
@@ -386,6 +388,10 @@ function buildFieldCreateInput(
386
388
  input.required = options.required;
387
389
  }
388
390
 
391
+ if (options.description !== undefined) {
392
+ input.description = options.description;
393
+ }
394
+
389
395
  applyFieldDefaultValue(input, options.default);
390
396
 
391
397
  if ("config" in input && isJsonObject(input.config) && isEmptyObject(input.config)) {
@@ -462,6 +468,10 @@ function buildFieldUpdateInput(
462
468
  input.required = options.required;
463
469
  }
464
470
 
471
+ if (options.description !== undefined) {
472
+ input.description = options.description;
473
+ }
474
+
465
475
  applyFieldDefaultValue(input, options.default);
466
476
 
467
477
  return input;
@@ -769,6 +779,7 @@ export function registerSchemasCommands(
769
779
  .option("--type <type>", `field type (${formatSchemaFieldTypes()})`)
770
780
  .option("--slug <slug>", "field slug")
771
781
  .option("--label <label>", "field label")
782
+ .option("--description <text>", "field description")
772
783
  .option("--required <bool>", "required flag", parseBooleanFlag)
773
784
  .option("--default <value>", "default field value")
774
785
  .option("--raw <json>", 'raw JSON object payload (see type-specific shapes in --help)')
@@ -803,6 +814,7 @@ export function registerSchemasCommands(
803
814
  .argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector)
804
815
  .argument("<field>", "field uuid or slug", parseUuidOrSlugSelector)
805
816
  .option("--label <label>", "field label")
817
+ .option("--description <text>", 'field description (use "" to clear)')
806
818
  .option("--required <bool>", "required flag", parseBooleanFlag)
807
819
  .option("--default <value>", "default field value")
808
820
  .option("--raw <json>", "raw JSON object payload")
@@ -832,7 +844,7 @@ export function registerSchemasCommands(
832
844
  if (isEmptyObject(input)) {
833
845
  throw new CliError(
834
846
  "invalid_cli_usage",
835
- 'Provide at least one field update via "--label", "--required", "--raw", or "--raw-file".',
847
+ 'Provide at least one field update via "--label", "--description", "--required", "--default", "--raw", or "--raw-file".',
836
848
  );
837
849
  }
838
850
 
@@ -404,6 +404,22 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
404
404
  },
405
405
  ]),
406
406
  "schemas fields update": buildCommandExamples([
407
+ {
408
+ description: "Set the field description without changing its label or configuration.",
409
+ command:
410
+ 'zinkee --json schemas fields update contacts contact-name --description "Primary name shown for the customer"',
411
+ raw: `{
412
+ "description": "Primary name shown for the customer"
413
+ }`,
414
+ },
415
+ {
416
+ description: "Clear the field description.",
417
+ command:
418
+ 'zinkee --json schemas fields update contacts contact-name --description ""',
419
+ raw: `{
420
+ "description": ""
421
+ }`,
422
+ },
407
423
  {
408
424
  description: "Rename slug and label.",
409
425
  command:
@@ -1229,11 +1245,12 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
1229
1245
  "displays create": buildCommandExamples([
1230
1246
  {
1231
1247
  description:
1232
- "Create a freeform display with an initial widget layout. Freeform layouts travel through --freeform-layouts.",
1248
+ "Create a described freeform display with an initial widget layout. Freeform layouts travel through --freeform-layouts.",
1233
1249
  command:
1234
- `zinkee --json displays create --name "Control Proyecto Freeform" --template freeform --freeform-layouts '{"lg":[{"i":"widget-1","x":0,"y":0,"w":6,"h":5}]}'`,
1250
+ `zinkee --json displays create --name "Control Proyecto Freeform" --description "Project delivery overview" --template freeform --freeform-layouts '{"lg":[{"i":"widget-1","x":0,"y":0,"w":6,"h":5}]}'`,
1235
1251
  raw: `{
1236
1252
  "name": "Control Proyecto Freeform",
1253
+ "description": "Project delivery overview",
1237
1254
  "layout": {
1238
1255
  "template": "freeform",
1239
1256
  "freeformLayouts": {
@@ -3135,6 +3152,22 @@ const specificExamples: Record<string, readonly CommandExample[]> = {
3135
3152
  },
3136
3153
  ]),
3137
3154
  "displays update": buildCommandExamples([
3155
+ {
3156
+ description: "Update only the display description.",
3157
+ command:
3158
+ 'zinkee --json displays update 0f6f062f-1478-4df2-96a1-c495d037d260 --description "Sales pipeline overview"',
3159
+ raw: `{
3160
+ "description": "Sales pipeline overview"
3161
+ }`,
3162
+ },
3163
+ {
3164
+ description: "Clear the display description.",
3165
+ command:
3166
+ 'zinkee --json displays update 0f6f062f-1478-4df2-96a1-c495d037d260 --description ""',
3167
+ raw: `{
3168
+ "description": ""
3169
+ }`,
3170
+ },
3138
3171
  {
3139
3172
  description: "Rename a display (patch of top-level metadata only).",
3140
3173
  command:
@@ -4517,8 +4550,26 @@ function buildSchemaFieldCreateExamples(type?: string): readonly CommandExample[
4517
4550
  definition.examplePayloads.map((example) => ({ definition, example })),
4518
4551
  );
4519
4552
 
4520
- return buildCommandExamples(
4521
- flat.map(({ example }, index) => {
4553
+ const flagExamples: CommandExample[] =
4554
+ selectedDefinition === undefined || selectedDefinition.key === "text"
4555
+ ? [
4556
+ {
4557
+ description: "Text field with a description.",
4558
+ command:
4559
+ 'zinkee --json schemas fields create contacts --type text --slug customer-name --label "Customer Name" --description "Primary name shown for the customer"',
4560
+ raw: `{
4561
+ "type": "TextField",
4562
+ "slug": "customer-name",
4563
+ "label": "Customer Name",
4564
+ "description": "Primary name shown for the customer"
4565
+ }`,
4566
+ },
4567
+ ]
4568
+ : [];
4569
+
4570
+ return buildCommandExamples([
4571
+ ...flagExamples,
4572
+ ...flat.map(({ example }, index) => {
4522
4573
  const slug =
4523
4574
  typeof example.payload.slug === "string" ? example.payload.slug : "field";
4524
4575
  const command = `zinkee --json schemas fields create contacts --raw '${JSON.stringify(example.payload)}'`;
@@ -4540,7 +4591,7 @@ function buildSchemaFieldCreateExamples(type?: string): readonly CommandExample[
4540
4591
  command,
4541
4592
  };
4542
4593
  }),
4543
- );
4594
+ ]);
4544
4595
  }
4545
4596
 
4546
4597
  export function getCommandExamples(