@camox/cli 0.58.0 → 0.59.0
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 +52 -210
- package/dist/api-DOHUrl0V.mjs +2 -0
- package/dist/api-DU0iBFMw.mjs +56 -0
- package/dist/index.mjs +273 -1374
- package/dist/lib/auth-core.mjs +1 -1
- package/package.json +2 -2
- package/dist/api-CBYfBQrH.mjs +0 -105
- package/dist/api-Ph2yMPXw.mjs +0 -2
- package/dist/{auth-core-xLeorUUV.mjs → auth-core-BpVsNVnR.mjs} +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as NOT_PUBLISHED_CODES, i as getProject, l as NO_DEVELOPMENT_DRAFT_ERROR, n as developmentDraftHeaders, o as DEVELOPMENT_DRAFT, r as getDevelopmentDraft, s as DRAFT_ENDED_ERROR_CODE, t as callTool } from "./api-CBYfBQrH.mjs";
|
|
3
2
|
import { t as installLocalhostDns } from "./local-dns-DQIT1ucr.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { i as UUID, n as pushCode, t as createCodeReadToken } from "./api-DU0iBFMw.mjs";
|
|
4
|
+
import { a as removeAuthTokenForUrl, i as readAuthTokenForUrl, o as verifyOneTimeToken, s as writeAuthTokenForUrl } from "./auth-core-BpVsNVnR.mjs";
|
|
5
5
|
import { object, or } from "@optique/core/constructs";
|
|
6
6
|
import { message } from "@optique/core/message";
|
|
7
7
|
import { defineProgram } from "@optique/core/program";
|
|
8
8
|
import { runSync } from "@optique/run";
|
|
9
|
-
import { mkdtemp, open, readFile, rm, stat } from "node:fs/promises";
|
|
10
|
-
import { multiple, optional } from "@optique/core/modifiers";
|
|
11
|
-
import { command, constant, option } from "@optique/core/primitives";
|
|
12
|
-
import { choice, string } from "@optique/core/valueparser";
|
|
13
9
|
import { execSync, spawnSync } from "node:child_process";
|
|
14
|
-
import
|
|
10
|
+
import path, { join, resolve } from "node:path";
|
|
15
11
|
import * as p from "@clack/prompts";
|
|
16
12
|
import { log } from "@clack/prompts";
|
|
17
|
-
import
|
|
13
|
+
import { optional } from "@optique/core/modifiers";
|
|
14
|
+
import { argument, command, constant, option } from "@optique/core/primitives";
|
|
15
|
+
import { string } from "@optique/core/valueparser";
|
|
16
|
+
import http from "node:http";
|
|
17
|
+
import fs, { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
|
-
import
|
|
19
|
+
import { text } from "node:stream/consumers";
|
|
20
20
|
import { ZodError, z } from "zod";
|
|
21
21
|
//#region src/lib/auth.ts
|
|
22
22
|
const CAMOX_URL = process.env.CAMOX_URL || "https://app.camox.ai";
|
|
@@ -118,7 +118,7 @@ async function authenticateUser() {
|
|
|
118
118
|
async function getOrAuthenticate() {
|
|
119
119
|
const stored = readAuthToken();
|
|
120
120
|
if (stored) {
|
|
121
|
-
const { verifySession } = await import("./api-
|
|
121
|
+
const { verifySession } = await import("./api-DOHUrl0V.mjs");
|
|
122
122
|
if (await verifySession(stored.token)) {
|
|
123
123
|
p.log.info(`Authenticated as ${stored.name}`);
|
|
124
124
|
return stored;
|
|
@@ -129,63 +129,133 @@ async function getOrAuthenticate() {
|
|
|
129
129
|
return authenticateUser();
|
|
130
130
|
}
|
|
131
131
|
//#endregion
|
|
132
|
-
//#region src/lib/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
//#region src/lib/git-credential.ts
|
|
133
|
+
/**
|
|
134
|
+
* The Git config entries that make `camox git-credential` the credential helper for a remote's
|
|
135
|
+
* host, written in a clone's own config only. An empty `helper` first drops every helper set
|
|
136
|
+
* elsewhere for that host, such as a keychain that would keep expired read tokens. Git sends the
|
|
137
|
+
* path, which names the project.
|
|
138
|
+
*/
|
|
139
|
+
function credentialHelperConfig(remote) {
|
|
140
|
+
const { origin } = new URL(remote);
|
|
141
|
+
return [
|
|
142
|
+
[`credential.${origin}.helper`, ""],
|
|
143
|
+
[`credential.${origin}.helper`, `!${helperCommand()}`],
|
|
144
|
+
[`credential.${origin}.useHttpPath`, "true"]
|
|
145
|
+
];
|
|
136
146
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
/** This CLI, as a shell runs it: with this Node.js, so it works without `camox` on the PATH. */
|
|
148
|
+
function helperCommand() {
|
|
149
|
+
const quote = (arg) => `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
150
|
+
return `${quote(process.execPath)} ${quote(process.argv[1])} git-credential`;
|
|
140
151
|
}
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
/** Parses what Git writes to a credential helper: `key=value` lines up to a blank one. */
|
|
153
|
+
function parseCredentialRequest(input) {
|
|
154
|
+
const request = {};
|
|
155
|
+
for (const line of input.split("\n")) {
|
|
156
|
+
if (line === "") break;
|
|
157
|
+
const at = line.indexOf("=");
|
|
158
|
+
if (at > 0) request[line.slice(0, at)] = line.slice(at + 1);
|
|
159
|
+
}
|
|
160
|
+
return request;
|
|
143
161
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
/** The Project ID a repo's remote path names: `git/<namespace>/<Project ID>.git`. */
|
|
163
|
+
function projectIdOfPath(path) {
|
|
164
|
+
return path?.match(/([^/]+?)(?:\.git)?\/?$/)?.[1];
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/commands/clone.ts
|
|
168
|
+
const parser$5 = command("clone", object({
|
|
169
|
+
command: constant("clone"),
|
|
170
|
+
project: argument(string({ metavar: "PROJECT" }), { description: message`The project's Project Slug or Project ID.` }),
|
|
171
|
+
directory: optional(argument(string({ metavar: "DIRECTORY" }), { description: message`Where to clone it. Defaults to its Project Slug.` }))
|
|
172
|
+
}), { description: message`Clone a project's code, for a project whose code can be worked on outside Camox.` });
|
|
173
|
+
async function handler$5(args) {
|
|
174
|
+
p.intro(`camox clone ${args.project}`);
|
|
175
|
+
const { token } = await getOrAuthenticate().catch(() => {
|
|
176
|
+
p.log.error("Authentication failed.");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
179
|
+
let access;
|
|
149
180
|
try {
|
|
150
|
-
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
181
|
+
access = await createCodeReadToken(token, args.project);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const directory = resolve(args.directory ?? access.projectSlug);
|
|
187
|
+
const config = credentialHelperConfig(access.remote).flatMap(([key, value]) => ["--config", `${key}=${value}`]);
|
|
188
|
+
const cloned = spawnSync("git", [
|
|
189
|
+
"clone",
|
|
190
|
+
...config,
|
|
191
|
+
access.remote,
|
|
192
|
+
directory
|
|
193
|
+
], { stdio: "inherit" });
|
|
194
|
+
if (cloned.error || cloned.status !== 0) {
|
|
195
|
+
p.log.error(cloned.error ? `Could not run Git: ${cloned.error.message}` : "Git couldn't clone it.");
|
|
196
|
+
process.exit(1);
|
|
158
197
|
}
|
|
198
|
+
p.log.info("Installing dependencies...");
|
|
199
|
+
const executable = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
200
|
+
const installed = spawnSync(executable, ["install"], {
|
|
201
|
+
cwd: directory,
|
|
202
|
+
stdio: "inherit"
|
|
203
|
+
});
|
|
204
|
+
if (installed.error || installed.status !== 0) {
|
|
205
|
+
p.log.error(`Cloned into ${directory}, but installing its dependencies failed.`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
p.outro(`Cloned into ${directory}.`);
|
|
209
|
+
process.exit(0);
|
|
159
210
|
}
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/commands/git-credential.ts
|
|
213
|
+
const parser$4 = command("git-credential", object({
|
|
214
|
+
command: constant("git-credential"),
|
|
215
|
+
action: argument(string({ metavar: "ACTION" }), { description: message`What Git asks: get, store or erase.` })
|
|
216
|
+
}), { description: message`The Git credential helper \`camox clone\` sets up: answers Git with short-lived read tokens.` });
|
|
160
217
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
218
|
+
* Answers Git's credential protocol (git-credential(1)). Only `get` is answered, with a fresh
|
|
219
|
+
* read token: tokens are short-lived, so there's nothing to store or erase. Errors go to stderr,
|
|
220
|
+
* since stdout is Git's.
|
|
163
221
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!
|
|
171
|
-
|
|
222
|
+
async function handler$4(args) {
|
|
223
|
+
const request = parseCredentialRequest(await text(process.stdin));
|
|
224
|
+
if (args.action !== "get") process.exit(0);
|
|
225
|
+
const project = projectIdOfPath(request.path);
|
|
226
|
+
if (!project) process.exit(0);
|
|
227
|
+
const session = readAuthToken();
|
|
228
|
+
if (!session) {
|
|
229
|
+
process.stderr.write("camox: not logged in. Run `camox login`, then try again.\n");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const { token, expiresAt } = await createCodeReadToken(session.token, project);
|
|
234
|
+
const expiry = Math.floor(new Date(expiresAt).getTime() / 1e3);
|
|
235
|
+
process.stdout.write(`username=camox\npassword=${token}\npassword_expiry_utc=${expiry}\n`);
|
|
236
|
+
process.exit(0);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
process.stderr.write(`camox: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
172
241
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/commands/login.ts
|
|
244
|
+
const parser$3 = command("login", object({ command: constant("login") }));
|
|
245
|
+
const handler$3 = login;
|
|
246
|
+
async function login() {
|
|
247
|
+
p.intro("camox login");
|
|
248
|
+
try {
|
|
249
|
+
await getOrAuthenticate();
|
|
250
|
+
} catch {
|
|
251
|
+
p.log.error("Authentication failed.");
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
p.outro("You're all set!");
|
|
255
|
+
process.exit(0);
|
|
184
256
|
}
|
|
185
257
|
//#endregion
|
|
186
258
|
//#region ../api-contract/src/ids.ts
|
|
187
|
-
/** The id of a content entity: one UUIDv7, kept in Live and every draft (ADR 0004). */
|
|
188
|
-
const uuidv7Schema = /* @__PURE__ */ z.uuidv7();
|
|
189
259
|
/**
|
|
190
260
|
* A Project ID: a UUIDv7 in lowercase only, the form the project's Worker is named by, so an
|
|
191
261
|
* uppercase one fails here rather than silently naming no project.
|
|
@@ -267,817 +337,15 @@ function loadRuntime(cwd = process.cwd()) {
|
|
|
267
337
|
}
|
|
268
338
|
}
|
|
269
339
|
//#endregion
|
|
270
|
-
//#region src/lib/dispatch.ts
|
|
271
|
-
/**
|
|
272
|
-
* Strip undefined fields. Optique returns `undefined` for absent optional
|
|
273
|
-
* flags, but the tool's Zod input often distinguishes `key: undefined` from
|
|
274
|
-
* the key being absent. Send only the keys the user actually passed.
|
|
275
|
-
*/
|
|
276
|
-
function compact(input) {
|
|
277
|
-
const out = {};
|
|
278
|
-
for (const [k, v] of Object.entries(input)) if (v !== void 0) out[k] = v;
|
|
279
|
-
return out;
|
|
280
|
-
}
|
|
281
|
-
function fail$2(err, code) {
|
|
282
|
-
printError(err);
|
|
283
|
-
process.exit(code);
|
|
284
|
-
}
|
|
285
|
-
async function resolveProjectId(token, project, apiUrl) {
|
|
286
|
-
try {
|
|
287
|
-
return (await getProject(token, project, apiUrl)).id;
|
|
288
|
-
} catch (err) {
|
|
289
|
-
if (isNoDevelopmentDraft(err)) return fail$2(NO_DEVELOPMENT_DRAFT_CLI_ERROR, 2);
|
|
290
|
-
return fail$2({
|
|
291
|
-
code: "PROJECT_LOOKUP_FAILED",
|
|
292
|
-
message: `Could not load project "${project}".`,
|
|
293
|
-
details: err instanceof Error ? err.message : String(err)
|
|
294
|
-
}, 2);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
async function callRemote(params) {
|
|
298
|
-
try {
|
|
299
|
-
return await callTool(params);
|
|
300
|
-
} catch (err) {
|
|
301
|
-
return fail$2(asCliError(err), 1);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Resolve auth + project, call the registered tool via `agent.callTool`,
|
|
306
|
-
* and render the result. Exit codes: 0 on success, 1 on tool error, 2 on
|
|
307
|
-
* auth/project resolution failure.
|
|
308
|
-
*
|
|
309
|
-
* Project, apiUrl, and authenticationUrl come from the vite plugin's
|
|
310
|
-
* `node_modules/.camox/runtime.json` sidecar. `--project` and `CAMOX_PROJECT`
|
|
311
|
-
* may override the project, by its Project ID or its Project Slug. Every request names the caller's
|
|
312
|
-
* development draft (ADR 0010); no command takes a draft, Live or production flag.
|
|
313
|
-
*/
|
|
314
|
-
async function resolveCommandContext(opts) {
|
|
315
|
-
let runtime;
|
|
316
|
-
try {
|
|
317
|
-
runtime = loadRuntime(opts.cwd);
|
|
318
|
-
} catch (err) {
|
|
319
|
-
if (err instanceof RuntimeDirectoryError) return fail$2({
|
|
320
|
-
code: "INVALID_CWD",
|
|
321
|
-
message: err.message
|
|
322
|
-
}, 2);
|
|
323
|
-
if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) return fail$2({
|
|
324
|
-
code: "RUNTIME_NOT_FOUND",
|
|
325
|
-
message: err.message
|
|
326
|
-
}, 2);
|
|
327
|
-
throw err;
|
|
328
|
-
}
|
|
329
|
-
const project = opts.projectFlag?.trim() || process.env.CAMOX_PROJECT?.trim() || runtime.projectId;
|
|
330
|
-
const token = readAuthTokenForUrl(runtime.authenticationUrl, opts.cwd);
|
|
331
|
-
if (!token) return fail$2({
|
|
332
|
-
code: "NOT_AUTHENTICATED",
|
|
333
|
-
message: `No stored credentials for ${runtime.authenticationUrl}. Run \`camox login\` against this backend first.`
|
|
334
|
-
}, 2);
|
|
335
|
-
const projectId = await resolveProjectId(token.token, project, runtime.apiUrl);
|
|
336
|
-
return {
|
|
337
|
-
token: token.token,
|
|
338
|
-
apiUrl: runtime.apiUrl,
|
|
339
|
-
projectId,
|
|
340
|
-
disableTelemetry: runtime.disableTelemetry
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Resolve auth + project and call a tool, returning its result for the command to render.
|
|
345
|
-
* Exits like `dispatch` when the call fails.
|
|
346
|
-
*/
|
|
347
|
-
async function callToolResult(opts) {
|
|
348
|
-
const response = await callRemote({
|
|
349
|
-
...await resolveCommandContext(opts),
|
|
350
|
-
name: opts.toolName,
|
|
351
|
-
args: compact({
|
|
352
|
-
...opts.args,
|
|
353
|
-
locale: opts.locale
|
|
354
|
-
})
|
|
355
|
-
});
|
|
356
|
-
if (!response.ok) {
|
|
357
|
-
printError(response.error);
|
|
358
|
-
process.exit(1);
|
|
359
|
-
}
|
|
360
|
-
return response.result;
|
|
361
|
-
}
|
|
362
|
-
async function dispatch(opts) {
|
|
363
|
-
printResult(await callToolResult(opts), opts.outputMode);
|
|
364
|
-
process.exit(0);
|
|
365
|
-
}
|
|
366
|
-
//#endregion
|
|
367
340
|
//#region src/lib/runtime-options.ts
|
|
368
|
-
const cwdFlag = optional(option("--cwd", string({ metavar: "PATH" }), { description: message`Start runtime lookup in this directory and walk upward. Relative to the shell's current directory
|
|
369
|
-
/** The project a command targets, overriding the one in the runtime sidecar. */
|
|
370
|
-
const projectFlag = optional(option("--project", string({ metavar: "PROJECT" }), { description: message`Target this project, by its Project ID or its Project Slug. Takes precedence over CAMOX_PROJECT. Defaults to the project of the app found by the runtime lookup.` }));
|
|
371
|
-
/** The locale a content command reads or writes (ADR 0015). */
|
|
372
|
-
const localeFlag = optional(option("--locale", string({ metavar: "TAG" }), { description: message`Read or write content in this locale, by its BCP 47 tag, such as de. Defaults to the default locale. List locales with locales list.` }));
|
|
373
|
-
//#endregion
|
|
374
|
-
//#region src/lib/uuidv7.ts
|
|
375
|
-
/** An entity id option: rejects anything that isn't a UUIDv7 before any request. */
|
|
376
|
-
function uuidv7() {
|
|
377
|
-
return {
|
|
378
|
-
mode: "sync",
|
|
379
|
-
metavar: "ID",
|
|
380
|
-
placeholder: "",
|
|
381
|
-
parse(input) {
|
|
382
|
-
if (uuidv7Schema.safeParse(input).success) return {
|
|
383
|
-
success: true,
|
|
384
|
-
value: input
|
|
385
|
-
};
|
|
386
|
-
return {
|
|
387
|
-
success: false,
|
|
388
|
-
error: message`Expected a UUIDv7, but got ${input}.`
|
|
389
|
-
};
|
|
390
|
-
},
|
|
391
|
-
format: (value) => value
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
//#endregion
|
|
395
|
-
//#region src/commands/blocks.ts
|
|
396
|
-
const jsonFlag$3 = option("--json");
|
|
397
|
-
const fieldFileFlag = multiple(option("--field-file", string({ metavar: "NAME=PATH" }), { description: message`Set the string field NAME to the raw contents of the file at PATH, without JSON escaping. Repeat to set several fields. Merges with --content; when both set a field, the file wins.` }));
|
|
398
|
-
const types = command("types", object({
|
|
399
|
-
command: constant("blocks.types"),
|
|
400
|
-
cwd: cwdFlag,
|
|
401
|
-
project: projectFlag,
|
|
402
|
-
json: jsonFlag$3
|
|
403
|
-
}));
|
|
404
|
-
const describe = command("describe", object({
|
|
405
|
-
command: constant("blocks.describe"),
|
|
406
|
-
type: multiple(option("--type", string({ metavar: "TYPE" })), { min: 1 }),
|
|
407
|
-
cwd: cwdFlag,
|
|
408
|
-
project: projectFlag,
|
|
409
|
-
json: jsonFlag$3
|
|
410
|
-
}));
|
|
411
|
-
const get$2 = command("get", object({
|
|
412
|
-
command: constant("blocks.get"),
|
|
413
|
-
id: option("--id", uuidv7()),
|
|
414
|
-
cwd: cwdFlag,
|
|
415
|
-
project: projectFlag,
|
|
416
|
-
locale: localeFlag,
|
|
417
|
-
json: jsonFlag$3
|
|
418
|
-
}));
|
|
419
|
-
const getMany = command("get-many", object({
|
|
420
|
-
command: constant("blocks.get-many"),
|
|
421
|
-
id: multiple(option("--id", uuidv7()), { min: 1 }),
|
|
422
|
-
cwd: cwdFlag,
|
|
423
|
-
project: projectFlag,
|
|
424
|
-
locale: localeFlag,
|
|
425
|
-
json: jsonFlag$3
|
|
426
|
-
}));
|
|
427
|
-
const create$1 = command("create", object({
|
|
428
|
-
command: constant("blocks.create"),
|
|
429
|
-
pageId: option("--page-id", uuidv7()),
|
|
430
|
-
type: option("--type", string({ metavar: "TYPE" })),
|
|
431
|
-
content: optional(option("--content", string({ metavar: "JSON" }))),
|
|
432
|
-
fieldFile: fieldFileFlag,
|
|
433
|
-
settings: optional(option("--settings", string({ metavar: "JSON" }))),
|
|
434
|
-
position: optional(option("--position", choice(["first", "last"], { metavar: "WHERE" }))),
|
|
435
|
-
afterId: optional(option("--after-id", uuidv7())),
|
|
436
|
-
beforeId: optional(option("--before-id", uuidv7())),
|
|
437
|
-
afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
|
|
438
|
-
beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
|
|
439
|
-
cwd: cwdFlag,
|
|
440
|
-
project: projectFlag,
|
|
441
|
-
locale: localeFlag,
|
|
442
|
-
json: jsonFlag$3
|
|
443
|
-
}));
|
|
444
|
-
const edit = command("edit", object({
|
|
445
|
-
command: constant("blocks.edit"),
|
|
446
|
-
id: option("--id", uuidv7()),
|
|
447
|
-
content: optional(option("--content", string({ metavar: "JSON" }))),
|
|
448
|
-
fieldFile: fieldFileFlag,
|
|
449
|
-
settings: optional(option("--settings", string({ metavar: "JSON" }))),
|
|
450
|
-
cwd: cwdFlag,
|
|
451
|
-
project: projectFlag,
|
|
452
|
-
locale: localeFlag,
|
|
453
|
-
json: jsonFlag$3
|
|
454
|
-
}));
|
|
455
|
-
const move = command("move", object({
|
|
456
|
-
command: constant("blocks.move"),
|
|
457
|
-
id: option("--id", uuidv7()),
|
|
458
|
-
position: optional(option("--position", choice(["first", "last"], { metavar: "WHERE" }))),
|
|
459
|
-
afterId: optional(option("--after-id", uuidv7())),
|
|
460
|
-
beforeId: optional(option("--before-id", uuidv7())),
|
|
461
|
-
afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
|
|
462
|
-
beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
|
|
463
|
-
cwd: cwdFlag,
|
|
464
|
-
project: projectFlag,
|
|
465
|
-
locale: localeFlag,
|
|
466
|
-
json: jsonFlag$3
|
|
467
|
-
}));
|
|
468
|
-
const del$1 = command("delete", object({
|
|
469
|
-
command: constant("blocks.delete"),
|
|
470
|
-
id: option("--id", uuidv7()),
|
|
471
|
-
cwd: cwdFlag,
|
|
472
|
-
project: projectFlag,
|
|
473
|
-
locale: localeFlag,
|
|
474
|
-
json: jsonFlag$3
|
|
475
|
-
}));
|
|
476
|
-
const parser$12 = command("blocks", or(types, describe, get$2, getMany, create$1, edit, move, del$1));
|
|
477
|
-
const POSITIONING_FLAGS = [
|
|
478
|
-
["position", "--position"],
|
|
479
|
-
["afterId", "--after-id"],
|
|
480
|
-
["beforeId", "--before-id"],
|
|
481
|
-
["afterPosition", "--after-position"],
|
|
482
|
-
["beforePosition", "--before-position"]
|
|
483
|
-
];
|
|
484
|
-
/**
|
|
485
|
-
* Both `create` and `move` accept multiple positioning inputs, but only one is
|
|
486
|
-
* meaningful per call. The server will reject conflicts, but we surface the
|
|
487
|
-
* mistake earlier (and with friendlier wording) by checking on the client.
|
|
488
|
-
*/
|
|
489
|
-
function collectPositioningArgs(args) {
|
|
490
|
-
const used = [];
|
|
491
|
-
const toolArgs = {};
|
|
492
|
-
for (const [key, flag] of POSITIONING_FLAGS) {
|
|
493
|
-
const value = args[key];
|
|
494
|
-
if (value === void 0) continue;
|
|
495
|
-
used.push(flag);
|
|
496
|
-
Object.assign(toolArgs, { [key]: value });
|
|
497
|
-
}
|
|
498
|
-
return {
|
|
499
|
-
used,
|
|
500
|
-
toolArgs
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
function fail$1(code, message) {
|
|
504
|
-
throw Object.assign(new Error(message), { __cliError: {
|
|
505
|
-
code,
|
|
506
|
-
message
|
|
507
|
-
} });
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Build block content from `--content` and repeated `--field-file NAME=PATH`
|
|
511
|
-
* flags. Every file is read before anything is sent, so a bad path fails the
|
|
512
|
-
* whole command without a partial write. Files win over `--content` on conflicts.
|
|
513
|
-
*/
|
|
514
|
-
async function resolveContent(rawContent, fieldFiles) {
|
|
515
|
-
const content = rawContent !== void 0 ? parseJsonFlag("--content", rawContent) : void 0;
|
|
516
|
-
if (fieldFiles.length === 0) return content;
|
|
517
|
-
if (content !== void 0 && (typeof content !== "object" || content === null || Array.isArray(content))) fail$1("INVALID_ARGS", "--content must be a JSON object when combined with --field-file.");
|
|
518
|
-
const merged = { ...content };
|
|
519
|
-
for (const entry of fieldFiles) {
|
|
520
|
-
const separator = entry.indexOf("=");
|
|
521
|
-
const name = entry.slice(0, separator);
|
|
522
|
-
const path = entry.slice(separator + 1);
|
|
523
|
-
if (separator <= 0 || !path) fail$1("INVALID_ARGS", `Invalid --field-file "${entry}". Use --field-file NAME=PATH.`);
|
|
524
|
-
try {
|
|
525
|
-
merged[name] = await readFile(path, "utf8");
|
|
526
|
-
} catch (err) {
|
|
527
|
-
fail$1("FILE_READ_ERROR", `Cannot read --field-file ${name}=${path} (${err instanceof Error && "code" in err ? String(err.code) : String(err)}).`);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return merged;
|
|
531
|
-
}
|
|
532
|
-
async function handler$12(args) {
|
|
533
|
-
const outputMode = args.json ? "json" : "auto";
|
|
534
|
-
const cwd = args.cwd;
|
|
535
|
-
const projectFlag = args.project;
|
|
536
|
-
const locale = args.locale;
|
|
537
|
-
try {
|
|
538
|
-
switch (args.command) {
|
|
539
|
-
case "blocks.types": return dispatch({
|
|
540
|
-
toolName: "listBlockTypes",
|
|
541
|
-
args: {},
|
|
542
|
-
cwd,
|
|
543
|
-
projectFlag,
|
|
544
|
-
locale,
|
|
545
|
-
outputMode
|
|
546
|
-
});
|
|
547
|
-
case "blocks.describe": return dispatch({
|
|
548
|
-
toolName: "describeBlockTypes",
|
|
549
|
-
args: { types: [...args.type] },
|
|
550
|
-
cwd,
|
|
551
|
-
projectFlag,
|
|
552
|
-
locale,
|
|
553
|
-
outputMode
|
|
554
|
-
});
|
|
555
|
-
case "blocks.get": return dispatch({
|
|
556
|
-
toolName: "getBlock",
|
|
557
|
-
args: { id: args.id },
|
|
558
|
-
cwd,
|
|
559
|
-
projectFlag,
|
|
560
|
-
locale,
|
|
561
|
-
outputMode
|
|
562
|
-
});
|
|
563
|
-
case "blocks.get-many": return dispatch({
|
|
564
|
-
toolName: "getBlocks",
|
|
565
|
-
args: { ids: [...args.id] },
|
|
566
|
-
cwd,
|
|
567
|
-
projectFlag,
|
|
568
|
-
locale,
|
|
569
|
-
outputMode
|
|
570
|
-
});
|
|
571
|
-
case "blocks.create": {
|
|
572
|
-
if (args.content === void 0 && args.fieldFile.length === 0) {
|
|
573
|
-
printError({
|
|
574
|
-
code: "INVALID_ARGS",
|
|
575
|
-
message: "Pass --content, --field-file, or both."
|
|
576
|
-
});
|
|
577
|
-
process.exit(2);
|
|
578
|
-
}
|
|
579
|
-
const content = await resolveContent(args.content, args.fieldFile);
|
|
580
|
-
const settings = args.settings !== void 0 ? parseJsonFlag("--settings", args.settings) : void 0;
|
|
581
|
-
const { used, toolArgs } = collectPositioningArgs(args);
|
|
582
|
-
if (used.length > 1) {
|
|
583
|
-
printError({
|
|
584
|
-
code: "INVALID_ARGS",
|
|
585
|
-
message: `Pass at most one positioning flag — got: ${used.join(", ")}.`
|
|
586
|
-
});
|
|
587
|
-
process.exit(2);
|
|
588
|
-
}
|
|
589
|
-
return dispatch({
|
|
590
|
-
toolName: "createBlock",
|
|
591
|
-
args: {
|
|
592
|
-
pageId: args.pageId,
|
|
593
|
-
type: args.type,
|
|
594
|
-
content,
|
|
595
|
-
settings,
|
|
596
|
-
...toolArgs
|
|
597
|
-
},
|
|
598
|
-
cwd,
|
|
599
|
-
projectFlag,
|
|
600
|
-
locale,
|
|
601
|
-
outputMode
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
case "blocks.edit": {
|
|
605
|
-
const content = await resolveContent(args.content, args.fieldFile);
|
|
606
|
-
const settings = args.settings !== void 0 ? parseJsonFlag("--settings", args.settings) : void 0;
|
|
607
|
-
return dispatch({
|
|
608
|
-
toolName: "editBlock",
|
|
609
|
-
args: {
|
|
610
|
-
id: args.id,
|
|
611
|
-
content,
|
|
612
|
-
settings
|
|
613
|
-
},
|
|
614
|
-
cwd,
|
|
615
|
-
projectFlag,
|
|
616
|
-
locale,
|
|
617
|
-
outputMode
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
case "blocks.move": {
|
|
621
|
-
const { used, toolArgs } = collectPositioningArgs(args);
|
|
622
|
-
if (used.length === 0) {
|
|
623
|
-
printError({
|
|
624
|
-
code: "INVALID_ARGS",
|
|
625
|
-
message: "Pass a positioning flag — one of --position, --after-id, --before-id, --after-position, --before-position. Use `--position last` to move a block to the end of the page."
|
|
626
|
-
});
|
|
627
|
-
process.exit(2);
|
|
628
|
-
}
|
|
629
|
-
if (used.length > 1) {
|
|
630
|
-
printError({
|
|
631
|
-
code: "INVALID_ARGS",
|
|
632
|
-
message: `Pass at most one positioning flag — got: ${used.join(", ")}.`
|
|
633
|
-
});
|
|
634
|
-
process.exit(2);
|
|
635
|
-
}
|
|
636
|
-
return dispatch({
|
|
637
|
-
toolName: "moveBlock",
|
|
638
|
-
args: {
|
|
639
|
-
id: args.id,
|
|
640
|
-
...toolArgs
|
|
641
|
-
},
|
|
642
|
-
cwd,
|
|
643
|
-
projectFlag,
|
|
644
|
-
locale,
|
|
645
|
-
outputMode
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
case "blocks.delete": return dispatch({
|
|
649
|
-
toolName: "deleteBlock",
|
|
650
|
-
args: { id: args.id },
|
|
651
|
-
cwd,
|
|
652
|
-
projectFlag,
|
|
653
|
-
locale,
|
|
654
|
-
outputMode
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
} catch (err) {
|
|
658
|
-
printError(asCliError(err));
|
|
659
|
-
process.exit(1);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
//#endregion
|
|
663
|
-
//#region src/commands/delete.ts
|
|
664
|
-
const parser$11 = command("delete", object({
|
|
665
|
-
command: constant("delete"),
|
|
666
|
-
yes: option("--yes", "-y", { description: message`Delete without asking for confirmation.` }),
|
|
667
|
-
cwd: cwdFlag,
|
|
668
|
-
project: projectFlag,
|
|
669
|
-
json: option("--json")
|
|
670
|
-
}), { description: message`Delete every change in your development draft. Live is untouched.` });
|
|
671
|
-
const WARNING = "This deletes every change in your development draft and can't be undone.";
|
|
672
|
-
async function confirmed() {
|
|
673
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
674
|
-
printError({
|
|
675
|
-
code: "CONFIRMATION_REQUIRED",
|
|
676
|
-
message: `${WARNING} Re-run with --yes to confirm.`
|
|
677
|
-
});
|
|
678
|
-
process.exit(2);
|
|
679
|
-
}
|
|
680
|
-
return await p.confirm({
|
|
681
|
-
message: `${WARNING} Delete it?`,
|
|
682
|
-
initialValue: false
|
|
683
|
-
}) === true;
|
|
684
|
-
}
|
|
685
|
-
async function handler$11(args) {
|
|
686
|
-
const outputMode = args.json ? "json" : "auto";
|
|
687
|
-
if (!args.yes && !await confirmed()) {
|
|
688
|
-
printError({
|
|
689
|
-
code: "CANCELLED",
|
|
690
|
-
message: "Nothing was deleted."
|
|
691
|
-
});
|
|
692
|
-
process.exit(1);
|
|
693
|
-
}
|
|
694
|
-
const result = await callToolResult({
|
|
695
|
-
toolName: "deleteDraft",
|
|
696
|
-
args: {},
|
|
697
|
-
cwd: args.cwd,
|
|
698
|
-
projectFlag: args.project
|
|
699
|
-
});
|
|
700
|
-
if (isJsonMode(outputMode)) printResult(result, outputMode);
|
|
701
|
-
else process.stdout.write("Deleted every change in your development draft.\n");
|
|
702
|
-
process.exit(0);
|
|
703
|
-
}
|
|
704
|
-
const TIMEOUT_MS = 12e4;
|
|
705
|
-
const MAX_REDIRECTS = 5;
|
|
706
|
-
function fail(code, message) {
|
|
707
|
-
throw Object.assign(new Error(message), { __cliError: {
|
|
708
|
-
code,
|
|
709
|
-
message
|
|
710
|
-
} });
|
|
711
|
-
}
|
|
712
|
-
function validateMetadata(options) {
|
|
713
|
-
if (options.alt !== void 0 && options.aiMetadata === "on") fail("INVALID_ARGS", "--alt cannot be combined with --ai-metadata on.");
|
|
714
|
-
}
|
|
715
|
-
function validateUpload(options) {
|
|
716
|
-
if (options.file !== void 0 === (options.url !== void 0)) fail("INVALID_ARGS", "Pass exactly one of --file or --url.");
|
|
717
|
-
validateMetadata(options);
|
|
718
|
-
validateFilename(options.filename);
|
|
719
|
-
}
|
|
720
|
-
function validateFilename(filename) {
|
|
721
|
-
if (filename !== void 0 && (!filename.trim() || /[\\/]/.test(filename) || filename.split("").some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || filename === "." || filename === "..")) fail("INVALID_ARGS", "--filename must be a non-empty filename without directories or control characters.");
|
|
722
|
-
}
|
|
723
|
-
function httpUrl(value) {
|
|
724
|
-
let url;
|
|
725
|
-
try {
|
|
726
|
-
url = new URL(value);
|
|
727
|
-
} catch {
|
|
728
|
-
return fail("DOWNLOAD_FAILED", "Invalid media URL.");
|
|
729
|
-
}
|
|
730
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) fail("DOWNLOAD_FAILED", "Media URLs must use HTTP(S) without embedded credentials.");
|
|
731
|
-
return url;
|
|
732
|
-
}
|
|
733
|
-
const MIME_TYPES = {
|
|
734
|
-
".jpg": "image/jpeg",
|
|
735
|
-
".jpeg": "image/jpeg",
|
|
736
|
-
".png": "image/png",
|
|
737
|
-
".webp": "image/webp",
|
|
738
|
-
".gif": "image/gif",
|
|
739
|
-
".avif": "image/avif",
|
|
740
|
-
".svg": "image/svg+xml",
|
|
741
|
-
".ico": "image/x-icon",
|
|
742
|
-
".bmp": "image/bmp",
|
|
743
|
-
".pdf": "application/pdf",
|
|
744
|
-
".mp4": "video/mp4",
|
|
745
|
-
".webm": "video/webm",
|
|
746
|
-
".mov": "video/quicktime",
|
|
747
|
-
".mp3": "audio/mpeg",
|
|
748
|
-
".wav": "audio/wav",
|
|
749
|
-
".ogg": "audio/ogg",
|
|
750
|
-
".txt": "text/plain",
|
|
751
|
-
".json": "application/json"
|
|
752
|
-
};
|
|
753
|
-
function mimeType(filename) {
|
|
754
|
-
return MIME_TYPES[extname(filename).toLowerCase()] ?? "application/octet-stream";
|
|
755
|
-
}
|
|
756
|
-
async function download(source, destination) {
|
|
757
|
-
try {
|
|
758
|
-
let url = httpUrl(source);
|
|
759
|
-
const signal = AbortSignal.timeout(TIMEOUT_MS);
|
|
760
|
-
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {
|
|
761
|
-
const response = await fetch(url, {
|
|
762
|
-
redirect: "manual",
|
|
763
|
-
signal
|
|
764
|
-
});
|
|
765
|
-
if ([
|
|
766
|
-
301,
|
|
767
|
-
302,
|
|
768
|
-
303,
|
|
769
|
-
307,
|
|
770
|
-
308
|
|
771
|
-
].includes(response.status)) {
|
|
772
|
-
await response.body?.cancel();
|
|
773
|
-
const location = response.headers.get("location");
|
|
774
|
-
if (!location || redirects === MAX_REDIRECTS) fail("DOWNLOAD_FAILED", "Missing redirect location or too many redirects.");
|
|
775
|
-
url = httpUrl(new URL(location, url).href);
|
|
776
|
-
continue;
|
|
777
|
-
}
|
|
778
|
-
if (!response.ok || !response.body) {
|
|
779
|
-
await response.body?.cancel();
|
|
780
|
-
fail("DOWNLOAD_FAILED", `Media download failed (HTTP ${response.status}).`);
|
|
781
|
-
}
|
|
782
|
-
if (Number(response.headers.get("content-length")) > 104857600) {
|
|
783
|
-
await response.body.cancel();
|
|
784
|
-
fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
785
|
-
}
|
|
786
|
-
const file = await open(destination, "w");
|
|
787
|
-
let size = 0;
|
|
788
|
-
try {
|
|
789
|
-
for await (const chunk of response.body) {
|
|
790
|
-
size += chunk.byteLength;
|
|
791
|
-
if (size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
792
|
-
await file.writeFile(chunk);
|
|
793
|
-
}
|
|
794
|
-
} finally {
|
|
795
|
-
await file.close();
|
|
796
|
-
}
|
|
797
|
-
const filename = basename(decodeURIComponent(url.pathname)) || "upload";
|
|
798
|
-
return {
|
|
799
|
-
filename,
|
|
800
|
-
mimeType: response.headers.get("content-type")?.split(";")[0]?.trim() || mimeType(filename)
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
return fail("DOWNLOAD_FAILED", "Too many redirects.");
|
|
804
|
-
} catch (error) {
|
|
805
|
-
if (error && typeof error === "object" && "__cliError" in error) throw error;
|
|
806
|
-
return fail("DOWNLOAD_FAILED", "Could not download media (network, timeout, or local I/O failure).");
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
async function uploadFile(context, options, targetId) {
|
|
810
|
-
validateUpload(options);
|
|
811
|
-
let temporaryDirectory;
|
|
812
|
-
try {
|
|
813
|
-
let path = options.file;
|
|
814
|
-
let filename = path ? basename(path) : "upload";
|
|
815
|
-
let type = mimeType(filename);
|
|
816
|
-
if (options.url !== void 0) {
|
|
817
|
-
temporaryDirectory = await mkdtemp(join(tmpdir(), "camox-upload-"));
|
|
818
|
-
path = join(temporaryDirectory, "download");
|
|
819
|
-
const downloaded = await download(options.url, path);
|
|
820
|
-
filename = downloaded.filename;
|
|
821
|
-
type = downloaded.mimeType;
|
|
822
|
-
}
|
|
823
|
-
if (!path) fail("INVALID_ARGS", "Provide a file path.");
|
|
824
|
-
const info = await stat(path);
|
|
825
|
-
if (!info.isFile()) fail("INVALID_ARGS", "Upload source must be a regular file.");
|
|
826
|
-
if (info.size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
827
|
-
const body = new FormData();
|
|
828
|
-
if (type === "application/octet-stream") type = mimeType(options.filename ?? filename);
|
|
829
|
-
body.set("file", await openAsBlob(path, { type }), options.filename ?? filename);
|
|
830
|
-
body.set("projectId", String(context.projectId));
|
|
831
|
-
if (options.alt !== void 0) body.set("alt", options.alt);
|
|
832
|
-
if (options.aiMetadata !== void 0 || options.alt !== void 0) body.set("aiMetadata", String(options.alt === void 0 && options.aiMetadata === "on"));
|
|
833
|
-
const headers = developmentDraftHeaders(context.token, context.disableTelemetry);
|
|
834
|
-
let response;
|
|
835
|
-
try {
|
|
836
|
-
const endpoint = targetId === void 0 ? "/files/upload" : `/files/${targetId}/content`;
|
|
837
|
-
response = await fetch(`${context.apiUrl}${endpoint}`, {
|
|
838
|
-
method: "POST",
|
|
839
|
-
headers,
|
|
840
|
-
body,
|
|
841
|
-
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
842
|
-
redirect: "error"
|
|
843
|
-
});
|
|
844
|
-
} catch {
|
|
845
|
-
return fail("UPLOAD_FAILED", targetId === void 0 ? "Media upload failed (network or timeout). Check files list before retrying." : `File replacement failed (network or timeout). Check files get --id ${targetId} before retrying.`);
|
|
846
|
-
}
|
|
847
|
-
if (!response.ok) {
|
|
848
|
-
if (isNoDevelopmentDraft(await response.json().catch(() => null))) throw Object.assign(new Error(NO_DEVELOPMENT_DRAFT_CLI_ERROR.message), { __cliError: NO_DEVELOPMENT_DRAFT_CLI_ERROR });
|
|
849
|
-
fail("UPLOAD_FAILED", `${targetId === void 0 ? "Media upload" : "File replacement"} failed (HTTP ${response.status}).`);
|
|
850
|
-
}
|
|
851
|
-
return await response.json();
|
|
852
|
-
} finally {
|
|
853
|
-
if (temporaryDirectory) await rm(temporaryDirectory, {
|
|
854
|
-
recursive: true,
|
|
855
|
-
force: true
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
//#endregion
|
|
860
|
-
//#region src/commands/files.ts
|
|
861
|
-
const common = {
|
|
862
|
-
cwd: cwdFlag,
|
|
863
|
-
project: projectFlag,
|
|
864
|
-
json: option("--json")
|
|
865
|
-
};
|
|
866
|
-
const metadata = {
|
|
867
|
-
alt: optional(option("--alt", string({ metavar: "TEXT" }), { description: message`Alt text (including an empty string). Alt text and filename are one group: writing either makes both manual, keeping the current value of the other.` })),
|
|
868
|
-
aiMetadata: optional(option("--ai-metadata", choice(["on", "off"]), { description: message`on makes alt text and filename generated again and schedules asynchronous generation for raster images; it cannot be combined with --alt, nor with --filename on a metadata-only update. off keeps the current values as manual.` }))
|
|
869
|
-
};
|
|
870
|
-
const parser$10 = command("files", or(command("upload", object({
|
|
871
|
-
command: constant("files.upload"),
|
|
872
|
-
file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Local file to upload. Pass exactly one of --file or --url.` })),
|
|
873
|
-
url: optional(option("--url", string({ metavar: "URL" }), { description: message`HTTP(S) URL to download and copy into Camox, not an external reference.` })),
|
|
874
|
-
filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Override the local basename or final URL path filename.` })),
|
|
875
|
-
...metadata,
|
|
876
|
-
...common
|
|
877
|
-
}), { description: message`Upload a file (maximum 100 MiB). Example: camox files upload --url https://example.com/hero.webp --alt "Product screenshot". Returns the stored file ID and URL. Without metadata options, raster images use automatic metadata.` }), command("update", object({
|
|
878
|
-
command: constant("files.update"),
|
|
879
|
-
id: option("--id", uuidv7()),
|
|
880
|
-
file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Replace file content from a local path, keeping its ID and references. Mutually exclusive with --url.` })),
|
|
881
|
-
url: optional(option("--url", string({ metavar: "URL" }), { description: message`Download and replace file content from an HTTP(S) URL, keeping its ID and references. Mutually exclusive with --file.` })),
|
|
882
|
-
filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Set the filename. Metadata-only renames preserve the URL and make alt text and filename manual; replacement uses the source filename unless overridden.` })),
|
|
883
|
-
...metadata,
|
|
884
|
-
...common
|
|
885
|
-
}), { description: message`Update metadata or replace file content with --file or --url (maximum 100 MiB). Replacing affects every reference to this file in your development draft; its ID stays unchanged but its URL changes. Omitted alt text and AI settings are preserved. Example: camox files update --id <FILE_ID> --file ./hero.jpg --alt "Updated description". Use --alt "" for intentionally empty alt text.` }), command("get", object({
|
|
886
|
-
command: constant("files.get"),
|
|
887
|
-
id: option("--id", uuidv7()),
|
|
888
|
-
...common
|
|
889
|
-
}), { description: message`Inspect a file in the selected project.` }), command("list", object({
|
|
890
|
-
command: constant("files.list"),
|
|
891
|
-
...common
|
|
892
|
-
}), { description: message`List files in the selected project.` })), { description: message`Upload and manage files in your development draft.` });
|
|
893
|
-
async function handler$10(args) {
|
|
894
|
-
const outputMode = args.json ? "json" : "auto";
|
|
895
|
-
const options = {
|
|
896
|
-
cwd: args.cwd,
|
|
897
|
-
projectFlag: args.project,
|
|
898
|
-
outputMode
|
|
899
|
-
};
|
|
900
|
-
try {
|
|
901
|
-
if (args.command === "files.upload") {
|
|
902
|
-
validateUpload(args);
|
|
903
|
-
printResult(await uploadFile(await resolveCommandContext(options), args), outputMode);
|
|
904
|
-
process.exit(0);
|
|
905
|
-
}
|
|
906
|
-
if (args.command === "files.list") return dispatch({
|
|
907
|
-
...options,
|
|
908
|
-
toolName: "listFiles",
|
|
909
|
-
args: {}
|
|
910
|
-
});
|
|
911
|
-
if (args.command === "files.get") return dispatch({
|
|
912
|
-
...options,
|
|
913
|
-
toolName: "getFile",
|
|
914
|
-
args: { id: args.id }
|
|
915
|
-
});
|
|
916
|
-
if (args.file !== void 0 || args.url !== void 0) {
|
|
917
|
-
validateUpload(args);
|
|
918
|
-
printResult(await uploadFile(await resolveCommandContext(options), args, args.id), outputMode);
|
|
919
|
-
process.exit(0);
|
|
920
|
-
}
|
|
921
|
-
validateMetadata(args);
|
|
922
|
-
validateFilename(args.filename);
|
|
923
|
-
if (args.filename !== void 0 && args.aiMetadata === "on") {
|
|
924
|
-
printError({
|
|
925
|
-
code: "INVALID_ARGS",
|
|
926
|
-
message: "--filename cannot be combined with --ai-metadata on."
|
|
927
|
-
});
|
|
928
|
-
process.exit(2);
|
|
929
|
-
}
|
|
930
|
-
if (args.alt === void 0 && args.aiMetadata === void 0 && args.filename === void 0) {
|
|
931
|
-
printError({
|
|
932
|
-
code: "INVALID_ARGS",
|
|
933
|
-
message: "Pass --file, --url, --filename, --alt or --ai-metadata."
|
|
934
|
-
});
|
|
935
|
-
process.exit(2);
|
|
936
|
-
}
|
|
937
|
-
return dispatch({
|
|
938
|
-
...options,
|
|
939
|
-
toolName: "updateFile",
|
|
940
|
-
args: {
|
|
941
|
-
id: args.id,
|
|
942
|
-
alt: args.alt,
|
|
943
|
-
filename: args.filename,
|
|
944
|
-
aiMetadata: args.aiMetadata === void 0 ? void 0 : args.aiMetadata === "on"
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
} catch (err) {
|
|
948
|
-
const error = asCliError(err);
|
|
949
|
-
printError(error);
|
|
950
|
-
process.exit(error.code === "INVALID_ARGS" ? 2 : 1);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
//#endregion
|
|
954
|
-
//#region src/commands/init.ts
|
|
955
|
-
const parser$9 = command("init", object({ command: constant("init") }));
|
|
956
|
-
const handler$9 = init;
|
|
957
|
-
const DASHBOARD_URL = process.env.CAMOX_URL || "https://app.camox.ai";
|
|
958
|
-
/**
|
|
959
|
-
* Projects are created from the dashboard, which hosts them, so `camox init` (and
|
|
960
|
-
* `npm create camox`, which runs it) only says where to go. It exits non-zero, since it created
|
|
961
|
-
* nothing.
|
|
962
|
-
*/
|
|
963
|
-
function init() {
|
|
964
|
-
p.intro("camox init");
|
|
965
|
-
p.log.info(`Projects are now created from the Camox dashboard: ${DASHBOARD_URL}`);
|
|
966
|
-
p.outro("Nothing was created.");
|
|
967
|
-
process.exit(1);
|
|
968
|
-
}
|
|
969
|
-
//#endregion
|
|
970
|
-
//#region src/commands/layouts.ts
|
|
971
|
-
const jsonFlag$2 = option("--json");
|
|
972
|
-
const list$2 = command("list", object({
|
|
973
|
-
command: constant("layouts.list"),
|
|
974
|
-
cwd: cwdFlag,
|
|
975
|
-
project: projectFlag,
|
|
976
|
-
json: jsonFlag$2
|
|
977
|
-
}), { description: message`List layout metadata. Use layouts get --layout-id NAME with a returned layoutId to discover its blocks.` });
|
|
978
|
-
const get$1 = command("get", object({
|
|
979
|
-
command: constant("layouts.get"),
|
|
980
|
-
layoutId: option("--layout-id", string({ metavar: "NAME" })),
|
|
981
|
-
cwd: cwdFlag,
|
|
982
|
-
project: projectFlag,
|
|
983
|
-
locale: localeFlag,
|
|
984
|
-
json: jsonFlag$2
|
|
985
|
-
}), { description: message`Inspect a layout and its blocks, including shared navigation and footer IDs for blocks get and blocks edit. A layout is named by the layoutId its code gives it, as listed by layouts list.` });
|
|
986
|
-
const parser$8 = command("layouts", or(list$2, get$1));
|
|
987
|
-
async function handler$8(args) {
|
|
988
|
-
const outputMode = args.json ? "json" : "auto";
|
|
989
|
-
const cwd = args.cwd;
|
|
990
|
-
const projectFlag = args.project;
|
|
991
|
-
const locale = args.locale;
|
|
992
|
-
switch (args.command) {
|
|
993
|
-
case "layouts.list": return dispatch({
|
|
994
|
-
toolName: "listLayouts",
|
|
995
|
-
args: {},
|
|
996
|
-
cwd,
|
|
997
|
-
projectFlag,
|
|
998
|
-
locale,
|
|
999
|
-
outputMode
|
|
1000
|
-
});
|
|
1001
|
-
case "layouts.get": return dispatch({
|
|
1002
|
-
toolName: "getLayout",
|
|
1003
|
-
args: { layoutId: args.layoutId },
|
|
1004
|
-
cwd,
|
|
1005
|
-
projectFlag,
|
|
1006
|
-
locale,
|
|
1007
|
-
outputMode
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
//#endregion
|
|
1012
|
-
//#region src/commands/locales.ts
|
|
1013
|
-
const jsonFlag$1 = option("--json");
|
|
1014
|
-
const tagFlag = option("--tag", string({ metavar: "TAG" }), { description: message`A BCP 47 language tag, such as de, pt-BR or fr-CA.` });
|
|
1015
|
-
const list$1 = command("list", object({
|
|
1016
|
-
command: constant("locales.list"),
|
|
1017
|
-
cwd: cwdFlag,
|
|
1018
|
-
project: projectFlag,
|
|
1019
|
-
json: jsonFlag$1
|
|
1020
|
-
}), { description: message`List the draft's locales, the default locale first. Pass a locale's tag to --locale on pages, blocks and layouts commands to read or write its content.` });
|
|
1021
|
-
const add = command("add", object({
|
|
1022
|
-
command: constant("locales.add"),
|
|
1023
|
-
tag: tagFlag,
|
|
1024
|
-
cwd: cwdFlag,
|
|
1025
|
-
project: projectFlag,
|
|
1026
|
-
json: jsonFlag$1
|
|
1027
|
-
}), { description: message`Add a locale. Every page, block, repeatable item, synced block and collection record of the default locale gets a copy in it, keeping its id, which Camox translates in the background. Publishing puts the locale on Live. Example: camox locales add --tag de.` });
|
|
1028
|
-
const remove = command("remove", object({
|
|
1029
|
-
command: constant("locales.remove"),
|
|
1030
|
-
tag: tagFlag,
|
|
1031
|
-
cwd: cwdFlag,
|
|
1032
|
-
project: projectFlag,
|
|
1033
|
-
json: jsonFlag$1
|
|
1034
|
-
}), { description: message`Remove a locale with all of its content. Publishing deletes them from Live for good. The default locale can't be removed.` });
|
|
1035
|
-
const setDefaultTag = command("set-default-tag", object({
|
|
1036
|
-
command: constant("locales.set-default-tag"),
|
|
1037
|
-
tag: tagFlag,
|
|
1038
|
-
cwd: cwdFlag,
|
|
1039
|
-
project: projectFlag,
|
|
1040
|
-
json: jsonFlag$1
|
|
1041
|
-
}), { description: message`Correct the default locale's tag, such as from en to en-GB, on Live and in every draft at once. Only possible while it's the only locale.` });
|
|
1042
|
-
const parser$7 = command("locales", or(list$1, add, remove, setDefaultTag));
|
|
1043
|
-
const TOOLS = {
|
|
1044
|
-
"locales.list": "listLocales",
|
|
1045
|
-
"locales.add": "addLocale",
|
|
1046
|
-
"locales.remove": "removeLocale",
|
|
1047
|
-
"locales.set-default-tag": "setDefaultLocaleTag"
|
|
1048
|
-
};
|
|
1049
|
-
async function handler$7(args) {
|
|
1050
|
-
const outputMode = args.json ? "json" : "auto";
|
|
1051
|
-
return dispatch({
|
|
1052
|
-
toolName: TOOLS[args.command],
|
|
1053
|
-
args: "tag" in args ? { tag: args.tag } : {},
|
|
1054
|
-
cwd: args.cwd,
|
|
1055
|
-
projectFlag: args.project,
|
|
1056
|
-
outputMode
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
//#endregion
|
|
1060
|
-
//#region src/commands/login.ts
|
|
1061
|
-
const parser$6 = command("login", object({ command: constant("login") }));
|
|
1062
|
-
const handler$6 = login;
|
|
1063
|
-
async function login() {
|
|
1064
|
-
p.intro("camox login");
|
|
1065
|
-
try {
|
|
1066
|
-
await getOrAuthenticate();
|
|
1067
|
-
} catch {
|
|
1068
|
-
p.log.error("Authentication failed.");
|
|
1069
|
-
process.exit(1);
|
|
1070
|
-
}
|
|
1071
|
-
p.outro("You're all set!");
|
|
1072
|
-
process.exit(0);
|
|
1073
|
-
}
|
|
341
|
+
const cwdFlag = optional(option("--cwd", string({ metavar: "PATH" }), { description: message`Start runtime lookup in this directory and walk upward. Relative to the shell's current directory.` }));
|
|
1074
342
|
//#endregion
|
|
1075
343
|
//#region src/commands/logout.ts
|
|
1076
|
-
const parser$
|
|
344
|
+
const parser$2 = command("logout", object({
|
|
1077
345
|
command: constant("logout"),
|
|
1078
346
|
cwd: cwdFlag
|
|
1079
347
|
}));
|
|
1080
|
-
const handler$
|
|
348
|
+
const handler$2 = logout;
|
|
1081
349
|
function logout(args = {}) {
|
|
1082
350
|
p.intro("camox logout");
|
|
1083
351
|
let authenticationUrl = null;
|
|
@@ -1100,389 +368,152 @@ function logout(args = {}) {
|
|
|
1100
368
|
p.log.success(`Logged out from ${token.name}.`);
|
|
1101
369
|
}
|
|
1102
370
|
//#endregion
|
|
1103
|
-
//#region src/commands/
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1116
|
-
cwd: cwdFlag,
|
|
1117
|
-
project: projectFlag,
|
|
1118
|
-
locale: localeFlag,
|
|
1119
|
-
json: jsonFlag
|
|
1120
|
-
}));
|
|
1121
|
-
const create = command("create", object({
|
|
1122
|
-
command: constant("pages.create"),
|
|
1123
|
-
nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
|
|
1124
|
-
pathSegment: option("--path-segment", string({ metavar: "SEGMENT" })),
|
|
1125
|
-
layoutId: option("--layout-id", string({ metavar: "NAME" })),
|
|
1126
|
-
parentPageId: optional(option("--parent-page-id", uuidv7())),
|
|
1127
|
-
cwd: cwdFlag,
|
|
1128
|
-
project: projectFlag,
|
|
1129
|
-
locale: localeFlag,
|
|
1130
|
-
json: jsonFlag
|
|
1131
|
-
}));
|
|
1132
|
-
const update = command("update", object({
|
|
1133
|
-
command: constant("pages.update"),
|
|
1134
|
-
id: optional(option("--id", uuidv7())),
|
|
1135
|
-
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1136
|
-
nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
|
|
1137
|
-
pathSegment: optional(option("--path-segment", string({ metavar: "SEGMENT" }))),
|
|
1138
|
-
parentPageId: optional(option("--parent-page-id", uuidv7())),
|
|
1139
|
-
cwd: cwdFlag,
|
|
1140
|
-
project: projectFlag,
|
|
1141
|
-
locale: localeFlag,
|
|
1142
|
-
json: jsonFlag
|
|
1143
|
-
}), { description: message`Update a page. Pass exactly one of --id or --path and at least one update field. Omitted fields are preserved. Example: camox pages update --path /about --nickname "About". Edit SEO metadata with pages set-seo.` });
|
|
1144
|
-
const setSeo = command("set-seo", object({
|
|
1145
|
-
command: constant("pages.set-seo"),
|
|
1146
|
-
id: optional(option("--id", uuidv7())),
|
|
1147
|
-
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1148
|
-
metaTitle: optional(option("--meta-title", string({ metavar: "TEXT" }), { description: message`Set the SEO title. Makes both fields manual.` })),
|
|
1149
|
-
metaDescription: optional(option("--meta-description", string({ metavar: "TEXT" }), { description: message`Set the SEO description. An empty string clears it. Makes both fields manual.` })),
|
|
1150
|
-
aiSeo: optional(option("--ai-seo", choice(["on", "off"]), { description: message`on makes both fields generated again and schedules asynchronous generation; it cannot be combined with --meta-title or --meta-description. off keeps the current values as manual.` })),
|
|
1151
|
-
cwd: cwdFlag,
|
|
1152
|
-
project: projectFlag,
|
|
1153
|
-
locale: localeFlag,
|
|
1154
|
-
json: jsonFlag
|
|
1155
|
-
}), { description: message`Set a page's SEO metadata. The meta title and description are one group: both generated by AI, or both manual. Writing one field makes both fields manual, keeping the current value of the other. Pass exactly one of --id or --path and at least one of --meta-title, --meta-description or --ai-seo. An empty meta title blocks publishing. Example: camox pages set-seo --path /about --meta-title "About us" --meta-description "Meet the team". Read metadata with pages get.` });
|
|
1156
|
-
const setLayout = command("set-layout", object({
|
|
1157
|
-
command: constant("pages.set-layout"),
|
|
1158
|
-
id: option("--id", uuidv7()),
|
|
1159
|
-
layoutId: option("--layout-id", string({ metavar: "NAME" })),
|
|
1160
|
-
cwd: cwdFlag,
|
|
1161
|
-
project: projectFlag,
|
|
1162
|
-
locale: localeFlag,
|
|
1163
|
-
json: jsonFlag
|
|
1164
|
-
}));
|
|
1165
|
-
const del = command("delete", object({
|
|
1166
|
-
command: constant("pages.delete"),
|
|
1167
|
-
id: option("--id", uuidv7()),
|
|
1168
|
-
cwd: cwdFlag,
|
|
1169
|
-
project: projectFlag,
|
|
1170
|
-
locale: localeFlag,
|
|
1171
|
-
json: jsonFlag
|
|
1172
|
-
}));
|
|
1173
|
-
const parser$4 = command("pages", or(list, get, create, update, setSeo, setLayout, del));
|
|
1174
|
-
async function handler$4(args) {
|
|
1175
|
-
const outputMode = args.json ? "json" : "auto";
|
|
1176
|
-
const cwd = args.cwd;
|
|
1177
|
-
const projectFlag = args.project;
|
|
1178
|
-
const locale = args.locale;
|
|
1179
|
-
switch (args.command) {
|
|
1180
|
-
case "pages.list": return dispatch({
|
|
1181
|
-
toolName: "listPages",
|
|
1182
|
-
args: {},
|
|
1183
|
-
cwd,
|
|
1184
|
-
projectFlag,
|
|
1185
|
-
locale,
|
|
1186
|
-
outputMode
|
|
1187
|
-
});
|
|
1188
|
-
case "pages.get":
|
|
1189
|
-
if (args.id == null === (args.path == null)) {
|
|
1190
|
-
printError({
|
|
1191
|
-
code: "INVALID_ARGS",
|
|
1192
|
-
message: "Pass exactly one of --id or --path."
|
|
1193
|
-
});
|
|
1194
|
-
process.exit(2);
|
|
1195
|
-
}
|
|
1196
|
-
return dispatch({
|
|
1197
|
-
toolName: "getPage",
|
|
1198
|
-
args: args.id != null ? { id: args.id } : { path: args.path },
|
|
1199
|
-
cwd,
|
|
1200
|
-
projectFlag,
|
|
1201
|
-
locale,
|
|
1202
|
-
outputMode
|
|
1203
|
-
});
|
|
1204
|
-
case "pages.create": return dispatch({
|
|
1205
|
-
toolName: "createPage",
|
|
1206
|
-
args: {
|
|
1207
|
-
nickname: args.nickname,
|
|
1208
|
-
pathSegment: args.pathSegment,
|
|
1209
|
-
layoutId: args.layoutId,
|
|
1210
|
-
parentPageId: args.parentPageId
|
|
1211
|
-
},
|
|
1212
|
-
cwd,
|
|
1213
|
-
projectFlag,
|
|
1214
|
-
locale,
|
|
1215
|
-
outputMode
|
|
1216
|
-
});
|
|
1217
|
-
case "pages.update":
|
|
1218
|
-
if (args.id == null === (args.path == null)) {
|
|
1219
|
-
printError({
|
|
1220
|
-
code: "INVALID_ARGS",
|
|
1221
|
-
message: "Pass exactly one of --id or --path."
|
|
1222
|
-
});
|
|
1223
|
-
process.exit(2);
|
|
1224
|
-
}
|
|
1225
|
-
if ([
|
|
1226
|
-
args.nickname,
|
|
1227
|
-
args.pathSegment,
|
|
1228
|
-
args.parentPageId
|
|
1229
|
-
].every((v) => v === void 0)) {
|
|
1230
|
-
printError({
|
|
1231
|
-
code: "INVALID_ARGS",
|
|
1232
|
-
message: "Pass at least one field to update."
|
|
1233
|
-
});
|
|
1234
|
-
process.exit(2);
|
|
1235
|
-
}
|
|
1236
|
-
return dispatch({
|
|
1237
|
-
toolName: "updatePage",
|
|
1238
|
-
args: {
|
|
1239
|
-
...args.id != null ? { id: args.id } : { path: args.path },
|
|
1240
|
-
nickname: args.nickname,
|
|
1241
|
-
pathSegment: args.pathSegment,
|
|
1242
|
-
parentPageId: args.parentPageId
|
|
1243
|
-
},
|
|
1244
|
-
cwd,
|
|
1245
|
-
projectFlag,
|
|
1246
|
-
locale,
|
|
1247
|
-
outputMode
|
|
1248
|
-
});
|
|
1249
|
-
case "pages.set-seo": {
|
|
1250
|
-
if (args.id == null === (args.path == null)) {
|
|
1251
|
-
printError({
|
|
1252
|
-
code: "INVALID_ARGS",
|
|
1253
|
-
message: "Pass exactly one of --id or --path."
|
|
1254
|
-
});
|
|
1255
|
-
process.exit(2);
|
|
1256
|
-
}
|
|
1257
|
-
const writesField = args.metaTitle !== void 0 || args.metaDescription !== void 0;
|
|
1258
|
-
if (!writesField && args.aiSeo === void 0) {
|
|
1259
|
-
printError({
|
|
1260
|
-
code: "INVALID_ARGS",
|
|
1261
|
-
message: "Pass at least one of --meta-title, --meta-description or --ai-seo."
|
|
1262
|
-
});
|
|
1263
|
-
process.exit(2);
|
|
1264
|
-
}
|
|
1265
|
-
if (args.aiSeo === "on" && writesField) {
|
|
1266
|
-
printError({
|
|
1267
|
-
code: "INVALID_ARGS",
|
|
1268
|
-
message: "--ai-seo on cannot be combined with --meta-title or --meta-description."
|
|
1269
|
-
});
|
|
1270
|
-
process.exit(2);
|
|
1271
|
-
}
|
|
1272
|
-
return dispatch({
|
|
1273
|
-
toolName: "setPageSeo",
|
|
1274
|
-
args: {
|
|
1275
|
-
...args.id != null ? { id: args.id } : { path: args.path },
|
|
1276
|
-
metaTitle: args.metaTitle,
|
|
1277
|
-
metaDescription: args.metaDescription,
|
|
1278
|
-
aiSeo: args.aiSeo === void 0 ? void 0 : args.aiSeo === "on"
|
|
1279
|
-
},
|
|
1280
|
-
cwd,
|
|
1281
|
-
projectFlag,
|
|
1282
|
-
locale,
|
|
1283
|
-
outputMode
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
case "pages.set-layout": return dispatch({
|
|
1287
|
-
toolName: "setPageLayout",
|
|
1288
|
-
args: {
|
|
1289
|
-
id: args.id,
|
|
1290
|
-
layoutId: args.layoutId
|
|
1291
|
-
},
|
|
1292
|
-
cwd,
|
|
1293
|
-
projectFlag,
|
|
1294
|
-
locale,
|
|
1295
|
-
outputMode
|
|
1296
|
-
});
|
|
1297
|
-
case "pages.delete": return dispatch({
|
|
1298
|
-
toolName: "deletePage",
|
|
1299
|
-
args: { id: args.id },
|
|
1300
|
-
cwd,
|
|
1301
|
-
projectFlag,
|
|
1302
|
-
locale,
|
|
1303
|
-
outputMode
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
//#endregion
|
|
1308
|
-
//#region src/commands/preview.ts
|
|
1309
|
-
const parser$3 = command("preview", object({
|
|
1310
|
-
command: constant("preview"),
|
|
1311
|
-
cwd: cwdFlag,
|
|
1312
|
-
url: option("--url", string()),
|
|
1313
|
-
json: option("--json")
|
|
1314
|
-
}), { description: message`Create a single-use sign-in URL for local verification. Open it in your automation browser within three minutes. The URL is a credential; do not share it.` });
|
|
1315
|
-
async function handler$3(args) {
|
|
1316
|
-
try {
|
|
1317
|
-
const url = new URL(args.url);
|
|
1318
|
-
if (!isLoopbackUrl(url)) throw new Error("Preview requires an http(s) loopback URL without credentials.");
|
|
1319
|
-
const runtime = loadRuntime(args.cwd);
|
|
1320
|
-
const auth = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
|
|
1321
|
-
if (!auth?.email) {
|
|
1322
|
-
printError({
|
|
1323
|
-
code: "NOT_AUTHENTICATED",
|
|
1324
|
-
message: `Run camox login for ${runtime.authenticationUrl} first.`
|
|
1325
|
-
});
|
|
1326
|
-
process.exitCode = 2;
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
const target = {
|
|
1330
|
-
projectUuid: runtime.projectId,
|
|
1331
|
-
apiUrl: normalizeUrl(runtime.apiUrl)
|
|
1332
|
-
};
|
|
1333
|
-
printResult({
|
|
1334
|
-
url: await createPreviewSignInUrl(url.href, target, auth.token),
|
|
1335
|
-
projectId: target.projectUuid,
|
|
1336
|
-
apiUrl: target.apiUrl,
|
|
1337
|
-
warning: "Single-use sign-in credential, valid for three minutes. Open in your verification browser; do not share or commit."
|
|
1338
|
-
}, args.json ? "json" : "auto");
|
|
1339
|
-
} catch (error) {
|
|
1340
|
-
printError({
|
|
1341
|
-
code: "PREVIEW_FAILED",
|
|
1342
|
-
message: error instanceof Error ? error.message : "Could not create preview."
|
|
1343
|
-
});
|
|
1344
|
-
process.exitCode = 2;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
//#endregion
|
|
1348
|
-
//#region src/lib/publish-preview.ts
|
|
1349
|
-
function where(owner) {
|
|
1350
|
-
if (!owner) return "";
|
|
1351
|
-
switch (owner.kind) {
|
|
1352
|
-
case "page": return ` on page ${owner.path}`;
|
|
1353
|
-
case "layout": return ` in layout ${owner.name}`;
|
|
1354
|
-
case "syncedBlock": return ` in synced block ${owner.title}`;
|
|
1355
|
-
case "collection": return ` in collection ${owner.title}`;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
const subject = (item) => `${item.entity} "${item.label}"${where(item.owner)}`;
|
|
1359
|
-
const when = (at) => at === null ? "" : ` on ${new Date(at).toLocaleString()}`;
|
|
1360
|
-
function conflictLine(conflict) {
|
|
1361
|
-
if (conflict.liveVersion === null) return `${subject(conflict)}: deleted on Live`;
|
|
1362
|
-
if (conflict.release) return `${subject(conflict)}: changed by a release${when(conflict.publishedAt)}`;
|
|
1363
|
-
const by = conflict.publishedBy?.name ?? "someone";
|
|
1364
|
-
return `${subject(conflict)}: published by ${by}${when(conflict.publishedAt)}`;
|
|
1365
|
-
}
|
|
1366
|
-
const LOST_REASONS = {
|
|
1367
|
-
parentGone: "what it belongs to is gone from Live and the draft",
|
|
1368
|
-
writtenAfterDeletion: "published after the draft deleted what it belongs to"
|
|
371
|
+
//#region src/commands/push.ts
|
|
372
|
+
const parser$1 = command("push", object({
|
|
373
|
+
command: constant("push"),
|
|
374
|
+
draft: optional(option("--draft", string({ metavar: "DRAFT_ID" }), { description: message`Push to this open draft, such as one Camox's agent started, rather than the branch's own.` }))
|
|
375
|
+
}), { description: message`Push the current branch's commits to its draft, on a project whose code can be worked on outside Camox: a new draft on its first push.` });
|
|
376
|
+
/** A draft's branch, as a branch's upstream names it: `origin/<draft id>`. */
|
|
377
|
+
const DRAFT_UPSTREAM = new RegExp(`^origin/(${UUID})$`, "i");
|
|
378
|
+
/** What the spinner says while the push is at each step. */
|
|
379
|
+
const STEPS = {
|
|
380
|
+
checking: "Checking your commits...",
|
|
381
|
+
waiting: "Waiting for Camox's agent to finish its turn in the draft...",
|
|
382
|
+
building: "Building your code..."
|
|
1369
383
|
};
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
const tags = new Set(changes.map((change) => change.locale));
|
|
1386
|
-
if ([...tags].every((tag) => tag === null || tag === defaultLocale)) return section("Changes", changes.map(changeLine));
|
|
1387
|
-
const rank = (tag) => tag === defaultLocale ? 0 : tag === null ? 2 : 1;
|
|
1388
|
-
const ordered = [...tags].sort((a, b) => rank(a) - rank(b) || (a ?? "").localeCompare(b ?? ""));
|
|
1389
|
-
return [
|
|
1390
|
-
`Changes (${changes.length})`,
|
|
1391
|
-
...ordered.flatMap((tag) => [` ${tag ?? "Every locale"}`, ...changes.filter((change) => change.locale === tag).map((change) => ` - ${changeLine(change)}`)]),
|
|
1392
|
-
""
|
|
1393
|
-
];
|
|
1394
|
-
}
|
|
1395
|
-
/** The preview as text, in the order ADR 0009 lists it, ending with the token. */
|
|
1396
|
-
function formatPreview(preview) {
|
|
1397
|
-
const lines = [
|
|
1398
|
-
...section("Blockers: fix these in your development draft before publishing", preview.blockers.map((problem) => `${subject(problem)}: ${problem.message}`)),
|
|
1399
|
-
...section("Conflicts: Live changed these since your draft did; publishing overwrites Live's version", preview.conflicts.map(conflictLine)),
|
|
1400
|
-
...section("Lost changes: publishing drops these", preview.lost.map((lost) => `${subject(lost)}: ${LOST_REASONS[lost.reason]}`)),
|
|
1401
|
-
...section("Warnings: already on Live, not blocking", preview.warnings.map((problem) => `${subject(problem)}: ${problem.message}`)),
|
|
1402
|
-
...changesSection(preview)
|
|
1403
|
-
];
|
|
1404
|
-
if (preview.changes.length === 0) lines.push("Your development draft has no changes.", "");
|
|
1405
|
-
lines.push(`Token: ${preview.token}`, ...nextStep(preview));
|
|
1406
|
-
return `${lines.join("\n")}\n`;
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* What to do next: fix the blockers, or publish when there is something to publish, waiting for
|
|
1410
|
-
* running translations or publishing anyway.
|
|
1411
|
-
*/
|
|
1412
|
-
function nextStep(preview) {
|
|
1413
|
-
if (preview.blockers.length > 0) return ["Publishing is blocked until the blockers are fixed."];
|
|
1414
|
-
if (preview.changes.length === 0) return [];
|
|
1415
|
-
const publish = `camox publish --confirm ${preview.token}`;
|
|
1416
|
-
if (preview.translating.length > 0) return [`Translations into ${preview.translating.join(", ")} are still running: publish once they finish, or publish anyway and Camox finishes them in a draft of its own.`, `Publish anyway with: ${publish} --anyway`];
|
|
1417
|
-
return [`Publish with: ${publish}`];
|
|
1418
|
-
}
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region src/commands/publish.ts
|
|
1421
|
-
const parser$2 = command("publish", object({
|
|
1422
|
-
command: constant("publish"),
|
|
1423
|
-
confirm: optional(option("--confirm", string({ metavar: "TOKEN" }), { description: message`Publish, with the token \`camox publish\` printed with the list you reviewed.` })),
|
|
1424
|
-
anyway: option("--anyway", { description: message`With --confirm, publish while translations are still running: Camox finishes them in a draft of its own and publishes it once they're done.` }),
|
|
1425
|
-
cwd: cwdFlag,
|
|
1426
|
-
project: projectFlag,
|
|
1427
|
-
json: option("--json")
|
|
1428
|
-
}), { description: message`List what publishing your development draft to Live would do, with a token; publish with --confirm <token>.` });
|
|
1429
|
-
async function handler$2(args) {
|
|
1430
|
-
const outputMode = args.json ? "json" : "auto";
|
|
1431
|
-
const target = {
|
|
1432
|
-
cwd: args.cwd,
|
|
1433
|
-
projectFlag: args.project
|
|
1434
|
-
};
|
|
1435
|
-
if (args.confirm === void 0) {
|
|
1436
|
-
printPreview(await callToolResult({
|
|
1437
|
-
...target,
|
|
1438
|
-
toolName: "previewPublish",
|
|
1439
|
-
args: {}
|
|
1440
|
-
}), outputMode);
|
|
1441
|
-
process.exit(0);
|
|
1442
|
-
}
|
|
1443
|
-
const result = await callToolResult({
|
|
1444
|
-
...target,
|
|
1445
|
-
toolName: "publishDraft",
|
|
1446
|
-
args: {
|
|
1447
|
-
token: args.confirm,
|
|
1448
|
-
...args.anyway && { publishAnyway: true }
|
|
1449
|
-
}
|
|
384
|
+
/** The Project ID the camox() plugin names in a Vite config. */
|
|
385
|
+
const PROJECT_ID = new RegExp(`\\bprojectId\\s*:\\s*["'](${UUID})["']`, "i");
|
|
386
|
+
const fail = (text) => {
|
|
387
|
+
p.log.error(text);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
};
|
|
390
|
+
/** Runs `git` in the checkout, returning its trimmed output, or failing with `why`. */
|
|
391
|
+
function git(args, why, { quiet = false } = {}) {
|
|
392
|
+
const run = spawnSync("git", args, {
|
|
393
|
+
encoding: "utf8",
|
|
394
|
+
stdio: [
|
|
395
|
+
"ignore",
|
|
396
|
+
"pipe",
|
|
397
|
+
"pipe"
|
|
398
|
+
]
|
|
1450
399
|
});
|
|
1451
|
-
if (
|
|
1452
|
-
if (
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
400
|
+
if (run.error || run.status !== 0) {
|
|
401
|
+
if (quiet) return void 0;
|
|
402
|
+
return fail(`${why}${run.stderr ? `\n${run.stderr.trim()}` : ""}`);
|
|
403
|
+
}
|
|
404
|
+
return run.stdout.trim();
|
|
405
|
+
}
|
|
406
|
+
const listed = (errors) => errors.map((error) => {
|
|
407
|
+
const column = "column" in error ? `:${error.column}` : "";
|
|
408
|
+
return error.file ? `${error.file}:${error.line}${column} ${error.message}` : error.message;
|
|
409
|
+
}).join("\n");
|
|
410
|
+
async function handler$1(args) {
|
|
411
|
+
p.intro("camox push");
|
|
412
|
+
const root = git(["rev-parse", "--show-toplevel"], "Run camox push inside a project's checkout.");
|
|
413
|
+
let config;
|
|
414
|
+
try {
|
|
415
|
+
config = readFileSync(join(root, "vite.config.ts"), "utf8");
|
|
416
|
+
} catch {
|
|
417
|
+
return fail("This checkout has no vite.config.ts naming its project.");
|
|
418
|
+
}
|
|
419
|
+
const projectUuid = config.match(PROJECT_ID)?.[1]?.toLowerCase();
|
|
420
|
+
if (!projectUuid) return fail("This checkout's vite.config.ts doesn't name a Project ID.");
|
|
421
|
+
const branch = git([
|
|
422
|
+
"symbolic-ref",
|
|
423
|
+
"--quiet",
|
|
424
|
+
"--short",
|
|
425
|
+
"HEAD"
|
|
426
|
+
], "Check out a branch to push.");
|
|
427
|
+
if (branch === "main") return fail("Push a branch of your own: main changes only by publishing.");
|
|
428
|
+
const upstream = git([
|
|
429
|
+
"rev-parse",
|
|
430
|
+
"--abbrev-ref",
|
|
431
|
+
"--symbolic-full-name",
|
|
432
|
+
`${branch}@{upstream}`
|
|
433
|
+
], "", { quiet: true });
|
|
434
|
+
const draftId = args.draft ?? upstream?.match(DRAFT_UPSTREAM)?.[1]?.toLowerCase();
|
|
435
|
+
const { token } = await getOrAuthenticate().catch(() => fail("Authentication failed."));
|
|
436
|
+
git([
|
|
437
|
+
"fetch",
|
|
438
|
+
"--quiet",
|
|
439
|
+
"origin",
|
|
440
|
+
"main"
|
|
441
|
+
], "Couldn't fetch main.");
|
|
442
|
+
const base = git([
|
|
443
|
+
"merge-base",
|
|
444
|
+
"HEAD",
|
|
445
|
+
"origin/main"
|
|
446
|
+
], "Your branch shares no history with main.");
|
|
447
|
+
const draftFetched = !!draftId && git([
|
|
448
|
+
"fetch",
|
|
449
|
+
"--quiet",
|
|
450
|
+
"origin",
|
|
451
|
+
`+refs/heads/${draftId}:refs/remotes/origin/${draftId}`
|
|
452
|
+
], "", { quiet: true }) !== void 0;
|
|
453
|
+
const scratch = mkdtempSync(join(tmpdir(), "camox-push-"));
|
|
454
|
+
let bundle;
|
|
455
|
+
try {
|
|
456
|
+
const file = join(scratch, "push.bundle");
|
|
457
|
+
git([
|
|
458
|
+
"bundle",
|
|
459
|
+
"create",
|
|
460
|
+
"--quiet",
|
|
461
|
+
file,
|
|
462
|
+
branch,
|
|
463
|
+
`^${base}`,
|
|
464
|
+
...draftFetched ? [`^origin/${draftId}`] : []
|
|
465
|
+
], "Your branch has no new commits to push.");
|
|
466
|
+
bundle = readFileSync(file);
|
|
467
|
+
} finally {
|
|
468
|
+
rmSync(scratch, {
|
|
469
|
+
recursive: true,
|
|
470
|
+
force: true
|
|
1465
471
|
});
|
|
1466
|
-
process.exit(1);
|
|
1467
472
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
473
|
+
const spinner = p.spinner();
|
|
474
|
+
spinner.start("Pushing your commits...");
|
|
475
|
+
let pushed;
|
|
476
|
+
try {
|
|
477
|
+
const events = await pushCode(token, {
|
|
478
|
+
projectUuid,
|
|
479
|
+
branch,
|
|
480
|
+
...draftId && { draftId },
|
|
481
|
+
bundle: new File([new Uint8Array(bundle)], `${branch}.bundle`)
|
|
482
|
+
});
|
|
483
|
+
for await (const event of events) if (event.type === "progress") spinner.message(STEPS[event.step] ?? "Pushing...");
|
|
484
|
+
else pushed = event;
|
|
485
|
+
if (!pushed) throw new Error("Camox ended the push without landing it. Push again.");
|
|
486
|
+
} catch (error) {
|
|
487
|
+
spinner.error("Camox refused the push.");
|
|
488
|
+
const data = error.data;
|
|
489
|
+
return fail([error instanceof Error ? error.message : String(error), data?.build && listed(data.build)].filter(Boolean).join("\n"));
|
|
490
|
+
}
|
|
491
|
+
spinner.stop("Pushed.");
|
|
492
|
+
git([
|
|
493
|
+
"fetch",
|
|
494
|
+
"--quiet",
|
|
495
|
+
"origin",
|
|
496
|
+
pushed.draftId
|
|
497
|
+
], "Couldn't fetch the draft's branch.");
|
|
498
|
+
git([
|
|
499
|
+
"branch",
|
|
500
|
+
`--set-upstream-to=origin/${pushed.draftId}`,
|
|
501
|
+
branch
|
|
502
|
+
], "Couldn't set your branch's upstream to the draft's branch.");
|
|
503
|
+
const { result } = pushed;
|
|
504
|
+
if (result.ok) p.log.success("Your code built.");
|
|
505
|
+
else if (result.types.length > 0) p.log.warn(`Your code built, but has type errors, which block publishing:\n${listed(result.types)}`);
|
|
506
|
+
else p.log.warn(`Your code built, but the preview can't show it:\n${listed(result.build)}`);
|
|
507
|
+
p.outro(`Review your draft in the studio: ${pushed.studioUrl}`);
|
|
508
|
+
process.exit(0);
|
|
1478
509
|
}
|
|
1479
510
|
//#endregion
|
|
1480
511
|
//#region src/commands/release.ts
|
|
1481
|
-
const parser
|
|
512
|
+
const parser = command("release", object({
|
|
1482
513
|
command: constant("release"),
|
|
1483
514
|
dryRun: option("--dry-run", { description: message`Build the website as a release would, without logging in or syncing its definitions to Live.` })
|
|
1484
515
|
}));
|
|
1485
|
-
function handler
|
|
516
|
+
function handler(args) {
|
|
1486
517
|
p.intro(args.dryRun ? "camox release --dry-run" : "camox release");
|
|
1487
518
|
if (!args.dryRun && !process.env.CAMOX_DEPLOY_TOKEN) {
|
|
1488
519
|
p.log.error("CAMOX_DEPLOY_TOKEN is required to release to Live.");
|
|
@@ -1509,100 +540,9 @@ function handler$1(args) {
|
|
|
1509
540
|
process.exit(0);
|
|
1510
541
|
}
|
|
1511
542
|
//#endregion
|
|
1512
|
-
//#region src/commands/status.ts
|
|
1513
|
-
const parser = command("status", object({
|
|
1514
|
-
command: constant("status"),
|
|
1515
|
-
cwd: cwdFlag,
|
|
1516
|
-
project: projectFlag,
|
|
1517
|
-
json: option("--json")
|
|
1518
|
-
}));
|
|
1519
|
-
async function developmentDraftStatus(params) {
|
|
1520
|
-
try {
|
|
1521
|
-
const project = await getProject(params.token, params.project, params.apiUrl);
|
|
1522
|
-
return {
|
|
1523
|
-
exists: true,
|
|
1524
|
-
changeCount: (await getDevelopmentDraft({
|
|
1525
|
-
...params,
|
|
1526
|
-
projectId: project.id
|
|
1527
|
-
})).changeCount
|
|
1528
|
-
};
|
|
1529
|
-
} catch (err) {
|
|
1530
|
-
if (isNoDevelopmentDraft(err)) return {
|
|
1531
|
-
exists: false,
|
|
1532
|
-
changeCount: 0
|
|
1533
|
-
};
|
|
1534
|
-
return {
|
|
1535
|
-
exists: null,
|
|
1536
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
function describeDraft(draft, authenticationUrl) {
|
|
1541
|
-
if (!draft) return `(sign in to see it — run \`camox login\` for ${authenticationUrl})`;
|
|
1542
|
-
if (draft.exists === null) return `(couldn't be checked: ${draft.error})`;
|
|
1543
|
-
if (!draft.exists) return "no development draft yet — run your site locally once to create it";
|
|
1544
|
-
if (draft.changeCount === 0) return "no changes";
|
|
1545
|
-
return `${draft.changeCount} change${draft.changeCount === 1 ? "" : "s"} — \`camox publish\` lists them`;
|
|
1546
|
-
}
|
|
1547
|
-
async function handler(args) {
|
|
1548
|
-
let runtime;
|
|
1549
|
-
try {
|
|
1550
|
-
runtime = loadRuntime(args.cwd);
|
|
1551
|
-
} catch (err) {
|
|
1552
|
-
if (err instanceof RuntimeDirectoryError) {
|
|
1553
|
-
printError({
|
|
1554
|
-
code: "INVALID_CWD",
|
|
1555
|
-
message: err.message
|
|
1556
|
-
});
|
|
1557
|
-
process.exit(2);
|
|
1558
|
-
}
|
|
1559
|
-
if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) {
|
|
1560
|
-
printError({
|
|
1561
|
-
code: "RUNTIME_NOT_FOUND",
|
|
1562
|
-
message: err.message
|
|
1563
|
-
});
|
|
1564
|
-
process.exit(2);
|
|
1565
|
-
}
|
|
1566
|
-
throw err;
|
|
1567
|
-
}
|
|
1568
|
-
const project = args.project?.trim() || process.env.CAMOX_PROJECT?.trim() || runtime.projectId;
|
|
1569
|
-
const token = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
|
|
1570
|
-
const developmentDraft = token ? await developmentDraftStatus({
|
|
1571
|
-
token: token.token,
|
|
1572
|
-
apiUrl: runtime.apiUrl,
|
|
1573
|
-
project,
|
|
1574
|
-
disableTelemetry: runtime.disableTelemetry
|
|
1575
|
-
}) : null;
|
|
1576
|
-
const status = {
|
|
1577
|
-
project,
|
|
1578
|
-
draft: DEVELOPMENT_DRAFT,
|
|
1579
|
-
developmentDraft,
|
|
1580
|
-
apiUrl: runtime.apiUrl,
|
|
1581
|
-
authenticationUrl: runtime.authenticationUrl,
|
|
1582
|
-
authenticated: token !== null,
|
|
1583
|
-
user: token ? {
|
|
1584
|
-
name: token.name,
|
|
1585
|
-
email: token.email
|
|
1586
|
-
} : null
|
|
1587
|
-
};
|
|
1588
|
-
if (args.json || !process.stdout.isTTY) {
|
|
1589
|
-
process.stdout.write(`${JSON.stringify(status, null, process.stdout.isTTY ? 2 : 0)}\n`);
|
|
1590
|
-
process.exit(0);
|
|
1591
|
-
}
|
|
1592
|
-
const lines = [
|
|
1593
|
-
`project: ${status.project}`,
|
|
1594
|
-
`draft: ${describeDraft(developmentDraft, status.authenticationUrl)}`,
|
|
1595
|
-
`api: ${status.apiUrl}`,
|
|
1596
|
-
`auth: ${status.authenticationUrl}`,
|
|
1597
|
-
status.user ? `signed in: ${status.user.name} <${status.user.email}>` : `signed in: (no token for ${status.authenticationUrl} — run \`camox login\`)`
|
|
1598
|
-
];
|
|
1599
|
-
process.stdout.write(`${lines.join("\n")}\n`);
|
|
1600
|
-
process.exit(0);
|
|
1601
|
-
}
|
|
1602
|
-
//#endregion
|
|
1603
543
|
//#region src/index.ts
|
|
1604
544
|
const program = defineProgram({
|
|
1605
|
-
parser: or(parser$
|
|
545
|
+
parser: or(parser$3, parser$2, parser, parser$5, parser$1, parser$4),
|
|
1606
546
|
metadata: {
|
|
1607
547
|
name: "camox",
|
|
1608
548
|
brief: message`Camox CLI`
|
|
@@ -1610,63 +550,22 @@ const program = defineProgram({
|
|
|
1610
550
|
});
|
|
1611
551
|
const result = runSync(program, { help: "both" });
|
|
1612
552
|
switch (result.command) {
|
|
1613
|
-
case "init":
|
|
1614
|
-
handler$9();
|
|
1615
|
-
break;
|
|
1616
553
|
case "login":
|
|
1617
|
-
await handler$
|
|
554
|
+
await handler$3();
|
|
1618
555
|
break;
|
|
1619
556
|
case "logout":
|
|
1620
|
-
handler$
|
|
557
|
+
handler$2(result);
|
|
1621
558
|
break;
|
|
1622
559
|
case "release":
|
|
1623
|
-
handler
|
|
1624
|
-
break;
|
|
1625
|
-
case "preview":
|
|
1626
|
-
await handler$3(result);
|
|
1627
|
-
break;
|
|
1628
|
-
case "status":
|
|
1629
|
-
await handler(result);
|
|
1630
|
-
break;
|
|
1631
|
-
case "pages.list":
|
|
1632
|
-
case "pages.get":
|
|
1633
|
-
case "pages.create":
|
|
1634
|
-
case "pages.update":
|
|
1635
|
-
case "pages.set-seo":
|
|
1636
|
-
case "pages.set-layout":
|
|
1637
|
-
case "pages.delete":
|
|
1638
|
-
await handler$4(result);
|
|
1639
|
-
break;
|
|
1640
|
-
case "blocks.types":
|
|
1641
|
-
case "blocks.describe":
|
|
1642
|
-
case "blocks.get":
|
|
1643
|
-
case "blocks.get-many":
|
|
1644
|
-
case "blocks.create":
|
|
1645
|
-
case "blocks.edit":
|
|
1646
|
-
case "blocks.move":
|
|
1647
|
-
case "blocks.delete":
|
|
1648
|
-
await handler$12(result);
|
|
1649
|
-
break;
|
|
1650
|
-
case "files.upload":
|
|
1651
|
-
case "files.update":
|
|
1652
|
-
case "files.get":
|
|
1653
|
-
case "files.list":
|
|
1654
|
-
await handler$10(result);
|
|
1655
|
-
break;
|
|
1656
|
-
case "layouts.list":
|
|
1657
|
-
case "layouts.get":
|
|
1658
|
-
await handler$8(result);
|
|
560
|
+
handler(result);
|
|
1659
561
|
break;
|
|
1660
|
-
case "
|
|
1661
|
-
|
|
1662
|
-
case "locales.remove":
|
|
1663
|
-
case "locales.set-default-tag":
|
|
1664
|
-
await handler$7(result);
|
|
562
|
+
case "clone":
|
|
563
|
+
await handler$5(result);
|
|
1665
564
|
break;
|
|
1666
|
-
case "
|
|
1667
|
-
await handler$
|
|
565
|
+
case "push":
|
|
566
|
+
await handler$1(result);
|
|
1668
567
|
break;
|
|
1669
|
-
case "
|
|
568
|
+
case "git-credential": await handler$4(result);
|
|
1670
569
|
}
|
|
1671
570
|
//#endregion
|
|
1672
571
|
export {};
|