zinkee 0.1.2 → 0.1.7
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 +14 -0
- package/AGENTS.md +1 -0
- package/README.md +16 -0
- package/dist/index.js +3421 -455
- package/dist/index.js.map +1 -1
- package/docs/cli-contract.md +41 -0
- package/docs/npm-release.md +95 -0
- package/docs/superpowers/plans/2026-03-24-zinkee-cli-implementation.md +837 -0
- package/docs/superpowers/plans/2026-03-25-cli-backend-error-contract.md +503 -0
- package/docs/superpowers/plans/2026-03-26-display-freeform-create.md +389 -0
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-backend-blockers.md +172 -0
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-design.md +1576 -0
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-checklist.md +215 -0
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-design.md +492 -0
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-status.md +307 -0
- package/package.json +1 -1
- package/src/api/logs.ts +34 -0
- package/src/cli-examples.ts +7 -0
- package/src/command-registry.ts +7 -0
- package/src/commands/logs.test.ts +123 -0
- package/src/commands/logs.ts +159 -0
- package/src/config.test.ts +45 -0
- package/src/config.ts +1 -0
- package/src/index.test.ts +1 -1
- package/src/parsers/expressions.test.ts +75 -30
- package/src/parsers/expressions.ts +21 -4
- package/src/program.ts +2 -0
- package/src/runtime-context.ts +2 -1
- package/src/utils/examples.test.ts +150 -0
- package/src/utils/examples.ts +3121 -138
package/src/config.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
DEFAULT_PRODUCTION_BASE_URL,
|
|
4
5
|
ENV_API_KEY,
|
|
5
6
|
ENV_BASE_URL,
|
|
6
7
|
ENV_PROFILE,
|
|
@@ -176,6 +177,50 @@ describe("resolveRuntimeContext", () => {
|
|
|
176
177
|
}
|
|
177
178
|
});
|
|
178
179
|
|
|
180
|
+
it("falls back to the production base URL when the resolved profile has no base URL", () => {
|
|
181
|
+
expect(
|
|
182
|
+
resolveRuntimeContext({
|
|
183
|
+
defaultProfile: "prod",
|
|
184
|
+
profiles: {
|
|
185
|
+
prod: { apiKey: "prod-token" },
|
|
186
|
+
},
|
|
187
|
+
}, {}),
|
|
188
|
+
).toEqual({
|
|
189
|
+
profile: "prod",
|
|
190
|
+
baseUrl: DEFAULT_PRODUCTION_BASE_URL,
|
|
191
|
+
apiKey: "prod-token",
|
|
192
|
+
readOnly: false,
|
|
193
|
+
json: false,
|
|
194
|
+
profileSource: "config",
|
|
195
|
+
baseUrlSource: "config",
|
|
196
|
+
apiKeySource: "config",
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("still prefers an explicit base URL override over the production fallback", () => {
|
|
201
|
+
expect(
|
|
202
|
+
resolveRuntimeContext({
|
|
203
|
+
defaultProfile: "prod",
|
|
204
|
+
profiles: {
|
|
205
|
+
prod: { apiKey: "prod-token" },
|
|
206
|
+
},
|
|
207
|
+
}, {
|
|
208
|
+
profile: "prod",
|
|
209
|
+
baseUrl: "http://localhost:8088",
|
|
210
|
+
apiKey: "override-token",
|
|
211
|
+
}),
|
|
212
|
+
).toEqual({
|
|
213
|
+
profile: "prod",
|
|
214
|
+
baseUrl: "http://localhost:8088",
|
|
215
|
+
apiKey: "override-token",
|
|
216
|
+
readOnly: false,
|
|
217
|
+
json: false,
|
|
218
|
+
profileSource: "flag",
|
|
219
|
+
baseUrlSource: "flag",
|
|
220
|
+
apiKeySource: "flag",
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
179
224
|
it("throws a clear error when runtime api key is provided without base url", () => {
|
|
180
225
|
const previousApiKey = process.env[ENV_API_KEY];
|
|
181
226
|
const previousBaseUrl = process.env[ENV_BASE_URL];
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const redactedSecretMask = "********";
|
|
|
11
11
|
export const ENV_API_KEY = "ZINKEE_API_KEY";
|
|
12
12
|
export const ENV_BASE_URL = "ZINKEE_API_BASE_URL";
|
|
13
13
|
export const ENV_PROFILE = "ZINKEE_PROFILE";
|
|
14
|
+
export const DEFAULT_PRODUCTION_BASE_URL = "https://api-z2.zinkee.com";
|
|
14
15
|
|
|
15
16
|
export class ProfileResolutionError extends Error {
|
|
16
17
|
override name = "ProfileResolutionError";
|
package/src/index.test.ts
CHANGED
|
@@ -120,7 +120,7 @@ describe("project bootstrap", () => {
|
|
|
120
120
|
},
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
expect(output).toContain("Create a scheduled automation
|
|
123
|
+
expect(output).toContain("Create a scheduled automation");
|
|
124
124
|
expect(output).toContain("Equivalent JSON payload:");
|
|
125
125
|
expect(output).toContain('"type": "scheduled"');
|
|
126
126
|
expect(output).toContain('"cronExpression": "0 15 10 * * 1"');
|
|
@@ -3,48 +3,93 @@ import { describe, expect, it } from "vitest";
|
|
|
3
3
|
import { parseExpression } from "./expressions.js";
|
|
4
4
|
|
|
5
5
|
describe("parseExpression", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
describe("scalar comparators", () => {
|
|
7
|
+
it.each([
|
|
8
|
+
["eq", "status eq active", "status", "active"],
|
|
9
|
+
["neq", "status neq archived", "status", "archived"],
|
|
10
|
+
["gt", "amount gt 1000", "amount", "1000"],
|
|
11
|
+
["gte", "amount gte 500", "amount", "500"],
|
|
12
|
+
["lt", "amount lt 100", "amount", "100"],
|
|
13
|
+
["lte", "due-date lte 2026-12-31T23:59:59Z", "due-date", "2026-12-31T23:59:59Z"],
|
|
14
|
+
["contains", "contact-name contains Lovelace", "contact-name", "Lovelace"],
|
|
15
|
+
])("parses %s expressions", (comparator, input, field, value) => {
|
|
16
|
+
expect(parseExpression(input)).toEqual({
|
|
17
|
+
field,
|
|
18
|
+
comparator,
|
|
19
|
+
value,
|
|
20
|
+
raw: input,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("preserves spaces inside contains values", () => {
|
|
25
|
+
expect(parseExpression("email contains @acme.com")).toEqual({
|
|
26
|
+
field: "email",
|
|
27
|
+
comparator: "contains",
|
|
28
|
+
value: "@acme.com",
|
|
29
|
+
raw: "email contains @acme.com",
|
|
30
|
+
});
|
|
12
31
|
});
|
|
13
32
|
});
|
|
14
33
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
describe("unary comparators", () => {
|
|
35
|
+
it.each([
|
|
36
|
+
["is_empty", "due-date is_empty", "due-date"],
|
|
37
|
+
["is_not_empty", "contact-email is_not_empty", "contact-email"],
|
|
38
|
+
])("parses %s expressions without a value", (comparator, input, field) => {
|
|
39
|
+
expect(parseExpression(input)).toEqual({
|
|
40
|
+
field,
|
|
41
|
+
comparator,
|
|
42
|
+
value: null,
|
|
43
|
+
raw: input,
|
|
44
|
+
});
|
|
21
45
|
});
|
|
22
46
|
});
|
|
23
47
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
describe("list comparators", () => {
|
|
49
|
+
it("parses in expressions with a comma-separated value", () => {
|
|
50
|
+
expect(parseExpression("ownerId in a,b,c")).toEqual({
|
|
51
|
+
field: "ownerId",
|
|
52
|
+
comparator: "in",
|
|
53
|
+
value: ["a", "b", "c"],
|
|
54
|
+
raw: "ownerId in a,b,c",
|
|
55
|
+
});
|
|
30
56
|
});
|
|
31
57
|
});
|
|
32
58
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
describe("delimiters", () => {
|
|
60
|
+
it("parses colon-delimited expressions", () => {
|
|
61
|
+
expect(parseExpression("amount:gt:1000")).toEqual({
|
|
62
|
+
field: "amount",
|
|
63
|
+
comparator: "gt",
|
|
64
|
+
value: "1000",
|
|
65
|
+
raw: "amount:gt:1000",
|
|
66
|
+
});
|
|
39
67
|
});
|
|
40
68
|
});
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
describe("errors", () => {
|
|
71
|
+
it("rejects unknown comparators", () => {
|
|
72
|
+
expect(() => parseExpression("status ne archived")).toThrow(
|
|
73
|
+
'Unsupported comparator "ne" in expression "status ne archived".',
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("rejects scalar comparators without a value", () => {
|
|
78
|
+
expect(() => parseExpression("status eq")).toThrow(
|
|
79
|
+
'Comparator "eq" requires a value.',
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("rejects unary comparators that carry a value", () => {
|
|
84
|
+
expect(() => parseExpression("due-date is_empty 2026-01-01")).toThrow(
|
|
85
|
+
'Comparator "is_empty" does not accept a value.',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("rejects in expressions with no values", () => {
|
|
90
|
+
expect(() => parseExpression("ownerId in")).toThrow(
|
|
91
|
+
'Comparator "in" requires at least one value.',
|
|
92
|
+
);
|
|
48
93
|
});
|
|
49
94
|
});
|
|
50
95
|
});
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
export type ExpressionComparator =
|
|
1
|
+
export type ExpressionComparator =
|
|
2
|
+
| "eq"
|
|
3
|
+
| "neq"
|
|
4
|
+
| "gt"
|
|
5
|
+
| "gte"
|
|
6
|
+
| "lt"
|
|
7
|
+
| "lte"
|
|
8
|
+
| "contains"
|
|
9
|
+
| "in"
|
|
10
|
+
| "is_empty"
|
|
11
|
+
| "is_not_empty";
|
|
2
12
|
|
|
3
13
|
export type ExpressionValue = string | readonly string[] | null;
|
|
4
14
|
|
|
@@ -11,12 +21,19 @@ export interface ParsedExpression {
|
|
|
11
21
|
|
|
12
22
|
const supportedComparators = new Set<ExpressionComparator>([
|
|
13
23
|
"eq",
|
|
14
|
-
"
|
|
24
|
+
"neq",
|
|
15
25
|
"gt",
|
|
16
|
-
"
|
|
26
|
+
"gte",
|
|
27
|
+
"lt",
|
|
28
|
+
"lte",
|
|
29
|
+
"contains",
|
|
17
30
|
"in",
|
|
31
|
+
"is_empty",
|
|
32
|
+
"is_not_empty",
|
|
18
33
|
]);
|
|
19
34
|
|
|
35
|
+
const noValueComparators = new Set<ExpressionComparator>(["is_empty", "is_not_empty"]);
|
|
36
|
+
|
|
20
37
|
function parseColonExpression(input: string): [string, string, string] {
|
|
21
38
|
const parts = input.split(":");
|
|
22
39
|
|
|
@@ -60,7 +77,7 @@ function parseSpaceExpression(input: string): [string, string, string] {
|
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
function normalizeValue(comparator: ExpressionComparator, rawValue: string): ExpressionValue {
|
|
63
|
-
if (comparator
|
|
80
|
+
if (noValueComparators.has(comparator)) {
|
|
64
81
|
if (rawValue.length > 0) {
|
|
65
82
|
throw new Error(`Comparator "${comparator}" does not accept a value.`);
|
|
66
83
|
}
|
package/src/program.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { registerAutomationsCommands } from "./commands/automations.js";
|
|
|
7
7
|
import { registerCommentsCommands } from "./commands/comments.js";
|
|
8
8
|
import { registerDisplaysCommands } from "./commands/displays.js";
|
|
9
9
|
import { registerFilesCommands } from "./commands/files.js";
|
|
10
|
+
import { registerLogsCommands } from "./commands/logs.js";
|
|
10
11
|
import { registerNavigationCommands } from "./commands/navigation.js";
|
|
11
12
|
import { registerProfilesCommands } from "./commands/profiles.js";
|
|
12
13
|
import { registerRecordsCommands } from "./commands/records.js";
|
|
@@ -50,6 +51,7 @@ export function buildProgram(options: BuildProgramOptions = {}): Command {
|
|
|
50
51
|
registerSchemasCommands(program, options);
|
|
51
52
|
registerRecordsCommands(program, options);
|
|
52
53
|
registerCommentsCommands(program, options);
|
|
54
|
+
registerLogsCommands(program, options);
|
|
53
55
|
registerTeamspaceCommands(program, options);
|
|
54
56
|
|
|
55
57
|
return program;
|
package/src/runtime-context.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppConfig, RuntimeContext, RuntimeOverrides } from "./types.js";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
DEFAULT_PRODUCTION_BASE_URL,
|
|
4
5
|
ENV_API_KEY,
|
|
5
6
|
ENV_BASE_URL,
|
|
6
7
|
readEnvApiKey,
|
|
@@ -70,7 +71,7 @@ export function resolveRuntimeContext(
|
|
|
70
71
|
|
|
71
72
|
return {
|
|
72
73
|
profile: profile.name,
|
|
73
|
-
baseUrl: baseUrlOverride ?? profile.baseUrl,
|
|
74
|
+
baseUrl: baseUrlOverride ?? profile.baseUrl ?? DEFAULT_PRODUCTION_BASE_URL,
|
|
74
75
|
apiKey: apiKeyOverride ?? profile.apiKey,
|
|
75
76
|
readOnly: overrides.readOnly ?? false,
|
|
76
77
|
json: overrides.json ?? false,
|
|
@@ -64,4 +64,154 @@ describe("getCommandExamples", () => {
|
|
|
64
64
|
expect(actionExamples.some((example) => example.raw?.includes('"rawValue"'))).toBe(true);
|
|
65
65
|
expect(webhookExamples[0]?.raw).toContain("idempotencyKeyJsonPath");
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
it("covers every records query operator documented by the backend", () => {
|
|
69
|
+
const examples = getCommandExamples("records query");
|
|
70
|
+
const backendOperators = [
|
|
71
|
+
"eq",
|
|
72
|
+
"neq",
|
|
73
|
+
"gt",
|
|
74
|
+
"gte",
|
|
75
|
+
"lt",
|
|
76
|
+
"lte",
|
|
77
|
+
"contains",
|
|
78
|
+
"in",
|
|
79
|
+
"is_empty",
|
|
80
|
+
"is_not_empty",
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
for (const op of backendOperators) {
|
|
84
|
+
expect(
|
|
85
|
+
examples.some((example) => example.raw?.includes(`"op": "${op}"`)),
|
|
86
|
+
`expected an example with op "${op}"`,
|
|
87
|
+
).toBe(true);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("offers both a minimal and a multi-type records create example", () => {
|
|
92
|
+
const examples = getCommandExamples("records create");
|
|
93
|
+
|
|
94
|
+
expect(examples).toHaveLength(2);
|
|
95
|
+
expect(examples[0]?.raw).toContain('"contact-name"');
|
|
96
|
+
expect(examples[1]?.raw).toContain('"customer-ref"');
|
|
97
|
+
expect(examples[1]?.raw).toContain('"owner"');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("provides full/minimal/folder variants for schemas create", () => {
|
|
101
|
+
const examples = getCommandExamples("schemas create");
|
|
102
|
+
|
|
103
|
+
expect(examples).toHaveLength(3);
|
|
104
|
+
expect(examples.some((example) => example.raw?.includes('"folderId"'))).toBe(true);
|
|
105
|
+
expect(examples.every((example) => example.command.startsWith("zinkee --json schemas create"))).toBe(
|
|
106
|
+
true,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("covers all widget types in displays widgets create", () => {
|
|
111
|
+
const examples = getCommandExamples("displays widgets create");
|
|
112
|
+
const rawPayloads = examples.map((example) => example.raw ?? "");
|
|
113
|
+
|
|
114
|
+
expect(examples.length).toBeGreaterThanOrEqual(7);
|
|
115
|
+
for (const widgetType of ["TABLE_OR_SUBTABLE", "DETAIL", "KPI", "CHART", "TIMELINE", "KANBAN", "HIERARCHY"]) {
|
|
116
|
+
expect(rawPayloads.some((raw) => raw.includes(`"type": "${widgetType}"`))).toBe(true);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("covers REFERENCE, MEMBER and DATE variants in displays variables create", () => {
|
|
121
|
+
const examples = getCommandExamples("displays variables create");
|
|
122
|
+
const rawPayloads = examples.map((example) => example.raw ?? "");
|
|
123
|
+
|
|
124
|
+
expect(examples.length).toBeGreaterThanOrEqual(6);
|
|
125
|
+
for (const variableType of ["REFERENCE", "MEMBER", "DATE"]) {
|
|
126
|
+
expect(rawPayloads.some((raw) => raw.includes(`"type": "${variableType}"`))).toBe(true);
|
|
127
|
+
}
|
|
128
|
+
expect(rawPayloads.some((raw) => raw.includes('"dynamicOption": "thisMonth"'))).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("covers SCHEMA, WORKFLOW and DISPLAY variants in navigation resources move", () => {
|
|
132
|
+
const examples = getCommandExamples("navigation resources move");
|
|
133
|
+
const rawPayloads = examples.map((example) => example.raw ?? "");
|
|
134
|
+
|
|
135
|
+
expect(examples.length).toBeGreaterThanOrEqual(5);
|
|
136
|
+
for (const resourceType of ["SCHEMA", "WORKFLOW", "DISPLAY", "SCHEDULE"]) {
|
|
137
|
+
expect(rawPayloads.some((raw) => raw.includes(`"resourceType": "${resourceType}"`))).toBe(true);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("covers slug and UUID variants in teamspace resources publish", () => {
|
|
142
|
+
const examples = getCommandExamples("teamspace resources publish");
|
|
143
|
+
|
|
144
|
+
expect(examples.length).toBeGreaterThanOrEqual(4);
|
|
145
|
+
expect(examples.some((example) => example.command.includes("customer-records"))).toBe(true);
|
|
146
|
+
expect(examples.some((example) => example.command.includes("2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e"))).toBe(
|
|
147
|
+
true,
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("enriches displays list with the documented response body", () => {
|
|
152
|
+
const examples = getCommandExamples("displays list");
|
|
153
|
+
|
|
154
|
+
expect(examples).toHaveLength(1);
|
|
155
|
+
expect(examples[0]?.response).toContain('"layoutTemplate": "widget_row_tabs"');
|
|
156
|
+
expect(examples[0]?.response).toContain('"layoutTemplate": "freeform"');
|
|
157
|
+
expect(examples[0]?.response).toContain('"layoutTemplate": "grid"');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("attaches a response body to schemas create and teamspace resources publish", () => {
|
|
161
|
+
const schemaExamples = getCommandExamples("schemas create");
|
|
162
|
+
const publishExamples = getCommandExamples("teamspace resources publish");
|
|
163
|
+
|
|
164
|
+
expect(schemaExamples[0]?.response).toContain('"schemaId"');
|
|
165
|
+
expect(publishExamples[0]?.response).toContain('"targetFolder"');
|
|
166
|
+
expect(publishExamples[0]?.response).toContain('"previousFolder"');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("attaches a rich response body to displays get", () => {
|
|
170
|
+
const examples = getCommandExamples("displays get");
|
|
171
|
+
|
|
172
|
+
expect(examples).toHaveLength(1);
|
|
173
|
+
expect(examples[0]?.response).toContain('"widgets"');
|
|
174
|
+
expect(examples[0]?.response).toContain('"variables"');
|
|
175
|
+
expect(examples[0]?.response).toContain('"crossWidgetFilter"');
|
|
176
|
+
expect(examples[0]?.response).toContain('"navigationTargets"');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("attaches responses to automations reads and mutations", () => {
|
|
180
|
+
expect(getCommandExamples("automations list")[0]?.response).toContain('"triggerType"');
|
|
181
|
+
expect(getCommandExamples("automations get")[0]?.response).toContain('"trigger"');
|
|
182
|
+
expect(getCommandExamples("automations create")[0]?.response).toContain('"automationId"');
|
|
183
|
+
expect(getCommandExamples("automations create")[0]?.response).toContain('"webhookEndpoint"');
|
|
184
|
+
expect(getCommandExamples("automations webhook set")[0]?.response).toContain('"endpointUrl"');
|
|
185
|
+
expect(getCommandExamples("automations folders create")[0]?.response).toContain('"automationCount": 0');
|
|
186
|
+
expect(getCommandExamples("automations connections create")[0]?.response).toContain(
|
|
187
|
+
'"ownerMemberId"',
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("covers every profiles and config subcommand with an example that emits JSON", () => {
|
|
192
|
+
const profileAndConfigCommands = [
|
|
193
|
+
"profiles list",
|
|
194
|
+
"profiles get",
|
|
195
|
+
"profiles use",
|
|
196
|
+
"profiles add",
|
|
197
|
+
"profiles update",
|
|
198
|
+
"profiles remove",
|
|
199
|
+
"config path",
|
|
200
|
+
"config show",
|
|
201
|
+
"config validate",
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
for (const commandName of profileAndConfigCommands) {
|
|
205
|
+
const examples = getCommandExamples(commandName);
|
|
206
|
+
expect(examples.length, `${commandName} has no examples`).toBeGreaterThan(0);
|
|
207
|
+
for (const example of examples) {
|
|
208
|
+
expect(example.command.startsWith("zinkee "), `${commandName}: ${example.command}`).toBe(true);
|
|
209
|
+
expect(example.response, `${commandName} missing response`).toBeTruthy();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
expect(getCommandExamples("profiles list")[0]?.response).toContain('"apiKey": "********cdef"');
|
|
214
|
+
expect(getCommandExamples("config show")[0]?.response).toContain('"defaultProfile": "prod"');
|
|
215
|
+
expect(getCommandExamples("config validate")[0]?.response).toContain('"valid": true');
|
|
216
|
+
});
|
|
67
217
|
});
|