@metabase/cli 0.1.0 → 0.1.1

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--zwkmE1S.mjs +11 -0
  3. package/dist/add-collection-B1qe0D1U.mjs +54 -0
  4. package/dist/api-key-gzCbKDjL.mjs +13 -0
  5. package/dist/archive-CitmlD1e.mjs +39 -0
  6. package/dist/{archive-CsWeHXle.mjs → archive-CnhWegtR.mjs} +7 -4
  7. package/dist/archive-DQjBOXnx.mjs +44 -0
  8. package/dist/archive-Ni8-lQ1Y.mjs +44 -0
  9. package/dist/auth-BPjsrFxM.mjs +19 -0
  10. package/dist/{body-Dv9hQ0Qk.mjs → body-DRBgxS6-.mjs} +3 -2
  11. package/dist/{branches-BujtceGr.mjs → branches-C5Jcw8wu.mjs} +8 -6
  12. package/dist/cancel-Ca3r7Y6v.mjs +56 -0
  13. package/dist/{cancel-task-CT2xUMRg.mjs → cancel-task-C1-8vDKS.mjs} +9 -7
  14. package/dist/card-BGAy3eIb.mjs +20 -0
  15. package/dist/{card-CsXk8T6A.mjs → card-CAEZWixN.mjs} +34 -15
  16. package/dist/cards-CILfMPUP.mjs +37 -0
  17. package/dist/cli.mjs +33 -14
  18. package/dist/collection-B3sPXRLs.mjs +163 -0
  19. package/dist/collection-D8cnCB98.mjs +19 -0
  20. package/dist/create-3Z6rm-4O.mjs +44 -0
  21. package/dist/create-BsY5RrVY.mjs +44 -0
  22. package/dist/create-C4OCclBD.mjs +48 -0
  23. package/dist/create-COsD7Vzm.mjs +48 -0
  24. package/dist/create-CP8ou91U.mjs +125 -0
  25. package/dist/create-CeIi_QLj.mjs +66 -0
  26. package/dist/create-CqNw6PmR.mjs +50 -0
  27. package/dist/create-DE_5NrFy.mjs +48 -0
  28. package/dist/{create-B8ektf-R.mjs → create-MEhhhgMC.mjs} +8 -6
  29. package/dist/create-QxDmleKJ.mjs +48 -0
  30. package/dist/{create-branch-goZBTNnr.mjs → create-branch-CKMYaAHk.mjs} +9 -7
  31. package/dist/credentials-CwRKvdP2.mjs +85 -0
  32. package/dist/{current-task-DBjRNCFq.mjs → current-task-Dutjys16.mjs} +9 -7
  33. package/dist/dashboard-B4fVp392.mjs +20 -0
  34. package/dist/dashboard-CnMD04PQ.mjs +163 -0
  35. package/dist/database-BMTb0CzV.mjs +17 -0
  36. package/dist/database-Dvkfy3JM.mjs +51 -0
  37. package/dist/db-ACuuaEok.mjs +22 -0
  38. package/dist/{delete-8vGU35r3.mjs → delete-BMQZuVXZ.mjs} +7 -5
  39. package/dist/{delete-B27KLF5X.mjs → delete-BvcA4jPj.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-DUPjHKk4.mjs} +7 -5
  42. package/dist/deprovision-Bsc1S15j.mjs +61 -0
  43. package/dist/{dirty-aNUuph4I.mjs → dirty-CXcdoUhY.mjs} +8 -6
  44. package/dist/docker-D-ieBsP7.mjs +612 -0
  45. package/dist/eid-pvOsEMPZ.mjs +13 -0
  46. package/dist/{export-QDkuuzSE.mjs → export-BjGhLEOi.mjs} +30 -23
  47. package/dist/field-BI2bt8e9.mjs +18 -0
  48. package/dist/field-DciLbuv-.mjs +276 -0
  49. package/dist/fields-Do8HHm_T.mjs +38 -0
  50. package/dist/flag-pair-DtR1AiBQ.mjs +17 -0
  51. package/dist/{get-BGBIzMKY.mjs → get-BGFGWkH0.mjs} +6 -4
  52. package/dist/get-BmE_VHdl.mjs +36 -0
  53. package/dist/{get-DI_IJvgk.mjs → get-C7sshmqF.mjs} +6 -4
  54. package/dist/get-CObKBj2J.mjs +36 -0
  55. package/dist/get-Cq5U_Eep.mjs +40 -0
  56. package/dist/get-D4GUJBiX.mjs +41 -0
  57. package/dist/{get-COXHplHP.mjs → get-DFrsi77F.mjs} +7 -5
  58. package/dist/get-DczxeETg.mjs +53 -0
  59. package/dist/{get-Cl8-IauC.mjs → get-DeQa3ThJ.mjs} +7 -4
  60. package/dist/get-DhZ_dGUb.mjs +36 -0
  61. package/dist/{get-i6LWOByV.mjs → get-DzCVafyO.mjs} +6 -4
  62. package/dist/get-YCnVqq-z.mjs +49 -0
  63. package/dist/get-run-CTyW29s3.mjs +36 -0
  64. package/dist/git-sync-BOmT8HEU.mjs +28 -0
  65. package/dist/{has-remote-changes-hjKoQuRy.mjs → has-remote-changes-xX8vMVsX.mjs} +8 -6
  66. package/dist/{import-HJsSKRYx.mjs → import-CaAUNtXz.mjs} +11 -9
  67. package/dist/{input-Dojr-RTw.mjs → input-ikCiip6x.mjs} +2 -1
  68. package/dist/is-dirty-CPu-xqkW.mjs +10 -0
  69. package/dist/{is-dirty-1Qy7hiHB.mjs → is-dirty-mgxEwEk4.mjs} +5 -4
  70. package/dist/items-Cg67tdto.mjs +77 -0
  71. package/dist/{key-DBxPSFwi.mjs → key-NDEARu2L.mjs} +1 -1
  72. package/dist/{license-MoWse3ZI.mjs → license-CwKzVMD0.mjs} +3 -3
  73. package/dist/list-BqdNQ1nU.mjs +47 -0
  74. package/dist/list-BwGdD45N.mjs +32 -0
  75. package/dist/list-CfOVsAZz.mjs +55 -0
  76. package/dist/list-CpyNn1Zn.mjs +32 -0
  77. package/dist/list-CwwOoGLK.mjs +40 -0
  78. package/dist/{list-C_PRdL5e.mjs → list-DD8CQx8l.mjs} +7 -5
  79. package/dist/{list-Bk6RsbJl.mjs → list-DL-RWpIE.mjs} +5 -3
  80. package/dist/list-DLlq3FyS.mjs +61 -0
  81. package/dist/list-DdQ4jmUQ.mjs +52 -0
  82. package/dist/{list-C4Ajrw8f.mjs → list-DshbLoqR.mjs} +6 -3
  83. package/dist/{list-C8tdLOH5.mjs → list-DzTMpoBs.mjs} +5 -3
  84. package/dist/list-JgRtCzz3.mjs +32 -0
  85. package/dist/{list-CWt3fqrZ.mjs → list-WzgJcwB5.mjs} +5 -3
  86. package/dist/{login-C9WTwNn6.mjs → login-DJnmR2wX.mjs} +14 -5
  87. package/dist/{logout-oLszGCOg.mjs → logout-BMe_1Zp8.mjs} +7 -6
  88. package/dist/logs-CQxKJ3HG.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-BGyYbtqO.mjs +19 -0
  92. package/dist/metadata-CLIALntn.mjs +37 -0
  93. package/dist/metadata-T-fNUWg_.mjs +38 -0
  94. package/dist/{package-BGfw4ZWJ.mjs → package-DBsS7a5x.mjs} +7 -1
  95. package/dist/paginate-CTSfuYiF.mjs +49 -0
  96. package/dist/parse-id-BUOZQqjp.mjs +12 -0
  97. package/dist/parse-ref-DGvh4aDn.mjs +17 -0
  98. package/dist/parse-schemas-BnW4T1_I.mjs +12 -0
  99. package/dist/{poll-ILanYysl.mjs → poll-DMmmZWvi.mjs} +2 -1
  100. package/dist/{poll-task-DbpsiQhl.mjs → poll-task-2Ckiwp8U.mjs} +8 -7
  101. package/dist/predicates-DiIiS3k7.mjs +153 -0
  102. package/dist/preflight-CC_g6EWU.mjs +91 -0
  103. package/dist/{prompt-DpT8yAVy.mjs → prompt-Bf3DQ-qE.mjs} +1 -1
  104. package/dist/provision-BUgWJWAV.mjs +77 -0
  105. package/dist/ps-BUNHygf-.mjs +10 -0
  106. package/dist/ps-Yv0JjLVN.mjs +78 -0
  107. package/dist/{query-PihYi-UZ.mjs → query-CzfbuG8a.mjs} +38 -13
  108. package/dist/query-UIebHmbT.mjs +90 -0
  109. package/dist/remove-BAUbcwuF.mjs +98 -0
  110. package/dist/{remove-B2hVYn1v.mjs → remove-CN2PNGTR.mjs} +6 -5
  111. package/dist/remove-collection-C6NxEh53.mjs +38 -0
  112. package/dist/render-DXv-D6fU.mjs +182 -0
  113. package/dist/rescan-values-CcB4F9qa.mjs +43 -0
  114. package/dist/revision-message-flag-CWQbKhdl.mjs +11 -0
  115. package/dist/{run-C2so6Qp6.mjs → run-BjXZtu_6.mjs} +27 -36
  116. package/dist/runs-CXx7l1NY.mjs +54 -0
  117. package/dist/{runtime-C9CEZhcn.mjs → runtime-D7jihh81.mjs} +425 -442
  118. package/dist/schema-tables-BCJT2DM_.mjs +45 -0
  119. package/dist/schemas-DlNpbn4H.mjs +47 -0
  120. package/dist/{search-CopOytXY.mjs → search-Dt-6mdHZ.mjs} +6 -19
  121. package/dist/segment-BMrUBz94.mjs +70 -0
  122. package/dist/segment-C52QNnSs.mjs +19 -0
  123. package/dist/{set-BcF7M1GQ.mjs → set-DCESWpi3.mjs} +6 -4
  124. package/dist/{set-CbibegpA.mjs → set-L7cuHjVZ.mjs} +8 -6
  125. package/dist/{setting-U3NtBMFo.mjs → setting-DysGAuYS.mjs} +3 -3
  126. package/dist/setup-_ypJDPAY.mjs +71 -0
  127. package/dist/snippet-Dw0Sjzkr.mjs +64 -0
  128. package/dist/snippet-vb3G9R8a.mjs +19 -0
  129. package/dist/start-BokXnb0V.mjs +350 -0
  130. package/dist/{stash-DOBbYozC.mjs → stash-CaGX6PfX.mjs} +9 -7
  131. package/dist/{status-Buf1ZbNR.mjs → status-BaX9vedb.mjs} +10 -8
  132. package/dist/{status-CUcs8XBH.mjs → status-CyecXzN4.mjs} +4 -2
  133. package/dist/{status-D1F5XHae.mjs → status-RpVyPEty.mjs} +4 -2
  134. package/dist/stop-BRuF_Cg1.mjs +81 -0
  135. package/dist/summary-CpEOiOlZ.mjs +41 -0
  136. package/dist/sync-schema-4Cl4h8Jn.mjs +43 -0
  137. package/dist/table-BeMWuvzO.mjs +19 -0
  138. package/dist/{table-Cfk7oSvw.mjs → table-jljEqZ0R.mjs} +22 -9
  139. package/dist/transform-DwRc-w6y.mjs +24 -0
  140. package/dist/{transform-B5uRpg1G.mjs → transform-IEX4Mx3X.mjs} +56 -2
  141. package/dist/transform-job-BigWrctt.mjs +19 -0
  142. package/dist/{transform-job-C7QXWTVE.mjs → transform-job-Csr86muI.mjs} +7 -0
  143. package/dist/translate-DqLlXXUx.mjs +111 -0
  144. package/dist/tree-BT24nkLM.mjs +32 -0
  145. package/dist/update-BCXKQi2n.mjs +52 -0
  146. package/dist/{update-CL8tRbxr.mjs → update-BXbLmC2b.mjs} +9 -7
  147. package/dist/update-C1Frz9GR.mjs +52 -0
  148. package/dist/update-C5goGhNr.mjs +56 -0
  149. package/dist/update-CCOyB0iT.mjs +73 -0
  150. package/dist/update-D04NMueX.mjs +59 -0
  151. package/dist/update-D6WVtNV1.mjs +57 -0
  152. package/dist/update-DFR46LsB.mjs +56 -0
  153. package/dist/update-DyLItrpV.mjs +56 -0
  154. package/dist/update-dashcard-av0_PYeg.mjs +71 -0
  155. package/dist/update-mrgvQF4i.mjs +51 -0
  156. package/dist/url-x4wn_l3k.mjs +54 -0
  157. package/dist/uuid-BZHbti8B.mjs +47 -0
  158. package/dist/validate-DCYx6jdL.mjs +1496 -0
  159. package/dist/validate-query-B07oGG4K.mjs +37 -0
  160. package/dist/values-Be6i0Fs9.mjs +36 -0
  161. package/dist/{wait-Bugr9eXD.mjs → wait-BMqQD8k_.mjs} +10 -8
  162. package/dist/wait-CWizX_sR.mjs +19 -0
  163. package/dist/wait-flags-DO3ar2tf.mjs +35 -0
  164. package/dist/workspace-CG1xyJ86.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
@@ -0,0 +1,350 @@
1
+ import "./package-DBsS7a5x.mjs";
2
+ import "./command-augment-D9pI9Vbh.mjs";
3
+ import { renderItem, warn } from "./render-DXv-D6fU.mjs";
4
+ import { ConfigError, errorMessage } from "./predicates-DiIiS3k7.mjs";
5
+ import { connectionFlags, createClient, defineMetabaseCommand, localUrl, outputFlags, parseInteger, parseOptionalInteger, profileFlag, resolveLicenseToken } from "./runtime-D7jihh81.mjs";
6
+ import { parseId } from "./parse-id-BUOZQqjp.mjs";
7
+ import { pollUntil } from "./poll-DMmmZWvi.mjs";
8
+ import { Workspace } from "./workspace-DVuqKJGG.mjs";
9
+ import { CONTAINER_REPO_DIR, checkDockerReady, containerLifecycleStatus, containerNameFor, pullImage, removeContainer, runProcess, runWorkspaceContainer, scrubContainerConfig, waitForConfigConsumed } from "./docker-D-ieBsP7.mjs";
10
+ import { REPO_SYNC_MODES, RepoSyncMode, buildCredentialsJson, generateWorkspaceCredentials, injectCredentialsIntoConfig, injectRepoSettingsIntoConfig } from "./workspace-credentials-B6BL-X0d.mjs";
11
+ import { z } from "zod";
12
+ import { stat } from "node:fs/promises";
13
+ import { resolve } from "node:path";
14
+ import { createServer } from "node:net";
15
+
16
+ //#region src/core/http/probe.ts
17
+ async function probeHealth(url, timeoutMs) {
18
+ try {
19
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
20
+ return {
21
+ ready: response.ok,
22
+ status: response.status
23
+ };
24
+ } catch (error) {
25
+ if (error instanceof Error) return {
26
+ ready: false,
27
+ status: null
28
+ };
29
+ throw error;
30
+ }
31
+ }
32
+
33
+ //#endregion
34
+ //#region src/runtime/port.ts
35
+ const PORT_SCAN_LIMIT = 100;
36
+ function isPortFree(port) {
37
+ return new Promise((resolve$1) => {
38
+ const server = createServer();
39
+ server.unref();
40
+ server.once("error", () => resolve$1(false));
41
+ server.once("listening", () => {
42
+ server.close(() => resolve$1(true));
43
+ });
44
+ server.listen(port, "0.0.0.0");
45
+ });
46
+ }
47
+ async function findFreePort(start) {
48
+ for (let port = start; port < start + PORT_SCAN_LIMIT; port++) if (await isPortFree(port)) return port;
49
+ throw new ConfigError(`no free port in range ${start}..${start + PORT_SCAN_LIMIT - 1}`);
50
+ }
51
+
52
+ //#endregion
53
+ //#region src/commands/workspace/start.ts
54
+ const DEFAULT_IMAGE = "metabase/metabase-dev:feature-workspaces-v2";
55
+ const DEFAULT_HOST_PORT = 3e3;
56
+ const DEFAULT_READY_TIMEOUT_MS = 24e4;
57
+ const HEALTH_INTERVAL_MS = 2e3;
58
+ const HEALTH_MAX_INTERVAL_MS = 1e4;
59
+ const HEALTH_PROBE_TIMEOUT_MS = 4e3;
60
+ const DEFAULT_REPO_MODE = "read-write";
61
+ const REPO_FILE_URL = `file://${CONTAINER_REPO_DIR}`;
62
+ const METADATA_IMPORT_TIMEOUT_MS = DEFAULT_READY_TIMEOUT_MS;
63
+ const MetadataImportResult = z.object({ queued: z.literal(true) });
64
+ const StartResult = z.object({
65
+ workspace_id: z.number().int().positive(),
66
+ workspace_name: z.string(),
67
+ container_name: z.string(),
68
+ state: z.enum(["running", "starting"]),
69
+ host_port: z.number().int().positive(),
70
+ url: z.string(),
71
+ image: z.string()
72
+ });
73
+ const startResultView = {
74
+ compactPick: StartResult.pick({
75
+ workspace_id: true,
76
+ workspace_name: true,
77
+ state: true,
78
+ url: true
79
+ }).strip(),
80
+ tableColumns: [
81
+ {
82
+ key: "workspace_id",
83
+ label: "ID"
84
+ },
85
+ {
86
+ key: "workspace_name",
87
+ label: "Name"
88
+ },
89
+ {
90
+ key: "state",
91
+ label: "State"
92
+ },
93
+ {
94
+ key: "url",
95
+ label: "URL"
96
+ }
97
+ ]
98
+ };
99
+ var start_default = defineMetabaseCommand({
100
+ meta: {
101
+ name: "start",
102
+ description: "Start a local Docker container that serves as the workspace's dev instance"
103
+ },
104
+ args: {
105
+ ...outputFlags,
106
+ ...profileFlag,
107
+ ...connectionFlags,
108
+ id: {
109
+ type: "positional",
110
+ description: "Workspace id",
111
+ required: true
112
+ },
113
+ port: {
114
+ type: "string",
115
+ description: `Host port to bind (default: ${DEFAULT_HOST_PORT}; auto-shifts up when this flag is omitted, fails on collision when set explicitly)`
116
+ },
117
+ image: {
118
+ type: "string",
119
+ description: `Docker image to run (default: ${DEFAULT_IMAGE})`,
120
+ default: DEFAULT_IMAGE
121
+ },
122
+ wait: {
123
+ type: "boolean",
124
+ description: "Block until /api/health is ready before returning. Default: return as soon as the container has consumed config.yml. (Implied when --metadata is on, since the import requires a live API.)",
125
+ default: false
126
+ },
127
+ timeout: {
128
+ type: "string",
129
+ description: `Per-phase readiness deadline in ms — covers post-create config consumption and (with --wait) the /api/health probe. Default: ${DEFAULT_READY_TIMEOUT_MS}.`,
130
+ default: String(DEFAULT_READY_TIMEOUT_MS)
131
+ },
132
+ pull: {
133
+ type: "boolean",
134
+ description: "Pull the image before starting",
135
+ default: true
136
+ },
137
+ metadata: {
138
+ type: "boolean",
139
+ description: "Fetch the workspace's warehouse metadata from the parent and POST it to the child instance once it is healthy",
140
+ default: true
141
+ },
142
+ force: {
143
+ type: "boolean",
144
+ description: "Remove and recreate the container even if it is running. Stopped containers (exited/created/dead) are recreated automatically without this flag.",
145
+ default: false
146
+ },
147
+ repo: {
148
+ type: "string",
149
+ description: `Bind-mount a host directory (typically a remote-sync git repo) into the container at ${CONTAINER_REPO_DIR}. Sets remote-sync-url=${REPO_FILE_URL} in the workspace config.yml so the child boots already wired to the repo.`
150
+ },
151
+ "repo-branch": {
152
+ type: "string",
153
+ description: "Branch to set as remote-sync-branch (default: the current branch of the host repo, read from HEAD)"
154
+ },
155
+ "repo-mode": {
156
+ type: "string",
157
+ description: "remote-sync-type: 'read-write' (default) or 'read-only'",
158
+ default: DEFAULT_REPO_MODE
159
+ }
160
+ },
161
+ outputSchema: StartResult,
162
+ examples: [
163
+ "metabase workspace start 1",
164
+ "metabase workspace start 1 --wait",
165
+ "metabase workspace start 1 --port 3100",
166
+ "metabase workspace start 1 --image metabase/metabase-dev:feature-workspaces-v2 --no-pull",
167
+ "metabase workspace start 1 --force",
168
+ "metabase workspace start 1 --repo /path/to/sync-repo --wait",
169
+ "metabase workspace start 1 --repo /path/to/sync-repo --repo-branch dev --repo-mode read-only"
170
+ ],
171
+ async run({ args, ctx, getClient, getResolvedConfig }) {
172
+ const workspaceId = parseId(args.id);
173
+ const containerName = containerNameFor(workspaceId);
174
+ const requestedPort = parseOptionalInteger(args.port, {
175
+ name: "--port",
176
+ min: 1
177
+ });
178
+ const readyTimeoutMs = parseInteger(args.timeout ?? String(DEFAULT_READY_TIMEOUT_MS), {
179
+ name: "--timeout",
180
+ min: 1e3
181
+ });
182
+ const client = await getClient();
183
+ const resolved = await getResolvedConfig();
184
+ const licenseToken = await resolveLicenseToken({});
185
+ await checkDockerReady();
186
+ await ensureNoExistingContainer(workspaceId, containerName, args.force);
187
+ const pullPromise = args.pull ? pullImage(args.image) : Promise.resolve();
188
+ const workspace = await client.requestParsed(Workspace, `/api/ee/workspace-manager/${workspaceId}`);
189
+ assertAllDatabasesProvisioned(workspace);
190
+ const hostPort = await resolveHostPort(requestedPort);
191
+ const [parentConfigYaml, metadataJson, repoOptions] = await Promise.all([
192
+ fetchConfigYaml(client, workspaceId),
193
+ args.metadata ? fetchMetadataJson(client, workspaceId) : Promise.resolve(null),
194
+ resolveRepoOptions({
195
+ hostPath: args.repo,
196
+ branch: args["repo-branch"],
197
+ mode: args["repo-mode"]
198
+ })
199
+ ]);
200
+ const credentials = generateWorkspaceCredentials(workspaceId);
201
+ const configWithCredentials = injectCredentialsIntoConfig(parentConfigYaml, credentials);
202
+ const configYaml = repoOptions !== null ? injectRepoSettingsIntoConfig(configWithCredentials, repoOptions.repo) : configWithCredentials;
203
+ const credentialsJson = buildCredentialsJson(credentials);
204
+ await pullPromise;
205
+ await runWorkspaceContainer({
206
+ workspaceId,
207
+ workspaceName: workspace.name,
208
+ profile: resolved.profile,
209
+ parentUrl: resolved.url,
210
+ image: args.image,
211
+ hostPort,
212
+ configYaml,
213
+ credentialsJson,
214
+ licenseToken,
215
+ bindMounts: repoOptions === null ? [] : [repoOptions.bindMount]
216
+ });
217
+ await waitForConfigConsumed(workspaceId, readyTimeoutMs);
218
+ try {
219
+ await scrubContainerConfig(workspaceId);
220
+ } catch (error) {
221
+ warn(`could not scrub in-container config.yml: ${errorMessage(error)}`);
222
+ }
223
+ const needsHealth = args.wait || metadataJson !== null;
224
+ if (needsHealth) await waitForHealth(hostPort, readyTimeoutMs);
225
+ if (metadataJson !== null) await importMetadataIntoChild(hostPort, credentials, metadataJson);
226
+ const result = {
227
+ workspace_id: workspaceId,
228
+ workspace_name: workspace.name,
229
+ container_name: containerName,
230
+ state: needsHealth ? "running" : "starting",
231
+ host_port: hostPort,
232
+ url: localUrl(hostPort),
233
+ image: args.image
234
+ };
235
+ renderItem(result, startResultView, ctx);
236
+ }
237
+ });
238
+ function assertAllDatabasesProvisioned(workspace) {
239
+ const databases = workspace.databases ?? [];
240
+ if (databases.length === 0) throw new ConfigError(`workspace ${workspace.id} has no databases — provision at least one before starting`);
241
+ const unready = databases.filter((entry) => entry.status !== "provisioned");
242
+ if (unready.length > 0) {
243
+ const summary = unready.map((entry) => `database ${entry.database_id}=${entry.status}`).join(", ");
244
+ throw new ConfigError(`workspace ${workspace.id} is not ready: ${summary}. Wait for provisioning to finish.`);
245
+ }
246
+ }
247
+ async function ensureNoExistingContainer(workspaceId, containerName, force) {
248
+ if (force) {
249
+ await removeContainer(containerName);
250
+ return;
251
+ }
252
+ const status = await containerLifecycleStatus(containerName);
253
+ if (status === "missing") return;
254
+ if (status === "exited" || status === "created" || status === "dead") {
255
+ await removeContainer(containerName);
256
+ return;
257
+ }
258
+ throw new ConfigError(`container ${containerName} is currently ${status}. Run \`metabase workspace stop ${workspaceId}\` first, or use --force to recreate it.`);
259
+ }
260
+ async function resolveHostPort(requested) {
261
+ if (requested !== null) {
262
+ if (!await isPortFree(requested)) throw new ConfigError(`port ${requested} is already in use`);
263
+ return requested;
264
+ }
265
+ if (await isPortFree(DEFAULT_HOST_PORT)) return DEFAULT_HOST_PORT;
266
+ return findFreePort(DEFAULT_HOST_PORT + 1);
267
+ }
268
+ async function fetchConfigYaml(client, workspaceId) {
269
+ const response = await client.requestRaw(`/api/ee/workspace-manager/${workspaceId}/config`, { expectContentType: "binary" });
270
+ return response.text();
271
+ }
272
+ async function fetchMetadataJson(client, workspaceId) {
273
+ const response = await client.requestRaw(`/api/ee/workspace-manager/${workspaceId}/metadata/export`, {
274
+ expectContentType: "binary",
275
+ query: {
276
+ "with-databases": true,
277
+ "with-tables": true,
278
+ "with-fields": true
279
+ }
280
+ });
281
+ return new Uint8Array(await response.arrayBuffer());
282
+ }
283
+ async function waitForHealth(hostPort, timeoutMs) {
284
+ const url = `${localUrl(hostPort)}/api/health`;
285
+ await pollUntil(() => probeHealth(url, HEALTH_PROBE_TIMEOUT_MS), (probe) => probe.ready, {
286
+ intervalMs: HEALTH_INTERVAL_MS,
287
+ maxIntervalMs: HEALTH_MAX_INTERVAL_MS,
288
+ backoff: "exponential",
289
+ timeoutMs
290
+ });
291
+ }
292
+ async function importMetadataIntoChild(hostPort, credentials, metadataJson) {
293
+ const childClient = createClient({
294
+ url: localUrl(hostPort),
295
+ apiKey: credentials.api_key.key
296
+ });
297
+ await childClient.requestParsed(MetadataImportResult, "/api/ee/serialization/metadata/import", {
298
+ method: "POST",
299
+ body: metadataJson,
300
+ timeoutMs: METADATA_IMPORT_TIMEOUT_MS
301
+ });
302
+ }
303
+ async function resolveRepoOptions(input) {
304
+ if (input.hostPath === void 0 || input.hostPath === "") {
305
+ const explicitBranch = input.branch !== void 0;
306
+ const explicitNonDefaultMode = input.mode !== void 0 && input.mode !== DEFAULT_REPO_MODE;
307
+ if (explicitBranch || explicitNonDefaultMode) throw new ConfigError("--repo-branch and --repo-mode require --repo to point at a host repo path");
308
+ return null;
309
+ }
310
+ const hostPath = resolve(input.hostPath);
311
+ const stats = await stat(hostPath).catch(() => null);
312
+ if (stats === null || !stats.isDirectory()) throw new ConfigError(`--repo path does not exist or is not a directory: ${hostPath}`);
313
+ const mode = parseRepoMode(input.mode);
314
+ const branch = input.branch ?? await detectBranch(hostPath);
315
+ return {
316
+ bindMount: {
317
+ hostPath,
318
+ containerPath: CONTAINER_REPO_DIR,
319
+ readOnly: mode === "read-only"
320
+ },
321
+ repo: {
322
+ url: REPO_FILE_URL,
323
+ branch,
324
+ mode
325
+ }
326
+ };
327
+ }
328
+ function parseRepoMode(raw) {
329
+ const result = RepoSyncMode.safeParse(raw ?? DEFAULT_REPO_MODE);
330
+ if (!result.success) throw new ConfigError(`invalid --repo-mode: "${raw}" (expected one of: ${REPO_SYNC_MODES.join(", ")})`);
331
+ return result.data;
332
+ }
333
+ async function detectBranch(hostPath) {
334
+ const result = await runProcess("git", [
335
+ "-C",
336
+ hostPath,
337
+ "symbolic-ref",
338
+ "--short",
339
+ "HEAD"
340
+ ]).catch((error) => {
341
+ throw new ConfigError(`--repo-branch not provided and could not detect a branch at ${hostPath}: ${errorMessage(error)}`);
342
+ });
343
+ if (result.exitCode !== 0) throw new ConfigError(`--repo-branch not provided and \`git symbolic-ref\` at ${hostPath} failed: ${result.stderr.trim() || "no output"}`);
344
+ const branch = result.stdout.trim();
345
+ if (branch === "") throw new ConfigError(`--repo-branch not provided and HEAD at ${hostPath} resolved to an empty branch name`);
346
+ return branch;
347
+ }
348
+
349
+ //#endregion
350
+ export { start_default as default };
@@ -1,12 +1,14 @@
1
- import "./package-BGfw4ZWJ.mjs";
1
+ import "./package-DBsS7a5x.mjs";
2
2
  import "./command-augment-D9pI9Vbh.mjs";
3
- import { ConfigError, connectionFlags, defineMetabaseCommand, outputFlags, profileFlag, renderItem } from "./runtime-C9CEZhcn.mjs";
4
- import { parseId } from "./parse-id-BhmmfyCP.mjs";
5
- import { REMOTE_SYNC_PATHS, SyncTask, pollFlags, pollSyncTask, throwIfFailedTask } from "./poll-task-DbpsiQhl.mjs";
6
- import "./poll-ILanYysl.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import { ConfigError } from "./predicates-DiIiS3k7.mjs";
5
+ import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-D7jihh81.mjs";
6
+ import { parseId } from "./parse-id-BUOZQqjp.mjs";
7
+ import { REMOTE_SYNC_PATHS, SyncTask, pollFlags, pollSyncTask, throwIfFailedTask } from "./poll-task-2Ckiwp8U.mjs";
8
+ import "./poll-DMmmZWvi.mjs";
7
9
  import { z } from "zod";
8
10
 
9
- //#region src/commands/sync/stash.ts
11
+ //#region src/commands/git-sync/stash.ts
10
12
  const SyncStashKickoff = z.object({
11
13
  status: z.literal("success"),
12
14
  message: z.string(),
@@ -60,7 +62,7 @@ var stash_default = defineMetabaseCommand({
60
62
  ...pollFlags
61
63
  },
62
64
  outputSchema: SyncStashResult,
63
- examples: ["metabase sync stash --new-branch wip", "metabase sync stash --new-branch wip -m \"work in progress\" --json"],
65
+ examples: ["metabase git-sync stash --new-branch wip", "metabase git-sync stash --new-branch wip -m \"work in progress\" --json"],
64
66
  async run({ args, ctx, getClient }) {
65
67
  const newBranch = args.newBranch.trim();
66
68
  if (newBranch === "") throw new ConfigError("invalid new-branch: must not be blank");
@@ -1,12 +1,14 @@
1
- import "./package-BGfw4ZWJ.mjs";
1
+ import "./package-DBsS7a5x.mjs";
2
2
  import "./command-augment-D9pI9Vbh.mjs";
3
- import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag, renderItem } from "./runtime-C9CEZhcn.mjs";
4
- import { REMOTE_SYNC_PATHS, SyncTask, fetchCurrentTask, fetchOptionalParsed } from "./poll-task-DbpsiQhl.mjs";
5
- import "./poll-ILanYysl.mjs";
6
- import { IsDirtyResult } from "./is-dirty-1Qy7hiHB.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-D7jihh81.mjs";
6
+ import { REMOTE_SYNC_PATHS, SyncTask, fetchCurrentTask, fetchOptionalParsed } from "./poll-task-2Ckiwp8U.mjs";
7
+ import "./poll-DMmmZWvi.mjs";
8
+ import { IsDirtyResult } from "./is-dirty-mgxEwEk4.mjs";
7
9
  import { z } from "zod";
8
10
 
9
- //#region src/commands/sync/status.ts
11
+ //#region src/commands/git-sync/status.ts
10
12
  const RemoteSyncBranch = z.string().nullable();
11
13
  const SyncStatus = z.object({
12
14
  branch: z.string().nullable(),
@@ -33,7 +35,7 @@ const syncStatusView = {
33
35
  var status_default = defineMetabaseCommand({
34
36
  meta: {
35
37
  name: "status",
36
- description: "Show current sync state (branch, dirty, current task)"
38
+ description: "Show current git-sync state (branch, dirty, current task)"
37
39
  },
38
40
  args: {
39
41
  ...outputFlags,
@@ -41,7 +43,7 @@ var status_default = defineMetabaseCommand({
41
43
  ...connectionFlags
42
44
  },
43
45
  outputSchema: SyncStatus,
44
- examples: ["metabase sync status", "metabase sync status --json"],
46
+ examples: ["metabase git-sync status", "metabase git-sync status --json"],
45
47
  async run({ ctx, getClient }) {
46
48
  const client = await getClient();
47
49
  const [branch, isDirty, currentTask] = await Promise.all([
@@ -1,6 +1,8 @@
1
- import "./package-BGfw4ZWJ.mjs";
1
+ import "./package-DBsS7a5x.mjs";
2
2
  import "./command-augment-D9pI9Vbh.mjs";
3
- import { account, credentials, defineMetabaseCommand, originOnly, outputFlags, profileFlag, renderItem, resolveProfileName } from "./runtime-C9CEZhcn.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { account, credentials, defineMetabaseCommand, originOnly, outputFlags, profileFlag, resolveProfileName } from "./runtime-D7jihh81.mjs";
4
6
  import { z } from "zod";
5
7
 
6
8
  //#region src/commands/auth/status.ts
@@ -1,6 +1,8 @@
1
- import "./package-BGfw4ZWJ.mjs";
1
+ import "./package-DBsS7a5x.mjs";
2
2
  import "./command-augment-D9pI9Vbh.mjs";
3
- import { account, credentials, defineMetabaseCommand, outputFlags, renderItem } from "./runtime-C9CEZhcn.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { account, credentials, defineMetabaseCommand, outputFlags } from "./runtime-D7jihh81.mjs";
4
6
  import { z } from "zod";
5
7
 
6
8
  //#region src/commands/license/status.ts
@@ -0,0 +1,81 @@
1
+ import "./package-DBsS7a5x.mjs";
2
+ import "./command-augment-D9pI9Vbh.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { defineMetabaseCommand, outputFlags } from "./runtime-D7jihh81.mjs";
6
+ import { parseId } from "./parse-id-BUOZQqjp.mjs";
7
+ import "./poll-DMmmZWvi.mjs";
8
+ import { checkDockerReady, containerLifecycleStatus, containerNameFor, stopContainer } from "./docker-D-ieBsP7.mjs";
9
+ import { LocalWorkspaceState } from "./ps-Yv0JjLVN.mjs";
10
+ import { z } from "zod";
11
+
12
+ //#region src/commands/workspace/stop.ts
13
+ const StopResult = z.object({
14
+ workspace_id: z.number().int().positive(),
15
+ container_name: z.string(),
16
+ stopped: z.boolean(),
17
+ prior_state: LocalWorkspaceState.nullable()
18
+ });
19
+ const stopResultView = {
20
+ compactPick: StopResult.pick({
21
+ workspace_id: true,
22
+ stopped: true,
23
+ prior_state: true
24
+ }).strip(),
25
+ tableColumns: [
26
+ {
27
+ key: "workspace_id",
28
+ label: "ID"
29
+ },
30
+ {
31
+ key: "container_name",
32
+ label: "Container"
33
+ },
34
+ {
35
+ key: "stopped",
36
+ label: "Stopped"
37
+ },
38
+ {
39
+ key: "prior_state",
40
+ label: "Prior State"
41
+ }
42
+ ]
43
+ };
44
+ var stop_default = defineMetabaseCommand({
45
+ meta: {
46
+ name: "stop",
47
+ description: "Stop the local Docker container for a workspace (does not remove it)"
48
+ },
49
+ args: {
50
+ ...outputFlags,
51
+ id: {
52
+ type: "positional",
53
+ description: "Workspace id",
54
+ required: true
55
+ }
56
+ },
57
+ outputSchema: StopResult,
58
+ examples: ["metabase workspace stop 1", "metabase workspace stop 1 --json"],
59
+ async run({ args, ctx }) {
60
+ const workspaceId = parseId(args.id);
61
+ const containerName = containerNameFor(workspaceId);
62
+ await checkDockerReady();
63
+ const status = await containerLifecycleStatus(containerName);
64
+ const priorState = status === "missing" ? null : status;
65
+ let stopped = false;
66
+ if (status === "running") {
67
+ await stopContainer(containerName);
68
+ stopped = true;
69
+ }
70
+ const result = {
71
+ workspace_id: workspaceId,
72
+ container_name: containerName,
73
+ stopped,
74
+ prior_state: priorState
75
+ };
76
+ renderItem(result, stopResultView, ctx);
77
+ }
78
+ });
79
+
80
+ //#endregion
81
+ export { stop_default as default };
@@ -0,0 +1,41 @@
1
+ import "./package-DBsS7a5x.mjs";
2
+ import "./command-augment-D9pI9Vbh.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-D7jihh81.mjs";
6
+ import { parseId } from "./parse-id-BUOZQqjp.mjs";
7
+ import { FieldSummary, FieldSummaryRaw, fieldSummaryView } from "./field-DciLbuv-.mjs";
8
+
9
+ //#region src/commands/field/summary.ts
10
+ var summary_default = defineMetabaseCommand({
11
+ meta: {
12
+ name: "summary",
13
+ description: "Get the row count and distinct count for a field"
14
+ },
15
+ args: {
16
+ ...outputFlags,
17
+ ...profileFlag,
18
+ ...connectionFlags,
19
+ id: {
20
+ type: "positional",
21
+ description: "Field id",
22
+ required: true
23
+ }
24
+ },
25
+ outputSchema: FieldSummary,
26
+ examples: ["metabase field summary 100", "metabase field summary 100 --json"],
27
+ async run({ args, ctx, getClient }) {
28
+ const id = parseId(args.id);
29
+ const client = await getClient();
30
+ const [[, count], [, distincts]] = await client.requestParsed(FieldSummaryRaw, `/api/field/${id}/summary`);
31
+ const summary = {
32
+ field_id: id,
33
+ count,
34
+ distincts
35
+ };
36
+ renderItem(summary, fieldSummaryView, ctx);
37
+ }
38
+ });
39
+
40
+ //#endregion
41
+ export { summary_default as default };
@@ -0,0 +1,43 @@
1
+ import "./package-DBsS7a5x.mjs";
2
+ import "./command-augment-D9pI9Vbh.mjs";
3
+ import { renderItem } from "./render-DXv-D6fU.mjs";
4
+ import "./predicates-DiIiS3k7.mjs";
5
+ import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-D7jihh81.mjs";
6
+ import { parseId } from "./parse-id-BUOZQqjp.mjs";
7
+ import "./field-DciLbuv-.mjs";
8
+ import "./table-jljEqZ0R.mjs";
9
+ import { DatabaseSyncResult, databaseSyncResultView } from "./database-Dvkfy3JM.mjs";
10
+ import { z } from "zod";
11
+
12
+ //#region src/commands/db/sync-schema.ts
13
+ const SyncSchemaApiResponse = z.object({ status: z.literal("ok") });
14
+ var sync_schema_default = defineMetabaseCommand({
15
+ meta: {
16
+ name: "sync-schema",
17
+ description: "Trigger a manual schema sync for a database"
18
+ },
19
+ args: {
20
+ ...outputFlags,
21
+ ...profileFlag,
22
+ ...connectionFlags,
23
+ id: {
24
+ type: "positional",
25
+ description: "Database id",
26
+ required: true
27
+ }
28
+ },
29
+ outputSchema: DatabaseSyncResult,
30
+ examples: ["metabase db sync-schema 1", "metabase db sync-schema 1 --json"],
31
+ async run({ args, ctx, getClient }) {
32
+ const id = parseId(args.id);
33
+ const client = await getClient();
34
+ const response = await client.requestParsed(SyncSchemaApiResponse, `/api/database/${id}/sync_schema`, { method: "POST" });
35
+ renderItem({
36
+ id,
37
+ status: response.status
38
+ }, databaseSyncResultView, ctx);
39
+ }
40
+ });
41
+
42
+ //#endregion
43
+ export { sync_schema_default as default };
@@ -0,0 +1,19 @@
1
+ import { defineCommand } from "citty";
2
+
3
+ //#region src/commands/table/index.ts
4
+ var table_default = defineCommand({
5
+ meta: {
6
+ name: "table",
7
+ description: "Inspect Metabase tables"
8
+ },
9
+ subCommands: {
10
+ list: () => import("./list-DD8CQx8l.mjs").then((m) => m.default),
11
+ get: () => import("./get-DczxeETg.mjs").then((m) => m.default),
12
+ metadata: () => import("./metadata-CLIALntn.mjs").then((m) => m.default),
13
+ fields: () => import("./fields-Do8HHm_T.mjs").then((m) => m.default),
14
+ update: () => import("./update-BCXKQi2n.mjs").then((m) => m.default)
15
+ }
16
+ });
17
+
18
+ //#endregion
19
+ export { table_default as default };