@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.
- package/LICENSE +661 -0
- package/README.md +762 -0
- package/dist/api-key-D9XxErQn.mjs +13 -0
- package/dist/archive-BPG5c88Y.mjs +38 -0
- package/dist/auth--Hpjwlaf.mjs +18 -0
- package/dist/body-DwU2s6Pg.mjs +19 -0
- package/dist/body-flags-7oqLhu5j.mjs +14 -0
- package/dist/branches-BbcoJXfp.mjs +41 -0
- package/dist/cancel-task-BDas45YO.mjs +29 -0
- package/dist/card-C31pGtBZ.mjs +113 -0
- package/dist/card-D4zZSPUb.mjs +19 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +61 -0
- package/dist/command-augment-D9pI9Vbh.mjs +11 -0
- package/dist/create-Bd_U1zWU.mjs +124 -0
- package/dist/create-CCzsCZMm.mjs +47 -0
- package/dist/create-CwVcoq0O.mjs +43 -0
- package/dist/create-DpnjQvPw.mjs +43 -0
- package/dist/create-_UOeEXAj.mjs +39 -0
- package/dist/create-branch-sDttBORB.mjs +54 -0
- package/dist/credentials-C0xKke5D.mjs +84 -0
- package/dist/current-task-BGt1mqaX.mjs +35 -0
- package/dist/database-4V1iiPEx.mjs +17 -0
- package/dist/database-BTX5qbSv.mjs +33 -0
- package/dist/db-Dm2u2ISJ.mjs +17 -0
- package/dist/delete-DRBTgyus.mjs +47 -0
- package/dist/delete-DUC_stoL.mjs +47 -0
- package/dist/delete-runtime-inOVw3IX.mjs +58 -0
- package/dist/delete-table-9Is631O_.mjs +47 -0
- package/dist/deprovision-BAMzZc6f.mjs +60 -0
- package/dist/dirty-CLjHbz6J.mjs +32 -0
- package/dist/docker-QWVMG2gl.mjs +605 -0
- package/dist/eid-BNhutC1U.mjs +13 -0
- package/dist/export-D2Anfu3p.mjs +97 -0
- package/dist/field-Dhs2AND3.mjs +13 -0
- package/dist/field-QwBMAWsq.mjs +76 -0
- package/dist/flag-pair-CWvvzDJ_.mjs +17 -0
- package/dist/get-2po1uv9i.mjs +35 -0
- package/dist/get-BHJA78zg.mjs +35 -0
- package/dist/get-CAPLfawI.mjs +35 -0
- package/dist/get-CAVVmdMX.mjs +49 -0
- package/dist/get-DDWpubE8.mjs +36 -0
- package/dist/get-DhIoNeOp.mjs +35 -0
- package/dist/get-qPOsuTPw.mjs +35 -0
- package/dist/has-remote-changes-DAL5jetW.mjs +63 -0
- package/dist/import-CUMxUfSF.mjs +92 -0
- package/dist/input-BNqSFl38.mjs +33 -0
- package/dist/is-dirty-B10S6MG0.mjs +35 -0
- package/dist/is-dirty-CUuq-aB6.mjs +9 -0
- package/dist/key-CyhOpgWt.mjs +12 -0
- package/dist/license-DtsGJi3l.mjs +17 -0
- package/dist/list-B8s7Qnzk.mjs +31 -0
- package/dist/list-C5MGydGU.mjs +31 -0
- package/dist/list-DeFGwhhJ.mjs +60 -0
- package/dist/list-OBx5B3gd.mjs +39 -0
- package/dist/list-Y7iGsOfE.mjs +31 -0
- package/dist/list-evtQS7jl.mjs +39 -0
- package/dist/list-qetY9OIN.mjs +31 -0
- package/dist/login-Dqw9ZtCx.mjs +172 -0
- package/dist/logout-DwYJ5OUi.mjs +74 -0
- package/dist/logs-B_lrY7Js.mjs +57 -0
- package/dist/manifest-wzEFG0JB.mjs +124 -0
- package/dist/package-t8dKf4m_.mjs +73 -0
- package/dist/parse-id-C1prc9US.mjs +12 -0
- package/dist/poll-D2sXM5rc.mjs +49 -0
- package/dist/poll-task-Byiunmaj.mjs +194 -0
- package/dist/prompt-fXeNtj0M.mjs +40 -0
- package/dist/provision-DC4_HWZD.mjs +80 -0
- package/dist/ps-1bZKIwWh.mjs +9 -0
- package/dist/ps-BiOrecEe.mjs +78 -0
- package/dist/query-BnGVGeM3.mjs +100 -0
- package/dist/remove-Bx48o-0S.mjs +62 -0
- package/dist/remove-DecoZzNd.mjs +97 -0
- package/dist/render-DlBijc5i.mjs +179 -0
- package/dist/run-D4NgvaRh.mjs +87 -0
- package/dist/runtime-DUgFfYkN.mjs +950 -0
- package/dist/search-4wKx5ug2.mjs +171 -0
- package/dist/set-BZnCRL4c.mjs +66 -0
- package/dist/set-DCjrmTFm.mjs +66 -0
- package/dist/setting-C4vQSqer.mjs +18 -0
- package/dist/setting-DM7pm7yh.mjs +55 -0
- package/dist/setup-Dqh9hN6l.mjs +70 -0
- package/dist/start-xXQypG5L.mjs +324 -0
- package/dist/stash-ZZkmW_V7.mjs +106 -0
- package/dist/status-9KAPIpX8.mjs +31 -0
- package/dist/status-DezF-PIM.mjs +63 -0
- package/dist/status-JH6BZppo.mjs +55 -0
- package/dist/stop-br-ZOnve.mjs +80 -0
- package/dist/sync-C7VOWD00.mjs +26 -0
- package/dist/table-BvAr2ixC.mjs +75 -0
- package/dist/table-D-Mb5Nvw.mjs +16 -0
- package/dist/transform-CqxZwhGs.mjs +21 -0
- package/dist/transform-DfVkUttP.mjs +137 -0
- package/dist/transform-job-DuB_OjhO.mjs +91 -0
- package/dist/transform-job-HjbqjEoP.mjs +19 -0
- package/dist/translate-DJxDVAE4.mjs +110 -0
- package/dist/update-CDtm71m2.mjs +50 -0
- package/dist/update-DYVeVjk2.mjs +76 -0
- package/dist/update-DxKlQ0hP.mjs +50 -0
- package/dist/url-DP88YHNo.mjs +53 -0
- package/dist/wait-Cj_8wu4y.mjs +52 -0
- package/dist/wait-DwZN3ZwR.mjs +19 -0
- package/dist/wait-flags-CjW4ogUJ.mjs +35 -0
- package/dist/workspace-CbwR0vX_.mjs +24 -0
- package/dist/workspace-Dr9lWU3D.mjs +72 -0
- package/dist/workspace-credentials-q5RRFMT8.mjs +139 -0
- package/package.json +62 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import "./package-t8dKf4m_.mjs";
|
|
2
|
+
import "./command-augment-D9pI9Vbh.mjs";
|
|
3
|
+
import { renderItem } from "./render-DlBijc5i.mjs";
|
|
4
|
+
import { ConfigError, connectionFlags, defineMetabaseCommand, outputFlags, parseJson, profileFlag } from "./runtime-DUgFfYkN.mjs";
|
|
5
|
+
import { parseId } from "./parse-id-C1prc9US.mjs";
|
|
6
|
+
import { CardQueryResult, cardQueryView } from "./card-C31pGtBZ.mjs";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { Writable } from "node:stream";
|
|
9
|
+
|
|
10
|
+
//#region src/output/stream.ts
|
|
11
|
+
async function pipeToStdout(stream) {
|
|
12
|
+
await stream.pipeTo(Writable.toWeb(process.stdout));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/commands/card/query.ts
|
|
17
|
+
const ExportFormat = z.enum(["csv", "xlsx"]);
|
|
18
|
+
const QueryParameters = z.array(z.unknown());
|
|
19
|
+
var query_default = defineMetabaseCommand({
|
|
20
|
+
meta: {
|
|
21
|
+
name: "query",
|
|
22
|
+
description: "Run a saved card and return results (json envelope, CSV, or XLSX)"
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
...outputFlags,
|
|
26
|
+
...profileFlag,
|
|
27
|
+
...connectionFlags,
|
|
28
|
+
id: {
|
|
29
|
+
type: "positional",
|
|
30
|
+
description: "Card id",
|
|
31
|
+
required: true
|
|
32
|
+
},
|
|
33
|
+
"export-format": {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: `Bypass JSON envelope and stream raw export: ${ExportFormat.options.join(" | ")}`
|
|
36
|
+
},
|
|
37
|
+
parameters: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "JSON array of Metabase parameter objects to pass with the query"
|
|
40
|
+
},
|
|
41
|
+
limit: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Cap rows kept in the JSON envelope (no effect on csv/xlsx exports)"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
outputSchema: CardQueryResult,
|
|
47
|
+
examples: [
|
|
48
|
+
"metabase card query 1",
|
|
49
|
+
"metabase card query 1 --json --limit 20",
|
|
50
|
+
"metabase card query 1 --export-format csv > results.csv",
|
|
51
|
+
"metabase card query 1 --parameters '[{\"type\":\"category\",\"value\":\"A\",\"target\":[\"variable\",[\"template-tag\",\"c\"]]}]'"
|
|
52
|
+
],
|
|
53
|
+
async run({ args, ctx, getClient }) {
|
|
54
|
+
const id = parseId(args.id);
|
|
55
|
+
const parameters = parseParameters(args.parameters);
|
|
56
|
+
const client = await getClient();
|
|
57
|
+
const exportFormatRaw = args["export-format"];
|
|
58
|
+
if (exportFormatRaw !== void 0 && exportFormatRaw !== "") {
|
|
59
|
+
const exportFormat = parseExportFormat(exportFormatRaw);
|
|
60
|
+
await streamExport(client, id, exportFormat, parameters);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const result = await client.requestParsed(CardQueryResult, `/api/card/${id}/query`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: { parameters }
|
|
66
|
+
});
|
|
67
|
+
const limit = args.limit === void 0 || args.limit === "" ? null : parseId(args.limit, "limit");
|
|
68
|
+
renderItem(applyLimit(result, limit), cardQueryView, ctx);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
function parseParameters(raw) {
|
|
72
|
+
if (raw === void 0 || raw === "") return [];
|
|
73
|
+
return parseJson(raw, QueryParameters, { source: "--parameters" });
|
|
74
|
+
}
|
|
75
|
+
function parseExportFormat(raw) {
|
|
76
|
+
const result = ExportFormat.safeParse(raw);
|
|
77
|
+
if (!result.success) throw new ConfigError(`invalid --export-format: "${raw}" (expected: ${ExportFormat.options.join(", ")})`);
|
|
78
|
+
return result.data;
|
|
79
|
+
}
|
|
80
|
+
function applyLimit(result, limit) {
|
|
81
|
+
if (limit === null || result.status !== "completed" || result.data.rows.length <= limit) return result;
|
|
82
|
+
return {
|
|
83
|
+
...result,
|
|
84
|
+
data: {
|
|
85
|
+
...result.data,
|
|
86
|
+
rows: result.data.rows.slice(0, limit)
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function streamExport(client, id, format, parameters) {
|
|
91
|
+
const body = new URLSearchParams({ parameters: JSON.stringify(parameters) });
|
|
92
|
+
const stream = await client.requestStream(`/api/card/${id}/query/${format}`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
body
|
|
95
|
+
});
|
|
96
|
+
await pipeToStdout(stream);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { query_default as default };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import "./package-t8dKf4m_.mjs";
|
|
2
|
+
import "./command-augment-D9pI9Vbh.mjs";
|
|
3
|
+
import { renderItem } from "./render-DlBijc5i.mjs";
|
|
4
|
+
import { ConfigError, clearLicense, defineMetabaseCommand, outputFlags } from "./runtime-DUgFfYkN.mjs";
|
|
5
|
+
import { promptConfirm } from "./prompt-fXeNtj0M.mjs";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/license/remove.ts
|
|
9
|
+
const LicenseRemoveResult = z.object({
|
|
10
|
+
removed: z.boolean(),
|
|
11
|
+
aborted: z.boolean()
|
|
12
|
+
});
|
|
13
|
+
const licenseRemoveView = {
|
|
14
|
+
compactPick: LicenseRemoveResult,
|
|
15
|
+
tableColumns: [{
|
|
16
|
+
key: "removed",
|
|
17
|
+
label: "Removed"
|
|
18
|
+
}, {
|
|
19
|
+
key: "aborted",
|
|
20
|
+
label: "Aborted"
|
|
21
|
+
}]
|
|
22
|
+
};
|
|
23
|
+
var remove_default = defineMetabaseCommand({
|
|
24
|
+
meta: {
|
|
25
|
+
name: "remove",
|
|
26
|
+
description: "Remove the stored license token"
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
...outputFlags,
|
|
30
|
+
yes: {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
description: "Skip confirmation",
|
|
33
|
+
default: false
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
outputSchema: LicenseRemoveResult,
|
|
37
|
+
examples: ["metabase license remove --yes"],
|
|
38
|
+
async run({ args, ctx }) {
|
|
39
|
+
if (!args.yes) {
|
|
40
|
+
if (!process.stdin.isTTY) throw new ConfigError("--yes required to remove license non-interactively");
|
|
41
|
+
const ok = await promptConfirm({
|
|
42
|
+
message: "Remove stored license token?",
|
|
43
|
+
initialValue: false
|
|
44
|
+
});
|
|
45
|
+
if (!ok) {
|
|
46
|
+
renderItem({
|
|
47
|
+
removed: false,
|
|
48
|
+
aborted: true
|
|
49
|
+
}, licenseRemoveView, ctx);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const removed = await clearLicense();
|
|
54
|
+
renderItem({
|
|
55
|
+
removed,
|
|
56
|
+
aborted: false
|
|
57
|
+
}, licenseRemoveView, ctx);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { remove_default as default };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import "./package-t8dKf4m_.mjs";
|
|
2
|
+
import "./command-augment-D9pI9Vbh.mjs";
|
|
3
|
+
import { renderItem } from "./render-DlBijc5i.mjs";
|
|
4
|
+
import { defineMetabaseCommand, outputFlags } from "./runtime-DUgFfYkN.mjs";
|
|
5
|
+
import { parseId } from "./parse-id-C1prc9US.mjs";
|
|
6
|
+
import { promptConfirm } from "./prompt-fXeNtj0M.mjs";
|
|
7
|
+
import "./poll-D2sXM5rc.mjs";
|
|
8
|
+
import { checkDockerReady, containerNameFor, removeContainer, removeVolume, volumeNameFor } from "./docker-QWVMG2gl.mjs";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
//#region src/commands/workspace/remove.ts
|
|
12
|
+
const RemoveResult = z.object({
|
|
13
|
+
workspace_id: z.number().int().positive(),
|
|
14
|
+
container_name: z.string(),
|
|
15
|
+
volume_name: z.string(),
|
|
16
|
+
removed_container: z.boolean(),
|
|
17
|
+
removed_volume: z.boolean()
|
|
18
|
+
});
|
|
19
|
+
const removeResultView = {
|
|
20
|
+
compactPick: RemoveResult.pick({
|
|
21
|
+
workspace_id: true,
|
|
22
|
+
removed_container: true,
|
|
23
|
+
removed_volume: 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: "volume_name",
|
|
36
|
+
label: "Volume"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "removed_container",
|
|
40
|
+
label: "Removed Container"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "removed_volume",
|
|
44
|
+
label: "Removed Volume"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
};
|
|
48
|
+
var remove_default = defineMetabaseCommand({
|
|
49
|
+
meta: {
|
|
50
|
+
name: "remove",
|
|
51
|
+
description: "Stop and remove the local container + app-db volume (does not affect remote)"
|
|
52
|
+
},
|
|
53
|
+
args: {
|
|
54
|
+
...outputFlags,
|
|
55
|
+
id: {
|
|
56
|
+
type: "positional",
|
|
57
|
+
description: "Workspace id",
|
|
58
|
+
required: true
|
|
59
|
+
},
|
|
60
|
+
"keep-volume": {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description: "Keep the workspace's app-db volume (faster restart, app-db survives)",
|
|
63
|
+
default: false
|
|
64
|
+
},
|
|
65
|
+
yes: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "Skip the confirmation prompt",
|
|
68
|
+
default: false
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
outputSchema: RemoveResult,
|
|
72
|
+
examples: ["metabase workspace remove 1 --yes", "metabase workspace remove 1 --keep-volume --yes"],
|
|
73
|
+
async run({ args, ctx }) {
|
|
74
|
+
const workspaceId = parseId(args.id);
|
|
75
|
+
const containerName = containerNameFor(workspaceId);
|
|
76
|
+
const volumeName = volumeNameFor(workspaceId);
|
|
77
|
+
const shouldRemoveVolume = args["keep-volume"] !== true;
|
|
78
|
+
await checkDockerReady();
|
|
79
|
+
if (!args.yes) {
|
|
80
|
+
const confirmed = await promptConfirm({ message: shouldRemoveVolume ? `Remove container ${containerName} and its app-db volume ${volumeName}?` : `Remove container ${containerName}? (volume ${volumeName} will be kept)` });
|
|
81
|
+
if (!confirmed) return;
|
|
82
|
+
}
|
|
83
|
+
const removedContainer = await removeContainer(containerName);
|
|
84
|
+
const removedVolume = shouldRemoveVolume ? await removeVolume(volumeName) : false;
|
|
85
|
+
const result = {
|
|
86
|
+
workspace_id: workspaceId,
|
|
87
|
+
container_name: containerName,
|
|
88
|
+
volume_name: volumeName,
|
|
89
|
+
removed_container: removedContainer,
|
|
90
|
+
removed_volume: removedVolume
|
|
91
|
+
};
|
|
92
|
+
renderItem(result, removeResultView, ctx);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { remove_default as default };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { ConfigError } from "./runtime-DUgFfYkN.mjs";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
|
|
4
|
+
//#region src/output/cap.ts
|
|
5
|
+
function capListEnvelope(envelope, maxBytes) {
|
|
6
|
+
if (maxBytes <= 0) return envelope;
|
|
7
|
+
const fullBytes = jsonByteLength(envelope);
|
|
8
|
+
if (fullBytes <= maxBytes) return envelope;
|
|
9
|
+
let lo = 0;
|
|
10
|
+
let hi = envelope.data.length;
|
|
11
|
+
while (lo < hi) {
|
|
12
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
13
|
+
if (jsonByteLength(truncate(envelope, mid, fullBytes)) <= maxBytes) lo = mid;
|
|
14
|
+
else hi = mid - 1;
|
|
15
|
+
}
|
|
16
|
+
return truncate(envelope, lo, fullBytes);
|
|
17
|
+
}
|
|
18
|
+
function truncate(envelope, count, originalBytes) {
|
|
19
|
+
return {
|
|
20
|
+
...envelope,
|
|
21
|
+
data: envelope.data.slice(0, count),
|
|
22
|
+
returned: count,
|
|
23
|
+
truncated: {
|
|
24
|
+
reason: "max_bytes",
|
|
25
|
+
bytes: originalBytes
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function jsonByteLength(value) {
|
|
30
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/output/notice.ts
|
|
35
|
+
function warn(message) {
|
|
36
|
+
process.stderr.write(message + "\n");
|
|
37
|
+
}
|
|
38
|
+
function listTruncationNotice(bytes) {
|
|
39
|
+
return `… cut at ${bytes} bytes; rerun with --max-bytes 0`;
|
|
40
|
+
}
|
|
41
|
+
function itemOversizeNotice(bytes) {
|
|
42
|
+
return `… item is ${bytes} bytes (exceeds --max-bytes); narrow with --fields, or pass --max-bytes 0`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/output/projection.ts
|
|
47
|
+
function applyProjection(value, view, full, fields) {
|
|
48
|
+
if (fields !== void 0) {
|
|
49
|
+
if (fields.length === 0) throw new ConfigError("--fields requires at least one path");
|
|
50
|
+
return projectFields(value, fields);
|
|
51
|
+
}
|
|
52
|
+
if (full) return value;
|
|
53
|
+
const parsed = view.compactPick.safeParse(value);
|
|
54
|
+
if (parsed.success) return parsed.data;
|
|
55
|
+
throw new ConfigError(`compact projection failed: ${parsed.error.message}`);
|
|
56
|
+
}
|
|
57
|
+
function projectFields(value, fields) {
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const path of fields) {
|
|
60
|
+
if (path.length === 0) throw new ConfigError(`empty field path`);
|
|
61
|
+
const parts = path.split(".");
|
|
62
|
+
if (parts.some((part) => part.length === 0)) throw new ConfigError(`invalid field path: "${path}"`);
|
|
63
|
+
setPath(out, parts, pickPath(value, parts));
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
function pickPath(value, parts) {
|
|
68
|
+
let cursor = value;
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
if (!isPlainObject(cursor) || !Object.hasOwn(cursor, part)) throw new ConfigError(`unknown field path: "${parts.join(".")}"`);
|
|
71
|
+
cursor = Reflect.get(cursor, part);
|
|
72
|
+
}
|
|
73
|
+
return cursor;
|
|
74
|
+
}
|
|
75
|
+
function setPath(target, parts, value) {
|
|
76
|
+
let cursor = target;
|
|
77
|
+
const lastIndex = parts.length - 1;
|
|
78
|
+
for (const [index, part] of parts.entries()) {
|
|
79
|
+
if (index === lastIndex) {
|
|
80
|
+
cursor[part] = value;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const existing = cursor[part];
|
|
84
|
+
if (isPlainObject(existing)) cursor = existing;
|
|
85
|
+
else {
|
|
86
|
+
const next = {};
|
|
87
|
+
cursor[part] = next;
|
|
88
|
+
cursor = next;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function isPlainObject(value) {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/output/table.ts
|
|
98
|
+
function renderTable(rows, columns) {
|
|
99
|
+
const head = columns.map((column) => column.label ?? column.key);
|
|
100
|
+
const widths = columns.map((column) => column.width ?? null);
|
|
101
|
+
const hasWidth = widths.some((width) => width !== null);
|
|
102
|
+
const table = new Table(hasWidth ? {
|
|
103
|
+
head,
|
|
104
|
+
colWidths: widths
|
|
105
|
+
} : { head });
|
|
106
|
+
for (const row of rows) table.push(columns.map((column) => formatCell(row, column)));
|
|
107
|
+
return table.toString();
|
|
108
|
+
}
|
|
109
|
+
function formatCell(row, column) {
|
|
110
|
+
const value = row[column.key];
|
|
111
|
+
if (column.format !== void 0) return column.format(value);
|
|
112
|
+
return formatScalar(value);
|
|
113
|
+
}
|
|
114
|
+
function formatScalar(value) {
|
|
115
|
+
if (value === null || value === void 0) return "";
|
|
116
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/output/render.ts
|
|
122
|
+
function renderItem(item, view, opts) {
|
|
123
|
+
const projected = applyProjection(item, view, opts.full, opts.fields);
|
|
124
|
+
const body = renderItemBody(item, view, projected, opts) + "\n";
|
|
125
|
+
process.stdout.write(body);
|
|
126
|
+
emitItemOversizeNotice(body, opts.maxBytes);
|
|
127
|
+
}
|
|
128
|
+
function renderList(envelope, view, opts) {
|
|
129
|
+
if (opts.format === "json" || opts.fields !== void 0) {
|
|
130
|
+
renderJsonEnvelope(envelope, view, opts);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (envelope.data.length === 0) {
|
|
134
|
+
process.stdout.write("(no results)\n");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const capped = capListEnvelope(envelope, opts.maxBytes);
|
|
138
|
+
process.stdout.write(renderTable(capped.data, view.tableColumns) + "\n");
|
|
139
|
+
if (capped.truncated !== void 0) warn(listTruncationNotice(capped.truncated.bytes));
|
|
140
|
+
}
|
|
141
|
+
function renderJsonEnvelope(envelope, view, opts) {
|
|
142
|
+
const projectedItems = envelope.data.map((item) => applyProjection(item, view, opts.full, opts.fields));
|
|
143
|
+
const projectedEnvelope = {
|
|
144
|
+
...envelope,
|
|
145
|
+
data: projectedItems
|
|
146
|
+
};
|
|
147
|
+
const capped = capListEnvelope(projectedEnvelope, opts.maxBytes);
|
|
148
|
+
process.stdout.write(JSON.stringify(capped, null, 2) + "\n");
|
|
149
|
+
if (capped.truncated !== void 0) warn(listTruncationNotice(capped.truncated.bytes));
|
|
150
|
+
}
|
|
151
|
+
function renderItemBody(item, view, projected, opts) {
|
|
152
|
+
if (opts.format === "json" || opts.fields !== void 0) return JSON.stringify(projected, null, 2);
|
|
153
|
+
if (!opts.full) return renderKeyValueLines(columnPairs(item, view.tableColumns));
|
|
154
|
+
return renderKeyValueLines(objectPairs(projected));
|
|
155
|
+
}
|
|
156
|
+
function columnPairs(item, columns) {
|
|
157
|
+
return columns.map((column) => [column.label ?? column.key, formatCell(item, column)]);
|
|
158
|
+
}
|
|
159
|
+
function objectPairs(value) {
|
|
160
|
+
if (!isPlainObject(value)) {
|
|
161
|
+
const scalar = formatScalar(value);
|
|
162
|
+
return scalar === "" ? [] : [["", scalar]];
|
|
163
|
+
}
|
|
164
|
+
return Object.entries(value).map(([key, raw]) => [key, formatScalar(raw)]);
|
|
165
|
+
}
|
|
166
|
+
function renderKeyValueLines(pairs) {
|
|
167
|
+
if (pairs.length === 0) return "";
|
|
168
|
+
const padding = Math.max(...pairs.map(([label]) => label.length));
|
|
169
|
+
return pairs.map(([label, value]) => `${label.padEnd(padding)} ${value}`).join("\n");
|
|
170
|
+
}
|
|
171
|
+
function emitItemOversizeNotice(body, maxBytes) {
|
|
172
|
+
if (maxBytes <= 0) return;
|
|
173
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
174
|
+
if (bytes <= maxBytes) return;
|
|
175
|
+
warn(itemOversizeNotice(bytes));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { renderItem, renderList, warn };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import "./package-t8dKf4m_.mjs";
|
|
2
|
+
import "./command-augment-D9pI9Vbh.mjs";
|
|
3
|
+
import { renderItem } from "./render-DlBijc5i.mjs";
|
|
4
|
+
import { connectionFlags, defineMetabaseCommand, outputFlags, profileFlag } from "./runtime-DUgFfYkN.mjs";
|
|
5
|
+
import { parseId } from "./parse-id-C1prc9US.mjs";
|
|
6
|
+
import { pollUntil } from "./poll-D2sXM5rc.mjs";
|
|
7
|
+
import { TransformRun } from "./transform-DfVkUttP.mjs";
|
|
8
|
+
import { parseWaitFlags, waitFlags } from "./wait-flags-CjW4ogUJ.mjs";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
//#region src/commands/transform/run.ts
|
|
12
|
+
const RUN_TERMINAL_STATUSES = new Set([
|
|
13
|
+
"succeeded",
|
|
14
|
+
"failed",
|
|
15
|
+
"timeout",
|
|
16
|
+
"canceled"
|
|
17
|
+
]);
|
|
18
|
+
const RUN_FAILURE_STATUSES = new Set([
|
|
19
|
+
"failed",
|
|
20
|
+
"timeout",
|
|
21
|
+
"canceled"
|
|
22
|
+
]);
|
|
23
|
+
const TransformRunKickoff = z.object({
|
|
24
|
+
message: z.string(),
|
|
25
|
+
run_id: z.number().int().positive().nullable()
|
|
26
|
+
});
|
|
27
|
+
const TransformRunResult = z.object({
|
|
28
|
+
message: z.string(),
|
|
29
|
+
run_id: z.number().int().positive().nullable(),
|
|
30
|
+
final: TransformRun.nullable().optional()
|
|
31
|
+
});
|
|
32
|
+
const transformRunView = {
|
|
33
|
+
compactPick: TransformRunResult,
|
|
34
|
+
tableColumns: [{
|
|
35
|
+
key: "run_id",
|
|
36
|
+
label: "Run ID"
|
|
37
|
+
}, {
|
|
38
|
+
key: "message",
|
|
39
|
+
label: "Message"
|
|
40
|
+
}]
|
|
41
|
+
};
|
|
42
|
+
var run_default = defineMetabaseCommand({
|
|
43
|
+
meta: {
|
|
44
|
+
name: "run",
|
|
45
|
+
description: "Trigger a transform run by id"
|
|
46
|
+
},
|
|
47
|
+
args: {
|
|
48
|
+
...outputFlags,
|
|
49
|
+
...profileFlag,
|
|
50
|
+
...connectionFlags,
|
|
51
|
+
...waitFlags,
|
|
52
|
+
id: {
|
|
53
|
+
type: "positional",
|
|
54
|
+
description: "Transform id",
|
|
55
|
+
required: true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
outputSchema: TransformRunResult,
|
|
59
|
+
examples: ["metabase transform run 1", "metabase transform run 1 --wait --json"],
|
|
60
|
+
async run({ args, ctx, getClient }) {
|
|
61
|
+
const id = parseId(args.id);
|
|
62
|
+
const wait = parseWaitFlags(args);
|
|
63
|
+
const client = await getClient();
|
|
64
|
+
const kickoff = await client.requestParsed(TransformRunKickoff, `/api/transform/${id}/run`, { method: "POST" });
|
|
65
|
+
if (!wait.enabled || kickoff.run_id === null) {
|
|
66
|
+
renderItem({
|
|
67
|
+
message: kickoff.message,
|
|
68
|
+
run_id: kickoff.run_id
|
|
69
|
+
}, transformRunView, ctx);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const runId = kickoff.run_id;
|
|
73
|
+
const final = await pollUntil(async () => client.requestParsed(TransformRun, `/api/transform/run/${runId}`), (run) => RUN_TERMINAL_STATUSES.has(run.status), wait.schedule);
|
|
74
|
+
renderItem({
|
|
75
|
+
message: kickoff.message,
|
|
76
|
+
run_id: runId,
|
|
77
|
+
final
|
|
78
|
+
}, transformRunView, ctx);
|
|
79
|
+
if (RUN_FAILURE_STATUSES.has(final.status)) {
|
|
80
|
+
const detail = final.message ? `: ${final.message}` : "";
|
|
81
|
+
throw new Error(`transform run ${runId} ${final.status}${detail}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { run_default as default };
|