@metabase/cli 0.1.0-alpha.workspaces-commands.818a8f1

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.
Files changed (107) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +762 -0
  3. package/dist/api-key-D9XxErQn.mjs +13 -0
  4. package/dist/archive-BPG5c88Y.mjs +38 -0
  5. package/dist/auth--Hpjwlaf.mjs +18 -0
  6. package/dist/body-DwU2s6Pg.mjs +19 -0
  7. package/dist/body-flags-7oqLhu5j.mjs +14 -0
  8. package/dist/branches-BbcoJXfp.mjs +41 -0
  9. package/dist/cancel-task-BDas45YO.mjs +29 -0
  10. package/dist/card-C31pGtBZ.mjs +113 -0
  11. package/dist/card-D4zZSPUb.mjs +19 -0
  12. package/dist/cli.d.mts +1 -0
  13. package/dist/cli.mjs +61 -0
  14. package/dist/command-augment-D9pI9Vbh.mjs +11 -0
  15. package/dist/create-Bd_U1zWU.mjs +124 -0
  16. package/dist/create-CCzsCZMm.mjs +47 -0
  17. package/dist/create-CwVcoq0O.mjs +43 -0
  18. package/dist/create-DpnjQvPw.mjs +43 -0
  19. package/dist/create-_UOeEXAj.mjs +39 -0
  20. package/dist/create-branch-sDttBORB.mjs +54 -0
  21. package/dist/credentials-C0xKke5D.mjs +84 -0
  22. package/dist/current-task-BGt1mqaX.mjs +35 -0
  23. package/dist/database-4V1iiPEx.mjs +17 -0
  24. package/dist/database-BTX5qbSv.mjs +33 -0
  25. package/dist/db-Dm2u2ISJ.mjs +17 -0
  26. package/dist/delete-DRBTgyus.mjs +47 -0
  27. package/dist/delete-DUC_stoL.mjs +47 -0
  28. package/dist/delete-runtime-inOVw3IX.mjs +58 -0
  29. package/dist/delete-table-9Is631O_.mjs +47 -0
  30. package/dist/deprovision-BAMzZc6f.mjs +60 -0
  31. package/dist/dirty-CLjHbz6J.mjs +32 -0
  32. package/dist/docker-QWVMG2gl.mjs +605 -0
  33. package/dist/eid-BNhutC1U.mjs +13 -0
  34. package/dist/export-D2Anfu3p.mjs +97 -0
  35. package/dist/field-Dhs2AND3.mjs +13 -0
  36. package/dist/field-QwBMAWsq.mjs +76 -0
  37. package/dist/flag-pair-CWvvzDJ_.mjs +17 -0
  38. package/dist/get-2po1uv9i.mjs +35 -0
  39. package/dist/get-BHJA78zg.mjs +35 -0
  40. package/dist/get-CAPLfawI.mjs +35 -0
  41. package/dist/get-CAVVmdMX.mjs +49 -0
  42. package/dist/get-DDWpubE8.mjs +36 -0
  43. package/dist/get-DhIoNeOp.mjs +35 -0
  44. package/dist/get-qPOsuTPw.mjs +35 -0
  45. package/dist/has-remote-changes-DAL5jetW.mjs +63 -0
  46. package/dist/import-CUMxUfSF.mjs +92 -0
  47. package/dist/input-BNqSFl38.mjs +33 -0
  48. package/dist/is-dirty-B10S6MG0.mjs +35 -0
  49. package/dist/is-dirty-CUuq-aB6.mjs +9 -0
  50. package/dist/key-CyhOpgWt.mjs +12 -0
  51. package/dist/license-DtsGJi3l.mjs +17 -0
  52. package/dist/list-B8s7Qnzk.mjs +31 -0
  53. package/dist/list-C5MGydGU.mjs +31 -0
  54. package/dist/list-DeFGwhhJ.mjs +60 -0
  55. package/dist/list-OBx5B3gd.mjs +39 -0
  56. package/dist/list-Y7iGsOfE.mjs +31 -0
  57. package/dist/list-evtQS7jl.mjs +39 -0
  58. package/dist/list-qetY9OIN.mjs +31 -0
  59. package/dist/login-Dqw9ZtCx.mjs +172 -0
  60. package/dist/logout-DwYJ5OUi.mjs +74 -0
  61. package/dist/logs-B_lrY7Js.mjs +57 -0
  62. package/dist/manifest-wzEFG0JB.mjs +124 -0
  63. package/dist/package-t8dKf4m_.mjs +73 -0
  64. package/dist/parse-id-C1prc9US.mjs +12 -0
  65. package/dist/poll-D2sXM5rc.mjs +49 -0
  66. package/dist/poll-task-Byiunmaj.mjs +194 -0
  67. package/dist/prompt-fXeNtj0M.mjs +40 -0
  68. package/dist/provision-DC4_HWZD.mjs +80 -0
  69. package/dist/ps-1bZKIwWh.mjs +9 -0
  70. package/dist/ps-BiOrecEe.mjs +78 -0
  71. package/dist/query-BnGVGeM3.mjs +100 -0
  72. package/dist/remove-Bx48o-0S.mjs +62 -0
  73. package/dist/remove-DecoZzNd.mjs +97 -0
  74. package/dist/render-DlBijc5i.mjs +179 -0
  75. package/dist/run-D4NgvaRh.mjs +87 -0
  76. package/dist/runtime-DUgFfYkN.mjs +950 -0
  77. package/dist/search-4wKx5ug2.mjs +171 -0
  78. package/dist/set-BZnCRL4c.mjs +66 -0
  79. package/dist/set-DCjrmTFm.mjs +66 -0
  80. package/dist/setting-C4vQSqer.mjs +18 -0
  81. package/dist/setting-DM7pm7yh.mjs +55 -0
  82. package/dist/setup-Dqh9hN6l.mjs +70 -0
  83. package/dist/start-xXQypG5L.mjs +324 -0
  84. package/dist/stash-ZZkmW_V7.mjs +106 -0
  85. package/dist/status-9KAPIpX8.mjs +31 -0
  86. package/dist/status-DezF-PIM.mjs +63 -0
  87. package/dist/status-JH6BZppo.mjs +55 -0
  88. package/dist/stop-br-ZOnve.mjs +80 -0
  89. package/dist/sync-C7VOWD00.mjs +26 -0
  90. package/dist/table-BvAr2ixC.mjs +75 -0
  91. package/dist/table-D-Mb5Nvw.mjs +16 -0
  92. package/dist/transform-CqxZwhGs.mjs +21 -0
  93. package/dist/transform-DfVkUttP.mjs +137 -0
  94. package/dist/transform-job-DuB_OjhO.mjs +91 -0
  95. package/dist/transform-job-HjbqjEoP.mjs +19 -0
  96. package/dist/translate-DJxDVAE4.mjs +110 -0
  97. package/dist/update-CDtm71m2.mjs +50 -0
  98. package/dist/update-DYVeVjk2.mjs +76 -0
  99. package/dist/update-DxKlQ0hP.mjs +50 -0
  100. package/dist/url-DP88YHNo.mjs +53 -0
  101. package/dist/wait-Cj_8wu4y.mjs +52 -0
  102. package/dist/wait-DwZN3ZwR.mjs +19 -0
  103. package/dist/wait-flags-CjW4ogUJ.mjs +35 -0
  104. package/dist/workspace-CbwR0vX_.mjs +24 -0
  105. package/dist/workspace-Dr9lWU3D.mjs +72 -0
  106. package/dist/workspace-credentials-q5RRFMT8.mjs +139 -0
  107. package/package.json +62 -0
@@ -0,0 +1,950 @@
1
+ import { package_default } from "./package-t8dKf4m_.mjs";
2
+ import { setMetabaseAugment } from "./command-augment-D9pI9Vbh.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { ZodError, z } from "zod";
5
+ import { isCancel } from "@clack/prompts";
6
+ import { promises } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { Entry } from "@napi-rs/keyring";
9
+ import { homedir } from "node:os";
10
+ import { setTimeout } from "node:timers/promises";
11
+
12
+ //#region src/core/errors.ts
13
+ var MetabaseError = class extends Error {
14
+ get userMessage() {
15
+ return this.message;
16
+ }
17
+ };
18
+ var NetworkError = class extends MetabaseError {
19
+ category = "network";
20
+ isRetryable = true;
21
+ exitCode = 1;
22
+ developerDetail;
23
+ constructor(message, developerDetail) {
24
+ super(message);
25
+ this.name = "NetworkError";
26
+ this.developerDetail = developerDetail;
27
+ }
28
+ };
29
+ var TimeoutError = class extends MetabaseError {
30
+ category = "timeout";
31
+ isRetryable = true;
32
+ exitCode = 1;
33
+ developerDetail;
34
+ constructor(message, developerDetail) {
35
+ super(message);
36
+ this.name = "TimeoutError";
37
+ this.developerDetail = developerDetail;
38
+ }
39
+ };
40
+ var ValidationError = class extends MetabaseError {
41
+ category = "validation";
42
+ isRetryable = false;
43
+ exitCode = 1;
44
+ developerDetail;
45
+ constructor(message, developerDetail) {
46
+ super(message);
47
+ this.name = "ValidationError";
48
+ this.developerDetail = developerDetail;
49
+ }
50
+ };
51
+ var ConfigError = class extends MetabaseError {
52
+ category = "config";
53
+ isRetryable = false;
54
+ exitCode = 2;
55
+ developerDetail = null;
56
+ constructor(message) {
57
+ super(message);
58
+ this.name = "ConfigError";
59
+ }
60
+ };
61
+ var AbortError = class extends MetabaseError {
62
+ category = "abort";
63
+ isRetryable = false;
64
+ exitCode = 130;
65
+ developerDetail = null;
66
+ constructor(message = "aborted") {
67
+ super(message);
68
+ this.name = "AbortError";
69
+ }
70
+ };
71
+ var UnknownError = class extends MetabaseError {
72
+ category = "unknown";
73
+ isRetryable = false;
74
+ exitCode = 1;
75
+ developerDetail;
76
+ constructor(input) {
77
+ super(input.originalMessage);
78
+ this.name = "UnknownError";
79
+ this.developerDetail = input;
80
+ }
81
+ };
82
+ function toMetabaseError(error) {
83
+ if (error instanceof MetabaseError) return error;
84
+ if (isCancel(error)) return new AbortError();
85
+ if (error instanceof ZodError) return new ConfigError(formatZodError(error));
86
+ if (error instanceof Error) return new UnknownError({
87
+ originalMessage: error.message,
88
+ stack: error.stack ?? null
89
+ });
90
+ return new UnknownError({
91
+ originalMessage: String(error),
92
+ stack: null
93
+ });
94
+ }
95
+ function formatZodError(error) {
96
+ return error.issues.map((issue) => {
97
+ const path = issue.path.join(".");
98
+ return path ? `${path}: ${issue.message}` : issue.message;
99
+ }).join("; ");
100
+ }
101
+ function isNotFoundError(value) {
102
+ return value instanceof Error && "code" in value && value.code === "ENOENT";
103
+ }
104
+ function errorMessage(value) {
105
+ return value instanceof Error ? value.message : String(value);
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/runtime/json.ts
110
+ function parseJson(input, schema, opts = {}) {
111
+ const result = parseJsonResult(input, schema, opts);
112
+ if (!result.ok) throw result.error;
113
+ return result.value;
114
+ }
115
+ function parseJsonResult(input, schema, opts = {}) {
116
+ const sourcePrefix = opts.source ? `${opts.source}: ` : "";
117
+ let raw;
118
+ try {
119
+ raw = JSON.parse(input);
120
+ } catch (error) {
121
+ return {
122
+ ok: false,
123
+ error: new ConfigError(`${sourcePrefix}invalid JSON: ${errorMessage(error)}`)
124
+ };
125
+ }
126
+ const parsed = schema.safeParse(raw);
127
+ if (!parsed.success) return {
128
+ ok: false,
129
+ error: new ValidationError(`${sourcePrefix}value did not match expected schema`, {
130
+ source: opts.source ?? "<input>",
131
+ zodIssues: parsed.error.issues
132
+ })
133
+ };
134
+ return {
135
+ ok: true,
136
+ value: parsed.data
137
+ };
138
+ }
139
+
140
+ //#endregion
141
+ //#region src/output/types.ts
142
+ const DEFAULT_MAX_BYTES = 65536;
143
+ function listEnvelopeSchema(item) {
144
+ return z.object({
145
+ data: z.array(item),
146
+ returned: z.number().int().nonnegative(),
147
+ total: z.number().int().nonnegative().optional(),
148
+ limit: z.number().int().nonnegative().optional(),
149
+ truncated: z.object({
150
+ reason: z.literal("max_bytes"),
151
+ bytes: z.number().int().nonnegative()
152
+ }).optional()
153
+ });
154
+ }
155
+ function wrapList(items) {
156
+ return {
157
+ data: items,
158
+ returned: items.length,
159
+ total: items.length
160
+ };
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/commands/flags.ts
165
+ const outputFlags = {
166
+ format: {
167
+ type: "string",
168
+ description: "auto | json | text",
169
+ default: "auto"
170
+ },
171
+ json: {
172
+ type: "boolean",
173
+ description: "Shorthand for --format json"
174
+ },
175
+ full: {
176
+ type: "boolean",
177
+ description: "Return the full object (default: compact)"
178
+ },
179
+ fields: {
180
+ type: "string",
181
+ description: "Dot-paths, comma separated (mutually exclusive with --full)"
182
+ },
183
+ maxBytes: {
184
+ type: "string",
185
+ description: "Output size cap; 0 disables",
186
+ default: String(DEFAULT_MAX_BYTES),
187
+ alias: "max-bytes"
188
+ }
189
+ };
190
+ const profileFlag = { profile: {
191
+ type: "string",
192
+ description: "Named profile (default: 'default')"
193
+ } };
194
+ const connectionFlags = {
195
+ url: {
196
+ type: "string",
197
+ description: "Metabase URL"
198
+ },
199
+ apiKey: {
200
+ type: "string",
201
+ description: "API key",
202
+ alias: "api-key"
203
+ }
204
+ };
205
+
206
+ //#endregion
207
+ //#region src/commands/parse-integer.ts
208
+ const INTEGER_PATTERN = /^-?\d+$/;
209
+ function parseInteger(value, options) {
210
+ const trimmed = value.trim();
211
+ if (!INTEGER_PATTERN.test(trimmed)) throw new ConfigError(`invalid ${options.name}: "${value}" (expected integer)`);
212
+ const parsed = Number.parseInt(trimmed, 10);
213
+ if (parsed < options.min) throw new ConfigError(`invalid ${options.name}: ${parsed} (must be ≥ ${options.min})`);
214
+ return parsed;
215
+ }
216
+ function parseOptionalInteger(value, options) {
217
+ if (value === void 0 || value === "") return null;
218
+ return parseInteger(value, options);
219
+ }
220
+
221
+ //#endregion
222
+ //#region src/core/paths.ts
223
+ const APP_DIR_NAME = "metabase-cli";
224
+ function configDir() {
225
+ if (process.platform === "win32") {
226
+ const appData = process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming");
227
+ return join(appData, APP_DIR_NAME);
228
+ }
229
+ const xdg = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
230
+ return join(xdg, APP_DIR_NAME);
231
+ }
232
+
233
+ //#endregion
234
+ //#region src/core/auth/storage.ts
235
+ const CredentialsFileSchema = z.record(z.string(), z.string());
236
+ const KEYRING_SERVICE = "metabase-cli";
237
+ const CREDENTIALS_FILE = "credentials.json";
238
+ const DEFAULT_PROFILE = "default";
239
+ const CREDENTIALS_FILE_MODE = 384;
240
+ const CREDENTIALS_DIR_MODE = 448;
241
+ const account = {
242
+ profileUrl: (profile) => `profile:${profile}:url`,
243
+ profileApiKey: (profile) => `profile:${profile}:apiKey`,
244
+ license: "license"
245
+ };
246
+ function fallbackFilePath() {
247
+ return join(configDir(), CREDENTIALS_FILE);
248
+ }
249
+ function keyringEnabled() {
250
+ return process.env["METABASE_CLI_DISABLE_KEYRING"] !== "1";
251
+ }
252
+ function trySetKeyring(key, value) {
253
+ if (!keyringEnabled()) return false;
254
+ try {
255
+ new Entry(KEYRING_SERVICE, key).setPassword(value);
256
+ return true;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ function tryReadKeyring(key) {
262
+ if (!keyringEnabled()) return void 0;
263
+ try {
264
+ return new Entry(KEYRING_SERVICE, key).getPassword();
265
+ } catch {
266
+ return void 0;
267
+ }
268
+ }
269
+ function tryRemoveKeyring(key) {
270
+ if (!keyringEnabled()) return void 0;
271
+ try {
272
+ return new Entry(KEYRING_SERVICE, key).deletePassword();
273
+ } catch {
274
+ return void 0;
275
+ }
276
+ }
277
+ async function readFileStore() {
278
+ const path = fallbackFilePath();
279
+ let raw;
280
+ try {
281
+ raw = await promises.readFile(path, "utf8");
282
+ } catch (error) {
283
+ if (isNotFoundError(error)) return {};
284
+ throw error;
285
+ }
286
+ return parseJson(raw, CredentialsFileSchema, { source: path });
287
+ }
288
+ async function writeFileStore(store) {
289
+ const path = fallbackFilePath();
290
+ await promises.mkdir(dirname(path), {
291
+ recursive: true,
292
+ mode: CREDENTIALS_DIR_MODE
293
+ });
294
+ await promises.writeFile(path, JSON.stringify(store, null, 2) + "\n", { mode: CREDENTIALS_FILE_MODE });
295
+ if (process.platform !== "win32") await promises.chmod(path, CREDENTIALS_FILE_MODE);
296
+ }
297
+ async function setFile(key, value) {
298
+ const store = await readFileStore();
299
+ store[key] = value;
300
+ await writeFileStore(store);
301
+ }
302
+ async function readFromFile(key) {
303
+ const store = await readFileStore();
304
+ return store[key] ?? null;
305
+ }
306
+ async function removeFromFile(key) {
307
+ const store = await readFileStore();
308
+ if (!(key in store)) return false;
309
+ delete store[key];
310
+ if (Object.keys(store).length === 0) await promises.unlink(fallbackFilePath()).catch(() => void 0);
311
+ else await writeFileStore(store);
312
+ return true;
313
+ }
314
+ const credentials = {
315
+ async set(key, value) {
316
+ if (trySetKeyring(key, value)) {
317
+ await removeFromFile(key).catch(() => void 0);
318
+ return {
319
+ backend: "keyring",
320
+ service: KEYRING_SERVICE,
321
+ account: key
322
+ };
323
+ }
324
+ await setFile(key, value);
325
+ return {
326
+ backend: "file",
327
+ path: fallbackFilePath(),
328
+ account: key
329
+ };
330
+ },
331
+ async read(key) {
332
+ const fromKeyring = tryReadKeyring(key);
333
+ if (fromKeyring !== void 0) return fromKeyring;
334
+ return readFromFile(key);
335
+ },
336
+ async has(key) {
337
+ return await credentials.read(key) !== null;
338
+ },
339
+ async remove(key) {
340
+ const fromKeyring = tryRemoveKeyring(key);
341
+ const fromFile = await removeFromFile(key).catch(() => false);
342
+ if (fromKeyring === void 0) return fromFile;
343
+ return fromKeyring || fromFile;
344
+ },
345
+ async location(key) {
346
+ if (tryReadKeyring(key) !== void 0) return {
347
+ backend: "keyring",
348
+ service: KEYRING_SERVICE,
349
+ account: key
350
+ };
351
+ return {
352
+ backend: "file",
353
+ path: fallbackFilePath(),
354
+ account: key
355
+ };
356
+ }
357
+ };
358
+ async function readProfile(name = DEFAULT_PROFILE) {
359
+ const [url, apiKey] = await Promise.all([credentials.read(account.profileUrl(name)), credentials.read(account.profileApiKey(name))]);
360
+ if (!url || !apiKey) return null;
361
+ return {
362
+ url,
363
+ apiKey
364
+ };
365
+ }
366
+ async function writeProfile(profile, name = DEFAULT_PROFILE) {
367
+ await credentials.set(account.profileUrl(name), profile.url);
368
+ return credentials.set(account.profileApiKey(name), profile.apiKey);
369
+ }
370
+ async function clearProfile(name = DEFAULT_PROFILE) {
371
+ const removedUrl = await credentials.remove(account.profileUrl(name));
372
+ const removedKey = await credentials.remove(account.profileApiKey(name));
373
+ return removedUrl || removedKey;
374
+ }
375
+ async function readLicense() {
376
+ return credentials.read(account.license);
377
+ }
378
+ async function writeLicense(token) {
379
+ return credentials.set(account.license, token);
380
+ }
381
+ async function clearLicense() {
382
+ return credentials.remove(account.license);
383
+ }
384
+
385
+ //#endregion
386
+ //#region src/core/url.ts
387
+ function normalizeUrl(input) {
388
+ const trimmed = input.trim().replace(/\/+$/, "");
389
+ if (!/^https?:\/\//i.test(trimmed)) throw new Error("URL must start with http:// or https://");
390
+ return trimmed;
391
+ }
392
+ function originOnly(input) {
393
+ const parsed = new URL(input);
394
+ parsed.username = "";
395
+ parsed.password = "";
396
+ return parsed.origin;
397
+ }
398
+ function localUrl(port) {
399
+ return `http://localhost:${port}`;
400
+ }
401
+
402
+ //#endregion
403
+ //#region src/core/config.ts
404
+ const ENV_URL = "METABASE_URL";
405
+ const ENV_API_KEY = "METABASE_API_KEY";
406
+ const ENV_PROFILE = "METABASE_PROFILE";
407
+ const ENV_LICENSE_TOKEN = "METABASE_LICENSE_TOKEN";
408
+ function resolveProfileName(profileFlag$1) {
409
+ return profileFlag$1 || process.env[ENV_PROFILE] || DEFAULT_PROFILE;
410
+ }
411
+ function readEnvCredentials() {
412
+ return {
413
+ url: process.env[ENV_URL] ?? null,
414
+ apiKey: process.env[ENV_API_KEY] ?? null
415
+ };
416
+ }
417
+ function readEnvLicenseToken() {
418
+ return process.env[ENV_LICENSE_TOKEN] ?? null;
419
+ }
420
+ async function resolveConfig(flags) {
421
+ const profile = resolveProfileName(flags.profile);
422
+ const env = readEnvCredentials();
423
+ const flagUrl = flags.url;
424
+ const flagKey = flags.apiKey;
425
+ const needsStored = !flagUrl && !env.url || !flagKey && !env.apiKey;
426
+ const stored = needsStored ? await readProfile(profile) : null;
427
+ const urlField = pickField(flagUrl, env.url, stored?.url);
428
+ const keyField = pickField(flagKey, env.apiKey, stored?.apiKey);
429
+ if (urlField === null || keyField === null) throw new ConfigError(`Not authenticated for profile "${profile}". Run \`metabase auth login\`, set ${ENV_URL}/${ENV_API_KEY}, or pass --url/--api-key.`);
430
+ return {
431
+ url: normalizeUrl(urlField.value),
432
+ apiKey: keyField.value,
433
+ profile,
434
+ source: urlField.source === keyField.source ? urlField.source : "mixed"
435
+ };
436
+ }
437
+ async function resolveLicenseToken(flags) {
438
+ const flag = flags.token;
439
+ const env = readEnvLicenseToken();
440
+ const stored = !flag && !env ? await readLicense() : null;
441
+ const value = flag ?? env ?? stored;
442
+ if (!value) throw new ConfigError(`No license token. Pass --token, set ${ENV_LICENSE_TOKEN}, or store one with \`metabase license set\`.`);
443
+ return value;
444
+ }
445
+ function pickField(flag, env, stored) {
446
+ if (flag) return {
447
+ value: flag,
448
+ source: "flag"
449
+ };
450
+ if (env) return {
451
+ value: env,
452
+ source: "env"
453
+ };
454
+ if (stored) return {
455
+ value: stored,
456
+ source: "stored"
457
+ };
458
+ return null;
459
+ }
460
+
461
+ //#endregion
462
+ //#region src/runtime/signal.ts
463
+ function createProcessAbortHandler() {
464
+ const controller = new AbortController();
465
+ const handler = () => {
466
+ if (!controller.signal.aborted) controller.abort(new AbortError("interrupted"));
467
+ process.off("SIGINT", handler);
468
+ };
469
+ process.on("SIGINT", handler);
470
+ return {
471
+ signal: controller.signal,
472
+ uninstall: () => process.off("SIGINT", handler)
473
+ };
474
+ }
475
+ let singleton = null;
476
+ function getProcessAbortSignal() {
477
+ if (singleton === null) singleton = createProcessAbortHandler().signal;
478
+ return singleton;
479
+ }
480
+ function combineAborts(timeoutSignal, callerSignal) {
481
+ const processSignal = getProcessAbortSignal();
482
+ const signals = [timeoutSignal, processSignal];
483
+ if (callerSignal) signals.push(callerSignal);
484
+ return {
485
+ combined: AbortSignal.any(signals),
486
+ processSignal
487
+ };
488
+ }
489
+ function throwIfAborted(...signals) {
490
+ for (const signal of signals) if (signal?.aborted) throw abortReason(signal);
491
+ }
492
+ function abortReason(signal) {
493
+ const reason = signal.reason;
494
+ if (reason instanceof MetabaseError) return reason;
495
+ if (reason instanceof Error || typeof reason === "string") return new AbortError(errorMessage(reason) || "aborted");
496
+ return new AbortError("aborted");
497
+ }
498
+
499
+ //#endregion
500
+ //#region src/core/http/sanitize.ts
501
+ const SECRET_HEADER_NAMES = new Set([
502
+ "authorization",
503
+ "x-api-key",
504
+ "x-metabase-session",
505
+ "cookie",
506
+ "set-cookie",
507
+ "proxy-authorization"
508
+ ]);
509
+ const REDACTED = "[REDACTED]";
510
+ function redactHeaders(headers) {
511
+ const result = {};
512
+ const entries = headers instanceof Headers ? headers.entries() : Object.entries(headers);
513
+ for (const [key, value] of entries) result[key] = SECRET_HEADER_NAMES.has(key.toLowerCase()) ? REDACTED : value;
514
+ return result;
515
+ }
516
+ function redactBody(body, ctx) {
517
+ let result = body;
518
+ for (const secret of ctx.knownSecrets) {
519
+ if (secret.length === 0) continue;
520
+ result = result.replaceAll(secret, REDACTED);
521
+ }
522
+ return result;
523
+ }
524
+
525
+ //#endregion
526
+ //#region src/core/http/errors.ts
527
+ const STATUS_CLASSIFICATIONS = {
528
+ 401: {
529
+ retryable: false,
530
+ message: "Invalid or unauthorized API key"
531
+ },
532
+ 403: {
533
+ retryable: false,
534
+ message: "Invalid or unauthorized API key"
535
+ },
536
+ 404: {
537
+ retryable: false,
538
+ message: "Endpoint not found — is this a Metabase instance?"
539
+ },
540
+ 408: {
541
+ retryable: true,
542
+ message: "Metabase timed out responding"
543
+ },
544
+ 425: { retryable: true },
545
+ 429: {
546
+ retryable: true,
547
+ message: "Metabase rate-limited the request"
548
+ },
549
+ 500: { retryable: true },
550
+ 502: { retryable: true },
551
+ 503: { retryable: true },
552
+ 504: {
553
+ retryable: true,
554
+ message: "Metabase timed out responding"
555
+ }
556
+ };
557
+ const ErrorEnvelope = z.object({
558
+ message: z.string().optional(),
559
+ error: z.string().optional(),
560
+ "error-message": z.string().optional()
561
+ });
562
+ var HttpError = class extends MetabaseError {
563
+ category = "http";
564
+ exitCode = 1;
565
+ status;
566
+ developerDetail;
567
+ constructor(input) {
568
+ const sanitizedBody = sanitizeBody(input.rawBody, input.redactionContext);
569
+ super(input.overrideUserMessage ?? extractUserMessage(input.status, sanitizedBody));
570
+ this.name = "HttpError";
571
+ this.status = input.status;
572
+ this.developerDetail = {
573
+ status: input.status,
574
+ statusText: input.statusText,
575
+ method: input.method,
576
+ url: input.url,
577
+ responseHeaders: redactHeaders(input.responseHeaders),
578
+ body: sanitizedBody
579
+ };
580
+ }
581
+ get isRetryable() {
582
+ return isRetryableStatus(this.status);
583
+ }
584
+ };
585
+ function isRetryableStatus(status) {
586
+ return STATUS_CLASSIFICATIONS[status]?.retryable === true;
587
+ }
588
+ function sanitizeBody(rawBody, ctx) {
589
+ if (rawBody === null) return null;
590
+ if (ctx === void 0) return rawBody;
591
+ return redactBody(rawBody, ctx);
592
+ }
593
+ function extractUserMessage(status, sanitizedBody) {
594
+ const fromBody = parseEnvelopeMessage(sanitizedBody);
595
+ if (fromBody) return fromBody;
596
+ return defaultMessageForStatus(status);
597
+ }
598
+ function parseEnvelopeMessage(sanitizedBody) {
599
+ if (!sanitizedBody) return null;
600
+ const result = parseJsonResult(sanitizedBody, ErrorEnvelope);
601
+ if (!result.ok) return null;
602
+ const envelope = result.value;
603
+ return envelope.message ?? envelope.error ?? envelope["error-message"] ?? null;
604
+ }
605
+ function defaultMessageForStatus(status) {
606
+ return STATUS_CLASSIFICATIONS[status]?.message ?? `Metabase returned ${status}`;
607
+ }
608
+
609
+ //#endregion
610
+ //#region src/core/http/retry.ts
611
+ const DEFAULT_MAX_RETRIES = 3;
612
+ const BASE_DELAY_MS = 250;
613
+ const MAX_DELAY_MS = 8e3;
614
+ const MS_PER_SECOND = 1e3;
615
+ function backoffDelay({ attempt, retryAfterHeader }) {
616
+ const fromHeader = parseRetryAfter(retryAfterHeader);
617
+ if (fromHeader !== null) return fromHeader;
618
+ return Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);
619
+ }
620
+ function parseRetryAfter(header) {
621
+ if (!header) return null;
622
+ const numeric = Number(header);
623
+ if (Number.isFinite(numeric) && numeric >= 0) return Math.round(numeric * MS_PER_SECOND);
624
+ const dateMs = Date.parse(header);
625
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
626
+ return null;
627
+ }
628
+ function sleep(ms, signal) {
629
+ return setTimeout(ms, void 0, { signal });
630
+ }
631
+
632
+ //#endregion
633
+ //#region src/core/http/client.ts
634
+ const DEFAULT_TIMEOUT_MS = 3e4;
635
+ const JSON_CONTENT_TYPE = "application/json";
636
+ const TEXT_CONTENT_TYPE_PREFIX = "text/";
637
+ const ERROR_BODY_BYTE_CAP = 64 * 1024;
638
+ const USER_AGENT = `metabase-cli/${package_default.version}`;
639
+ const IDEMPOTENT_METHODS = new Set([
640
+ "GET",
641
+ "HEAD",
642
+ "OPTIONS"
643
+ ]);
644
+ function createClient(config, overrides = {}) {
645
+ const fetchImpl = overrides.fetchImpl ?? globalThis.fetch.bind(globalThis);
646
+ const redactionContext = { knownSecrets: new Set([config.apiKey]) };
647
+ async function attemptOnce(prepared, attempt) {
648
+ const hasRetriesLeft = attempt < prepared.retries;
649
+ const timeoutSignal = AbortSignal.timeout(prepared.timeoutMs);
650
+ const { combined, processSignal } = combineAborts(timeoutSignal, prepared.callerSignal);
651
+ let response;
652
+ try {
653
+ response = await fetchImpl(prepared.url, {
654
+ method: prepared.method,
655
+ headers: prepared.headers,
656
+ body: prepared.body,
657
+ signal: combined
658
+ });
659
+ } catch (error) {
660
+ throwIfAborted(prepared.callerSignal, processSignal);
661
+ if (hasRetriesLeft) return {
662
+ kind: "retry",
663
+ delayMs: backoffDelay({ attempt })
664
+ };
665
+ if (timeoutSignal.aborted) throw new TimeoutError(`Request timed out after ${prepared.timeoutMs}ms`, {
666
+ kind: "http",
667
+ method: prepared.method,
668
+ url: prepared.url,
669
+ timeoutMs: prepared.timeoutMs
670
+ });
671
+ const message = errorMessage(error);
672
+ throw new NetworkError(`Could not reach Metabase: ${message}`, {
673
+ method: prepared.method,
674
+ url: prepared.url,
675
+ cause: message
676
+ });
677
+ }
678
+ const canRetryStatus = hasRetriesLeft && prepared.idempotent;
679
+ if (!response.ok && isRetryableStatus(response.status) && canRetryStatus) {
680
+ const retryAfter = response.headers.get("Retry-After");
681
+ response.body?.cancel().catch(() => void 0);
682
+ return {
683
+ kind: "retry",
684
+ delayMs: backoffDelay({
685
+ attempt,
686
+ retryAfterHeader: retryAfter
687
+ })
688
+ };
689
+ }
690
+ if (!response.ok) {
691
+ const rawBody = await readBodyForError(response);
692
+ throw new HttpError({
693
+ status: response.status,
694
+ statusText: response.statusText,
695
+ method: prepared.method,
696
+ url: prepared.url,
697
+ responseHeaders: response.headers,
698
+ rawBody,
699
+ redactionContext
700
+ });
701
+ }
702
+ assertContentType(response, prepared);
703
+ return {
704
+ kind: "success",
705
+ response
706
+ };
707
+ }
708
+ async function executeRaw(prepared) {
709
+ let attempt = 0;
710
+ while (true) {
711
+ const result = await attemptOnce(prepared, attempt);
712
+ if (result.kind === "success") return result.response;
713
+ await sleep(result.delayMs, prepared.callerSignal);
714
+ attempt += 1;
715
+ }
716
+ }
717
+ function prepare(path, opts = {}) {
718
+ const method = opts.method ?? "GET";
719
+ const expectContentType = opts.expectContentType ?? "json";
720
+ const url = buildUrl(config.url, path, opts.query);
721
+ const headers = new Headers();
722
+ headers.set("x-api-key", config.apiKey);
723
+ headers.set("accept", acceptHeader(expectContentType));
724
+ headers.set("user-agent", USER_AGENT);
725
+ let body = null;
726
+ if (opts.body !== void 0 && opts.body !== null) if (typeof opts.body === "string" || opts.body instanceof URLSearchParams) body = opts.body;
727
+ else if (opts.body instanceof FormData || opts.body instanceof ReadableStream) body = opts.body;
728
+ else {
729
+ body = JSON.stringify(opts.body);
730
+ headers.set("content-type", JSON_CONTENT_TYPE);
731
+ }
732
+ return {
733
+ url,
734
+ method,
735
+ headers,
736
+ body,
737
+ expectContentType,
738
+ retries: opts.retries ?? DEFAULT_MAX_RETRIES,
739
+ idempotent: opts.idempotent ?? IDEMPOTENT_METHODS.has(method),
740
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
741
+ callerSignal: opts.signal
742
+ };
743
+ }
744
+ return {
745
+ async requestRaw(path, opts) {
746
+ return executeRaw(prepare(path, opts));
747
+ },
748
+ async requestParsed(schema, path, opts) {
749
+ const prepared = prepare(path, {
750
+ ...opts,
751
+ expectContentType: "json"
752
+ });
753
+ const response = await executeRaw(prepared);
754
+ const text$1 = await response.text();
755
+ try {
756
+ return parseJson(text$1, schema, { source: prepared.url });
757
+ } catch (error) {
758
+ if (error instanceof ConfigError) throw new HttpError({
759
+ status: response.status,
760
+ statusText: response.statusText,
761
+ method: prepared.method,
762
+ url: prepared.url,
763
+ responseHeaders: response.headers,
764
+ rawBody: text$1,
765
+ redactionContext
766
+ });
767
+ throw error;
768
+ }
769
+ },
770
+ async requestStream(path, opts) {
771
+ const prepared = prepare(path, {
772
+ ...opts,
773
+ expectContentType: opts?.expectContentType ?? "binary"
774
+ });
775
+ const response = await executeRaw(prepared);
776
+ if (!response.body) throw new NetworkError("Response had no body to stream", {
777
+ method: prepared.method,
778
+ url: prepared.url,
779
+ cause: "missing body"
780
+ });
781
+ return response.body;
782
+ }
783
+ };
784
+ }
785
+ function buildUrl(baseUrl, path, query) {
786
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
787
+ const target = new URL(baseUrl + normalizedPath);
788
+ if (query) for (const [key, value] of Object.entries(query)) {
789
+ if (value === void 0) continue;
790
+ if (Array.isArray(value)) for (const entry of value) target.searchParams.append(key, String(entry));
791
+ else target.searchParams.append(key, String(value));
792
+ }
793
+ return target.toString();
794
+ }
795
+ function acceptHeader(expected) {
796
+ if (expected === "json") return JSON_CONTENT_TYPE;
797
+ if (expected === "text") return "text/*";
798
+ return "*/*";
799
+ }
800
+ function assertContentType(response, prepared) {
801
+ if (prepared.expectContentType === "binary") return;
802
+ const contentType = response.headers.get("content-type");
803
+ if (contentType === null) throwContentTypeMismatch(response, prepared, prepared.expectContentType);
804
+ if (prepared.expectContentType === "json" && !contentType.includes(JSON_CONTENT_TYPE)) throwContentTypeMismatch(response, prepared, "json");
805
+ if (prepared.expectContentType === "text" && !contentType.startsWith(TEXT_CONTENT_TYPE_PREFIX)) throwContentTypeMismatch(response, prepared, "text");
806
+ }
807
+ function throwContentTypeMismatch(response, prepared, expected) {
808
+ const actual = response.headers.get("content-type") ?? "no content-type";
809
+ throw new HttpError({
810
+ status: response.status,
811
+ statusText: response.statusText,
812
+ method: prepared.method,
813
+ url: prepared.url,
814
+ responseHeaders: response.headers,
815
+ rawBody: null,
816
+ overrideUserMessage: `Expected ${expected} response but got ${actual}`
817
+ });
818
+ }
819
+ async function readBodyForError(response) {
820
+ try {
821
+ const buffer = Buffer.from(await response.arrayBuffer());
822
+ return buffer.subarray(0, ERROR_BODY_BYTE_CAP).toString("utf8");
823
+ } catch (error) {
824
+ return `[body read failed: ${errorMessage(error)}]`;
825
+ }
826
+ }
827
+
828
+ //#endregion
829
+ //#region src/output/error.ts
830
+ function reportError(error) {
831
+ const handled = toMetabaseError(error);
832
+ process.stderr.write(handled.userMessage + "\n");
833
+ if (process.env["METABASE_VERBOSE"] === "1" && handled.developerDetail !== null) process.stderr.write(JSON.stringify(handled.developerDetail, null, 2) + "\n");
834
+ process.exitCode = handled.exitCode;
835
+ }
836
+
837
+ //#endregion
838
+ //#region src/output/format.ts
839
+ function resolveFormat({ json, format, isTty }) {
840
+ const explicit = format !== void 0 && format !== "auto" ? format : null;
841
+ if (explicit !== null && explicit !== "json" && explicit !== "text") throw new ConfigError(`invalid --format value: "${explicit}" (expected: auto, json, text)`);
842
+ if (json && explicit !== null && explicit !== "json") throw new ConfigError(`--json conflicts with --format ${explicit}`);
843
+ if (json) return "json";
844
+ if (explicit !== null) return explicit;
845
+ return isTty ? "text" : "json";
846
+ }
847
+
848
+ //#endregion
849
+ //#region src/runtime/csv.ts
850
+ function parseCsv(raw) {
851
+ return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
852
+ }
853
+
854
+ //#endregion
855
+ //#region src/commands/context.ts
856
+ function resolveCommonFlags(args, options = {}) {
857
+ const isTty = options.isTty ?? Boolean(process.stdout.isTTY);
858
+ const fields = parseFields(args.fields);
859
+ const full = args.full === true;
860
+ if (full && fields !== void 0) throw new ConfigError("--full conflicts with --fields (use one or neither)");
861
+ return {
862
+ format: resolveFormat({
863
+ json: args.json,
864
+ format: args.format,
865
+ isTty
866
+ }),
867
+ full,
868
+ fields,
869
+ maxBytes: parseMaxBytes(args.maxBytes),
870
+ url: args.url,
871
+ apiKey: args.apiKey,
872
+ profile: args.profile
873
+ };
874
+ }
875
+ function parseFields(value) {
876
+ if (value === void 0 || value === "") return void 0;
877
+ const parts = parseCsv(value);
878
+ return parts.length > 0 ? parts : void 0;
879
+ }
880
+ function parseMaxBytes(value) {
881
+ return parseInteger(value ?? String(DEFAULT_MAX_BYTES), {
882
+ name: "--max-bytes",
883
+ min: 0
884
+ });
885
+ }
886
+
887
+ //#endregion
888
+ //#region src/commands/runtime.ts
889
+ function defineMetabaseCommand(def) {
890
+ const cmd = defineCommand({
891
+ meta: def.meta,
892
+ args: def.args,
893
+ async run({ args }) {
894
+ try {
895
+ const ctx = resolveCommonFlags(pickCommonArgs(args));
896
+ let cachedConfig = null;
897
+ let cachedClient = null;
898
+ const getResolvedConfig = async () => {
899
+ if (cachedConfig === null) cachedConfig = await resolveConfig(buildConfigFlags(ctx));
900
+ return cachedConfig;
901
+ };
902
+ const getClient = async () => {
903
+ if (cachedClient === null) {
904
+ const resolved = await getResolvedConfig();
905
+ cachedClient = createClient({
906
+ url: resolved.url,
907
+ apiKey: resolved.apiKey
908
+ });
909
+ }
910
+ return cachedClient;
911
+ };
912
+ await def.run({
913
+ args,
914
+ ctx,
915
+ getClient,
916
+ getResolvedConfig
917
+ });
918
+ } catch (error) {
919
+ reportError(error);
920
+ }
921
+ }
922
+ });
923
+ setMetabaseAugment(cmd, {
924
+ examples: def.examples ?? [],
925
+ outputSchema: def.outputSchema ?? null
926
+ });
927
+ return cmd;
928
+ }
929
+ function pickCommonArgs(args) {
930
+ const out = {};
931
+ if (typeof args["format"] === "string") out.format = args["format"];
932
+ if (typeof args["json"] === "boolean") out.json = args["json"];
933
+ if (typeof args["full"] === "boolean") out.full = args["full"];
934
+ if (typeof args["fields"] === "string") out.fields = args["fields"];
935
+ if (typeof args["maxBytes"] === "string") out.maxBytes = args["maxBytes"];
936
+ if (typeof args["profile"] === "string") out.profile = args["profile"];
937
+ if (typeof args["url"] === "string") out.url = args["url"];
938
+ if (typeof args["apiKey"] === "string") out.apiKey = args["apiKey"];
939
+ return out;
940
+ }
941
+ function buildConfigFlags(ctx) {
942
+ const flags = {};
943
+ if (ctx.profile !== void 0) flags.profile = ctx.profile;
944
+ if (ctx.url !== void 0) flags.url = ctx.url;
945
+ if (ctx.apiKey !== void 0) flags.apiKey = ctx.apiKey;
946
+ return flags;
947
+ }
948
+
949
+ //#endregion
950
+ export { AbortError, ConfigError, HttpError, MetabaseError, TimeoutError, ValidationError, account, clearLicense, clearProfile, combineAborts, connectionFlags, createClient, credentials, defineMetabaseCommand, errorMessage, isNotFoundError, listEnvelopeSchema, localUrl, normalizeUrl, originOnly, outputFlags, parseCsv, parseInteger, parseJson, parseOptionalInteger, profileFlag, readEnvCredentials, readEnvLicenseToken, resolveLicenseToken, resolveProfileName, throwIfAborted, wrapList, writeLicense, writeProfile };