zinkee 0.1.12 → 0.1.17

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.12",
3
+ "version": "0.1.17",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "type": "module",
6
6
  "bin": {
@@ -133,6 +133,13 @@ export class RecordsApi {
133
133
  );
134
134
  }
135
135
 
136
+ clearRecords(schema: NormalizedUuidOrSlug): Promise<Record<string, unknown>> {
137
+ return this.client.requestJson<Record<string, unknown>>(
138
+ `/api/v2/schemas/${selectorValue(schema)}/records:clear`,
139
+ { method: "DELETE" },
140
+ );
141
+ }
142
+
136
143
  private normalizeQueryInput(query: RecordQueryInput): Record<string, unknown> {
137
144
  const where = query.where?.map((expression) => ({
138
145
  field: expression.field,
@@ -18,11 +18,6 @@ export interface ExampleRequest {
18
18
  filters: {
19
19
  type?: string;
20
20
  };
21
- overrides: {
22
- profile?: string;
23
- baseUrl?: string;
24
- apiKey?: string;
25
- };
26
21
  json: boolean;
27
22
  }
28
23
 
@@ -48,46 +43,6 @@ function extractExampleFilters(tokens: readonly string[]): { type?: string } {
48
43
  return filters;
49
44
  }
50
45
 
51
- function extractExampleOverrides(tokens: readonly string[]): {
52
- profile?: string;
53
- baseUrl?: string;
54
- apiKey?: string;
55
- } {
56
- const overrides: {
57
- profile?: string;
58
- baseUrl?: string;
59
- apiKey?: string;
60
- } = {};
61
-
62
- for (let index = 0; index < tokens.length; index += 1) {
63
- const token = tokens[index];
64
- const value = tokens[index + 1];
65
-
66
- if (!value || value.startsWith("-")) {
67
- continue;
68
- }
69
-
70
- if (token === "--profile") {
71
- overrides.profile = value;
72
- index += 1;
73
- continue;
74
- }
75
-
76
- if (token === "--base-url") {
77
- overrides.baseUrl = value;
78
- index += 1;
79
- continue;
80
- }
81
-
82
- if (token === "--api-key") {
83
- overrides.apiKey = value;
84
- index += 1;
85
- }
86
- }
87
-
88
- return overrides;
89
- }
90
-
91
46
  export function extractExampleRequest(
92
47
  argv: readonly string[],
93
48
  optionMeta: OptionMetadata = createDefaultOptionMetadata(),
@@ -109,27 +64,17 @@ export function extractExampleRequest(
109
64
  return {
110
65
  commandName: cmdPath.commandName,
111
66
  filters: extractExampleFilters(remainingTokens),
112
- overrides: extractExampleOverrides(argv),
113
67
  json: argv.includes("--json"),
114
68
  };
115
69
  }
116
70
 
117
- export async function showExamples(
71
+ export function showExamples(
118
72
  commandName: string,
119
73
  json: boolean,
120
74
  stdout?: CommandIo,
121
75
  filters: { type?: string } = {},
122
- options: {
123
- enabled?: boolean;
124
- configPath?: string;
125
- overrides?: {
126
- profile?: string;
127
- baseUrl?: string;
128
- apiKey?: string;
129
- };
130
- } = {},
131
- ): Promise<void> {
132
- const examples = await getResolvedCommandExamples(commandName, filters, options);
76
+ ): void {
77
+ const examples = getResolvedCommandExamples(commandName, filters);
133
78
 
134
79
  if (examples.length === 0) {
135
80
  throw new CliError("invalid_cli_usage", `No examples registered for "${commandName}".`);
package/src/cli-runner.ts CHANGED
@@ -65,15 +65,11 @@ export async function runCli(argv: string[]): Promise<void> {
65
65
  const exampleRequest = extractExampleRequest(argv, optionMeta);
66
66
 
67
67
  if (exampleRequest) {
68
- await showExamples(
68
+ showExamples(
69
69
  exampleRequest.commandName,
70
70
  exampleRequest.json,
71
71
  undefined,
72
72
  exampleRequest.filters,
73
- {
74
- enabled: true,
75
- overrides: exampleRequest.overrides,
76
- },
77
73
  );
78
74
  throw new ExampleDisplayed();
79
75
  }
@@ -665,6 +665,13 @@ export const commandRegistry = {
665
665
  supportsRaw: false,
666
666
  output: writeOutput,
667
667
  },
668
+ "records clear": {
669
+ command: "records clear",
670
+ accessLevel: "destructive",
671
+ supportsExample: true,
672
+ supportsRaw: false,
673
+ output: writeOutput,
674
+ },
668
675
  "comments list": {
669
676
  command: "comments list",
670
677
  accessLevel: "read",
@@ -335,7 +335,7 @@ describe("automations commands", () => {
335
335
  "--target-schema",
336
336
  "schema-1",
337
337
  "--map-json",
338
- 'owner={"rawValue":null,"source":{"type":"TRIGGER","id":"payload_value"}}',
338
+ 'owner={"rawValue":null,"source":{"type":"TRIGGER","id":"f1e1d000-0000-0000-0000-000000000005"}}',
339
339
  ]);
340
340
 
341
341
  expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
@@ -348,7 +348,7 @@ describe("automations commands", () => {
348
348
  rawValue: null,
349
349
  source: {
350
350
  type: "TRIGGER",
351
- id: "payload_value",
351
+ id: "f1e1d000-0000-0000-0000-000000000005",
352
352
  },
353
353
  },
354
354
  },
@@ -378,7 +378,7 @@ describe("automations commands", () => {
378
378
  "--target-schema",
379
379
  "schema-1",
380
380
  "--condition-json",
381
- '{"field":"status","comparator":"eq","values":[{"rawValue":null,"source":{"type":"TRIGGER","id":"payload_value"}}]}',
381
+ '{"field":"status","comparator":"eq","values":[{"rawValue":null,"source":{"type":"TRIGGER","id":"f1e1d000-0000-0000-0000-000000000005"}}]}',
382
382
  ]);
383
383
 
384
384
  expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
@@ -395,7 +395,7 @@ describe("automations commands", () => {
395
395
  rawValue: null,
396
396
  source: {
397
397
  type: "TRIGGER",
398
- id: "payload_value",
398
+ id: "f1e1d000-0000-0000-0000-000000000005",
399
399
  },
400
400
  },
401
401
  ],
@@ -471,7 +471,7 @@ describe("automations commands", () => {
471
471
  "--trigger-field",
472
472
  "field-1",
473
473
  "--condition-json",
474
- '{"field":"status","comparator":"eq","values":[{"rawValue":null,"source":{"type":"TRIGGER","id":"payload_value"}}]}',
474
+ '{"field":"status","comparator":"eq","values":[{"rawValue":null,"source":{"type":"TRIGGER","id":"f1e1d000-0000-0000-0000-000000000005"}}]}',
475
475
  ]),
476
476
  ).rejects.toThrowError('Expected trigger condition 1 value 1 to use only literal "rawValue" values.');
477
477
 
@@ -151,7 +151,7 @@ describe("displays commands", () => {
151
151
  "--name",
152
152
  "Revenue",
153
153
  "--chart-type",
154
- "bar",
154
+ "bars",
155
155
  ]);
156
156
 
157
157
  const init = fetchMock.mock.calls[0]?.[1];
@@ -159,7 +159,7 @@ describe("displays commands", () => {
159
159
  expect(JSON.parse(String(init?.body))).toMatchObject({
160
160
  type: "CHART",
161
161
  presentation: { name: "Revenue" },
162
- chart: { chartType: "bar" },
162
+ chart: { chartType: "bars" },
163
163
  });
164
164
  expect(JSON.parse(output.read())).toEqual({
165
165
  data: { displayId: "display-1", widgetId: "widget-1" },
@@ -364,4 +364,62 @@ describe("records commands", () => {
364
364
 
365
365
  expect(fetchMock).not.toHaveBeenCalled();
366
366
  });
367
+
368
+ it("clears all records in a schema", async () => {
369
+ const configPath = createApiConfig();
370
+ const output = createStdoutBuffer();
371
+ const fetchMock = createFetchMock();
372
+ mockSchemaLookup(fetchMock);
373
+ fetchMock.mockResolvedValueOnce(
374
+ jsonResponse({
375
+ schemaId: "schema-1",
376
+ cleared: true,
377
+ }),
378
+ );
379
+ const program = buildProgram({ configPath, stdout: output.stdout });
380
+
381
+ await program.parseAsync([
382
+ "node",
383
+ "zinkee",
384
+ "--json",
385
+ "records",
386
+ "clear",
387
+ "contacts",
388
+ ]);
389
+
390
+ const init = fetchMock.mock.calls[1]?.[1];
391
+
392
+ expect(fetchMock.mock.calls[1]?.[0]).toBe(
393
+ "https://api.zinkee.test/api/v2/schemas/schema-1/records:clear",
394
+ );
395
+ expect(init?.method).toBe("DELETE");
396
+ expect(init?.body).toBeUndefined();
397
+
398
+ const payload = JSON.parse(output.read());
399
+
400
+ expect(payload.data).toEqual({
401
+ schemaId: "schema-1",
402
+ cleared: true,
403
+ });
404
+ });
405
+
406
+ it("blocks records clear in read-only mode", async () => {
407
+ const configPath = createApiConfig();
408
+ const output = createStdoutBuffer();
409
+ const fetchMock = createFetchMock();
410
+ const program = buildProgram({ configPath, stdout: output.stdout });
411
+
412
+ await expect(
413
+ program.parseAsync([
414
+ "node",
415
+ "zinkee",
416
+ "--read-only",
417
+ "records",
418
+ "clear",
419
+ "contacts",
420
+ ]),
421
+ ).rejects.toThrowError('Command "records clear" is not allowed in read-only mode.');
422
+
423
+ expect(fetchMock).not.toHaveBeenCalled();
424
+ });
367
425
  });
@@ -723,4 +723,23 @@ export function registerRecordsCommands(
723
723
 
724
724
  writeLine(stdout, `Deleted record "${record.value}" from schema "${schema.value}".`);
725
725
  });
726
+
727
+ records
728
+ .command("clear")
729
+ .description("Delete all records from a schema's table")
730
+ .argument("<schema>", "schema uuid or slug", parseUuidOrSlugSelector)
731
+ .action(async function action(this: Command, schema: NormalizedUuidOrSlug) {
732
+ const { api, schemasApi, json, readOnly } = createRecordsApi(this, filePath);
733
+ ensureWritable("records clear", readOnly, "destructive");
734
+ const resolvedSchema = await resolveSchemaSelector(schemasApi, schema);
735
+
736
+ const data = await api.clearRecords(resolvedSchema);
737
+
738
+ if (json) {
739
+ emitJson(stdout, "records clear", data, filePath);
740
+ return;
741
+ }
742
+
743
+ writeLine(stdout, `Cleared all records from schema "${schema.value}".`);
744
+ });
726
745
  }
package/src/index.test.ts CHANGED
@@ -37,7 +37,6 @@ describe("project bootstrap", () => {
37
37
  ).toEqual({
38
38
  commandName: "records query",
39
39
  filters: {},
40
- overrides: {},
41
40
  json: true,
42
41
  });
43
42
  });
@@ -62,9 +61,6 @@ describe("project bootstrap", () => {
62
61
  filters: {
63
62
  type: "option",
64
63
  },
65
- overrides: {
66
- profile: "local",
67
- },
68
64
  json: false,
69
65
  });
70
66
  });
@@ -75,7 +71,6 @@ describe("project bootstrap", () => {
75
71
  ).toEqual({
76
72
  commandName: "schemas list",
77
73
  filters: {},
78
- overrides: {},
79
74
  json: false,
80
75
  });
81
76
  });
@@ -96,12 +91,11 @@ describe("project bootstrap", () => {
96
91
  ).toEqual({
97
92
  commandName: "schemas fields create",
98
93
  filters: { type: "option" },
99
- overrides: {},
100
94
  json: false,
101
95
  });
102
96
  });
103
97
 
104
- it("extracts example overrides regardless of --example position", () => {
98
+ it("extracts example requests regardless of --example position", () => {
105
99
  expect(
106
100
  extractExampleRequest([
107
101
  "node",
@@ -116,7 +110,6 @@ describe("project bootstrap", () => {
116
110
  ).toEqual({
117
111
  commandName: "schemas list",
118
112
  filters: {},
119
- overrides: { profile: "staging" },
120
113
  json: true,
121
114
  });
122
115
  });
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import { getCommandExamples } from "./examples.js";
3
+ import { commandRegistry } from "../command-registry.js";
4
+ import { getCommandExamples, getResolvedCommandExamples } from "./examples.js";
4
5
 
5
6
  describe("getCommandExamples", () => {
6
7
  it("returns registered examples for complex commands", () => {
@@ -250,4 +251,24 @@ describe("getCommandExamples", () => {
250
251
  expect(getCommandExamples("config show")[0]?.response).toContain('"defaultProfile": "prod"');
251
252
  expect(getCommandExamples("config validate")[0]?.response).toContain('"valid": true');
252
253
  });
254
+
255
+ it.each(Object.keys(commandRegistry))(
256
+ "returns at least one example for %s",
257
+ (commandName) => {
258
+ const examples = getCommandExamples(commandName);
259
+ expect(examples.length, `${commandName} has no examples`).toBeGreaterThan(0);
260
+ for (const example of examples) {
261
+ expect(example.command, `${commandName} example missing command`).toBeTruthy();
262
+ expect(example.description, `${commandName} example missing description`).toBeTruthy();
263
+ }
264
+ },
265
+ );
266
+
267
+ it("getResolvedCommandExamples is synchronous and matches getCommandExamples", () => {
268
+ for (const commandName of Object.keys(commandRegistry)) {
269
+ const resolved = getResolvedCommandExamples(commandName);
270
+ expect(resolved, `${commandName} resolved must not be a thenable`).not.toHaveProperty("then");
271
+ expect(resolved).toEqual(getCommandExamples(commandName));
272
+ }
273
+ });
253
274
  });