contentrain 0.3.2 → 0.3.4
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/README.md +54 -0
- package/dist/activity-CtOcAA4X.mjs +88 -0
- package/dist/branches-C_kgqSv9.mjs +142 -0
- package/dist/cdn-build-CDv_yM15.mjs +106 -0
- package/dist/cdn-init-DXQ-3V_c.mjs +90 -0
- package/dist/client-D4xW8aLz.mjs +275 -0
- package/dist/index.mjs +3 -2
- package/dist/login-CO0hxICh.mjs +243 -0
- package/dist/logout-CRM4fCSH.mjs +29 -0
- package/dist/resolve-context-BMV7JwCw.mjs +58 -0
- package/dist/status-ts5FvDEZ.mjs +128 -0
- package/dist/studio-BmXS_bCq.mjs +23 -0
- package/dist/submissions-6_OCrUNF.mjs +169 -0
- package/dist/usage-BAb4q7Pz.mjs +81 -0
- package/dist/webhooks-C39IItU1.mjs +235 -0
- package/dist/whoami-D8NyFKs_.mjs +65 -0
- package/package.json +5 -5
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
+
import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
|
+
//#region src/studio/commands/status.ts
|
|
7
|
+
var status_default = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "status",
|
|
10
|
+
description: "Show project overview from Studio"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
workspace: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Workspace ID",
|
|
16
|
+
required: false
|
|
17
|
+
},
|
|
18
|
+
project: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Project ID",
|
|
21
|
+
required: false
|
|
22
|
+
},
|
|
23
|
+
json: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "JSON output",
|
|
26
|
+
required: false
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async run({ args }) {
|
|
30
|
+
if (!args.json) intro(pc.bold("contentrain studio status"));
|
|
31
|
+
try {
|
|
32
|
+
const client = await resolveStudioClient();
|
|
33
|
+
const ctx = await resolveStudioContext(client, args);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
log.warning("No workspace or project found.");
|
|
36
|
+
outro("");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const { workspaceId, projectId } = ctx;
|
|
40
|
+
const s = args.json ? null : spinner();
|
|
41
|
+
s?.start("Fetching project status...");
|
|
42
|
+
const [workspaces, projects, branches] = await Promise.all([
|
|
43
|
+
client.listWorkspaces(),
|
|
44
|
+
client.listProjects(workspaceId),
|
|
45
|
+
client.listBranches(workspaceId, projectId).catch(() => [])
|
|
46
|
+
]);
|
|
47
|
+
const workspace = workspaces.find((w) => w.id === workspaceId);
|
|
48
|
+
const project = projects.find((p) => p.id === projectId);
|
|
49
|
+
let cdnBuilds;
|
|
50
|
+
try {
|
|
51
|
+
cdnBuilds = await client.listCdnBuilds(workspaceId, projectId, { limit: "1" });
|
|
52
|
+
} catch {}
|
|
53
|
+
s?.stop("Status loaded");
|
|
54
|
+
if (args.json) {
|
|
55
|
+
process.stdout.write(JSON.stringify({
|
|
56
|
+
workspace: workspace ? {
|
|
57
|
+
id: workspace.id,
|
|
58
|
+
name: workspace.name,
|
|
59
|
+
plan: workspace.plan
|
|
60
|
+
} : null,
|
|
61
|
+
project: project ? {
|
|
62
|
+
id: project.id,
|
|
63
|
+
name: project.name,
|
|
64
|
+
stack: project.stack
|
|
65
|
+
} : null,
|
|
66
|
+
branches: {
|
|
67
|
+
total: branches.length,
|
|
68
|
+
items: branches
|
|
69
|
+
},
|
|
70
|
+
cdn: cdnBuilds?.data[0] ? { lastBuild: {
|
|
71
|
+
status: cdnBuilds.data[0].status,
|
|
72
|
+
fileCount: cdnBuilds.data[0].fileCount,
|
|
73
|
+
createdAt: cdnBuilds.data[0].createdAt
|
|
74
|
+
} } : null
|
|
75
|
+
}, null, 2));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
log.info(pc.bold("Project"));
|
|
79
|
+
if (project) {
|
|
80
|
+
log.message(` Name: ${pc.cyan(project.name)}`);
|
|
81
|
+
log.message(` Stack: ${project.stack}`);
|
|
82
|
+
log.message(` Members: ${project.memberCount}`);
|
|
83
|
+
}
|
|
84
|
+
if (workspace) {
|
|
85
|
+
log.info(pc.bold("\nWorkspace"));
|
|
86
|
+
log.message(` Name: ${pc.cyan(workspace.name)}`);
|
|
87
|
+
log.message(` Plan: ${workspace.plan}`);
|
|
88
|
+
}
|
|
89
|
+
if (branches.length > 0) {
|
|
90
|
+
log.info(pc.bold(`\nPending branches (${branches.length})`));
|
|
91
|
+
const rows = branches.slice(0, 10).map((b) => [
|
|
92
|
+
b.name,
|
|
93
|
+
`+${b.ahead}`,
|
|
94
|
+
b.author ?? "—",
|
|
95
|
+
timeAgo(b.lastCommitDate)
|
|
96
|
+
]);
|
|
97
|
+
log.message(formatTable([
|
|
98
|
+
"Branch",
|
|
99
|
+
"Ahead",
|
|
100
|
+
"Author",
|
|
101
|
+
"Updated"
|
|
102
|
+
], rows));
|
|
103
|
+
if (branches.length > 10) log.message(pc.dim(` ... and ${formatCount(branches.length - 10, "more branch", "more branches")}`));
|
|
104
|
+
} else log.message("\nNo pending branches.");
|
|
105
|
+
const lastBuild = cdnBuilds?.data[0];
|
|
106
|
+
if (lastBuild) {
|
|
107
|
+
log.info(pc.bold("\nCDN"));
|
|
108
|
+
const statusColor = lastBuild.status === "success" ? pc.green : lastBuild.status === "failed" ? pc.red : pc.yellow;
|
|
109
|
+
log.message(` Last build: ${statusColor(lastBuild.status)} (${formatCount(lastBuild.fileCount, "file")}, ${timeAgo(lastBuild.createdAt)})`);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
115
|
+
if (!args.json) outro("");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
function timeAgo(dateStr) {
|
|
119
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
120
|
+
const minutes = Math.floor(diff / 6e4);
|
|
121
|
+
if (minutes < 1) return "just now";
|
|
122
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
123
|
+
const hours = Math.floor(minutes / 60);
|
|
124
|
+
if (hours < 24) return `${hours}h ago`;
|
|
125
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { status_default as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
//#region src/studio/index.ts
|
|
3
|
+
var studio_default = defineCommand({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "studio",
|
|
6
|
+
description: "Interact with Contentrain Studio"
|
|
7
|
+
},
|
|
8
|
+
subCommands: {
|
|
9
|
+
login: () => import("./login-CO0hxICh.mjs").then((m) => m.default),
|
|
10
|
+
logout: () => import("./logout-CRM4fCSH.mjs").then((m) => m.default),
|
|
11
|
+
whoami: () => import("./whoami-D8NyFKs_.mjs").then((m) => m.default),
|
|
12
|
+
status: () => import("./status-ts5FvDEZ.mjs").then((m) => m.default),
|
|
13
|
+
activity: () => import("./activity-CtOcAA4X.mjs").then((m) => m.default),
|
|
14
|
+
usage: () => import("./usage-BAb4q7Pz.mjs").then((m) => m.default),
|
|
15
|
+
branches: () => import("./branches-C_kgqSv9.mjs").then((m) => m.default),
|
|
16
|
+
"cdn-init": () => import("./cdn-init-DXQ-3V_c.mjs").then((m) => m.default),
|
|
17
|
+
"cdn-build": () => import("./cdn-build-CDv_yM15.mjs").then((m) => m.default),
|
|
18
|
+
webhooks: () => import("./webhooks-C39IItU1.mjs").then((m) => m.default),
|
|
19
|
+
submissions: () => import("./submissions-6_OCrUNF.mjs").then((m) => m.default)
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
//#endregion
|
|
23
|
+
export { studio_default as default };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
+
import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
|
|
6
|
+
//#region src/studio/commands/submissions.ts
|
|
7
|
+
var submissions_default = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "submissions",
|
|
10
|
+
description: "Manage form submissions on Studio"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
workspace: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Workspace ID",
|
|
16
|
+
required: false
|
|
17
|
+
},
|
|
18
|
+
project: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Project ID",
|
|
21
|
+
required: false
|
|
22
|
+
},
|
|
23
|
+
form: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Form model ID",
|
|
26
|
+
required: false
|
|
27
|
+
},
|
|
28
|
+
status: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Filter by status (pending, approved, rejected)",
|
|
31
|
+
required: false
|
|
32
|
+
},
|
|
33
|
+
json: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "JSON output",
|
|
36
|
+
required: false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async run({ args }) {
|
|
40
|
+
if (!args.json) intro(pc.bold("contentrain studio submissions"));
|
|
41
|
+
try {
|
|
42
|
+
const client = await resolveStudioClient();
|
|
43
|
+
const ctx = await resolveStudioContext(client, args);
|
|
44
|
+
if (!ctx) {
|
|
45
|
+
log.warning("No workspace or project found.");
|
|
46
|
+
outro("");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { workspaceId, projectId } = ctx;
|
|
50
|
+
const formId = args.form ?? await resolveFormId(args.json);
|
|
51
|
+
if (!formId) {
|
|
52
|
+
if (!args.json) outro("");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const s = args.json ? null : spinner();
|
|
56
|
+
s?.start("Fetching submissions...");
|
|
57
|
+
const query = {};
|
|
58
|
+
if (args.status) query["status"] = args.status;
|
|
59
|
+
const result = await client.listSubmissions(workspaceId, projectId, formId, query);
|
|
60
|
+
s?.stop(`${formatCount(result.data.length, "submission")}`);
|
|
61
|
+
if (args.json) {
|
|
62
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (result.data.length === 0) {
|
|
66
|
+
log.info("No submissions found.");
|
|
67
|
+
outro("");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const rows = result.data.map((sub) => {
|
|
71
|
+
const preview = summarizeData(sub.data);
|
|
72
|
+
const statusColor = sub.status === "approved" ? pc.green : sub.status === "rejected" ? pc.red : sub.status === "spam" ? pc.dim : pc.yellow;
|
|
73
|
+
return [
|
|
74
|
+
sub.id.slice(0, 8),
|
|
75
|
+
preview,
|
|
76
|
+
statusColor(sub.status),
|
|
77
|
+
timeAgo(sub.submittedAt)
|
|
78
|
+
];
|
|
79
|
+
});
|
|
80
|
+
log.message(formatTable([
|
|
81
|
+
"ID",
|
|
82
|
+
"Preview",
|
|
83
|
+
"Status",
|
|
84
|
+
"Submitted"
|
|
85
|
+
], rows));
|
|
86
|
+
const pendingSubs = result.data.filter((sub) => sub.status === "pending");
|
|
87
|
+
if (pendingSubs.length === 0) {
|
|
88
|
+
outro("");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const action = await select({
|
|
92
|
+
message: `${formatCount(pendingSubs.length, "pending submission")}. What would you like to do?`,
|
|
93
|
+
options: [
|
|
94
|
+
{
|
|
95
|
+
value: "approve",
|
|
96
|
+
label: "Approve a submission"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: "reject",
|
|
100
|
+
label: "Reject a submission"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: "cancel",
|
|
104
|
+
label: pc.dim("Done")
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
if (isCancel(action) || action === "cancel") {
|
|
109
|
+
outro("");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const subChoice = await select({
|
|
113
|
+
message: "Select submission",
|
|
114
|
+
options: pendingSubs.map((sub) => ({
|
|
115
|
+
value: sub.id,
|
|
116
|
+
label: `${sub.id.slice(0, 8)} — ${summarizeData(sub.data)}`
|
|
117
|
+
}))
|
|
118
|
+
});
|
|
119
|
+
if (isCancel(subChoice)) {
|
|
120
|
+
outro("");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const targetStatus = action === "approve" ? "approved" : "rejected";
|
|
124
|
+
const confirmed = await confirm({ message: `${action === "approve" ? "Approve" : "Reject"} submission ${subChoice.slice(0, 8)}?` });
|
|
125
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
126
|
+
outro(pc.dim("Cancelled"));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const ms = spinner();
|
|
130
|
+
ms.start(`${action === "approve" ? "Approving" : "Rejecting"}...`);
|
|
131
|
+
await client.updateSubmissionStatus(workspaceId, projectId, formId, subChoice, targetStatus);
|
|
132
|
+
ms.stop(`Submission ${targetStatus}`);
|
|
133
|
+
log.success(`Submission ${pc.cyan(subChoice.slice(0, 8))} ${targetStatus}.`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
|
138
|
+
if (!args.json) outro("");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
async function resolveFormId(isJson) {
|
|
142
|
+
if (isJson) {
|
|
143
|
+
const { log: l } = await import("@clack/prompts");
|
|
144
|
+
l.error("--form is required for JSON output.");
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const { text: textPrompt, isCancel: isCancelled } = await import("@clack/prompts");
|
|
149
|
+
const input = await textPrompt({ message: "Form model ID" });
|
|
150
|
+
if (isCancelled(input)) return null;
|
|
151
|
+
return input;
|
|
152
|
+
}
|
|
153
|
+
function summarizeData(data) {
|
|
154
|
+
return Object.entries(data).slice(0, 2).map(([k, v]) => {
|
|
155
|
+
const val = typeof v === "string" ? v : JSON.stringify(v);
|
|
156
|
+
return `${k}: ${val.length > 20 ? val.slice(0, 17) + "..." : val}`;
|
|
157
|
+
}).join(", ") || "(empty)";
|
|
158
|
+
}
|
|
159
|
+
function timeAgo(dateStr) {
|
|
160
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
161
|
+
const minutes = Math.floor(diff / 6e4);
|
|
162
|
+
if (minutes < 1) return "just now";
|
|
163
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
164
|
+
const hours = Math.floor(minutes / 60);
|
|
165
|
+
if (hours < 24) return `${hours}h ago`;
|
|
166
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { submissions_default as default };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { i as pc, n as formatPercent, r as formatTable } from "./ui-B5mXontH.mjs";
|
|
2
|
+
import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
|
+
//#region src/studio/commands/usage.ts
|
|
7
|
+
var usage_default = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "usage",
|
|
10
|
+
description: "Show workspace usage metrics"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
workspace: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Workspace ID",
|
|
16
|
+
required: false
|
|
17
|
+
},
|
|
18
|
+
project: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Project ID (for context resolution)",
|
|
21
|
+
required: false
|
|
22
|
+
},
|
|
23
|
+
json: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "JSON output",
|
|
26
|
+
required: false
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async run({ args }) {
|
|
30
|
+
if (!args.json) intro(pc.bold("contentrain studio usage"));
|
|
31
|
+
try {
|
|
32
|
+
const client = await resolveStudioClient();
|
|
33
|
+
const ctx = await resolveStudioContext(client, args);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
log.warning("No workspace found.");
|
|
36
|
+
outro("");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const s = args.json ? null : spinner();
|
|
40
|
+
s?.start("Fetching usage metrics...");
|
|
41
|
+
const usage = await client.getWorkspaceUsage(ctx.workspaceId);
|
|
42
|
+
s?.stop("Usage loaded");
|
|
43
|
+
if (args.json) {
|
|
44
|
+
process.stdout.write(JSON.stringify(usage, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const rows = [
|
|
48
|
+
metricRow("AI messages", usage.aiMessages),
|
|
49
|
+
metricRow("Form submissions", usage.formSubmissions),
|
|
50
|
+
metricRow("CDN bandwidth", usage.cdnBandwidthGb, "GB"),
|
|
51
|
+
metricRow("Media storage", usage.mediaStorageGb, "GB")
|
|
52
|
+
];
|
|
53
|
+
log.message(formatTable([
|
|
54
|
+
"Metric",
|
|
55
|
+
"Used",
|
|
56
|
+
"Limit",
|
|
57
|
+
"%"
|
|
58
|
+
], rows));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
if (!args.json) outro("");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
function metricRow(name, metric, unit) {
|
|
67
|
+
const suffix = unit ? ` ${unit}` : "";
|
|
68
|
+
return [
|
|
69
|
+
name,
|
|
70
|
+
formatNumber(metric.current) + suffix,
|
|
71
|
+
metric.limit < 0 ? "unlimited" : formatNumber(metric.limit) + suffix,
|
|
72
|
+
metric.limit < 0 ? pc.dim("—") : formatPercent(metric.current, metric.limit)
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
function formatNumber(n) {
|
|
76
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
77
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
78
|
+
return String(n);
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { usage_default as default };
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { i as pc, o as statusIcon, r as formatTable } from "./ui-B5mXontH.mjs";
|
|
2
|
+
import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
6
|
+
//#region src/studio/commands/webhooks.ts
|
|
7
|
+
const ALL_EVENTS = [
|
|
8
|
+
{
|
|
9
|
+
value: "content.saved",
|
|
10
|
+
label: "Content saved"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: "content.deleted",
|
|
14
|
+
label: "Content deleted"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: "model.saved",
|
|
18
|
+
label: "Model updated"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "branch.merged",
|
|
22
|
+
label: "Branch merged"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "branch.rejected",
|
|
26
|
+
label: "Branch rejected"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "cdn.build_complete",
|
|
30
|
+
label: "CDN build complete"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "media.uploaded",
|
|
34
|
+
label: "Media uploaded"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: "form.submitted",
|
|
38
|
+
label: "Form submitted"
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
var webhooks_default = defineCommand({
|
|
42
|
+
meta: {
|
|
43
|
+
name: "webhooks",
|
|
44
|
+
description: "Manage webhooks on Studio"
|
|
45
|
+
},
|
|
46
|
+
args: {
|
|
47
|
+
workspace: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Workspace ID",
|
|
50
|
+
required: false
|
|
51
|
+
},
|
|
52
|
+
project: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Project ID",
|
|
55
|
+
required: false
|
|
56
|
+
},
|
|
57
|
+
json: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
description: "JSON output",
|
|
60
|
+
required: false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async run({ args }) {
|
|
64
|
+
if (!args.json) intro(pc.bold("contentrain studio webhooks"));
|
|
65
|
+
try {
|
|
66
|
+
const client = await resolveStudioClient();
|
|
67
|
+
const ctx = await resolveStudioContext(client, args);
|
|
68
|
+
if (!ctx) {
|
|
69
|
+
log.warning("No workspace or project found.");
|
|
70
|
+
outro("");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const { workspaceId, projectId } = ctx;
|
|
74
|
+
const s = args.json ? null : spinner();
|
|
75
|
+
s?.start("Fetching webhooks...");
|
|
76
|
+
const webhooks = await client.listWebhooks(workspaceId, projectId);
|
|
77
|
+
s?.stop(`${webhooks.length} webhook(s)`);
|
|
78
|
+
if (args.json) {
|
|
79
|
+
process.stdout.write(JSON.stringify(webhooks, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (webhooks.length > 0) {
|
|
83
|
+
const rows = webhooks.map((w) => [
|
|
84
|
+
w.name,
|
|
85
|
+
w.url.length > 40 ? w.url.slice(0, 37) + "..." : w.url,
|
|
86
|
+
w.events.join(", "),
|
|
87
|
+
statusIcon(w.active)
|
|
88
|
+
]);
|
|
89
|
+
log.message(formatTable([
|
|
90
|
+
"Name",
|
|
91
|
+
"URL",
|
|
92
|
+
"Events",
|
|
93
|
+
"Active"
|
|
94
|
+
], rows));
|
|
95
|
+
} else log.info("No webhooks configured.");
|
|
96
|
+
const action = await select({
|
|
97
|
+
message: "What would you like to do?",
|
|
98
|
+
options: [
|
|
99
|
+
{
|
|
100
|
+
value: "create",
|
|
101
|
+
label: "Create new webhook"
|
|
102
|
+
},
|
|
103
|
+
...webhooks.length > 0 ? [
|
|
104
|
+
{
|
|
105
|
+
value: "test",
|
|
106
|
+
label: "Test a webhook"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: "delete",
|
|
110
|
+
label: "Delete a webhook"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
value: "deliveries",
|
|
114
|
+
label: "View delivery history"
|
|
115
|
+
}
|
|
116
|
+
] : [],
|
|
117
|
+
{
|
|
118
|
+
value: "cancel",
|
|
119
|
+
label: pc.dim("Done")
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
if (isCancel(action) || action === "cancel") {
|
|
124
|
+
outro("");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (action === "create") await createWebhook(client, workspaceId, projectId);
|
|
128
|
+
else if (action === "test") await testWebhook(client, workspaceId, projectId, webhooks);
|
|
129
|
+
else if (action === "delete") await deleteWebhook(client, workspaceId, projectId, webhooks);
|
|
130
|
+
else if (action === "deliveries") await viewDeliveries(client, workspaceId, projectId, webhooks);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
135
|
+
if (!args.json) outro("");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
async function createWebhook(client, wid, pid) {
|
|
139
|
+
const name = await text({ message: "Webhook name" });
|
|
140
|
+
if (isCancel(name)) return;
|
|
141
|
+
const url = await text({
|
|
142
|
+
message: "Endpoint URL",
|
|
143
|
+
validate: (v) => {
|
|
144
|
+
if (!v.startsWith("https://")) return "URL must start with https://";
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
if (isCancel(url)) return;
|
|
148
|
+
const events = await multiselect({
|
|
149
|
+
message: "Events to subscribe to",
|
|
150
|
+
options: ALL_EVENTS,
|
|
151
|
+
required: true
|
|
152
|
+
});
|
|
153
|
+
if (isCancel(events)) return;
|
|
154
|
+
const s = spinner();
|
|
155
|
+
s.start("Creating webhook...");
|
|
156
|
+
const webhook = await client.createWebhook(wid, pid, {
|
|
157
|
+
name,
|
|
158
|
+
url,
|
|
159
|
+
events
|
|
160
|
+
});
|
|
161
|
+
s.stop("Webhook created");
|
|
162
|
+
log.success(`${pc.cyan(webhook.name)} → ${webhook.url}`);
|
|
163
|
+
}
|
|
164
|
+
async function testWebhook(client, wid, pid, webhooks) {
|
|
165
|
+
const choice = await select({
|
|
166
|
+
message: "Select webhook to test",
|
|
167
|
+
options: webhooks.map((w) => ({
|
|
168
|
+
value: w.id,
|
|
169
|
+
label: w.name
|
|
170
|
+
}))
|
|
171
|
+
});
|
|
172
|
+
if (isCancel(choice)) return;
|
|
173
|
+
const s = spinner();
|
|
174
|
+
s.start("Sending test event...");
|
|
175
|
+
const result = await client.testWebhook(wid, pid, choice);
|
|
176
|
+
s.stop(result.success ? "Test succeeded" : "Test failed");
|
|
177
|
+
if (result.httpStatus) log.message(` HTTP status: ${result.httpStatus}`);
|
|
178
|
+
}
|
|
179
|
+
async function deleteWebhook(client, wid, pid, webhooks) {
|
|
180
|
+
const choice = await select({
|
|
181
|
+
message: "Select webhook to delete",
|
|
182
|
+
options: webhooks.map((w) => ({
|
|
183
|
+
value: w.id,
|
|
184
|
+
label: w.name
|
|
185
|
+
}))
|
|
186
|
+
});
|
|
187
|
+
if (isCancel(choice)) return;
|
|
188
|
+
const confirmed = await confirm({ message: `Delete webhook "${webhooks.find((w) => w.id === choice)?.name}"? This cannot be undone.` });
|
|
189
|
+
if (isCancel(confirmed) || !confirmed) return;
|
|
190
|
+
const s = spinner();
|
|
191
|
+
s.start("Deleting...");
|
|
192
|
+
await client.deleteWebhook(wid, pid, choice);
|
|
193
|
+
s.stop("Webhook deleted");
|
|
194
|
+
}
|
|
195
|
+
async function viewDeliveries(client, wid, pid, webhooks) {
|
|
196
|
+
const choice = await select({
|
|
197
|
+
message: "Select webhook",
|
|
198
|
+
options: webhooks.map((w) => ({
|
|
199
|
+
value: w.id,
|
|
200
|
+
label: w.name
|
|
201
|
+
}))
|
|
202
|
+
});
|
|
203
|
+
if (isCancel(choice)) return;
|
|
204
|
+
const s = spinner();
|
|
205
|
+
s.start("Fetching deliveries...");
|
|
206
|
+
const result = await client.listWebhookDeliveries(wid, pid, choice);
|
|
207
|
+
s.stop(`${result.data.length} deliveries`);
|
|
208
|
+
if (result.data.length === 0) {
|
|
209
|
+
log.info("No deliveries yet.");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const rows = result.data.map((d) => [
|
|
213
|
+
d.event,
|
|
214
|
+
d.status === "success" ? pc.green(d.status) : d.status === "failed" ? pc.red(d.status) : pc.yellow(d.status),
|
|
215
|
+
d.httpStatus ? String(d.httpStatus) : "—",
|
|
216
|
+
timeAgo(d.createdAt)
|
|
217
|
+
]);
|
|
218
|
+
log.message(formatTable([
|
|
219
|
+
"Event",
|
|
220
|
+
"Status",
|
|
221
|
+
"HTTP",
|
|
222
|
+
"Time"
|
|
223
|
+
], rows));
|
|
224
|
+
}
|
|
225
|
+
function timeAgo(dateStr) {
|
|
226
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
227
|
+
const minutes = Math.floor(diff / 6e4);
|
|
228
|
+
if (minutes < 1) return "just now";
|
|
229
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
230
|
+
const hours = Math.floor(minutes / 60);
|
|
231
|
+
if (hours < 24) return `${hours}h ago`;
|
|
232
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { webhooks_default as default };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
+
import { i as checkPermissions, n as resolveStudioClient, r as AuthExpiredError } from "./client-D4xW8aLz.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { intro, log, outro } from "@clack/prompts";
|
|
5
|
+
//#region src/studio/commands/whoami.ts
|
|
6
|
+
var whoami_default = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "whoami",
|
|
9
|
+
description: "Show current Studio authentication status"
|
|
10
|
+
},
|
|
11
|
+
args: { json: {
|
|
12
|
+
type: "boolean",
|
|
13
|
+
description: "JSON output",
|
|
14
|
+
required: false
|
|
15
|
+
} },
|
|
16
|
+
async run({ args }) {
|
|
17
|
+
if (!args.json) intro(pc.bold("contentrain studio whoami"));
|
|
18
|
+
try {
|
|
19
|
+
const client = await resolveStudioClient();
|
|
20
|
+
const user = await client.me();
|
|
21
|
+
if (args.json) {
|
|
22
|
+
const workspaces = await client.listWorkspaces();
|
|
23
|
+
process.stdout.write(JSON.stringify({
|
|
24
|
+
user: {
|
|
25
|
+
id: user.id,
|
|
26
|
+
email: user.email,
|
|
27
|
+
name: user.name,
|
|
28
|
+
provider: user.provider
|
|
29
|
+
},
|
|
30
|
+
workspaces: workspaces.map((w) => ({
|
|
31
|
+
id: w.id,
|
|
32
|
+
name: w.name,
|
|
33
|
+
slug: w.slug,
|
|
34
|
+
plan: w.plan,
|
|
35
|
+
role: w.role
|
|
36
|
+
}))
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
log.info(pc.bold("Profile"));
|
|
41
|
+
log.message(` Email: ${pc.cyan(user.email)}`);
|
|
42
|
+
if (user.name) log.message(` Name: ${user.name}`);
|
|
43
|
+
log.message(` Provider: ${user.provider}`);
|
|
44
|
+
const workspaces = await client.listWorkspaces();
|
|
45
|
+
if (workspaces.length > 0) {
|
|
46
|
+
log.info(pc.bold(`\nWorkspaces (${workspaces.length})`));
|
|
47
|
+
for (const ws of workspaces) log.message(` ${pc.cyan(ws.name)} (${ws.plan}) — ${pc.dim(ws.role)}`);
|
|
48
|
+
}
|
|
49
|
+
const permWarning = await checkPermissions();
|
|
50
|
+
if (permWarning) log.warning(`\n${permWarning}`);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof AuthExpiredError) {
|
|
53
|
+
if (args.json) process.stdout.write(JSON.stringify({ error: "not_authenticated" }));
|
|
54
|
+
else log.error(error.message);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
} else {
|
|
57
|
+
if (!args.json) log.error(error instanceof Error ? error.message : String(error));
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!args.json) outro("");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
//#endregion
|
|
65
|
+
export { whoami_default as default };
|