@metabase/cli 0.1.0 → 0.1.2

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 (187) hide show
  1. package/README.md +972 -57
  2. package/dist/add-collection-DwxQDXzL.mjs +54 -0
  3. package/dist/add-collection-SL08iMub.mjs +11 -0
  4. package/dist/api-key-BktzvPb7.mjs +13 -0
  5. package/dist/{archive-CsWeHXle.mjs → archive-C1mF-9Kj.mjs} +7 -4
  6. package/dist/archive-CLWtbvvH.mjs +44 -0
  7. package/dist/archive-Cq4WKmJt.mjs +44 -0
  8. package/dist/archive-kYoy5LK5.mjs +39 -0
  9. package/dist/auth-DfYkakP3.mjs +19 -0
  10. package/dist/{body-Dv9hQ0Qk.mjs → body-rDrR-C1c.mjs} +3 -2
  11. package/dist/{branches-BujtceGr.mjs → branches-CH2UcCpX.mjs} +8 -6
  12. package/dist/cancel-CgLZcItQ.mjs +56 -0
  13. package/dist/{cancel-task-CT2xUMRg.mjs → cancel-task-DcYrFsM6.mjs} +9 -7
  14. package/dist/{card-CsXk8T6A.mjs → card-CQxvHeyP.mjs} +34 -15
  15. package/dist/card-ZCGU2JEh.mjs +20 -0
  16. package/dist/cards-C4NIaERo.mjs +37 -0
  17. package/dist/cli.mjs +33 -14
  18. package/dist/collection-B3sPXRLs.mjs +163 -0
  19. package/dist/collection-D_uFLIAS.mjs +19 -0
  20. package/dist/create-BUCLNqiN.mjs +48 -0
  21. package/dist/create-CB0Yp__0.mjs +66 -0
  22. package/dist/create-CNvd5T8h.mjs +48 -0
  23. package/dist/create-Cbh1cGj9.mjs +48 -0
  24. package/dist/create-CzfNOhOF.mjs +48 -0
  25. package/dist/create-DU0ZhnZu.mjs +44 -0
  26. package/dist/create-Dh0p-c2Y.mjs +44 -0
  27. package/dist/create-DvrVZ2hS.mjs +125 -0
  28. package/dist/create-QgN369N5.mjs +50 -0
  29. package/dist/{create-B8ektf-R.mjs → create-bqc_rmix.mjs} +8 -6
  30. package/dist/{create-branch-goZBTNnr.mjs → create-branch-BJFH9Hda.mjs} +9 -7
  31. package/dist/credentials-DTP1xuKz.mjs +85 -0
  32. package/dist/{current-task-DBjRNCFq.mjs → current-task-z_TiJ0kt.mjs} +9 -7
  33. package/dist/dashboard-CnMD04PQ.mjs +163 -0
  34. package/dist/dashboard-G1-dGLUR.mjs +20 -0
  35. package/dist/database-DQkUxTLd.mjs +17 -0
  36. package/dist/database-vvig8k4x.mjs +51 -0
  37. package/dist/db-CBaEfumR.mjs +22 -0
  38. package/dist/{delete-8vGU35r3.mjs → delete-CVYII8mq.mjs} +7 -5
  39. package/dist/{delete-B27KLF5X.mjs → delete-DeZQ1r9w.mjs} +7 -5
  40. package/dist/{delete-runtime-Byr60cR3.mjs → delete-runtime-BMzvfj_B.mjs} +4 -4
  41. package/dist/{delete-table-BNaJ_gA4.mjs → delete-table-ZiR9-ndv.mjs} +7 -5
  42. package/dist/deprovision-BhD3J-Am.mjs +61 -0
  43. package/dist/{dirty-aNUuph4I.mjs → dirty-D9agt7Os.mjs} +8 -6
  44. package/dist/docker-CHpV8PRz.mjs +612 -0
  45. package/dist/eid-B5wawMmO.mjs +13 -0
  46. package/dist/{export-QDkuuzSE.mjs → export-Bfk7JAlR.mjs} +30 -23
  47. package/dist/field-B3gvaqpK.mjs +278 -0
  48. package/dist/field-BDJ1pEgr.mjs +18 -0
  49. package/dist/fields-7ByLsxLg.mjs +38 -0
  50. package/dist/flag-pair-DtR1AiBQ.mjs +17 -0
  51. package/dist/get-BE6Izpus.mjs +36 -0
  52. package/dist/get-C3CcAJGg.mjs +49 -0
  53. package/dist/{get-DI_IJvgk.mjs → get-CQGeF-eP.mjs} +6 -4
  54. package/dist/get-D2m4jhwT.mjs +53 -0
  55. package/dist/{get-BGBIzMKY.mjs → get-DKy3DAJX.mjs} +6 -4
  56. package/dist/{get-COXHplHP.mjs → get-DUSR5i99.mjs} +7 -5
  57. package/dist/get-DikegGzi.mjs +36 -0
  58. package/dist/get-StkjKuh0.mjs +40 -0
  59. package/dist/get-bYc7eGYe.mjs +36 -0
  60. package/dist/{get-Cl8-IauC.mjs → get-cuHp9-6U.mjs} +7 -4
  61. package/dist/{get-i6LWOByV.mjs → get-gOT_RarI.mjs} +6 -4
  62. package/dist/get-run-D59Yqaoh.mjs +36 -0
  63. package/dist/get-tISo-cmg.mjs +41 -0
  64. package/dist/git-sync-BiTWfLgY.mjs +28 -0
  65. package/dist/{has-remote-changes-hjKoQuRy.mjs → has-remote-changes-B1TciDVD.mjs} +8 -6
  66. package/dist/{import-HJsSKRYx.mjs → import-DnnmmJbp.mjs} +11 -9
  67. package/dist/{input-Dojr-RTw.mjs → input-ikCiip6x.mjs} +2 -1
  68. package/dist/is-dirty-DClGFOGV.mjs +10 -0
  69. package/dist/{is-dirty-1Qy7hiHB.mjs → is-dirty-DlfX7e39.mjs} +5 -4
  70. package/dist/items-DQFQSpjF.mjs +77 -0
  71. package/dist/{key-DBxPSFwi.mjs → key-NDEARu2L.mjs} +1 -1
  72. package/dist/{license-MoWse3ZI.mjs → license-DBh13sc8.mjs} +3 -3
  73. package/dist/list-4kYCGv01.mjs +32 -0
  74. package/dist/list-9AOWhxqp.mjs +61 -0
  75. package/dist/{list-Bk6RsbJl.mjs → list-BwjqQ6pp.mjs} +5 -3
  76. package/dist/{list-C_PRdL5e.mjs → list-CP5RNjO6.mjs} +7 -5
  77. package/dist/{list-C8tdLOH5.mjs → list-Cy0VhXQs.mjs} +5 -3
  78. package/dist/list-D067ZSE5.mjs +47 -0
  79. package/dist/list-DAZP-IM5.mjs +32 -0
  80. package/dist/list-DJN-OvTZ.mjs +52 -0
  81. package/dist/list-DQj-QJAs.mjs +40 -0
  82. package/dist/list-Di529OJD.mjs +55 -0
  83. package/dist/{list-C4Ajrw8f.mjs → list-DlKzgnqo.mjs} +6 -3
  84. package/dist/list-GFfR9SuT.mjs +32 -0
  85. package/dist/{list-CWt3fqrZ.mjs → list-iFVEdi2J.mjs} +5 -3
  86. package/dist/{login-C9WTwNn6.mjs → login-DxgkosGx.mjs} +30 -9
  87. package/dist/{logout-oLszGCOg.mjs → logout-BlVwqBog.mjs} +7 -6
  88. package/dist/logs-CudNEkT4.mjs +58 -0
  89. package/dist/{manifest-CAdjQYH8.mjs → manifest-Dv5B9Blc.mjs} +3 -7
  90. package/dist/measure-BEQfnLdN.mjs +67 -0
  91. package/dist/measure-C7SbdYQk.mjs +19 -0
  92. package/dist/metadata-B2Td415K.mjs +38 -0
  93. package/dist/metadata-BTJAFVvZ.mjs +37 -0
  94. package/dist/{package-BGfw4ZWJ.mjs → package-DV6Asqim.mjs} +7 -1
  95. package/dist/paginate-CTSfuYiF.mjs +49 -0
  96. package/dist/parse-id-B38zTlYs.mjs +12 -0
  97. package/dist/parse-ref-DGvh4aDn.mjs +17 -0
  98. package/dist/parse-schemas-Ds-cVE-O.mjs +12 -0
  99. package/dist/{poll-ILanYysl.mjs → poll-Bh6oAifO.mjs} +2 -1
  100. package/dist/{poll-task-DbpsiQhl.mjs → poll-task-vPwV31Fs.mjs} +8 -7
  101. package/dist/predicates-DiIiS3k7.mjs +153 -0
  102. package/dist/preflight-DxJb-hUV.mjs +91 -0
  103. package/dist/{prompt-DpT8yAVy.mjs → prompt-Bf3DQ-qE.mjs} +1 -1
  104. package/dist/provision-B-I0zuDe.mjs +77 -0
  105. package/dist/ps-BmYQYC7t.mjs +10 -0
  106. package/dist/ps-CaiOFCv2.mjs +78 -0
  107. package/dist/query-BtF1yWZZ.mjs +90 -0
  108. package/dist/{query-PihYi-UZ.mjs → query-jmfqaXRP.mjs} +38 -13
  109. package/dist/remove-C2iv0g03.mjs +98 -0
  110. package/dist/remove-collection-DhZghaZy.mjs +38 -0
  111. package/dist/{remove-B2hVYn1v.mjs → remove-xskleeru.mjs} +6 -5
  112. package/dist/render-DXv-D6fU.mjs +182 -0
  113. package/dist/rescan-values-DW6u90ep.mjs +43 -0
  114. package/dist/revision-message-flag-CWQbKhdl.mjs +11 -0
  115. package/dist/{run-C2so6Qp6.mjs → run-DxVzhcF3.mjs} +27 -36
  116. package/dist/runs-BOHk1XnM.mjs +54 -0
  117. package/dist/{runtime-C9CEZhcn.mjs → runtime-cwBS8wwK.mjs} +428 -442
  118. package/dist/schema-tables-CcFbY_jN.mjs +45 -0
  119. package/dist/schemas-DZmv_V62.mjs +47 -0
  120. package/dist/{search-CopOytXY.mjs → search-CYMuc7Fg.mjs} +6 -19
  121. package/dist/segment-BMrUBz94.mjs +70 -0
  122. package/dist/segment-Df4pfjco.mjs +19 -0
  123. package/dist/{set-BcF7M1GQ.mjs → set-B_rrVwU4.mjs} +6 -4
  124. package/dist/{set-CbibegpA.mjs → set-CbGfQ7Ye.mjs} +8 -6
  125. package/dist/{setting-U3NtBMFo.mjs → setting-DqZY9NXP.mjs} +3 -3
  126. package/dist/setup-DxmcAorA.mjs +71 -0
  127. package/dist/snippet-CwSHjQyn.mjs +19 -0
  128. package/dist/snippet-Dw0Sjzkr.mjs +64 -0
  129. package/dist/start-Cn0epTks.mjs +380 -0
  130. package/dist/{stash-DOBbYozC.mjs → stash-BFZIl9F4.mjs} +9 -7
  131. package/dist/{status-Buf1ZbNR.mjs → status-BjCeJNLp.mjs} +10 -8
  132. package/dist/{status-CUcs8XBH.mjs → status-FDIDmqvM.mjs} +4 -2
  133. package/dist/{status-D1F5XHae.mjs → status-UALK3OJl.mjs} +4 -2
  134. package/dist/stop-DUwrDWw8.mjs +81 -0
  135. package/dist/summary-CS4UGiFJ.mjs +41 -0
  136. package/dist/sync-schema-IrHdJxmX.mjs +43 -0
  137. package/dist/{table-Cfk7oSvw.mjs → table-B-PYcgGb.mjs} +22 -9
  138. package/dist/table-Cdr5bKp1.mjs +19 -0
  139. package/dist/transform-CeZusR_w.mjs +24 -0
  140. package/dist/{transform-B5uRpg1G.mjs → transform-IEX4Mx3X.mjs} +56 -2
  141. package/dist/transform-job-BOn9-CGa.mjs +19 -0
  142. package/dist/{transform-job-C7QXWTVE.mjs → transform-job-Csr86muI.mjs} +7 -0
  143. package/dist/translate-B__zbDKm.mjs +111 -0
  144. package/dist/tree-Mh0uQ_Wy.mjs +32 -0
  145. package/dist/update-1Di9hbPo.mjs +56 -0
  146. package/dist/update-B5_pp6Jj.mjs +56 -0
  147. package/dist/update-B9DBMo30.mjs +52 -0
  148. package/dist/update-BfBsM_y1.mjs +56 -0
  149. package/dist/update-Bw0WZix_.mjs +73 -0
  150. package/dist/update-Cp1789qq.mjs +52 -0
  151. package/dist/update-D2VI_5cy.mjs +57 -0
  152. package/dist/update-D8GwQTcL.mjs +59 -0
  153. package/dist/{update-CL8tRbxr.mjs → update-Masp5WeT.mjs} +9 -7
  154. package/dist/update-dashcard-CNiQw1MD.mjs +71 -0
  155. package/dist/update-j9vgemKR.mjs +51 -0
  156. package/dist/url-GFM76VIK.mjs +54 -0
  157. package/dist/uuid-Uif0lNk8.mjs +47 -0
  158. package/dist/validate-DCYx6jdL.mjs +1496 -0
  159. package/dist/validate-query-B07oGG4K.mjs +37 -0
  160. package/dist/values-DrwNHUAI.mjs +36 -0
  161. package/dist/{wait-Bugr9eXD.mjs → wait-BoKk8CJy.mjs} +10 -8
  162. package/dist/wait-DO7tS7NI.mjs +19 -0
  163. package/dist/wait-flags-CjX2sEGm.mjs +35 -0
  164. package/dist/workspace-CyEX40D-.mjs +24 -0
  165. package/dist/workspace-DVuqKJGG.mjs +72 -0
  166. package/dist/workspace-credentials-B6BL-X0d.mjs +139 -0
  167. package/package.json +7 -1
  168. package/dist/auth-BF7IjZIH.mjs +0 -18
  169. package/dist/card-_Ta7zdYe.mjs +0 -19
  170. package/dist/create-CI2Cunq5.mjs +0 -38
  171. package/dist/create-DdbU3TLX.mjs +0 -42
  172. package/dist/database-PA9Goi25.mjs +0 -33
  173. package/dist/db-DMghzgb6.mjs +0 -17
  174. package/dist/field-C8IVs6rp.mjs +0 -76
  175. package/dist/field-DaYo_90x.mjs +0 -13
  176. package/dist/get-Cwpj7lDe.mjs +0 -35
  177. package/dist/get-Dh_acl8q.mjs +0 -34
  178. package/dist/is-dirty-DpKn9HJp.mjs +0 -8
  179. package/dist/list-CBSBHtK-.mjs +0 -38
  180. package/dist/parse-id-BhmmfyCP.mjs +0 -14
  181. package/dist/sync-BPyGXfUk.mjs +0 -26
  182. package/dist/table-D7nJt7JO.mjs +0 -16
  183. package/dist/transform-UbyewMxY.mjs +0 -21
  184. package/dist/transform-job-CrYkr-Ma.mjs +0 -19
  185. package/dist/update-DU2oU2j-.mjs +0 -49
  186. /package/dist/{body-flags-BUA9XV1u.mjs → body-flags-BK7J6Daz.mjs} +0 -0
  187. /package/dist/{setting-26ckqHAP.mjs → setting-CTaAeMci.mjs} +0 -0
@@ -1,113 +1,16 @@
1
- import { package_default } from "./package-BGfw4ZWJ.mjs";
1
+ import { package_default } from "./package-DV6Asqim.mjs";
2
2
  import { setMetabaseAugment } from "./command-augment-D9pI9Vbh.mjs";
3
+ import { AbortError, ConfigError, MetabaseError, NetworkError, TimeoutError, VERBOSE_ENV, ValidationError, errorMessage, isNotFoundError, isPlainObject, toMetabaseError } from "./predicates-DiIiS3k7.mjs";
3
4
  import { defineCommand } from "citty";
4
- import { ZodError, z } from "zod";
5
+ import { z } from "zod";
5
6
  import { promises } from "node:fs";
6
- import { homedir } from "node:os";
7
7
  import { dirname, join } from "node:path";
8
+ import { homedir } from "node:os";
8
9
  import { Entry } from "@napi-rs/keyring";
9
- import { isCancel } from "@clack/prompts";
10
10
  import { setTimeout } from "node:timers/promises";
11
- import Table from "cli-table3";
12
11
 
13
- //#region src/core/errors.ts
14
- var MetabaseError = class extends Error {
15
- get userMessage() {
16
- return this.message;
17
- }
18
- };
19
- var NetworkError = class extends MetabaseError {
20
- category = "network";
21
- isRetryable = true;
22
- exitCode = 1;
23
- developerDetail;
24
- constructor(message, developerDetail) {
25
- super(message);
26
- this.name = "NetworkError";
27
- this.developerDetail = developerDetail;
28
- }
29
- };
30
- var TimeoutError = class extends MetabaseError {
31
- category = "timeout";
32
- isRetryable = true;
33
- exitCode = 1;
34
- developerDetail;
35
- constructor(message, developerDetail) {
36
- super(message);
37
- this.name = "TimeoutError";
38
- this.developerDetail = developerDetail;
39
- }
40
- };
41
- var ValidationError = class extends MetabaseError {
42
- category = "validation";
43
- isRetryable = false;
44
- exitCode = 1;
45
- developerDetail;
46
- constructor(message, developerDetail) {
47
- super(message);
48
- this.name = "ValidationError";
49
- this.developerDetail = developerDetail;
50
- }
51
- };
52
- var ConfigError = class extends MetabaseError {
53
- category = "config";
54
- isRetryable = false;
55
- exitCode = 2;
56
- developerDetail = null;
57
- constructor(message) {
58
- super(message);
59
- this.name = "ConfigError";
60
- }
61
- };
62
- var AbortError = class extends MetabaseError {
63
- category = "abort";
64
- isRetryable = false;
65
- exitCode = 130;
66
- developerDetail = null;
67
- constructor(message = "aborted") {
68
- super(message);
69
- this.name = "AbortError";
70
- }
71
- };
72
- var UnknownError = class extends MetabaseError {
73
- category = "unknown";
74
- isRetryable = false;
75
- exitCode = 1;
76
- developerDetail;
77
- constructor(input) {
78
- super(input.originalMessage);
79
- this.name = "UnknownError";
80
- this.developerDetail = input;
81
- }
82
- };
83
- function toMetabaseError(error) {
84
- if (error instanceof MetabaseError) return error;
85
- if (isCancel(error)) return new AbortError();
86
- if (error instanceof ZodError) return new ConfigError(formatZodError(error));
87
- if (error instanceof Error) return new UnknownError({
88
- originalMessage: error.message,
89
- stack: error.stack ?? null
90
- });
91
- return new UnknownError({
92
- originalMessage: String(error),
93
- stack: null
94
- });
95
- }
96
- function formatZodError(error) {
97
- return error.issues.map((issue) => {
98
- const path = issue.path.join(".");
99
- return path ? `${path}: ${issue.message}` : issue.message;
100
- }).join("; ");
101
- }
102
- function isNotFoundError(value) {
103
- return value instanceof Error && "code" in value && value.code === "ENOENT";
104
- }
105
- function errorMessage(value) {
106
- return value instanceof Error ? value.message : String(value);
107
- }
108
-
109
- //#endregion
110
12
  //#region src/runtime/json.ts
13
+ const JSON_CONTENT_TYPE$1 = "application/json";
111
14
  function parseJson(input, schema, opts = {}) {
112
15
  const result = parseJsonResult(input, schema, opts);
113
16
  if (!result.ok) throw result.error;
@@ -137,12 +40,175 @@ function parseJsonResult(input, schema, opts = {}) {
137
40
  value: parsed.data
138
41
  };
139
42
  }
43
+ function parseJsonOrPlain(text, contentType, schema, opts = {}) {
44
+ if (!isJsonContentType(contentType)) return parseJson(JSON.stringify(text), schema, opts);
45
+ const attempt = parseJsonResult(text, schema, opts);
46
+ if (attempt.ok) return attempt.value;
47
+ if (attempt.error instanceof ValidationError) throw attempt.error;
48
+ return parseJson(JSON.stringify(text), schema, opts);
49
+ }
50
+ function isJsonContentType(contentType) {
51
+ return contentType !== null && contentType.includes(JSON_CONTENT_TYPE$1);
52
+ }
53
+
54
+ //#endregion
55
+ //#region src/output/types.ts
56
+ const DEFAULT_MAX_BYTES = 65536;
57
+ function listEnvelopeSchema(item) {
58
+ return z.object({
59
+ data: z.array(item),
60
+ returned: z.number().int().nonnegative(),
61
+ total: z.number().int().nonnegative().nullable().optional(),
62
+ limit: z.number().int().nonnegative().optional(),
63
+ truncated: z.object({
64
+ reason: z.literal("max_bytes"),
65
+ bytes: z.number().int().nonnegative()
66
+ }).optional()
67
+ });
68
+ }
69
+ function wrapList(items) {
70
+ return {
71
+ data: items,
72
+ returned: items.length,
73
+ total: items.length
74
+ };
75
+ }
76
+
77
+ //#endregion
78
+ //#region src/commands/flags.ts
79
+ const outputFlags = {
80
+ format: {
81
+ type: "string",
82
+ description: "auto | json | text",
83
+ default: "auto"
84
+ },
85
+ json: {
86
+ type: "boolean",
87
+ description: "Shorthand for --format json"
88
+ },
89
+ full: {
90
+ type: "boolean",
91
+ description: "Return the full object (default: compact)"
92
+ },
93
+ fields: {
94
+ type: "string",
95
+ description: "Dot-paths, comma separated (mutually exclusive with --full)"
96
+ },
97
+ maxBytes: {
98
+ type: "string",
99
+ description: "Output size cap; 0 disables",
100
+ default: String(DEFAULT_MAX_BYTES),
101
+ alias: "max-bytes"
102
+ }
103
+ };
104
+ const profileFlag = { profile: {
105
+ type: "string",
106
+ description: "Named profile (default: 'default')"
107
+ } };
108
+ const connectionFlags = {
109
+ url: {
110
+ type: "string",
111
+ description: "Metabase URL"
112
+ },
113
+ apiKey: {
114
+ type: "string",
115
+ description: "API key",
116
+ alias: "api-key"
117
+ }
118
+ };
119
+
120
+ //#endregion
121
+ //#region src/commands/parse-integer.ts
122
+ const INTEGER_PATTERN = /^-?\d+$/;
123
+ function parseInteger(value, options) {
124
+ const trimmed = value.trim();
125
+ if (!INTEGER_PATTERN.test(trimmed)) throw new ConfigError(`invalid ${options.name}: "${value}" (expected integer)`);
126
+ const parsed = Number.parseInt(trimmed, 10);
127
+ if (parsed < options.min) throw new ConfigError(`invalid ${options.name}: ${parsed} (must be ≥ ${options.min})`);
128
+ return parsed;
129
+ }
130
+ function parseOptionalInteger(value, options) {
131
+ if (value === void 0 || value === "") return null;
132
+ return parseInteger(value, options);
133
+ }
134
+
135
+ //#endregion
136
+ //#region src/core/paths.ts
137
+ const APP_DIR_NAME = "metabase-cli";
138
+ function configDir() {
139
+ if (process.platform === "win32") {
140
+ const appData = process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming");
141
+ return join(appData, APP_DIR_NAME);
142
+ }
143
+ const xdg = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
144
+ return join(xdg, APP_DIR_NAME);
145
+ }
146
+
147
+ //#endregion
148
+ //#region src/core/auth/rejection.ts
149
+ const REJECTIONS_FILE = "rejections.json";
150
+ const REJECTIONS_FILE_MODE = 384;
151
+ const REJECTIONS_DIR_MODE = 448;
152
+ const RejectionRecord = z.object({
153
+ reason: z.string(),
154
+ url: z.string(),
155
+ rejectedAt: z.string()
156
+ });
157
+ const RejectionsFileSchema = z.record(z.string(), RejectionRecord);
158
+ function rejectionsFilePath() {
159
+ return join(configDir(), REJECTIONS_FILE);
160
+ }
161
+ async function readRejectionsFile() {
162
+ const path = rejectionsFilePath();
163
+ let raw;
164
+ try {
165
+ raw = await promises.readFile(path, "utf8");
166
+ } catch (error) {
167
+ if (isNotFoundError(error)) return {};
168
+ throw error;
169
+ }
170
+ return parseJson(raw, RejectionsFileSchema, { source: path });
171
+ }
172
+ async function writeRejectionsFile(store) {
173
+ const path = rejectionsFilePath();
174
+ if (Object.keys(store).length === 0) {
175
+ await promises.unlink(path).catch(() => void 0);
176
+ return;
177
+ }
178
+ await promises.mkdir(dirname(path), {
179
+ recursive: true,
180
+ mode: REJECTIONS_DIR_MODE
181
+ });
182
+ await promises.writeFile(path, JSON.stringify(store, null, 2) + "\n", { mode: REJECTIONS_FILE_MODE });
183
+ if (process.platform !== "win32") await promises.chmod(path, REJECTIONS_FILE_MODE);
184
+ }
185
+ async function recordRejection(profile, input) {
186
+ const store = await readRejectionsFile();
187
+ store[profile] = {
188
+ reason: input.reason,
189
+ url: input.url,
190
+ rejectedAt: new Date().toISOString()
191
+ };
192
+ await writeRejectionsFile(store);
193
+ }
194
+ async function clearRejection(profile) {
195
+ const store = await readRejectionsFile();
196
+ if (!(profile in store)) return false;
197
+ delete store[profile];
198
+ await writeRejectionsFile(store);
199
+ return true;
200
+ }
201
+ async function readRejection(profile) {
202
+ const store = await readRejectionsFile();
203
+ return store[profile] ?? null;
204
+ }
140
205
 
141
206
  //#endregion
142
207
  //#region src/core/auth/storage.ts
143
208
  const CredentialsFileSchema = z.record(z.string(), z.string());
144
209
  const KEYRING_SERVICE = "metabase-cli";
145
210
  const CREDENTIALS_FILE = "credentials.json";
211
+ const PROFILE_INDEX_FILE = "profiles.json";
146
212
  const DEFAULT_PROFILE = "default";
147
213
  const CREDENTIALS_FILE_MODE = 384;
148
214
  const CREDENTIALS_DIR_MODE = 448;
@@ -151,17 +217,14 @@ const account = {
151
217
  profileApiKey: (profile) => `profile:${profile}:apiKey`,
152
218
  license: "license"
153
219
  };
154
- function configDir() {
155
- if (process.platform === "win32") {
156
- const appData = process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming");
157
- return join(appData, "metabase-cli");
158
- }
159
- const xdg = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
160
- return join(xdg, "metabase-cli");
161
- }
220
+ const ProfileIndexSchema = z.array(z.string());
221
+ const FILE_STORE_PROFILE_URL_PATTERN = /^profile:(.+):url$/;
162
222
  function fallbackFilePath() {
163
223
  return join(configDir(), CREDENTIALS_FILE);
164
224
  }
225
+ function profileIndexPath() {
226
+ return join(configDir(), PROFILE_INDEX_FILE);
227
+ }
165
228
  function keyringEnabled() {
166
229
  return process.env["METABASE_CLI_DISABLE_KEYRING"] !== "1";
167
230
  }
@@ -281,13 +344,74 @@ async function readProfile(name = DEFAULT_PROFILE) {
281
344
  }
282
345
  async function writeProfile(profile, name = DEFAULT_PROFILE) {
283
346
  await credentials.set(account.profileUrl(name), profile.url);
284
- return credentials.set(account.profileApiKey(name), profile.apiKey);
347
+ const location = await credentials.set(account.profileApiKey(name), profile.apiKey);
348
+ await addToProfileIndex(name);
349
+ return location;
285
350
  }
286
351
  async function clearProfile(name = DEFAULT_PROFILE) {
287
352
  const removedUrl = await credentials.remove(account.profileUrl(name));
288
353
  const removedKey = await credentials.remove(account.profileApiKey(name));
354
+ await removeFromProfileIndex(name);
289
355
  return removedUrl || removedKey;
290
356
  }
357
+ async function listProfileNames() {
358
+ const stored = await readProfileIndex();
359
+ if (stored !== null) return stored;
360
+ const backfilled = await backfillProfileIndexFromFile();
361
+ if (backfilled.length > 0) await writeProfileIndex(backfilled);
362
+ return backfilled;
363
+ }
364
+ async function readProfileIndex() {
365
+ const path = profileIndexPath();
366
+ let raw;
367
+ try {
368
+ raw = await promises.readFile(path, "utf8");
369
+ } catch (error) {
370
+ if (isNotFoundError(error)) return null;
371
+ throw error;
372
+ }
373
+ return parseJson(raw, ProfileIndexSchema, { source: path });
374
+ }
375
+ async function writeProfileIndex(names) {
376
+ const path = profileIndexPath();
377
+ const unique = [...new Set(names)].toSorted();
378
+ await promises.mkdir(dirname(path), {
379
+ recursive: true,
380
+ mode: CREDENTIALS_DIR_MODE
381
+ });
382
+ await promises.writeFile(path, JSON.stringify(unique, null, 2) + "\n", { mode: CREDENTIALS_FILE_MODE });
383
+ if (process.platform !== "win32") await promises.chmod(path, CREDENTIALS_FILE_MODE);
384
+ }
385
+ async function deleteProfileIndex() {
386
+ await promises.unlink(profileIndexPath()).catch(() => void 0);
387
+ }
388
+ async function addToProfileIndex(name) {
389
+ const current = await listProfileNames();
390
+ if (current.includes(name)) return;
391
+ await writeProfileIndex([...current, name]);
392
+ }
393
+ async function removeFromProfileIndex(name) {
394
+ const current = await listProfileNames();
395
+ const next = current.filter((entry) => entry !== name);
396
+ if (next.length === current.length) return;
397
+ if (next.length === 0) {
398
+ await deleteProfileIndex();
399
+ return;
400
+ }
401
+ await writeProfileIndex(next);
402
+ }
403
+ async function backfillProfileIndexFromFile() {
404
+ const store = await readFileStore();
405
+ const names = new Set();
406
+ for (const key of Object.keys(store)) {
407
+ const name = FILE_STORE_PROFILE_URL_PATTERN.exec(key)?.[1];
408
+ if (name !== void 0) names.add(name);
409
+ }
410
+ return [...names];
411
+ }
412
+ async function readLicense() {
413
+ return credentials.read(account.license);
414
+ }
291
415
  async function writeLicense(token) {
292
416
  return credentials.set(account.license, token);
293
417
  }
@@ -295,6 +419,89 @@ async function clearLicense() {
295
419
  return credentials.remove(account.license);
296
420
  }
297
421
 
422
+ //#endregion
423
+ //#region src/core/url.ts
424
+ function normalizeUrl(input) {
425
+ const trimmed = input.trim().replace(/\/+$/, "");
426
+ if (!/^https?:\/\//i.test(trimmed)) throw new Error("URL must start with http:// or https://");
427
+ return trimmed;
428
+ }
429
+ function originOnly(input) {
430
+ const parsed = new URL(input);
431
+ parsed.username = "";
432
+ parsed.password = "";
433
+ return parsed.origin;
434
+ }
435
+ function localUrl(port) {
436
+ return `http://localhost:${port}`;
437
+ }
438
+
439
+ //#endregion
440
+ //#region src/core/config.ts
441
+ const ENV_URL = "METABASE_URL";
442
+ const ENV_API_KEY = "METABASE_API_KEY";
443
+ const ENV_PROFILE = "METABASE_PROFILE";
444
+ const ENV_LICENSE_TOKEN = "METABASE_LICENSE_TOKEN";
445
+ function resolveProfileName(profileFlag$1) {
446
+ return explicitProfileName(profileFlag$1) ?? DEFAULT_PROFILE;
447
+ }
448
+ function explicitProfileName(profileFlag$1) {
449
+ return profileFlag$1 || process.env[ENV_PROFILE] || null;
450
+ }
451
+ function readEnvCredentials() {
452
+ return {
453
+ url: process.env[ENV_URL] ?? null,
454
+ apiKey: process.env[ENV_API_KEY] ?? null
455
+ };
456
+ }
457
+ function readEnvLicenseToken() {
458
+ return process.env[ENV_LICENSE_TOKEN] ?? null;
459
+ }
460
+ async function resolveConfig(flags) {
461
+ const profile = resolveProfileName(flags.profile);
462
+ const env = readEnvCredentials();
463
+ const flagUrl = flags.url;
464
+ const flagKey = flags.apiKey;
465
+ const needsStored = !flagUrl && !env.url || !flagKey && !env.apiKey;
466
+ const stored = needsStored ? await readProfile(profile) : null;
467
+ const urlField = pickField(flagUrl, env.url, stored?.url);
468
+ const keyField = pickField(flagKey, env.apiKey, stored?.apiKey);
469
+ if (urlField === null || keyField === null) {
470
+ const rejection = await readRejection(profile);
471
+ if (rejection !== null) throw new ConfigError(`Last login for profile "${profile}" was rejected by ${originOnly(rejection.url)}: ${rejection.reason}. Re-run \`metabase auth login --profile ${profile}\` with valid credentials.`);
472
+ throw new ConfigError(`Not authenticated for profile "${profile}". Run \`metabase auth login\`, set ${ENV_URL}/${ENV_API_KEY}, or pass --url/--api-key.`);
473
+ }
474
+ return {
475
+ url: normalizeUrl(urlField.value),
476
+ apiKey: keyField.value,
477
+ profile,
478
+ source: urlField.source === keyField.source ? urlField.source : "mixed"
479
+ };
480
+ }
481
+ async function resolveLicenseToken(flags) {
482
+ const flag = flags.token;
483
+ const env = readEnvLicenseToken();
484
+ const stored = !flag && !env ? await readLicense() : null;
485
+ const value = flag ?? env ?? stored;
486
+ if (!value) throw new ConfigError(`No license token. Pass --token, set ${ENV_LICENSE_TOKEN}, or store one with \`metabase license set\`.`);
487
+ return value;
488
+ }
489
+ function pickField(flag, env, stored) {
490
+ if (flag) return {
491
+ value: flag,
492
+ source: "flag"
493
+ };
494
+ if (env) return {
495
+ value: env,
496
+ source: "env"
497
+ };
498
+ if (stored) return {
499
+ value: stored,
500
+ source: "stored"
501
+ };
502
+ return null;
503
+ }
504
+
298
505
  //#endregion
299
506
  //#region src/runtime/signal.ts
300
507
  function createProcessAbortHandler() {
@@ -394,8 +601,13 @@ const STATUS_CLASSIFICATIONS = {
394
601
  const ErrorEnvelope = z.object({
395
602
  message: z.string().optional(),
396
603
  error: z.string().optional(),
397
- "error-message": z.string().optional()
398
- });
604
+ "error-message": z.string().optional(),
605
+ via: z.array(z.object({ message: z.string().optional() }).loose()).optional(),
606
+ "specific-errors": z.unknown().optional(),
607
+ errors: z.unknown().optional()
608
+ }).loose();
609
+ const MAX_EXTRACTED_MESSAGE_LEN = 500;
610
+ const ELLIPSIS = "…";
399
611
  var HttpError = class extends MetabaseError {
400
612
  category = "http";
401
613
  exitCode = 1;
@@ -437,7 +649,46 @@ function parseEnvelopeMessage(sanitizedBody) {
437
649
  const result = parseJsonResult(sanitizedBody, ErrorEnvelope);
438
650
  if (!result.ok) return null;
439
651
  const envelope = result.value;
440
- return envelope.message ?? envelope.error ?? envelope["error-message"] ?? null;
652
+ const topLevel = envelope.message ?? envelope.error ?? envelope["error-message"];
653
+ if (topLevel) return capLength(topLevel);
654
+ const viaMessage = envelope.via?.find((entry) => entry.message)?.message;
655
+ if (viaMessage) return capLength(viaMessage);
656
+ const specific = formatErrorTree(envelope["specific-errors"]);
657
+ if (specific) return capLength(specific);
658
+ const generic = formatErrorTree(envelope.errors);
659
+ if (generic) return capLength(generic);
660
+ return null;
661
+ }
662
+ function formatErrorTree(value) {
663
+ const entries = collectLeafEntries(value, []);
664
+ if (entries.length === 0) return null;
665
+ return entries.map(formatLeafEntry).join("; ");
666
+ }
667
+ function formatLeafEntry(entry) {
668
+ return entry.path === "" ? entry.message : `${entry.path}: ${entry.message}`;
669
+ }
670
+ function collectLeafEntries(value, path) {
671
+ if (typeof value === "string") {
672
+ const trimmed = value.trim();
673
+ return trimmed === "" ? [] : [{
674
+ path: path.join("."),
675
+ message: trimmed
676
+ }];
677
+ }
678
+ if (Array.isArray(value)) {
679
+ const messages = value.filter((entry) => typeof entry === "string" && entry.trim() !== "");
680
+ if (messages.length === 0) return [];
681
+ return [{
682
+ path: path.join("."),
683
+ message: messages.join("; ")
684
+ }];
685
+ }
686
+ if (isPlainObject(value)) return Object.entries(value).flatMap(([key, child]) => collectLeafEntries(child, [...path, key]));
687
+ return [];
688
+ }
689
+ function capLength(message) {
690
+ if (message.length <= MAX_EXTRACTED_MESSAGE_LEN) return message;
691
+ return message.slice(0, MAX_EXTRACTED_MESSAGE_LEN - ELLIPSIS.length) + ELLIPSIS;
441
692
  }
442
693
  function defaultMessageForStatus(status) {
443
694
  return STATUS_CLASSIFICATIONS[status]?.message ?? `Metabase returned ${status}`;
@@ -470,6 +721,7 @@ function sleep(ms, signal) {
470
721
  //#region src/core/http/client.ts
471
722
  const DEFAULT_TIMEOUT_MS = 3e4;
472
723
  const JSON_CONTENT_TYPE = "application/json";
724
+ const OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
473
725
  const TEXT_CONTENT_TYPE_PREFIX = "text/";
474
726
  const ERROR_BODY_BYTE_CAP = 64 * 1024;
475
727
  const USER_AGENT = `metabase-cli/${package_default.version}`;
@@ -562,7 +814,10 @@ function createClient(config, overrides = {}) {
562
814
  let body = null;
563
815
  if (opts.body !== void 0 && opts.body !== null) if (typeof opts.body === "string" || opts.body instanceof URLSearchParams) body = opts.body;
564
816
  else if (opts.body instanceof FormData || opts.body instanceof ReadableStream) body = opts.body;
565
- else {
817
+ else if (opts.body instanceof Uint8Array) {
818
+ body = opts.body;
819
+ headers.set("content-type", OCTET_STREAM_CONTENT_TYPE);
820
+ } else {
566
821
  body = JSON.stringify(opts.body);
567
822
  headers.set("content-type", JSON_CONTENT_TYPE);
568
823
  }
@@ -588,9 +843,9 @@ function createClient(config, overrides = {}) {
588
843
  expectContentType: "json"
589
844
  });
590
845
  const response = await executeRaw(prepared);
591
- const text$1 = await response.text();
846
+ const text = await response.text();
592
847
  try {
593
- return parseJson(text$1, schema, { source: prepared.url });
848
+ return parseJson(text, schema, { source: prepared.url });
594
849
  } catch (error) {
595
850
  if (error instanceof ConfigError) throw new HttpError({
596
851
  status: response.status,
@@ -598,7 +853,7 @@ function createClient(config, overrides = {}) {
598
853
  method: prepared.method,
599
854
  url: prepared.url,
600
855
  responseHeaders: response.headers,
601
- rawBody: text$1,
856
+ rawBody: text,
602
857
  redactionContext
603
858
  });
604
859
  throw error;
@@ -662,318 +917,12 @@ async function readBodyForError(response) {
662
917
  }
663
918
  }
664
919
 
665
- //#endregion
666
- //#region src/core/url.ts
667
- function normalizeUrl(input) {
668
- const trimmed = input.trim().replace(/\/+$/, "");
669
- if (!/^https?:\/\//i.test(trimmed)) throw new Error("URL must start with http:// or https://");
670
- return trimmed;
671
- }
672
- function originOnly(input) {
673
- const parsed = new URL(input);
674
- parsed.username = "";
675
- parsed.password = "";
676
- return parsed.origin;
677
- }
678
-
679
- //#endregion
680
- //#region src/core/config.ts
681
- const ENV_URL = "METABASE_URL";
682
- const ENV_API_KEY = "METABASE_API_KEY";
683
- const ENV_PROFILE = "METABASE_PROFILE";
684
- const ENV_LICENSE_TOKEN = "METABASE_LICENSE_TOKEN";
685
- function resolveProfileName(profileFlag$1) {
686
- return profileFlag$1 || process.env[ENV_PROFILE] || DEFAULT_PROFILE;
687
- }
688
- function readEnvCredentials() {
689
- return {
690
- url: process.env[ENV_URL] ?? null,
691
- apiKey: process.env[ENV_API_KEY] ?? null
692
- };
693
- }
694
- function readEnvLicenseToken() {
695
- return process.env[ENV_LICENSE_TOKEN] ?? null;
696
- }
697
- async function resolveConfig(flags) {
698
- const profile = resolveProfileName(flags.profile);
699
- const env = readEnvCredentials();
700
- const flagUrl = flags.url;
701
- const flagKey = flags.apiKey;
702
- const needsStored = !flagUrl && !env.url || !flagKey && !env.apiKey;
703
- const stored = needsStored ? await readProfile(profile) : null;
704
- const urlField = pickField(flagUrl, env.url, stored?.url);
705
- const keyField = pickField(flagKey, env.apiKey, stored?.apiKey);
706
- 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.`);
707
- return {
708
- url: normalizeUrl(urlField.value),
709
- apiKey: keyField.value,
710
- profile,
711
- source: urlField.source === keyField.source ? urlField.source : "mixed"
712
- };
713
- }
714
- function pickField(flag, env, stored) {
715
- if (flag) return {
716
- value: flag,
717
- source: "flag"
718
- };
719
- if (env) return {
720
- value: env,
721
- source: "env"
722
- };
723
- if (stored) return {
724
- value: stored,
725
- source: "stored"
726
- };
727
- return null;
728
- }
729
-
730
- //#endregion
731
- //#region src/output/notice.ts
732
- function warn(message) {
733
- process.stderr.write(message + "\n");
734
- }
735
- function listTruncationNotice(bytes) {
736
- return `… cut at ${bytes} bytes; rerun with --max-bytes 0`;
737
- }
738
- function itemOversizeNotice(bytes) {
739
- return `… item is ${bytes} bytes (exceeds --max-bytes); narrow with --fields, or pass --max-bytes 0`;
740
- }
741
-
742
- //#endregion
743
- //#region src/output/cap.ts
744
- function capListEnvelope(envelope, maxBytes) {
745
- if (maxBytes <= 0) return envelope;
746
- const fullBytes = jsonByteLength(envelope);
747
- if (fullBytes <= maxBytes) return envelope;
748
- let lo = 0;
749
- let hi = envelope.data.length;
750
- while (lo < hi) {
751
- const mid = Math.ceil((lo + hi) / 2);
752
- if (jsonByteLength(truncate(envelope, mid, fullBytes)) <= maxBytes) lo = mid;
753
- else hi = mid - 1;
754
- }
755
- return truncate(envelope, lo, fullBytes);
756
- }
757
- function truncate(envelope, count, originalBytes) {
758
- return {
759
- ...envelope,
760
- data: envelope.data.slice(0, count),
761
- returned: count,
762
- truncated: {
763
- reason: "max_bytes",
764
- bytes: originalBytes
765
- }
766
- };
767
- }
768
- function jsonByteLength(value) {
769
- return Buffer.byteLength(JSON.stringify(value), "utf8");
770
- }
771
-
772
- //#endregion
773
- //#region src/output/projection.ts
774
- function applyProjection(value, view, full, fields) {
775
- if (fields !== void 0) {
776
- if (fields.length === 0) throw new ConfigError("--fields requires at least one path");
777
- return projectFields(value, fields);
778
- }
779
- if (full) return value;
780
- const parsed = view.compactPick.safeParse(value);
781
- if (parsed.success) return parsed.data;
782
- throw new ConfigError(`compact projection failed: ${parsed.error.message}`);
783
- }
784
- function projectFields(value, fields) {
785
- const out = {};
786
- for (const path of fields) {
787
- if (path.length === 0) throw new ConfigError(`empty field path`);
788
- const parts = path.split(".");
789
- if (parts.some((part) => part.length === 0)) throw new ConfigError(`invalid field path: "${path}"`);
790
- setPath(out, parts, pickPath(value, parts));
791
- }
792
- return out;
793
- }
794
- function pickPath(value, parts) {
795
- let cursor = value;
796
- for (const part of parts) {
797
- if (!isPlainObject(cursor) || !Object.hasOwn(cursor, part)) throw new ConfigError(`unknown field path: "${parts.join(".")}"`);
798
- cursor = Reflect.get(cursor, part);
799
- }
800
- return cursor;
801
- }
802
- function setPath(target, parts, value) {
803
- let cursor = target;
804
- const lastIndex = parts.length - 1;
805
- for (const [index, part] of parts.entries()) {
806
- if (index === lastIndex) {
807
- cursor[part] = value;
808
- return;
809
- }
810
- const existing = cursor[part];
811
- if (isPlainObject(existing)) cursor = existing;
812
- else {
813
- const next = {};
814
- cursor[part] = next;
815
- cursor = next;
816
- }
817
- }
818
- }
819
- function isPlainObject(value) {
820
- return typeof value === "object" && value !== null && !Array.isArray(value);
821
- }
822
-
823
- //#endregion
824
- //#region src/output/table.ts
825
- function renderTable(rows, columns) {
826
- const head = columns.map((column) => column.label ?? column.key);
827
- const widths = columns.map((column) => column.width ?? null);
828
- const hasWidth = widths.some((width) => width !== null);
829
- const table = new Table(hasWidth ? {
830
- head,
831
- colWidths: widths
832
- } : { head });
833
- for (const row of rows) table.push(columns.map((column) => formatCell(row, column)));
834
- return table.toString();
835
- }
836
- function formatCell(row, column) {
837
- const value = row[column.key];
838
- if (column.format !== void 0) return column.format(value);
839
- return formatScalar(value);
840
- }
841
- function formatScalar(value) {
842
- if (value === null || value === void 0) return "";
843
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
844
- return JSON.stringify(value);
845
- }
846
-
847
- //#endregion
848
- //#region src/output/render.ts
849
- function renderItem(item, view, opts) {
850
- const projected = applyProjection(item, view, opts.full, opts.fields);
851
- const body = renderItemBody(item, view, projected, opts) + "\n";
852
- process.stdout.write(body);
853
- emitItemOversizeNotice(body, opts.maxBytes);
854
- }
855
- function renderList(envelope, view, opts) {
856
- if (opts.format === "json" || opts.fields !== void 0) {
857
- renderJsonEnvelope(envelope, view, opts);
858
- return;
859
- }
860
- if (envelope.data.length === 0) {
861
- process.stdout.write("(no results)\n");
862
- return;
863
- }
864
- const capped = capListEnvelope(envelope, opts.maxBytes);
865
- process.stdout.write(renderTable(capped.data, view.tableColumns) + "\n");
866
- if (capped.truncated !== void 0) warn(listTruncationNotice(capped.truncated.bytes));
867
- }
868
- function renderJsonEnvelope(envelope, view, opts) {
869
- const projectedItems = envelope.data.map((item) => applyProjection(item, view, opts.full, opts.fields));
870
- const projectedEnvelope = {
871
- ...envelope,
872
- data: projectedItems
873
- };
874
- const capped = capListEnvelope(projectedEnvelope, opts.maxBytes);
875
- process.stdout.write(JSON.stringify(capped, null, 2) + "\n");
876
- if (capped.truncated !== void 0) warn(listTruncationNotice(capped.truncated.bytes));
877
- }
878
- function renderItemBody(item, view, projected, opts) {
879
- if (opts.format === "json" || opts.fields !== void 0) return JSON.stringify(projected, null, 2);
880
- if (!opts.full) return renderKeyValueLines(columnPairs(item, view.tableColumns));
881
- return renderKeyValueLines(objectPairs(projected));
882
- }
883
- function columnPairs(item, columns) {
884
- return columns.map((column) => [column.label ?? column.key, formatCell(item, column)]);
885
- }
886
- function objectPairs(value) {
887
- if (!isPlainObject(value)) {
888
- const scalar = formatScalar(value);
889
- return scalar === "" ? [] : [["", scalar]];
890
- }
891
- return Object.entries(value).map(([key, raw]) => [key, formatScalar(raw)]);
892
- }
893
- function renderKeyValueLines(pairs) {
894
- if (pairs.length === 0) return "";
895
- const padding = Math.max(...pairs.map(([label]) => label.length));
896
- return pairs.map(([label, value]) => `${label.padEnd(padding)} ${value}`).join("\n");
897
- }
898
- function emitItemOversizeNotice(body, maxBytes) {
899
- if (maxBytes <= 0) return;
900
- const bytes = Buffer.byteLength(body, "utf8");
901
- if (bytes <= maxBytes) return;
902
- warn(itemOversizeNotice(bytes));
903
- }
904
-
905
- //#endregion
906
- //#region src/output/types.ts
907
- const DEFAULT_MAX_BYTES = 65536;
908
- function listEnvelopeSchema(item) {
909
- return z.object({
910
- data: z.array(item),
911
- returned: z.number().int().nonnegative(),
912
- total: z.number().int().nonnegative().optional(),
913
- limit: z.number().int().nonnegative().optional(),
914
- truncated: z.object({
915
- reason: z.literal("max_bytes"),
916
- bytes: z.number().int().nonnegative()
917
- }).optional()
918
- });
919
- }
920
- function wrapList(items) {
921
- return {
922
- data: items,
923
- returned: items.length,
924
- total: items.length
925
- };
926
- }
927
-
928
- //#endregion
929
- //#region src/commands/flags.ts
930
- const outputFlags = {
931
- format: {
932
- type: "string",
933
- description: "auto | json | text",
934
- default: "auto"
935
- },
936
- json: {
937
- type: "boolean",
938
- description: "Shorthand for --format json"
939
- },
940
- full: {
941
- type: "boolean",
942
- description: "Return the full object (default: compact)"
943
- },
944
- fields: {
945
- type: "string",
946
- description: "Dot-paths, comma separated (mutually exclusive with --full)"
947
- },
948
- maxBytes: {
949
- type: "string",
950
- description: "Output size cap; 0 disables",
951
- default: String(DEFAULT_MAX_BYTES),
952
- alias: "max-bytes"
953
- }
954
- };
955
- const profileFlag = { profile: {
956
- type: "string",
957
- description: "Named profile (default: 'default')"
958
- } };
959
- const connectionFlags = {
960
- url: {
961
- type: "string",
962
- description: "Metabase URL"
963
- },
964
- apiKey: {
965
- type: "string",
966
- description: "API key",
967
- alias: "api-key"
968
- }
969
- };
970
-
971
920
  //#endregion
972
921
  //#region src/output/error.ts
973
922
  function reportError(error) {
974
923
  const handled = toMetabaseError(error);
975
924
  process.stderr.write(handled.userMessage + "\n");
976
- if (process.env["METABASE_VERBOSE"] === "1" && handled.developerDetail !== null) process.stderr.write(JSON.stringify(handled.developerDetail, null, 2) + "\n");
925
+ if (process.env[VERBOSE_ENV] === "1" && handled.developerDetail !== null) process.stderr.write(JSON.stringify(handled.developerDetail, null, 2) + "\n");
977
926
  process.exitCode = handled.exitCode;
978
927
  }
979
928
 
@@ -988,9 +937,40 @@ function resolveFormat({ json, format, isTty }) {
988
937
  return isTty ? "text" : "json";
989
938
  }
990
939
 
940
+ //#endregion
941
+ //#region src/runtime/csv.ts
942
+ function parseCsv(raw) {
943
+ return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
944
+ }
945
+ function parseEnumCsv(raw, schema, flagName) {
946
+ if (raw === void 0 || raw === "") return void 0;
947
+ const parts = parseCsv(raw);
948
+ if (parts.length === 0) return void 0;
949
+ const accepted = [];
950
+ const rejected = [];
951
+ for (const part of parts) {
952
+ const result = schema.safeParse(part);
953
+ if (result.success) accepted.push(result.data);
954
+ else rejected.push(part);
955
+ }
956
+ if (rejected.length > 0) {
957
+ const allowed = Object.values(schema.enum).join(", ");
958
+ throw new ConfigError(`invalid ${flagName} value: ${rejected.join(", ")} (expected one of: ${allowed})`);
959
+ }
960
+ return accepted;
961
+ }
962
+ function parseEnum(raw, schema, flagName) {
963
+ if (raw === void 0 || raw === "") return void 0;
964
+ const result = schema.safeParse(raw);
965
+ if (!result.success) {
966
+ const allowed = Object.values(schema.enum).join(", ");
967
+ throw new ConfigError(`invalid ${flagName} value: "${raw}" (expected one of: ${allowed})`);
968
+ }
969
+ return result.data;
970
+ }
971
+
991
972
  //#endregion
992
973
  //#region src/commands/context.ts
993
- const INTEGER_PATTERN = /^-?\d+$/;
994
974
  function resolveCommonFlags(args, options = {}) {
995
975
  const isTty = options.isTty ?? Boolean(process.stdout.isTTY);
996
976
  const fields = parseFields(args.fields);
@@ -1012,15 +992,14 @@ function resolveCommonFlags(args, options = {}) {
1012
992
  }
1013
993
  function parseFields(value) {
1014
994
  if (value === void 0 || value === "") return void 0;
1015
- const parts = value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
995
+ const parts = parseCsv(value);
1016
996
  return parts.length > 0 ? parts : void 0;
1017
997
  }
1018
998
  function parseMaxBytes(value) {
1019
- const raw = value ?? String(DEFAULT_MAX_BYTES);
1020
- if (!INTEGER_PATTERN.test(raw)) throw new ConfigError(`invalid --max-bytes value: "${raw}" (expected non-negative integer)`);
1021
- const parsed = Number.parseInt(raw, 10);
1022
- if (parsed < 0) throw new ConfigError(`invalid --max-bytes value: ${parsed} (must be non-negative)`);
1023
- return parsed;
999
+ return parseInteger(value ?? String(DEFAULT_MAX_BYTES), {
1000
+ name: "--max-bytes",
1001
+ min: 0
1002
+ });
1024
1003
  }
1025
1004
 
1026
1005
  //#endregion
@@ -1032,20 +1011,27 @@ function defineMetabaseCommand(def) {
1032
1011
  async run({ args }) {
1033
1012
  try {
1034
1013
  const ctx = resolveCommonFlags(pickCommonArgs(args));
1035
- let cached = null;
1014
+ let cachedConfig = null;
1015
+ let cachedClient = null;
1016
+ const getResolvedConfig = async () => {
1017
+ if (cachedConfig === null) cachedConfig = await resolveConfig(buildConfigFlags(ctx));
1018
+ return cachedConfig;
1019
+ };
1036
1020
  const getClient = async () => {
1037
- if (cached) return cached;
1038
- const resolved = await resolveConfig(buildConfigFlags(ctx));
1039
- cached = createClient({
1040
- url: resolved.url,
1041
- apiKey: resolved.apiKey
1042
- });
1043
- return cached;
1021
+ if (cachedClient === null) {
1022
+ const resolved = await getResolvedConfig();
1023
+ cachedClient = createClient({
1024
+ url: resolved.url,
1025
+ apiKey: resolved.apiKey
1026
+ });
1027
+ }
1028
+ return cachedClient;
1044
1029
  };
1045
1030
  await def.run({
1046
1031
  args,
1047
1032
  ctx,
1048
- getClient
1033
+ getClient,
1034
+ getResolvedConfig
1049
1035
  });
1050
1036
  } catch (error) {
1051
1037
  reportError(error);
@@ -1079,4 +1065,4 @@ function buildConfigFlags(ctx) {
1079
1065
  }
1080
1066
 
1081
1067
  //#endregion
1082
- export { AbortError, ConfigError, HttpError, MetabaseError, TimeoutError, account, clearLicense, clearProfile, combineAborts, connectionFlags, createClient, credentials, defineMetabaseCommand, errorMessage, isNotFoundError, listEnvelopeSchema, normalizeUrl, originOnly, outputFlags, parseJson, profileFlag, readEnvCredentials, readEnvLicenseToken, renderItem, renderList, resolveProfileName, throwIfAborted, warn, wrapList, writeLicense, writeProfile };
1068
+ export { DEFAULT_PROFILE, HttpError, account, clearLicense, clearProfile, clearRejection, combineAborts, connectionFlags, createClient, credentials, defineMetabaseCommand, explicitProfileName, listEnvelopeSchema, listProfileNames, localUrl, normalizeUrl, originOnly, outputFlags, parseCsv, parseEnum, parseEnumCsv, parseInteger, parseJson, parseJsonOrPlain, parseOptionalInteger, profileFlag, readEnvCredentials, readEnvLicenseToken, readProfile, recordRejection, resolveLicenseToken, resolveProfileName, throwIfAborted, wrapList, writeLicense, writeProfile };