zinkee 0.1.15 → 0.1.18

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.15",
3
+ "version": "0.1.18",
4
4
  "description": "CLI for Zinkee API v2",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
  }
@@ -113,6 +113,67 @@ describe("ApiClient", () => {
113
113
  }
114
114
  });
115
115
 
116
+ it("surfaces the underlying network cause in the error message", async () => {
117
+ const dnsCause = Object.assign(new Error("getaddrinfo ENOTFOUND api-z2.zinkee.invalid"), {
118
+ code: "ENOTFOUND",
119
+ });
120
+ const fetchError = Object.assign(new TypeError("fetch failed"), { cause: dnsCause });
121
+ const fetchMock = vi.fn(async () => {
122
+ throw fetchError;
123
+ });
124
+
125
+ const client = new ApiClient({
126
+ baseUrl: "https://api-z2.zinkee.invalid",
127
+ fetch: fetchMock,
128
+ });
129
+
130
+ await expect(client.getJson("/api/v2/schemas")).rejects.toMatchObject({
131
+ name: "ApiClientError",
132
+ code: "network_error",
133
+ message:
134
+ "Request failed for GET https://api-z2.zinkee.invalid/api/v2/schemas: getaddrinfo ENOTFOUND api-z2.zinkee.invalid.",
135
+ cause: fetchError,
136
+ });
137
+ });
138
+
139
+ it("appends the cause code when it is not already part of the message", async () => {
140
+ const tlsCause = Object.assign(new Error("self signed certificate"), {
141
+ code: "DEPTH_ZERO_SELF_SIGNED_CERT",
142
+ });
143
+ const fetchError = Object.assign(new TypeError("fetch failed"), { cause: tlsCause });
144
+ const fetchMock = vi.fn(async () => {
145
+ throw fetchError;
146
+ });
147
+
148
+ const client = new ApiClient({
149
+ baseUrl: "https://self-signed.example.com",
150
+ fetch: fetchMock,
151
+ });
152
+
153
+ await expect(client.getJson("/api/v2/schemas")).rejects.toMatchObject({
154
+ code: "network_error",
155
+ message:
156
+ "Request failed for GET https://self-signed.example.com/api/v2/schemas: self signed certificate (DEPTH_ZERO_SELF_SIGNED_CERT).",
157
+ });
158
+ });
159
+
160
+ it("falls back to the bare message when the network cause has no useful detail", async () => {
161
+ const fetchMock = vi.fn(async () => {
162
+ throw new TypeError("fetch failed");
163
+ });
164
+
165
+ const client = new ApiClient({
166
+ baseUrl: "https://api.example.com",
167
+ fetch: fetchMock,
168
+ });
169
+
170
+ await expect(client.getJson("/api/v2/schemas")).rejects.toMatchObject({
171
+ name: "ApiClientError",
172
+ code: "network_error",
173
+ message: "Request failed for GET https://api.example.com/api/v2/schemas.",
174
+ });
175
+ });
176
+
116
177
  it("returns binary responses as ArrayBuffers", async () => {
117
178
  const fetchMock = vi.fn(async () => binaryResponse([1, 2, 3]));
118
179
 
package/src/client.ts CHANGED
@@ -110,6 +110,27 @@ function parseErrorBody(text: string, contentType: string | null): unknown {
110
110
  }
111
111
  }
112
112
 
113
+ function summarizeNetworkCause(cause: unknown): string | undefined {
114
+ const seen = new Set<unknown>();
115
+ let current: unknown = cause;
116
+
117
+ while (current && typeof current === "object" && !seen.has(current)) {
118
+ seen.add(current);
119
+
120
+ const candidate = current as { message?: unknown; code?: unknown; cause?: unknown };
121
+ const message = typeof candidate.message === "string" ? candidate.message.trim() : "";
122
+ const code = typeof candidate.code === "string" ? candidate.code : undefined;
123
+
124
+ if (message && message !== "fetch failed") {
125
+ return code && !message.includes(code) ? `${message} (${code})` : message;
126
+ }
127
+
128
+ current = candidate.cause;
129
+ }
130
+
131
+ return undefined;
132
+ }
133
+
113
134
  export class ApiClient {
114
135
  private readonly fetchImpl: typeof fetch;
115
136
 
@@ -206,7 +227,12 @@ export class ApiClient {
206
227
  });
207
228
  }
208
229
 
209
- throw new ApiClientError("network_error", `Request failed for ${method} ${url}.`, {
230
+ const causeSummary = summarizeNetworkCause(cause);
231
+ const message = causeSummary
232
+ ? `Request failed for ${method} ${url}: ${causeSummary}.`
233
+ : `Request failed for ${method} ${url}.`;
234
+
235
+ throw new ApiClientError("network_error", message, {
210
236
  url,
211
237
  method,
212
238
  cause,
@@ -79,4 +79,113 @@ base_url = "https://api.zinkee.test"
79
79
  expect(saved).toContain('base_url = "http://localhost:8088"');
80
80
  expect(saved).toContain('default_profile = "local"');
81
81
  });
82
+
83
+ it("rejects a malformed base URL when adding a profile", async () => {
84
+ const configPath = join(
85
+ mkdtempSync(join(tmpdir(), "zinkee-profiles-add-bad-url-")),
86
+ "config.toml",
87
+ );
88
+ const output = createStdoutBuffer();
89
+ const program = buildProgram({ configPath, stdout: output.stdout });
90
+
91
+ await expect(
92
+ program.parseAsync([
93
+ "node",
94
+ "zinkee",
95
+ "profiles",
96
+ "add",
97
+ "broken",
98
+ "--api-key",
99
+ "any",
100
+ "--base-url",
101
+ "api-z2.zinkee.com",
102
+ ]),
103
+ ).rejects.toMatchObject({
104
+ name: "CliError",
105
+ code: "configuration_error",
106
+ message: '"--base-url" must be an absolute http(s) URL.',
107
+ });
108
+ });
109
+
110
+ it("rejects a non-http(s) base URL when adding a profile", async () => {
111
+ const configPath = join(
112
+ mkdtempSync(join(tmpdir(), "zinkee-profiles-add-ftp-")),
113
+ "config.toml",
114
+ );
115
+ const output = createStdoutBuffer();
116
+ const program = buildProgram({ configPath, stdout: output.stdout });
117
+
118
+ await expect(
119
+ program.parseAsync([
120
+ "node",
121
+ "zinkee",
122
+ "profiles",
123
+ "add",
124
+ "broken",
125
+ "--api-key",
126
+ "any",
127
+ "--base-url",
128
+ "ftp://api-z2.zinkee.com",
129
+ ]),
130
+ ).rejects.toMatchObject({
131
+ name: "CliError",
132
+ code: "configuration_error",
133
+ message: '"--base-url" must be an absolute http(s) URL.',
134
+ });
135
+ });
136
+
137
+ it("normalizes trailing slashes when adding a profile", async () => {
138
+ const configPath = join(
139
+ mkdtempSync(join(tmpdir(), "zinkee-profiles-add-slash-")),
140
+ "config.toml",
141
+ );
142
+ const output = createStdoutBuffer();
143
+ const program = buildProgram({ configPath, stdout: output.stdout });
144
+
145
+ await program.parseAsync([
146
+ "node",
147
+ "zinkee",
148
+ "profiles",
149
+ "add",
150
+ "local",
151
+ "--api-key",
152
+ "any",
153
+ "--base-url",
154
+ "https://api-z2.zinkee.com///",
155
+ ]);
156
+
157
+ const saved = readFileSync(configPath, "utf8");
158
+
159
+ expect(saved).toContain('base_url = "https://api-z2.zinkee.com"');
160
+ });
161
+
162
+ it("rejects a malformed base URL when updating a profile", async () => {
163
+ const configPath = createTempConfig(`
164
+ [profiles.prod]
165
+ api_key = "secret"
166
+ base_url = "https://api-z2.zinkee.com"
167
+ `);
168
+ const output = createStdoutBuffer();
169
+ const program = buildProgram({ configPath, stdout: output.stdout });
170
+
171
+ await expect(
172
+ program.parseAsync([
173
+ "node",
174
+ "zinkee",
175
+ "profiles",
176
+ "update",
177
+ "prod",
178
+ "--base-url",
179
+ "not a url",
180
+ ]),
181
+ ).rejects.toMatchObject({
182
+ name: "CliError",
183
+ code: "configuration_error",
184
+ message: '"--base-url" must be an absolute http(s) URL.',
185
+ });
186
+
187
+ const saved = readFileSync(configPath, "utf8");
188
+
189
+ expect(saved).toContain('base_url = "https://api-z2.zinkee.com"');
190
+ });
82
191
  });
@@ -4,11 +4,13 @@ import { Command } from "commander";
4
4
 
5
5
  import {
6
6
  defaultConfigPath,
7
+ ProfileResolutionError,
7
8
  readConfigIfExists,
8
9
  redactProfile,
9
10
  removeProfileConfig,
10
11
  setDefaultProfile,
11
12
  setProfileConfig,
13
+ validateBaseUrl,
12
14
  writeConfig,
13
15
  } from "../config.js";
14
16
  import type { AppConfig, ProfileConfig } from "../types.js";
@@ -93,6 +95,22 @@ function loadConfigForAdd(filePath: string): AppConfig {
93
95
  return readConfigIfExists(filePath) ?? { profiles: {} };
94
96
  }
95
97
 
98
+ function normalizeProfileBaseUrl(baseUrl: string | undefined): string | undefined {
99
+ if (baseUrl === undefined) {
100
+ return undefined;
101
+ }
102
+
103
+ try {
104
+ return validateBaseUrl(baseUrl, '"--base-url"');
105
+ } catch (error) {
106
+ if (error instanceof ProfileResolutionError) {
107
+ throw new CliError("configuration_error", error.message);
108
+ }
109
+
110
+ throw error;
111
+ }
112
+ }
113
+
96
114
  function emitProfilesTable(stdout: CommandIo, data: readonly DisplayProfile[]): void {
97
115
  printTable(
98
116
  data.map((profile) => ({
@@ -191,9 +209,11 @@ export function registerProfilesCommands(
191
209
  );
192
210
  }
193
211
 
212
+ const baseUrl = normalizeProfileBaseUrl(commandOptions.baseUrl);
213
+
194
214
  let nextConfig = setProfileConfig(config, profileName, {
195
215
  apiKey: commandOptions.apiKey,
196
- baseUrl: commandOptions.baseUrl,
216
+ baseUrl,
197
217
  });
198
218
 
199
219
  if (!nextConfig.defaultProfile) {
@@ -236,9 +256,11 @@ export function registerProfilesCommands(
236
256
  const config = readRequiredConfig(filePath);
237
257
  getRequiredProfile(config, profileName);
238
258
 
259
+ const baseUrl = normalizeProfileBaseUrl(commandOptions.baseUrl);
260
+
239
261
  const nextConfig = setProfileConfig(config, profileName, {
240
262
  apiKey: commandOptions.apiKey,
241
- baseUrl: commandOptions.baseUrl,
263
+ baseUrl,
242
264
  });
243
265
 
244
266
  writeConfig(nextConfig, filePath);
@@ -256,6 +256,28 @@ describe("resolveRuntimeContext", () => {
256
256
  else process.env[ENV_BASE_URL] = previousBaseUrl;
257
257
  }
258
258
  });
259
+
260
+ it("rejects a --base-url override that is not an absolute http(s) URL", () => {
261
+ expect(() =>
262
+ resolveRuntimeContext({ profiles: {} }, { baseUrl: "api-z2.zinkee.com", apiKey: "k" }),
263
+ ).toThrowError('"--base-url" must be an absolute http(s) URL.');
264
+
265
+ expect(() =>
266
+ resolveRuntimeContext(
267
+ { profiles: {} },
268
+ { baseUrl: "ftp://api-z2.zinkee.com", apiKey: "k" },
269
+ ),
270
+ ).toThrowError('"--base-url" must be an absolute http(s) URL.');
271
+ });
272
+
273
+ it("normalizes trailing slashes on a --base-url override", () => {
274
+ expect(
275
+ resolveRuntimeContext(
276
+ { profiles: {} },
277
+ { baseUrl: "https://api-z2.zinkee.com///", apiKey: "k" },
278
+ ).baseUrl,
279
+ ).toBe("https://api-z2.zinkee.com");
280
+ });
259
281
  });
260
282
 
261
283
  describe("env readers", () => {
package/src/config.ts CHANGED
@@ -260,21 +260,17 @@ export function resolveProfile(
260
260
  throw new ProfileResolutionError("No profile could be resolved from config.");
261
261
  }
262
262
 
263
- export function validateBaseUrl(value: string): string {
263
+ export function validateBaseUrl(value: string, source: string = "Base URL"): string {
264
264
  let parsed: URL;
265
265
 
266
266
  try {
267
267
  parsed = new URL(value);
268
268
  } catch {
269
- throw new ProfileResolutionError(
270
- `Environment variable ${ENV_BASE_URL} must be an absolute http(s) URL.`,
271
- );
269
+ throw new ProfileResolutionError(`${source} must be an absolute http(s) URL.`);
272
270
  }
273
271
 
274
272
  if (!["http:", "https:"].includes(parsed.protocol) || !parsed.host) {
275
- throw new ProfileResolutionError(
276
- `Environment variable ${ENV_BASE_URL} must be an absolute http(s) URL.`,
277
- );
273
+ throw new ProfileResolutionError(`${source} must be an absolute http(s) URL.`);
278
274
  }
279
275
 
280
276
  return value.replace(/\/+$/, "");
@@ -290,7 +286,7 @@ export function readEnvBaseUrl(env: NodeJS.ProcessEnv = process.env): string | u
290
286
  return undefined;
291
287
  }
292
288
 
293
- return validateBaseUrl(baseUrl);
289
+ return validateBaseUrl(baseUrl, `Environment variable ${ENV_BASE_URL}`);
294
290
  }
295
291
 
296
292
  export function readEnvProfile(env: NodeJS.ProcessEnv = process.env): string | undefined {
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
  });
@@ -9,6 +9,7 @@ import {
9
9
  readEnvProfile,
10
10
  resolveProfile,
11
11
  ProfileResolutionError,
12
+ validateBaseUrl,
12
13
  } from "./config.js";
13
14
 
14
15
  export function resolveRuntimeContext(
@@ -19,9 +20,14 @@ export function resolveRuntimeContext(
19
20
  const envApiKey = readEnvApiKey();
20
21
  const envBaseUrl = readEnvBaseUrl();
21
22
 
23
+ const flagBaseUrl =
24
+ overrides.baseUrl !== undefined
25
+ ? validateBaseUrl(overrides.baseUrl, '"--base-url"')
26
+ : undefined;
27
+
22
28
  const profileOverride = overrides.profile ?? envProfile;
23
29
  const apiKeyOverride = overrides.apiKey ?? envApiKey;
24
- const baseUrlOverride = overrides.baseUrl ?? envBaseUrl;
30
+ const baseUrlOverride = flagBaseUrl ?? envBaseUrl;
25
31
 
26
32
  if (apiKeyOverride && !baseUrlOverride) {
27
33
  throw new ProfileResolutionError(
@@ -28,7 +28,7 @@ describe("formatCliErrorJson", () => {
28
28
 
29
29
  expect(JSON.parse(formatCliErrorJson(error, { exitCode: 1 }))).toEqual({
30
30
  error: {
31
- code: "unknown_error",
31
+ code: "http_error",
32
32
  message: "Schema is locked.",
33
33
  exitCode: 1,
34
34
  details: {
@@ -45,4 +45,41 @@ describe("formatCliErrorJson", () => {
45
45
  },
46
46
  });
47
47
  });
48
+
49
+ it("preserves network_error code for transport-layer failures", () => {
50
+ const error = new ApiClientError(
51
+ "network_error",
52
+ "Request failed for GET https://api.example.com/api/v2/schemas: getaddrinfo ENOTFOUND api.example.com.",
53
+ {
54
+ url: "https://api.example.com/api/v2/schemas",
55
+ method: "GET",
56
+ cause: new Error("getaddrinfo ENOTFOUND api.example.com"),
57
+ },
58
+ );
59
+
60
+ expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("network_error");
61
+ });
62
+
63
+ it("preserves timeout code for aborted requests", () => {
64
+ const error = new ApiClientError("timeout", "Request timed out after 10ms.", {
65
+ url: "https://api.example.com/api/v2/schemas",
66
+ method: "GET",
67
+ });
68
+
69
+ expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("timeout");
70
+ });
71
+
72
+ it("preserves invalid_json code for unparseable responses", () => {
73
+ const error = new ApiClientError(
74
+ "invalid_json",
75
+ "Failed to parse JSON response from GET https://api.example.com/api/v2/schemas.",
76
+ {
77
+ url: "https://api.example.com/api/v2/schemas",
78
+ method: "GET",
79
+ responseText: "<html>oops</html>",
80
+ },
81
+ );
82
+
83
+ expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("invalid_json");
84
+ });
48
85
  });
@@ -7,13 +7,21 @@ export type CliErrorCode =
7
7
  | "unknown_error"
8
8
  | "invalid_cli_usage"
9
9
  | "configuration_error"
10
- | "command_not_allowed_in_read_only_mode";
10
+ | "command_not_allowed_in_read_only_mode"
11
+ | "network_error"
12
+ | "http_error"
13
+ | "timeout"
14
+ | "invalid_json";
11
15
 
12
16
  export const cliExitCodeByErrorCode = {
13
17
  unknown_error: 1,
14
18
  invalid_cli_usage: 2,
15
19
  configuration_error: 2,
16
20
  command_not_allowed_in_read_only_mode: 7,
21
+ network_error: 1,
22
+ http_error: 1,
23
+ timeout: 1,
24
+ invalid_json: 1,
17
25
  } as const satisfies Record<CliErrorCode, number>;
18
26
 
19
27
  export interface CliErrorDetails {
@@ -144,7 +152,7 @@ export function toCliError(error: unknown): CliError {
144
152
  if (error instanceof ApiClientError) {
145
153
  const apiError = extractApiErrorDetails(error);
146
154
 
147
- return new CliError("unknown_error", apiError.message, apiError.details);
155
+ return new CliError(error.code, apiError.message, apiError.details);
148
156
  }
149
157
 
150
158
  if (error instanceof Error) {
@@ -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
  });