contentrain 0.3.1 → 0.3.3
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 +56 -2
- 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/{diff-otz0kGqT.mjs → diff-Cq6iSnaD.mjs} +45 -13
- package/dist/index.mjs +5 -4
- package/dist/login-CO0hxICh.mjs +243 -0
- package/dist/logout-CRM4fCSH.mjs +29 -0
- package/dist/resolve-context-BMV7JwCw.mjs +58 -0
- package/dist/{serve-CaCZMeHF.mjs → serve-DL4LhWxl.mjs} +1 -1
- package/dist/{server-DW0KXB0T.mjs → server-WLDOAZa4.mjs} +94 -36
- package/dist/{status-kF5miVty.mjs → status-Bi9HYgcA.mjs} +38 -5
- 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 +6 -6
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { c as saveDefaults, o as loadCredentials } from "./client-D4xW8aLz.mjs";
|
|
2
|
+
import { confirm, isCancel, select } from "@clack/prompts";
|
|
3
|
+
//#region src/studio/resolve-context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolve workspace + project IDs for a Studio command.
|
|
6
|
+
*
|
|
7
|
+
* Falls back to interactive prompts when IDs are not provided via args
|
|
8
|
+
* or saved defaults.
|
|
9
|
+
*/
|
|
10
|
+
async function resolveStudioContext(client, args) {
|
|
11
|
+
const credentials = await loadCredentials();
|
|
12
|
+
let workspaceId = args.workspace ?? credentials?.defaultWorkspaceId;
|
|
13
|
+
if (!workspaceId) {
|
|
14
|
+
const workspaces = await client.listWorkspaces();
|
|
15
|
+
if (workspaces.length === 0) return null;
|
|
16
|
+
if (workspaces.length === 1) workspaceId = workspaces[0].id;
|
|
17
|
+
else {
|
|
18
|
+
const choice = await select({
|
|
19
|
+
message: "Select workspace",
|
|
20
|
+
options: workspaces.map((w) => ({
|
|
21
|
+
value: w.id,
|
|
22
|
+
label: w.name,
|
|
23
|
+
hint: w.plan
|
|
24
|
+
}))
|
|
25
|
+
});
|
|
26
|
+
if (isCancel(choice)) return null;
|
|
27
|
+
workspaceId = choice;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let projectId = args.project ?? credentials?.defaultProjectId;
|
|
31
|
+
if (!projectId) {
|
|
32
|
+
const projects = await client.listProjects(workspaceId);
|
|
33
|
+
if (projects.length === 0) return null;
|
|
34
|
+
if (projects.length === 1) projectId = projects[0].id;
|
|
35
|
+
else {
|
|
36
|
+
const choice = await select({
|
|
37
|
+
message: "Select project",
|
|
38
|
+
options: projects.map((p) => ({
|
|
39
|
+
value: p.id,
|
|
40
|
+
label: p.name,
|
|
41
|
+
hint: p.stack
|
|
42
|
+
}))
|
|
43
|
+
});
|
|
44
|
+
if (isCancel(choice)) return null;
|
|
45
|
+
projectId = choice;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if ((workspaceId !== credentials?.defaultWorkspaceId || projectId !== credentials?.defaultProjectId) && credentials) {
|
|
49
|
+
const shouldSave = await confirm({ message: "Save as default workspace/project?" });
|
|
50
|
+
if (!isCancel(shouldSave) && shouldSave) await saveDefaults(workspaceId, projectId);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
workspaceId,
|
|
54
|
+
projectId
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { resolveStudioContext as t };
|
|
@@ -66,7 +66,7 @@ var serve_default = defineCommand({
|
|
|
66
66
|
consola.warn("Serve UI not found. Running in API-only mode.");
|
|
67
67
|
consola.info("UI will be available after building: cd packages/cli/src/serve-ui && pnpm build");
|
|
68
68
|
}
|
|
69
|
-
const { createServeApp } = await import("./server-
|
|
69
|
+
const { createServeApp } = await import("./server-WLDOAZa4.mjs");
|
|
70
70
|
const { app, handleUpgrade } = await createServeApp({
|
|
71
71
|
projectRoot,
|
|
72
72
|
port,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { simpleGit } from "simple-git";
|
|
2
|
+
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
2
5
|
import { join } from "node:path";
|
|
3
6
|
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
4
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -202,18 +205,19 @@ async function createServeApp(options) {
|
|
|
202
205
|
}));
|
|
203
206
|
router.add("/api/branches", defineEventHandler(async () => {
|
|
204
207
|
const branches = await simpleGit(projectRoot).branchLocal();
|
|
205
|
-
const crBranches = branches.all.filter((b) => b.startsWith("
|
|
208
|
+
const crBranches = branches.all.filter((b) => b.startsWith("cr/")).map((b) => ({
|
|
206
209
|
name: b,
|
|
207
|
-
current: b === branches.current
|
|
210
|
+
current: b === branches.current,
|
|
211
|
+
system: b === CONTENTRAIN_BRANCH
|
|
208
212
|
}));
|
|
209
213
|
return {
|
|
210
214
|
branches: crBranches,
|
|
211
|
-
total: crBranches.length
|
|
215
|
+
total: crBranches.filter((b) => !b.system).length
|
|
212
216
|
};
|
|
213
217
|
}));
|
|
214
218
|
router.add("/api/branches/diff", defineEventHandler(async (event) => {
|
|
215
219
|
const branchName = getQuery(event)["name"];
|
|
216
|
-
if (!branchName?.startsWith("
|
|
220
|
+
if (!branchName?.startsWith("cr/")) throw createError({
|
|
217
221
|
statusCode: 400,
|
|
218
222
|
message: "Invalid branch name"
|
|
219
223
|
});
|
|
@@ -232,29 +236,53 @@ async function createServeApp(options) {
|
|
|
232
236
|
message: "Method not allowed"
|
|
233
237
|
});
|
|
234
238
|
const branchName = (await readBody(event)).branch;
|
|
235
|
-
if (!branchName?.startsWith("
|
|
239
|
+
if (!branchName?.startsWith("cr/")) throw createError({
|
|
236
240
|
statusCode: 400,
|
|
237
241
|
message: "Invalid branch name"
|
|
238
242
|
});
|
|
239
243
|
const git = simpleGit(projectRoot);
|
|
240
244
|
const baseBranch = getDefaultBranch();
|
|
241
|
-
await git.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
|
|
245
|
+
if (!(await git.branchLocal()).all.includes(CONTENTRAIN_BRANCH)) await git.branch([CONTENTRAIN_BRANCH, baseBranch]);
|
|
246
|
+
const mergePath = join(tmpdir(), `cr-merge-${randomUUID()}`);
|
|
247
|
+
await git.raw([
|
|
248
|
+
"worktree",
|
|
249
|
+
"add",
|
|
250
|
+
mergePath,
|
|
251
|
+
CONTENTRAIN_BRANCH
|
|
247
252
|
]);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
const mergeGit = simpleGit(mergePath);
|
|
254
|
+
try {
|
|
255
|
+
await mergeGit.merge([baseBranch, "--no-edit"]).catch(() => {});
|
|
256
|
+
await mergeGit.merge([branchName, "--no-edit"]);
|
|
257
|
+
const tip = (await mergeGit.raw(["rev-parse", "HEAD"])).trim();
|
|
258
|
+
await git.raw([
|
|
259
|
+
"update-ref",
|
|
260
|
+
`refs/heads/${baseBranch}`,
|
|
261
|
+
tip
|
|
262
|
+
]);
|
|
263
|
+
if ((await git.raw(["branch", "--show-current"])).trim() === baseBranch) await git.checkout([
|
|
264
|
+
tip,
|
|
265
|
+
"--",
|
|
266
|
+
".contentrain/"
|
|
267
|
+
]);
|
|
268
|
+
await git.deleteLocalBranch(branchName, true);
|
|
269
|
+
broadcast({
|
|
270
|
+
type: "branch:merged",
|
|
271
|
+
branch: branchName
|
|
272
|
+
});
|
|
273
|
+
return {
|
|
274
|
+
status: "merged",
|
|
275
|
+
branch: branchName,
|
|
276
|
+
into: baseBranch
|
|
277
|
+
};
|
|
278
|
+
} finally {
|
|
279
|
+
await git.raw([
|
|
280
|
+
"worktree",
|
|
281
|
+
"remove",
|
|
282
|
+
mergePath,
|
|
283
|
+
"--force"
|
|
284
|
+
]).catch(() => {});
|
|
285
|
+
}
|
|
258
286
|
}));
|
|
259
287
|
router.add("/api/branches/reject", defineEventHandler(async (event) => {
|
|
260
288
|
if (event.method !== "POST") throw createError({
|
|
@@ -262,7 +290,7 @@ async function createServeApp(options) {
|
|
|
262
290
|
message: "Method not allowed"
|
|
263
291
|
});
|
|
264
292
|
const branchName = (await readBody(event)).branch;
|
|
265
|
-
if (!branchName?.startsWith("
|
|
293
|
+
if (!branchName?.startsWith("cr/")) throw createError({
|
|
266
294
|
statusCode: 400,
|
|
267
295
|
message: "Invalid branch name"
|
|
268
296
|
});
|
|
@@ -417,7 +445,7 @@ async function createServeApp(options) {
|
|
|
417
445
|
const op = JSON.parse(raw)?.lastOperation;
|
|
418
446
|
if (op?.tool === "contentrain_scan" || op?.tool === "contentrain_apply") lastNormalize = op;
|
|
419
447
|
} catch {}
|
|
420
|
-
const branches = (await simpleGit(projectRoot).branchLocal()).all.filter((b) => b.startsWith("
|
|
448
|
+
const branches = (await simpleGit(projectRoot).branchLocal()).all.filter((b) => b.startsWith("cr/normalize/")).map((b) => ({ name: b }));
|
|
421
449
|
return {
|
|
422
450
|
lastOperation: lastNormalize,
|
|
423
451
|
pendingBranches: branches
|
|
@@ -511,23 +539,53 @@ async function createServeApp(options) {
|
|
|
511
539
|
message: "Method not allowed"
|
|
512
540
|
});
|
|
513
541
|
const branchName = (await readBody(event)).branch;
|
|
514
|
-
if (!branchName?.startsWith("
|
|
542
|
+
if (!branchName?.startsWith("cr/")) throw createError({
|
|
515
543
|
statusCode: 400,
|
|
516
544
|
message: "Invalid branch name"
|
|
517
545
|
});
|
|
518
546
|
const git = simpleGit(projectRoot);
|
|
519
|
-
const
|
|
520
|
-
await git.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
547
|
+
const baseBranch = getDefaultBranch();
|
|
548
|
+
if (!(await git.branchLocal()).all.includes(CONTENTRAIN_BRANCH)) await git.branch([CONTENTRAIN_BRANCH, baseBranch]);
|
|
549
|
+
const mergePath = join(tmpdir(), `cr-merge-${randomUUID()}`);
|
|
550
|
+
await git.raw([
|
|
551
|
+
"worktree",
|
|
552
|
+
"add",
|
|
553
|
+
mergePath,
|
|
554
|
+
CONTENTRAIN_BRANCH
|
|
555
|
+
]);
|
|
556
|
+
const mergeGit = simpleGit(mergePath);
|
|
557
|
+
try {
|
|
558
|
+
await mergeGit.merge([baseBranch, "--no-edit"]).catch(() => {});
|
|
559
|
+
await mergeGit.merge([branchName, "--no-edit"]);
|
|
560
|
+
const tip = (await mergeGit.raw(["rev-parse", "HEAD"])).trim();
|
|
561
|
+
await git.raw([
|
|
562
|
+
"update-ref",
|
|
563
|
+
`refs/heads/${baseBranch}`,
|
|
564
|
+
tip
|
|
565
|
+
]);
|
|
566
|
+
if ((await git.raw(["branch", "--show-current"])).trim() === baseBranch) await git.checkout([
|
|
567
|
+
tip,
|
|
568
|
+
"--",
|
|
569
|
+
".contentrain/"
|
|
570
|
+
]);
|
|
571
|
+
await git.deleteLocalBranch(branchName, true);
|
|
572
|
+
broadcast({
|
|
573
|
+
type: "branch:merged",
|
|
574
|
+
branch: branchName
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
status: "merged",
|
|
578
|
+
branch: branchName,
|
|
579
|
+
into: baseBranch
|
|
580
|
+
};
|
|
581
|
+
} finally {
|
|
582
|
+
await git.raw([
|
|
583
|
+
"worktree",
|
|
584
|
+
"remove",
|
|
585
|
+
mergePath,
|
|
586
|
+
"--force"
|
|
587
|
+
]).catch(() => {});
|
|
588
|
+
}
|
|
531
589
|
}));
|
|
532
590
|
router.add("/api/normalize/sources", defineEventHandler(async () => {
|
|
533
591
|
const sourcesPath = join(crDir, "normalize-sources.json");
|
|
@@ -3,6 +3,7 @@ import { i as pc, n as formatPercent, r as formatTable, t as formatCount } from
|
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { intro, log, outro } from "@clack/prompts";
|
|
5
5
|
import { simpleGit } from "simple-git";
|
|
6
|
+
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
6
7
|
import { countEntries, readModel } from "@contentrain/mcp/core/model-manager";
|
|
7
8
|
import { validateProject } from "@contentrain/mcp/core/validator";
|
|
8
9
|
//#region src/commands/status.ts
|
|
@@ -43,7 +44,22 @@ var status_default = defineCommand({
|
|
|
43
44
|
};
|
|
44
45
|
} catch {}
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
+
const git = simpleGit(projectRoot);
|
|
48
|
+
const branches = await git.branch(["--list", "cr/*"]);
|
|
49
|
+
const allLocal = await git.branchLocal();
|
|
50
|
+
jsonResult["pending_branches"] = branches.all.filter((b) => b !== CONTENTRAIN_BRANCH);
|
|
51
|
+
const contentBranchExists = allLocal.all.includes(CONTENTRAIN_BRANCH);
|
|
52
|
+
const contentBranchInfo = { exists: contentBranchExists };
|
|
53
|
+
if (contentBranchExists) try {
|
|
54
|
+
const baseBranch = ctx.config?.repository?.default_branch ?? "main";
|
|
55
|
+
const aheadRaw = await git.raw([
|
|
56
|
+
"rev-list",
|
|
57
|
+
"--count",
|
|
58
|
+
`${baseBranch}..${CONTENTRAIN_BRANCH}`
|
|
59
|
+
]);
|
|
60
|
+
contentBranchInfo["ahead"] = Number.parseInt(aheadRaw.trim(), 10);
|
|
61
|
+
} catch {}
|
|
62
|
+
jsonResult["content_branch"] = contentBranchInfo;
|
|
47
63
|
} catch {}
|
|
48
64
|
}
|
|
49
65
|
process.stdout.write(JSON.stringify(jsonResult, null, 2));
|
|
@@ -91,9 +107,26 @@ var status_default = defineCommand({
|
|
|
91
107
|
], rows));
|
|
92
108
|
} else log.message(" No models yet. Run `contentrain init` with a template or create models.");
|
|
93
109
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
const
|
|
110
|
+
const git = simpleGit(projectRoot);
|
|
111
|
+
if ((await git.branchLocal()).all.includes(CONTENTRAIN_BRANCH)) try {
|
|
112
|
+
const baseBranch = ctx.config?.repository?.default_branch ?? "main";
|
|
113
|
+
const aheadRaw = await git.raw([
|
|
114
|
+
"rev-list",
|
|
115
|
+
"--count",
|
|
116
|
+
`${baseBranch}..${CONTENTRAIN_BRANCH}`
|
|
117
|
+
]);
|
|
118
|
+
const ahead = Number.parseInt(aheadRaw.trim(), 10);
|
|
119
|
+
if (ahead > 0) {
|
|
120
|
+
log.info(pc.bold(`\nContent branch`));
|
|
121
|
+
log.message(` ${pc.cyan(CONTENTRAIN_BRANCH)} is ${pc.yellow(String(ahead))} commit(s) ahead of ${baseBranch}`);
|
|
122
|
+
} else {
|
|
123
|
+
log.info(pc.bold(`\nContent branch`));
|
|
124
|
+
log.message(` ${pc.cyan(CONTENTRAIN_BRANCH)} is in sync with ${baseBranch}`);
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
const featureBranches = (await git.branch(["--list", "cr/*"])).all.filter((b) => b !== CONTENTRAIN_BRANCH);
|
|
128
|
+
if (featureBranches.length > 0) {
|
|
129
|
+
const count = featureBranches.length;
|
|
97
130
|
if (count >= 80) {
|
|
98
131
|
log.error(pc.bold(`\nBLOCKED: ${count} active contentrain branches (limit: 80)`));
|
|
99
132
|
log.message(` New writes are blocked. Merge or delete old branches with ${pc.cyan("contentrain diff")}.`);
|
|
@@ -101,7 +134,7 @@ var status_default = defineCommand({
|
|
|
101
134
|
log.warning(pc.bold(`\nWARNING: ${count} active contentrain branches (limit: 50)`));
|
|
102
135
|
log.message(` Consider merging or deleting old branches with ${pc.cyan("contentrain diff")}.`);
|
|
103
136
|
} else log.info(pc.bold(`\nPending branches (${count})`));
|
|
104
|
-
for (const branch of
|
|
137
|
+
for (const branch of featureBranches) log.message(` ${pc.yellow("●")} ${branch}`);
|
|
105
138
|
log.message(` Run ${pc.cyan("contentrain diff")} to review.`);
|
|
106
139
|
}
|
|
107
140
|
} catch {}
|
|
@@ -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 };
|