@camox/cli 0.58.1 → 0.60.2
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 +301 -1369
- 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,23 +1,24 @@
|
|
|
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, { dirname, 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, { existsSync, 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
|
+
import { pathToFileURL } from "node:url";
|
|
21
22
|
//#region src/lib/auth.ts
|
|
22
23
|
const CAMOX_URL = process.env.CAMOX_URL || "https://app.camox.ai";
|
|
23
24
|
const CAMOX_API_URL = process.env.CAMOX_API_URL || "https://api.camox.ai";
|
|
@@ -118,7 +119,7 @@ async function authenticateUser() {
|
|
|
118
119
|
async function getOrAuthenticate() {
|
|
119
120
|
const stored = readAuthToken();
|
|
120
121
|
if (stored) {
|
|
121
|
-
const { verifySession } = await import("./api-
|
|
122
|
+
const { verifySession } = await import("./api-DOHUrl0V.mjs");
|
|
122
123
|
if (await verifySession(stored.token)) {
|
|
123
124
|
p.log.info(`Authenticated as ${stored.name}`);
|
|
124
125
|
return stored;
|
|
@@ -129,63 +130,133 @@ async function getOrAuthenticate() {
|
|
|
129
130
|
return authenticateUser();
|
|
130
131
|
}
|
|
131
132
|
//#endregion
|
|
132
|
-
//#region src/lib/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
//#region src/lib/git-credential.ts
|
|
134
|
+
/**
|
|
135
|
+
* The Git config entries that make `camox git-credential` the credential helper for a remote's
|
|
136
|
+
* host, written in a clone's own config only. An empty `helper` first drops every helper set
|
|
137
|
+
* elsewhere for that host, such as a keychain that would keep expired read tokens. Git sends the
|
|
138
|
+
* path, which names the project.
|
|
139
|
+
*/
|
|
140
|
+
function credentialHelperConfig(remote) {
|
|
141
|
+
const { origin } = new URL(remote);
|
|
142
|
+
return [
|
|
143
|
+
[`credential.${origin}.helper`, ""],
|
|
144
|
+
[`credential.${origin}.helper`, `!${helperCommand()}`],
|
|
145
|
+
[`credential.${origin}.useHttpPath`, "true"]
|
|
146
|
+
];
|
|
136
147
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
/** This CLI, as a shell runs it: with this Node.js, so it works without `camox` on the PATH. */
|
|
149
|
+
function helperCommand() {
|
|
150
|
+
const quote = (arg) => `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
151
|
+
return `${quote(process.execPath)} ${quote(process.argv[1])} git-credential`;
|
|
140
152
|
}
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
/** Parses what Git writes to a credential helper: `key=value` lines up to a blank one. */
|
|
154
|
+
function parseCredentialRequest(input) {
|
|
155
|
+
const request = {};
|
|
156
|
+
for (const line of input.split("\n")) {
|
|
157
|
+
if (line === "") break;
|
|
158
|
+
const at = line.indexOf("=");
|
|
159
|
+
if (at > 0) request[line.slice(0, at)] = line.slice(at + 1);
|
|
160
|
+
}
|
|
161
|
+
return request;
|
|
143
162
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
/** The Project ID a repo's remote path names: `git/<namespace>/<Project ID>.git`. */
|
|
164
|
+
function projectIdOfPath(path) {
|
|
165
|
+
return path?.match(/([^/]+?)(?:\.git)?\/?$/)?.[1];
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/commands/clone.ts
|
|
169
|
+
const parser$5 = command("clone", object({
|
|
170
|
+
command: constant("clone"),
|
|
171
|
+
project: argument(string({ metavar: "PROJECT" }), { description: message`The project's Project Slug or Project ID.` }),
|
|
172
|
+
directory: optional(argument(string({ metavar: "DIRECTORY" }), { description: message`Where to clone it. Defaults to its Project Slug.` }))
|
|
173
|
+
}), { description: message`Clone a project's code, for a project whose code can be worked on outside Camox.` });
|
|
174
|
+
async function handler$5(args) {
|
|
175
|
+
p.intro(`camox clone ${args.project}`);
|
|
176
|
+
const { token } = await getOrAuthenticate().catch(() => {
|
|
177
|
+
p.log.error("Authentication failed.");
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
180
|
+
let access;
|
|
149
181
|
try {
|
|
150
|
-
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
access = await createCodeReadToken(token, args.project);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const directory = resolve(args.directory ?? access.projectSlug);
|
|
188
|
+
const config = credentialHelperConfig(access.remote).flatMap(([key, value]) => ["--config", `${key}=${value}`]);
|
|
189
|
+
const cloned = spawnSync("git", [
|
|
190
|
+
"clone",
|
|
191
|
+
...config,
|
|
192
|
+
access.remote,
|
|
193
|
+
directory
|
|
194
|
+
], { stdio: "inherit" });
|
|
195
|
+
if (cloned.error || cloned.status !== 0) {
|
|
196
|
+
p.log.error(cloned.error ? `Could not run Git: ${cloned.error.message}` : "Git couldn't clone it.");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
p.log.info("Installing dependencies...");
|
|
200
|
+
const executable = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
201
|
+
const installed = spawnSync(executable, ["install"], {
|
|
202
|
+
cwd: directory,
|
|
203
|
+
stdio: "inherit"
|
|
204
|
+
});
|
|
205
|
+
if (installed.error || installed.status !== 0) {
|
|
206
|
+
p.log.error(`Cloned into ${directory}, but installing its dependencies failed.`);
|
|
207
|
+
process.exit(1);
|
|
158
208
|
}
|
|
209
|
+
p.outro(`Cloned into ${directory}.`);
|
|
210
|
+
process.exit(0);
|
|
159
211
|
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/commands/git-credential.ts
|
|
214
|
+
const parser$4 = command("git-credential", object({
|
|
215
|
+
command: constant("git-credential"),
|
|
216
|
+
action: argument(string({ metavar: "ACTION" }), { description: message`What Git asks: get, store or erase.` })
|
|
217
|
+
}), { description: message`The Git credential helper \`camox clone\` sets up: answers Git with short-lived read tokens.` });
|
|
160
218
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
219
|
+
* Answers Git's credential protocol (git-credential(1)). Only `get` is answered, with a fresh
|
|
220
|
+
* read token: tokens are short-lived, so there's nothing to store or erase. Errors go to stderr,
|
|
221
|
+
* since stdout is Git's.
|
|
163
222
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!
|
|
171
|
-
|
|
223
|
+
async function handler$4(args) {
|
|
224
|
+
const request = parseCredentialRequest(await text(process.stdin));
|
|
225
|
+
if (args.action !== "get") process.exit(0);
|
|
226
|
+
const project = projectIdOfPath(request.path);
|
|
227
|
+
if (!project) process.exit(0);
|
|
228
|
+
const session = readAuthToken();
|
|
229
|
+
if (!session) {
|
|
230
|
+
process.stderr.write("camox: not logged in. Run `camox login`, then try again.\n");
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const { token, expiresAt } = await createCodeReadToken(session.token, project);
|
|
235
|
+
const expiry = Math.floor(new Date(expiresAt).getTime() / 1e3);
|
|
236
|
+
process.stdout.write(`username=camox\npassword=${token}\npassword_expiry_utc=${expiry}\n`);
|
|
237
|
+
process.exit(0);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
process.stderr.write(`camox: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
172
242
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/commands/login.ts
|
|
245
|
+
const parser$3 = command("login", object({ command: constant("login") }));
|
|
246
|
+
const handler$3 = login;
|
|
247
|
+
async function login() {
|
|
248
|
+
p.intro("camox login");
|
|
249
|
+
try {
|
|
250
|
+
await getOrAuthenticate();
|
|
251
|
+
} catch {
|
|
252
|
+
p.log.error("Authentication failed.");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
p.outro("You're all set!");
|
|
256
|
+
process.exit(0);
|
|
184
257
|
}
|
|
185
258
|
//#endregion
|
|
186
259
|
//#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
260
|
/**
|
|
190
261
|
* A Project ID: a UUIDv7 in lowercase only, the form the project's Worker is named by, so an
|
|
191
262
|
* uppercase one fails here rather than silently naming no project.
|
|
@@ -267,817 +338,15 @@ function loadRuntime(cwd = process.cwd()) {
|
|
|
267
338
|
}
|
|
268
339
|
}
|
|
269
340
|
//#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
341
|
//#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
|
-
}
|
|
342
|
+
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
343
|
//#endregion
|
|
1075
344
|
//#region src/commands/logout.ts
|
|
1076
|
-
const parser$
|
|
345
|
+
const parser$2 = command("logout", object({
|
|
1077
346
|
command: constant("logout"),
|
|
1078
347
|
cwd: cwdFlag
|
|
1079
348
|
}));
|
|
1080
|
-
const handler$
|
|
349
|
+
const handler$2 = logout;
|
|
1081
350
|
function logout(args = {}) {
|
|
1082
351
|
p.intro("camox logout");
|
|
1083
352
|
let authenticationUrl = null;
|
|
@@ -1100,389 +369,152 @@ function logout(args = {}) {
|
|
|
1100
369
|
p.log.success(`Logged out from ${token.name}.`);
|
|
1101
370
|
}
|
|
1102
371
|
//#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"
|
|
372
|
+
//#region src/commands/push.ts
|
|
373
|
+
const parser$1 = command("push", object({
|
|
374
|
+
command: constant("push"),
|
|
375
|
+
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.` }))
|
|
376
|
+
}), { 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.` });
|
|
377
|
+
/** A draft's branch, as a branch's upstream names it: `origin/<draft id>`. */
|
|
378
|
+
const DRAFT_UPSTREAM = new RegExp(`^origin/(${UUID})$`, "i");
|
|
379
|
+
/** What the spinner says while the push is at each step. */
|
|
380
|
+
const STEPS = {
|
|
381
|
+
checking: "Checking your commits...",
|
|
382
|
+
waiting: "Waiting for Camox's agent to finish its turn in the draft...",
|
|
383
|
+
building: "Building your code..."
|
|
1369
384
|
};
|
|
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
|
-
}
|
|
385
|
+
/** The Project ID the camox() plugin names in a Vite config. */
|
|
386
|
+
const PROJECT_ID = new RegExp(`\\bprojectId\\s*:\\s*["'](${UUID})["']`, "i");
|
|
387
|
+
const fail = (text) => {
|
|
388
|
+
p.log.error(text);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
};
|
|
391
|
+
/** Runs `git` in the checkout, returning its trimmed output, or failing with `why`. */
|
|
392
|
+
function git(args, why, { quiet = false } = {}) {
|
|
393
|
+
const run = spawnSync("git", args, {
|
|
394
|
+
encoding: "utf8",
|
|
395
|
+
stdio: [
|
|
396
|
+
"ignore",
|
|
397
|
+
"pipe",
|
|
398
|
+
"pipe"
|
|
399
|
+
]
|
|
1450
400
|
});
|
|
1451
|
-
if (
|
|
1452
|
-
if (
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
401
|
+
if (run.error || run.status !== 0) {
|
|
402
|
+
if (quiet) return void 0;
|
|
403
|
+
return fail(`${why}${run.stderr ? `\n${run.stderr.trim()}` : ""}`);
|
|
404
|
+
}
|
|
405
|
+
return run.stdout.trim();
|
|
406
|
+
}
|
|
407
|
+
const listed = (errors) => errors.map((error) => {
|
|
408
|
+
const column = "column" in error ? `:${error.column}` : "";
|
|
409
|
+
return error.file ? `${error.file}:${error.line}${column} ${error.message}` : error.message;
|
|
410
|
+
}).join("\n");
|
|
411
|
+
async function handler$1(args) {
|
|
412
|
+
p.intro("camox push");
|
|
413
|
+
const root = git(["rev-parse", "--show-toplevel"], "Run camox push inside a project's checkout.");
|
|
414
|
+
let config;
|
|
415
|
+
try {
|
|
416
|
+
config = readFileSync(join(root, "vite.config.ts"), "utf8");
|
|
417
|
+
} catch {
|
|
418
|
+
return fail("This checkout has no vite.config.ts naming its project.");
|
|
419
|
+
}
|
|
420
|
+
const projectUuid = config.match(PROJECT_ID)?.[1]?.toLowerCase();
|
|
421
|
+
if (!projectUuid) return fail("This checkout's vite.config.ts doesn't name a Project ID.");
|
|
422
|
+
const branch = git([
|
|
423
|
+
"symbolic-ref",
|
|
424
|
+
"--quiet",
|
|
425
|
+
"--short",
|
|
426
|
+
"HEAD"
|
|
427
|
+
], "Check out a branch to push.");
|
|
428
|
+
if (branch === "main") return fail("Push a branch of your own: main changes only by publishing.");
|
|
429
|
+
const upstream = git([
|
|
430
|
+
"rev-parse",
|
|
431
|
+
"--abbrev-ref",
|
|
432
|
+
"--symbolic-full-name",
|
|
433
|
+
`${branch}@{upstream}`
|
|
434
|
+
], "", { quiet: true });
|
|
435
|
+
const draftId = args.draft ?? upstream?.match(DRAFT_UPSTREAM)?.[1]?.toLowerCase();
|
|
436
|
+
const { token } = await getOrAuthenticate().catch(() => fail("Authentication failed."));
|
|
437
|
+
git([
|
|
438
|
+
"fetch",
|
|
439
|
+
"--quiet",
|
|
440
|
+
"origin",
|
|
441
|
+
"main"
|
|
442
|
+
], "Couldn't fetch main.");
|
|
443
|
+
const base = git([
|
|
444
|
+
"merge-base",
|
|
445
|
+
"HEAD",
|
|
446
|
+
"origin/main"
|
|
447
|
+
], "Your branch shares no history with main.");
|
|
448
|
+
const draftFetched = !!draftId && git([
|
|
449
|
+
"fetch",
|
|
450
|
+
"--quiet",
|
|
451
|
+
"origin",
|
|
452
|
+
`+refs/heads/${draftId}:refs/remotes/origin/${draftId}`
|
|
453
|
+
], "", { quiet: true }) !== void 0;
|
|
454
|
+
const scratch = mkdtempSync(join(tmpdir(), "camox-push-"));
|
|
455
|
+
let bundle;
|
|
456
|
+
try {
|
|
457
|
+
const file = join(scratch, "push.bundle");
|
|
458
|
+
git([
|
|
459
|
+
"bundle",
|
|
460
|
+
"create",
|
|
461
|
+
"--quiet",
|
|
462
|
+
file,
|
|
463
|
+
branch,
|
|
464
|
+
`^${base}`,
|
|
465
|
+
...draftFetched ? [`^origin/${draftId}`] : []
|
|
466
|
+
], "Your branch has no new commits to push.");
|
|
467
|
+
bundle = readFileSync(file);
|
|
468
|
+
} finally {
|
|
469
|
+
rmSync(scratch, {
|
|
470
|
+
recursive: true,
|
|
471
|
+
force: true
|
|
1465
472
|
});
|
|
1466
|
-
process.exit(1);
|
|
1467
473
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
474
|
+
const spinner = p.spinner();
|
|
475
|
+
spinner.start("Pushing your commits...");
|
|
476
|
+
let pushed;
|
|
477
|
+
try {
|
|
478
|
+
const events = await pushCode(token, {
|
|
479
|
+
projectUuid,
|
|
480
|
+
branch,
|
|
481
|
+
...draftId && { draftId },
|
|
482
|
+
bundle: new File([new Uint8Array(bundle)], `${branch}.bundle`)
|
|
483
|
+
});
|
|
484
|
+
for await (const event of events) if (event.type === "progress") spinner.message(STEPS[event.step] ?? "Pushing...");
|
|
485
|
+
else pushed = event;
|
|
486
|
+
if (!pushed) throw new Error("Camox ended the push without landing it. Push again.");
|
|
487
|
+
} catch (error) {
|
|
488
|
+
spinner.error("Camox refused the push.");
|
|
489
|
+
const data = error.data;
|
|
490
|
+
return fail([error instanceof Error ? error.message : String(error), data?.build && listed(data.build)].filter(Boolean).join("\n"));
|
|
491
|
+
}
|
|
492
|
+
spinner.stop("Pushed.");
|
|
493
|
+
git([
|
|
494
|
+
"fetch",
|
|
495
|
+
"--quiet",
|
|
496
|
+
"origin",
|
|
497
|
+
pushed.draftId
|
|
498
|
+
], "Couldn't fetch the draft's branch.");
|
|
499
|
+
git([
|
|
500
|
+
"branch",
|
|
501
|
+
`--set-upstream-to=origin/${pushed.draftId}`,
|
|
502
|
+
branch
|
|
503
|
+
], "Couldn't set your branch's upstream to the draft's branch.");
|
|
504
|
+
const { result } = pushed;
|
|
505
|
+
if (result.ok) p.log.success("Your code built.");
|
|
506
|
+
else if (result.types.length > 0) p.log.warn(`Your code built, but has type errors, which block publishing:\n${listed(result.types)}`);
|
|
507
|
+
else p.log.warn(`Your code built, but the preview can't show it:\n${listed(result.build)}`);
|
|
508
|
+
p.outro(`Review your draft in the studio: ${pushed.studioUrl}`);
|
|
509
|
+
process.exit(0);
|
|
1478
510
|
}
|
|
1479
511
|
//#endregion
|
|
1480
512
|
//#region src/commands/release.ts
|
|
1481
|
-
const parser
|
|
513
|
+
const parser = command("release", object({
|
|
1482
514
|
command: constant("release"),
|
|
1483
515
|
dryRun: option("--dry-run", { description: message`Build the website as a release would, without logging in or syncing its definitions to Live.` })
|
|
1484
516
|
}));
|
|
1485
|
-
function handler
|
|
517
|
+
async function handler(args) {
|
|
1486
518
|
p.intro(args.dryRun ? "camox release --dry-run" : "camox release");
|
|
1487
519
|
if (!args.dryRun && !process.env.CAMOX_DEPLOY_TOKEN) {
|
|
1488
520
|
p.log.error("CAMOX_DEPLOY_TOKEN is required to release to Live.");
|
|
@@ -1505,104 +537,45 @@ function handler$1(args) {
|
|
|
1505
537
|
p.log.error(args.dryRun ? "Dry run failed." : "Release failed.");
|
|
1506
538
|
process.exit(result.status ?? 1);
|
|
1507
539
|
}
|
|
1508
|
-
p.outro(args.dryRun ? "Dry run built successfully." : "Release prepared successfully.");
|
|
1509
|
-
process.exit(0);
|
|
1510
|
-
}
|
|
1511
|
-
//#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
540
|
try {
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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
|
-
};
|
|
541
|
+
const names = await addSdkBundle(process.cwd());
|
|
542
|
+
if (names) p.log.info(`Added the SDK Bundle ${names.browser} (browser) and ${names.workerd} (workerd).`);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
p.log.error(`Could not add the SDK Bundle: ${error instanceof Error ? error.message : String(error)}`);
|
|
545
|
+
process.exit(1);
|
|
1538
546
|
}
|
|
547
|
+
p.outro(args.dryRun ? "Dry run built successfully." : "Release prepared successfully.");
|
|
548
|
+
process.exit(0);
|
|
1539
549
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Adds the SDK Bundle the build names to its output, so it deploys on its own, with the website's
|
|
552
|
+
* own camox. A camox without SDK Bundles builds whole-app, so there's nothing to add.
|
|
553
|
+
*/
|
|
554
|
+
async function addSdkBundle(root) {
|
|
555
|
+
const module = websiteModule(root, "camox", "./_internal/sdkBundle");
|
|
556
|
+
if (!module) return null;
|
|
557
|
+
const { addSdkBundleToBuild } = await import(pathToFileURL(module).href);
|
|
558
|
+
return addSdkBundleToBuild ? addSdkBundleToBuild({ root }) : null;
|
|
1546
559
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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);
|
|
560
|
+
/**
|
|
561
|
+
* The file a package's export resolves to for `import`, from the website in `root`, or null when
|
|
562
|
+
* it isn't installed or doesn't export it.
|
|
563
|
+
*/
|
|
564
|
+
function websiteModule(root, name, subpath) {
|
|
565
|
+
for (let dir = resolve(root);; dir = dirname(dir)) {
|
|
566
|
+
const manifestPath = join(dir, "node_modules", name, "package.json");
|
|
567
|
+
if (existsSync(manifestPath)) {
|
|
568
|
+
let target = JSON.parse(readFileSync(manifestPath, "utf8")).exports?.[subpath];
|
|
569
|
+
while (target && typeof target === "object") target = target.import ?? target.default;
|
|
570
|
+
return typeof target === "string" ? join(dirname(manifestPath), target) : null;
|
|
1565
571
|
}
|
|
1566
|
-
|
|
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);
|
|
572
|
+
if (dirname(dir) === dir) return null;
|
|
1591
573
|
}
|
|
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
574
|
}
|
|
1602
575
|
//#endregion
|
|
1603
576
|
//#region src/index.ts
|
|
1604
577
|
const program = defineProgram({
|
|
1605
|
-
parser: or(parser$
|
|
578
|
+
parser: or(parser$3, parser$2, parser, parser$5, parser$1, parser$4),
|
|
1606
579
|
metadata: {
|
|
1607
580
|
name: "camox",
|
|
1608
581
|
brief: message`Camox CLI`
|
|
@@ -1610,63 +583,22 @@ const program = defineProgram({
|
|
|
1610
583
|
});
|
|
1611
584
|
const result = runSync(program, { help: "both" });
|
|
1612
585
|
switch (result.command) {
|
|
1613
|
-
case "init":
|
|
1614
|
-
handler$9();
|
|
1615
|
-
break;
|
|
1616
586
|
case "login":
|
|
1617
|
-
await handler$
|
|
587
|
+
await handler$3();
|
|
1618
588
|
break;
|
|
1619
589
|
case "logout":
|
|
1620
|
-
handler$
|
|
590
|
+
handler$2(result);
|
|
1621
591
|
break;
|
|
1622
592
|
case "release":
|
|
1623
|
-
handler$1(result);
|
|
1624
|
-
break;
|
|
1625
|
-
case "preview":
|
|
1626
|
-
await handler$3(result);
|
|
1627
|
-
break;
|
|
1628
|
-
case "status":
|
|
1629
593
|
await handler(result);
|
|
1630
594
|
break;
|
|
1631
|
-
case "
|
|
1632
|
-
|
|
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);
|
|
1659
|
-
break;
|
|
1660
|
-
case "locales.list":
|
|
1661
|
-
case "locales.add":
|
|
1662
|
-
case "locales.remove":
|
|
1663
|
-
case "locales.set-default-tag":
|
|
1664
|
-
await handler$7(result);
|
|
595
|
+
case "clone":
|
|
596
|
+
await handler$5(result);
|
|
1665
597
|
break;
|
|
1666
|
-
case "
|
|
1667
|
-
await handler$
|
|
598
|
+
case "push":
|
|
599
|
+
await handler$1(result);
|
|
1668
600
|
break;
|
|
1669
|
-
case "
|
|
601
|
+
case "git-credential": await handler$4(result);
|
|
1670
602
|
}
|
|
1671
603
|
//#endregion
|
|
1672
604
|
export {};
|