@camox/cli 0.42.0 → 0.44.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 +146 -0
- package/dist/auth-core-4Jdf5q-u.mjs +86 -0
- package/dist/index.mjs +542 -62
- package/dist/lib/auth-core.d.mts +3 -3
- package/dist/lib/auth-core.mjs +1 -1
- package/package.json +3 -2
- package/templates/default/package.json +1 -0
- package/templates/default/scripts/README.md +35 -0
- package/templates/default/scripts/production-smoke.mjs +124 -0
- package/templates/default/vite.config.ts +2 -2
- package/dist/auth-core-cCuk5aZy.mjs +0 -63
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as getProjectBySlug, i as createProject, n as checkSlugAvailability, o as listOrganizations, r as createOrganization, s as setActiveOrganization, t as callTool } from "./api-COfB7AKI.mjs";
|
|
3
|
-
import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl } from "./auth-core-
|
|
3
|
+
import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl, t as normalizeUrl } from "./auth-core-4Jdf5q-u.mjs";
|
|
4
4
|
import { object, or } from "@optique/core/constructs";
|
|
5
5
|
import { message } from "@optique/core/message";
|
|
6
6
|
import { defineProgram } from "@optique/core/program";
|
|
@@ -12,9 +12,11 @@ import { execSync, spawn, spawnSync } from "node:child_process";
|
|
|
12
12
|
import http from "node:http";
|
|
13
13
|
import * as p from "@clack/prompts";
|
|
14
14
|
import { log } from "@clack/prompts";
|
|
15
|
-
import fs from "node:fs";
|
|
16
|
-
import
|
|
15
|
+
import fs, { openAsBlob } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import path, { basename, extname, join } from "node:path";
|
|
17
18
|
import { ZodError, z } from "zod";
|
|
19
|
+
import { mkdtemp, open, rm, stat } from "node:fs/promises";
|
|
18
20
|
import { fileURLToPath } from "node:url";
|
|
19
21
|
import slugify from "slugify";
|
|
20
22
|
//#region src/lib/auth.ts
|
|
@@ -188,11 +190,17 @@ const SIDECAR = path.join("node_modules", ".camox", "runtime.json");
|
|
|
188
190
|
var RuntimeNotFoundError = class extends Error {
|
|
189
191
|
cwd;
|
|
190
192
|
constructor(cwd) {
|
|
191
|
-
super(`No
|
|
193
|
+
super(`No Camox runtime found. Looked for "${SIDECAR}" starting from ${cwd} and its parent directories. Change into your app directory or pass --cwd <app-path>. If you're already targeting the correct app, run its dev server or build once to generate the runtime file.`);
|
|
192
194
|
this.cwd = cwd;
|
|
193
195
|
this.name = "RuntimeNotFoundError";
|
|
194
196
|
}
|
|
195
197
|
};
|
|
198
|
+
var RuntimeDirectoryError = class extends Error {
|
|
199
|
+
constructor(directory) {
|
|
200
|
+
super(`Runtime lookup directory "${directory}" does not exist, is not a directory, or cannot be accessed. Check --cwd <app-path>.`);
|
|
201
|
+
this.name = "RuntimeDirectoryError";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
196
204
|
var RuntimeMalformedError = class extends Error {
|
|
197
205
|
constructor(file, reason) {
|
|
198
206
|
super(`Camox runtime sidecar at ${file} is malformed (${reason}). Re-run your project's dev server to regenerate it.`);
|
|
@@ -205,7 +213,13 @@ var RuntimeMalformedError = class extends Error {
|
|
|
205
213
|
* that tell the user how to fix it.
|
|
206
214
|
*/
|
|
207
215
|
function loadRuntime(cwd = process.cwd()) {
|
|
208
|
-
let dir = cwd;
|
|
216
|
+
let dir = path.resolve(cwd);
|
|
217
|
+
try {
|
|
218
|
+
if (!fs.statSync(dir).isDirectory()) throw new RuntimeDirectoryError(dir);
|
|
219
|
+
} catch {
|
|
220
|
+
throw new RuntimeDirectoryError(dir);
|
|
221
|
+
}
|
|
222
|
+
const start = dir;
|
|
209
223
|
while (true) {
|
|
210
224
|
const candidate = path.join(dir, SIDECAR);
|
|
211
225
|
if (fs.existsSync(candidate)) {
|
|
@@ -223,7 +237,7 @@ function loadRuntime(cwd = process.cwd()) {
|
|
|
223
237
|
}
|
|
224
238
|
}
|
|
225
239
|
const parent = path.dirname(dir);
|
|
226
|
-
if (parent === dir) throw new RuntimeNotFoundError(
|
|
240
|
+
if (parent === dir) throw new RuntimeNotFoundError(start);
|
|
227
241
|
dir = parent;
|
|
228
242
|
}
|
|
229
243
|
}
|
|
@@ -239,7 +253,7 @@ function compact(input) {
|
|
|
239
253
|
for (const [k, v] of Object.entries(input)) if (v !== void 0) out[k] = v;
|
|
240
254
|
return out;
|
|
241
255
|
}
|
|
242
|
-
function fail(err, code) {
|
|
256
|
+
function fail$1(err, code) {
|
|
243
257
|
printError(err);
|
|
244
258
|
process.exit(code);
|
|
245
259
|
}
|
|
@@ -247,7 +261,7 @@ async function resolveProjectId(token, slug, apiUrl) {
|
|
|
247
261
|
try {
|
|
248
262
|
return (await getProjectBySlug(token, slug, apiUrl)).id;
|
|
249
263
|
} catch (err) {
|
|
250
|
-
return fail({
|
|
264
|
+
return fail$1({
|
|
251
265
|
code: "PROJECT_LOOKUP_FAILED",
|
|
252
266
|
message: `Could not load project "${slug}".`,
|
|
253
267
|
details: err instanceof Error ? err.message : String(err)
|
|
@@ -258,7 +272,7 @@ async function callRemote(params) {
|
|
|
258
272
|
try {
|
|
259
273
|
return await callTool(params);
|
|
260
274
|
} catch (err) {
|
|
261
|
-
return fail(asCliError(err), 1);
|
|
275
|
+
return fail$1(asCliError(err), 1);
|
|
262
276
|
}
|
|
263
277
|
}
|
|
264
278
|
/**
|
|
@@ -272,37 +286,46 @@ async function callRemote(params) {
|
|
|
272
286
|
* from the sidecar — it's derived from auth (`dev:<email>`) by default,
|
|
273
287
|
* with `--production` as the only opt-in for prod.
|
|
274
288
|
*/
|
|
275
|
-
async function
|
|
289
|
+
async function resolveCommandContext(opts) {
|
|
276
290
|
let runtime;
|
|
277
291
|
try {
|
|
278
|
-
runtime = loadRuntime();
|
|
292
|
+
runtime = loadRuntime(opts.cwd);
|
|
279
293
|
} catch (err) {
|
|
280
|
-
if (err instanceof
|
|
294
|
+
if (err instanceof RuntimeDirectoryError) return fail$1({
|
|
295
|
+
code: "INVALID_CWD",
|
|
296
|
+
message: err.message
|
|
297
|
+
}, 2);
|
|
298
|
+
if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) return fail$1({
|
|
281
299
|
code: "RUNTIME_NOT_FOUND",
|
|
282
300
|
message: err.message
|
|
283
301
|
}, 2);
|
|
284
302
|
throw err;
|
|
285
303
|
}
|
|
286
304
|
const slug = opts.projectFlag?.trim() || process.env.CAMOX_PROJECT?.trim() || runtime.projectSlug;
|
|
287
|
-
const token = readAuthTokenForUrl(runtime.authenticationUrl);
|
|
288
|
-
if (!token) return fail({
|
|
305
|
+
const token = readAuthTokenForUrl(runtime.authenticationUrl, opts.cwd);
|
|
306
|
+
if (!token) return fail$1({
|
|
289
307
|
code: "NOT_AUTHENTICATED",
|
|
290
308
|
message: `No stored credentials for ${runtime.authenticationUrl}. Run \`camox login\` against this backend first.`
|
|
291
309
|
}, 2);
|
|
292
|
-
if (!opts.production && !token.email) return fail({
|
|
310
|
+
if (!opts.production && !token.email) return fail$1({
|
|
293
311
|
code: "AUTH_INCOMPLETE",
|
|
294
312
|
message: "Auth token is missing an email. Run `camox login` to refresh credentials."
|
|
295
313
|
}, 2);
|
|
296
314
|
const environmentName = opts.production ? "production" : `dev:${token.email}`;
|
|
297
315
|
const projectId = await resolveProjectId(token.token, slug, runtime.apiUrl);
|
|
298
|
-
|
|
316
|
+
return {
|
|
299
317
|
token: token.token,
|
|
300
318
|
apiUrl: runtime.apiUrl,
|
|
301
319
|
environmentName,
|
|
302
320
|
projectId,
|
|
303
|
-
name: opts.toolName,
|
|
304
|
-
args: compact(opts.args),
|
|
305
321
|
disableTelemetry: runtime.disableTelemetry
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
async function dispatch(opts) {
|
|
325
|
+
const response = await callRemote({
|
|
326
|
+
...await resolveCommandContext(opts),
|
|
327
|
+
name: opts.toolName,
|
|
328
|
+
args: compact(opts.args)
|
|
306
329
|
});
|
|
307
330
|
if (!response.ok) {
|
|
308
331
|
printError(response.error);
|
|
@@ -312,19 +335,24 @@ async function dispatch(opts) {
|
|
|
312
335
|
process.exit(0);
|
|
313
336
|
}
|
|
314
337
|
//#endregion
|
|
338
|
+
//#region src/lib/runtime-options.ts
|
|
339
|
+
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; does not change how other file paths resolve.` }));
|
|
340
|
+
//#endregion
|
|
315
341
|
//#region src/commands/blocks.ts
|
|
316
342
|
const projectFlag$3 = optional(option("--project", string({ metavar: "SLUG" })));
|
|
317
343
|
const jsonFlag$3 = option("--json");
|
|
318
344
|
const productionFlag$2 = option("--production");
|
|
319
345
|
const liveFlag$1 = option("--live");
|
|
320
|
-
const parser$
|
|
346
|
+
const parser$11 = command("blocks", or(command("types", object({
|
|
321
347
|
command: constant("blocks.types"),
|
|
348
|
+
cwd: cwdFlag,
|
|
322
349
|
project: projectFlag$3,
|
|
323
350
|
production: productionFlag$2,
|
|
324
351
|
json: jsonFlag$3
|
|
325
352
|
})), command("describe", object({
|
|
326
353
|
command: constant("blocks.describe"),
|
|
327
354
|
type: multiple(option("--type", string({ metavar: "TYPE" })), { min: 1 }),
|
|
355
|
+
cwd: cwdFlag,
|
|
328
356
|
project: projectFlag$3,
|
|
329
357
|
production: productionFlag$2,
|
|
330
358
|
json: jsonFlag$3
|
|
@@ -332,6 +360,7 @@ const parser$8 = command("blocks", or(command("types", object({
|
|
|
332
360
|
command: constant("blocks.get"),
|
|
333
361
|
id: option("--id", integer({ metavar: "ID" })),
|
|
334
362
|
live: liveFlag$1,
|
|
363
|
+
cwd: cwdFlag,
|
|
335
364
|
project: projectFlag$3,
|
|
336
365
|
production: productionFlag$2,
|
|
337
366
|
json: jsonFlag$3
|
|
@@ -339,6 +368,7 @@ const parser$8 = command("blocks", or(command("types", object({
|
|
|
339
368
|
command: constant("blocks.get-many"),
|
|
340
369
|
id: multiple(option("--id", integer({ metavar: "ID" })), { min: 1 }),
|
|
341
370
|
live: liveFlag$1,
|
|
371
|
+
cwd: cwdFlag,
|
|
342
372
|
project: projectFlag$3,
|
|
343
373
|
production: productionFlag$2,
|
|
344
374
|
json: jsonFlag$3
|
|
@@ -353,6 +383,7 @@ const parser$8 = command("blocks", or(command("types", object({
|
|
|
353
383
|
beforeId: optional(option("--before-id", integer({ metavar: "ID" }))),
|
|
354
384
|
afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
|
|
355
385
|
beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
|
|
386
|
+
cwd: cwdFlag,
|
|
356
387
|
project: projectFlag$3,
|
|
357
388
|
production: productionFlag$2,
|
|
358
389
|
json: jsonFlag$3
|
|
@@ -361,6 +392,7 @@ const parser$8 = command("blocks", or(command("types", object({
|
|
|
361
392
|
id: option("--id", integer({ metavar: "ID" })),
|
|
362
393
|
content: optional(option("--content", string({ metavar: "JSON" }))),
|
|
363
394
|
settings: optional(option("--settings", string({ metavar: "JSON" }))),
|
|
395
|
+
cwd: cwdFlag,
|
|
364
396
|
project: projectFlag$3,
|
|
365
397
|
production: productionFlag$2,
|
|
366
398
|
json: jsonFlag$3
|
|
@@ -372,12 +404,14 @@ const parser$8 = command("blocks", or(command("types", object({
|
|
|
372
404
|
beforeId: optional(option("--before-id", integer({ metavar: "ID" }))),
|
|
373
405
|
afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
|
|
374
406
|
beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
|
|
407
|
+
cwd: cwdFlag,
|
|
375
408
|
project: projectFlag$3,
|
|
376
409
|
production: productionFlag$2,
|
|
377
410
|
json: jsonFlag$3
|
|
378
411
|
})), command("delete", object({
|
|
379
412
|
command: constant("blocks.delete"),
|
|
380
413
|
id: option("--id", integer({ metavar: "ID" })),
|
|
414
|
+
cwd: cwdFlag,
|
|
381
415
|
project: projectFlag$3,
|
|
382
416
|
production: productionFlag$2,
|
|
383
417
|
json: jsonFlag$3
|
|
@@ -408,8 +442,9 @@ function collectPositioningArgs(args) {
|
|
|
408
442
|
toolArgs
|
|
409
443
|
};
|
|
410
444
|
}
|
|
411
|
-
async function handler$
|
|
445
|
+
async function handler$11(args) {
|
|
412
446
|
const outputMode = args.json ? "json" : "auto";
|
|
447
|
+
const cwd = args.cwd;
|
|
413
448
|
const projectFlag = args.project;
|
|
414
449
|
const production = args.production;
|
|
415
450
|
try {
|
|
@@ -417,6 +452,7 @@ async function handler$8(args) {
|
|
|
417
452
|
case "blocks.types": return dispatch({
|
|
418
453
|
toolName: "listBlockTypes",
|
|
419
454
|
args: {},
|
|
455
|
+
cwd,
|
|
420
456
|
projectFlag,
|
|
421
457
|
production,
|
|
422
458
|
outputMode
|
|
@@ -424,6 +460,7 @@ async function handler$8(args) {
|
|
|
424
460
|
case "blocks.describe": return dispatch({
|
|
425
461
|
toolName: "describeBlockTypes",
|
|
426
462
|
args: { types: [...args.type] },
|
|
463
|
+
cwd,
|
|
427
464
|
projectFlag,
|
|
428
465
|
production,
|
|
429
466
|
outputMode
|
|
@@ -434,6 +471,7 @@ async function handler$8(args) {
|
|
|
434
471
|
id: args.id,
|
|
435
472
|
source: args.live ? "live" : "draft"
|
|
436
473
|
},
|
|
474
|
+
cwd,
|
|
437
475
|
projectFlag,
|
|
438
476
|
production,
|
|
439
477
|
outputMode
|
|
@@ -444,6 +482,7 @@ async function handler$8(args) {
|
|
|
444
482
|
ids: [...args.id],
|
|
445
483
|
source: args.live ? "live" : "draft"
|
|
446
484
|
},
|
|
485
|
+
cwd,
|
|
447
486
|
projectFlag,
|
|
448
487
|
production,
|
|
449
488
|
outputMode
|
|
@@ -468,6 +507,7 @@ async function handler$8(args) {
|
|
|
468
507
|
settings,
|
|
469
508
|
...toolArgs
|
|
470
509
|
},
|
|
510
|
+
cwd,
|
|
471
511
|
projectFlag,
|
|
472
512
|
production,
|
|
473
513
|
outputMode
|
|
@@ -483,6 +523,7 @@ async function handler$8(args) {
|
|
|
483
523
|
content,
|
|
484
524
|
settings
|
|
485
525
|
},
|
|
526
|
+
cwd,
|
|
486
527
|
projectFlag,
|
|
487
528
|
production,
|
|
488
529
|
outputMode
|
|
@@ -510,6 +551,7 @@ async function handler$8(args) {
|
|
|
510
551
|
id: args.id,
|
|
511
552
|
...toolArgs
|
|
512
553
|
},
|
|
554
|
+
cwd,
|
|
513
555
|
projectFlag,
|
|
514
556
|
production,
|
|
515
557
|
outputMode
|
|
@@ -518,6 +560,7 @@ async function handler$8(args) {
|
|
|
518
560
|
case "blocks.delete": return dispatch({
|
|
519
561
|
toolName: "deleteBlock",
|
|
520
562
|
args: { id: args.id },
|
|
563
|
+
cwd,
|
|
521
564
|
projectFlag,
|
|
522
565
|
production,
|
|
523
566
|
outputMode
|
|
@@ -529,32 +572,74 @@ async function handler$8(args) {
|
|
|
529
572
|
}
|
|
530
573
|
}
|
|
531
574
|
//#endregion
|
|
575
|
+
//#region src/commands/comments.ts
|
|
576
|
+
const common$1 = {
|
|
577
|
+
cwd: cwdFlag,
|
|
578
|
+
project: optional(option("--project", string({ metavar: "SLUG" }))),
|
|
579
|
+
production: option("--production"),
|
|
580
|
+
json: option("--json")
|
|
581
|
+
};
|
|
582
|
+
const parser$10 = command("comments", or(command("list", object({
|
|
583
|
+
command: constant("comments.list"),
|
|
584
|
+
pageId: option("--page-id", integer({
|
|
585
|
+
min: 1,
|
|
586
|
+
metavar: "ID"
|
|
587
|
+
})),
|
|
588
|
+
...common$1
|
|
589
|
+
}), { description: message`List all comments (including resolved ones) for a page.` }), command("resolve", object({
|
|
590
|
+
command: constant("comments.resolve"),
|
|
591
|
+
pageId: option("--page-id", integer({
|
|
592
|
+
min: 1,
|
|
593
|
+
metavar: "ID"
|
|
594
|
+
})),
|
|
595
|
+
id: option("--id", string({ metavar: "UUID" })),
|
|
596
|
+
...common$1
|
|
597
|
+
}), { description: message`Resolve a comment by its UUID and page ID. Use comments list to find both.` })), { description: message`Read and resolve page comments in the selected environment. Defaults to development; use --production to target production.` });
|
|
598
|
+
async function handler$10(args) {
|
|
599
|
+
return dispatch({
|
|
600
|
+
toolName: args.command === "comments.list" ? "listComments" : "resolveComment",
|
|
601
|
+
args: {
|
|
602
|
+
pageId: args.pageId,
|
|
603
|
+
...args.command === "comments.resolve" ? { id: args.id } : {}
|
|
604
|
+
},
|
|
605
|
+
cwd: args.cwd,
|
|
606
|
+
projectFlag: args.project,
|
|
607
|
+
production: args.production,
|
|
608
|
+
outputMode: args.json ? "json" : "auto"
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
532
612
|
//#region src/commands/env.ts
|
|
533
613
|
const projectFlag$2 = optional(option("--project", string({ metavar: "SLUG" })));
|
|
534
614
|
const jsonFlag$2 = option("--json");
|
|
535
615
|
const yesFlag = option("--yes", "-y");
|
|
536
|
-
const parser$
|
|
616
|
+
const parser$9 = command("env", or(command("check", object({
|
|
537
617
|
command: constant("env.check"),
|
|
618
|
+
cwd: cwdFlag,
|
|
538
619
|
project: projectFlag$2,
|
|
539
620
|
json: jsonFlag$2
|
|
540
621
|
})), command("push", object({
|
|
541
622
|
command: constant("env.push"),
|
|
542
623
|
yes: yesFlag,
|
|
624
|
+
cwd: cwdFlag,
|
|
543
625
|
project: projectFlag$2,
|
|
544
626
|
json: jsonFlag$2
|
|
545
627
|
})), command("pull", object({
|
|
546
628
|
command: constant("env.pull"),
|
|
547
629
|
yes: yesFlag,
|
|
630
|
+
cwd: cwdFlag,
|
|
548
631
|
project: projectFlag$2,
|
|
549
632
|
json: jsonFlag$2
|
|
550
633
|
}))));
|
|
551
|
-
async function handler$
|
|
634
|
+
async function handler$9(args) {
|
|
552
635
|
const outputMode = args.json ? "json" : "auto";
|
|
636
|
+
const cwd = args.cwd;
|
|
553
637
|
const projectFlag = args.project;
|
|
554
638
|
switch (args.command) {
|
|
555
639
|
case "env.check": return dispatch({
|
|
556
640
|
toolName: "checkEnvironmentCompatibility",
|
|
557
641
|
args: {},
|
|
642
|
+
cwd,
|
|
558
643
|
projectFlag,
|
|
559
644
|
outputMode
|
|
560
645
|
});
|
|
@@ -571,12 +656,265 @@ async function handler$7(args) {
|
|
|
571
656
|
return dispatch({
|
|
572
657
|
toolName: "replicateEnvironment",
|
|
573
658
|
args: { direction: isPush ? "push" : "pull" },
|
|
659
|
+
cwd,
|
|
574
660
|
projectFlag,
|
|
575
661
|
outputMode
|
|
576
662
|
});
|
|
577
663
|
}
|
|
578
664
|
}
|
|
579
665
|
}
|
|
666
|
+
const TIMEOUT_MS = 12e4;
|
|
667
|
+
const MAX_REDIRECTS = 5;
|
|
668
|
+
function fail(code, message) {
|
|
669
|
+
throw Object.assign(new Error(message), { __cliError: {
|
|
670
|
+
code,
|
|
671
|
+
message
|
|
672
|
+
} });
|
|
673
|
+
}
|
|
674
|
+
function validateMetadata(options) {
|
|
675
|
+
if (options.alt !== void 0 && options.aiMetadata === "on") fail("INVALID_ARGS", "--alt cannot be combined with --ai-metadata on.");
|
|
676
|
+
}
|
|
677
|
+
function validateUpload(options) {
|
|
678
|
+
if (options.file !== void 0 === (options.url !== void 0)) fail("INVALID_ARGS", "Pass exactly one of --file or --url.");
|
|
679
|
+
validateMetadata(options);
|
|
680
|
+
validateFilename(options.filename);
|
|
681
|
+
}
|
|
682
|
+
function validateFilename(filename) {
|
|
683
|
+
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.");
|
|
684
|
+
}
|
|
685
|
+
function httpUrl(value) {
|
|
686
|
+
let url;
|
|
687
|
+
try {
|
|
688
|
+
url = new URL(value);
|
|
689
|
+
} catch {
|
|
690
|
+
return fail("DOWNLOAD_FAILED", "Invalid media URL.");
|
|
691
|
+
}
|
|
692
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) fail("DOWNLOAD_FAILED", "Media URLs must use HTTP(S) without embedded credentials.");
|
|
693
|
+
return url;
|
|
694
|
+
}
|
|
695
|
+
const MIME_TYPES = {
|
|
696
|
+
".jpg": "image/jpeg",
|
|
697
|
+
".jpeg": "image/jpeg",
|
|
698
|
+
".png": "image/png",
|
|
699
|
+
".webp": "image/webp",
|
|
700
|
+
".gif": "image/gif",
|
|
701
|
+
".avif": "image/avif",
|
|
702
|
+
".svg": "image/svg+xml",
|
|
703
|
+
".ico": "image/x-icon",
|
|
704
|
+
".bmp": "image/bmp",
|
|
705
|
+
".pdf": "application/pdf",
|
|
706
|
+
".mp4": "video/mp4",
|
|
707
|
+
".webm": "video/webm",
|
|
708
|
+
".mov": "video/quicktime",
|
|
709
|
+
".mp3": "audio/mpeg",
|
|
710
|
+
".wav": "audio/wav",
|
|
711
|
+
".ogg": "audio/ogg",
|
|
712
|
+
".txt": "text/plain",
|
|
713
|
+
".json": "application/json"
|
|
714
|
+
};
|
|
715
|
+
function mimeType(filename) {
|
|
716
|
+
return MIME_TYPES[extname(filename).toLowerCase()] ?? "application/octet-stream";
|
|
717
|
+
}
|
|
718
|
+
async function download(source, destination) {
|
|
719
|
+
try {
|
|
720
|
+
let url = httpUrl(source);
|
|
721
|
+
const signal = AbortSignal.timeout(TIMEOUT_MS);
|
|
722
|
+
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {
|
|
723
|
+
const response = await fetch(url, {
|
|
724
|
+
redirect: "manual",
|
|
725
|
+
signal
|
|
726
|
+
});
|
|
727
|
+
if ([
|
|
728
|
+
301,
|
|
729
|
+
302,
|
|
730
|
+
303,
|
|
731
|
+
307,
|
|
732
|
+
308
|
|
733
|
+
].includes(response.status)) {
|
|
734
|
+
await response.body?.cancel();
|
|
735
|
+
const location = response.headers.get("location");
|
|
736
|
+
if (!location || redirects === MAX_REDIRECTS) fail("DOWNLOAD_FAILED", "Missing redirect location or too many redirects.");
|
|
737
|
+
url = httpUrl(new URL(location, url).href);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (!response.ok || !response.body) {
|
|
741
|
+
await response.body?.cancel();
|
|
742
|
+
fail("DOWNLOAD_FAILED", `Media download failed (HTTP ${response.status}).`);
|
|
743
|
+
}
|
|
744
|
+
if (Number(response.headers.get("content-length")) > 104857600) {
|
|
745
|
+
await response.body.cancel();
|
|
746
|
+
fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
747
|
+
}
|
|
748
|
+
const file = await open(destination, "w");
|
|
749
|
+
let size = 0;
|
|
750
|
+
try {
|
|
751
|
+
for await (const chunk of response.body) {
|
|
752
|
+
size += chunk.byteLength;
|
|
753
|
+
if (size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
754
|
+
await file.writeFile(chunk);
|
|
755
|
+
}
|
|
756
|
+
} finally {
|
|
757
|
+
await file.close();
|
|
758
|
+
}
|
|
759
|
+
const filename = basename(decodeURIComponent(url.pathname)) || "upload";
|
|
760
|
+
return {
|
|
761
|
+
filename,
|
|
762
|
+
mimeType: response.headers.get("content-type")?.split(";")[0]?.trim() || mimeType(filename)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
return fail("DOWNLOAD_FAILED", "Too many redirects.");
|
|
766
|
+
} catch (error) {
|
|
767
|
+
if (error && typeof error === "object" && "__cliError" in error) throw error;
|
|
768
|
+
return fail("DOWNLOAD_FAILED", "Could not download media (network, timeout, or local I/O failure).");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async function uploadFile(context, options, targetId) {
|
|
772
|
+
validateUpload(options);
|
|
773
|
+
let temporaryDirectory;
|
|
774
|
+
try {
|
|
775
|
+
let path = options.file;
|
|
776
|
+
let filename = path ? basename(path) : "upload";
|
|
777
|
+
let type = mimeType(filename);
|
|
778
|
+
if (options.url !== void 0) {
|
|
779
|
+
temporaryDirectory = await mkdtemp(join(tmpdir(), "camox-upload-"));
|
|
780
|
+
path = join(temporaryDirectory, "download");
|
|
781
|
+
const downloaded = await download(options.url, path);
|
|
782
|
+
filename = downloaded.filename;
|
|
783
|
+
type = downloaded.mimeType;
|
|
784
|
+
}
|
|
785
|
+
if (!path) fail("INVALID_ARGS", "Provide a file path.");
|
|
786
|
+
const info = await stat(path);
|
|
787
|
+
if (!info.isFile()) fail("INVALID_ARGS", "Upload source must be a regular file.");
|
|
788
|
+
if (info.size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
|
|
789
|
+
const body = new FormData();
|
|
790
|
+
if (type === "application/octet-stream") type = mimeType(options.filename ?? filename);
|
|
791
|
+
body.set("file", await openAsBlob(path, { type }), options.filename ?? filename);
|
|
792
|
+
body.set("projectId", String(context.projectId));
|
|
793
|
+
if (options.alt !== void 0) body.set("alt", options.alt);
|
|
794
|
+
if (options.aiMetadata !== void 0 || options.alt !== void 0) body.set("aiMetadataEnabled", String(options.alt === void 0 && options.aiMetadata === "on"));
|
|
795
|
+
const headers = {
|
|
796
|
+
Authorization: `Bearer ${context.token}`,
|
|
797
|
+
"x-camox-client": "cli",
|
|
798
|
+
"x-environment-name": context.environmentName
|
|
799
|
+
};
|
|
800
|
+
if (context.disableTelemetry) headers["x-camox-telemetry-disabled"] = "1";
|
|
801
|
+
let response;
|
|
802
|
+
try {
|
|
803
|
+
const endpoint = targetId === void 0 ? "/files/upload" : `/files/${targetId}/content`;
|
|
804
|
+
response = await fetch(`${context.apiUrl}${endpoint}`, {
|
|
805
|
+
method: "POST",
|
|
806
|
+
headers,
|
|
807
|
+
body,
|
|
808
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
809
|
+
redirect: "error"
|
|
810
|
+
});
|
|
811
|
+
} catch {
|
|
812
|
+
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.`);
|
|
813
|
+
}
|
|
814
|
+
if (!response.ok) fail("UPLOAD_FAILED", `${targetId === void 0 ? "Media upload" : "File replacement"} failed (HTTP ${response.status}).`);
|
|
815
|
+
return await response.json();
|
|
816
|
+
} finally {
|
|
817
|
+
if (temporaryDirectory) await rm(temporaryDirectory, {
|
|
818
|
+
recursive: true,
|
|
819
|
+
force: true
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
//#endregion
|
|
824
|
+
//#region src/commands/files.ts
|
|
825
|
+
const common = {
|
|
826
|
+
cwd: cwdFlag,
|
|
827
|
+
project: optional(option("--project", string({ metavar: "SLUG" }))),
|
|
828
|
+
production: option("--production"),
|
|
829
|
+
json: option("--json")
|
|
830
|
+
};
|
|
831
|
+
const metadata = {
|
|
832
|
+
alt: optional(option("--alt", string({ metavar: "TEXT" }), { description: message`Alt text (including an empty string). Disables automatic metadata.` })),
|
|
833
|
+
aiMetadata: optional(option("--ai-metadata", choice(["on", "off"]), { description: message`Enable or disable automatic metadata. Cannot enable alongside --alt. Enabling schedules generation for raster images.` }))
|
|
834
|
+
};
|
|
835
|
+
const parser$8 = command("files", or(command("upload", object({
|
|
836
|
+
command: constant("files.upload"),
|
|
837
|
+
file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Local file to upload. Pass exactly one of --file or --url.` })),
|
|
838
|
+
url: optional(option("--url", string({ metavar: "URL" }), { description: message`HTTP(S) URL to download and copy into Camox, not an external reference.` })),
|
|
839
|
+
filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Override the local basename or final URL path filename.` })),
|
|
840
|
+
...metadata,
|
|
841
|
+
...common
|
|
842
|
+
}), { description: message`Upload media (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({
|
|
843
|
+
command: constant("files.update"),
|
|
844
|
+
id: option("--id", integer({
|
|
845
|
+
min: 1,
|
|
846
|
+
metavar: "ID"
|
|
847
|
+
})),
|
|
848
|
+
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.` })),
|
|
849
|
+
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.` })),
|
|
850
|
+
filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Set the filename. Metadata-only renames preserve the URL; replacement uses the source filename unless overridden. Disable AI to prevent future automatic renaming.` })),
|
|
851
|
+
...metadata,
|
|
852
|
+
...common
|
|
853
|
+
}), { description: message`Update metadata or replace file content with --file or --url (maximum 100 MiB). Replacing affects every reference to this file in the selected environment; its ID stays unchanged but its URL changes. Omitted alt text and AI settings are preserved. Example: camox files update --id 123 --file ./hero.jpg --alt "Updated description". Use --alt "" for intentionally empty alt text.` }), command("get", object({
|
|
854
|
+
command: constant("files.get"),
|
|
855
|
+
id: option("--id", integer({
|
|
856
|
+
min: 1,
|
|
857
|
+
metavar: "ID"
|
|
858
|
+
})),
|
|
859
|
+
...common
|
|
860
|
+
}), { description: message`Inspect a file in the selected project and environment.` }), command("list", object({
|
|
861
|
+
command: constant("files.list"),
|
|
862
|
+
...common
|
|
863
|
+
}), { description: message`List files in the selected project and environment.` })), { description: message`Upload and manage media. Defaults to your development environment; --production explicitly targets production.` });
|
|
864
|
+
async function handler$8(args) {
|
|
865
|
+
const outputMode = args.json ? "json" : "auto";
|
|
866
|
+
const options = {
|
|
867
|
+
cwd: args.cwd,
|
|
868
|
+
projectFlag: args.project,
|
|
869
|
+
production: args.production,
|
|
870
|
+
outputMode
|
|
871
|
+
};
|
|
872
|
+
try {
|
|
873
|
+
if (args.command === "files.upload") {
|
|
874
|
+
validateUpload(args);
|
|
875
|
+
printResult(await uploadFile(await resolveCommandContext(options), args), outputMode);
|
|
876
|
+
process.exit(0);
|
|
877
|
+
}
|
|
878
|
+
if (args.command === "files.list") return dispatch({
|
|
879
|
+
...options,
|
|
880
|
+
toolName: "listFiles",
|
|
881
|
+
args: {}
|
|
882
|
+
});
|
|
883
|
+
if (args.command === "files.get") return dispatch({
|
|
884
|
+
...options,
|
|
885
|
+
toolName: "getFile",
|
|
886
|
+
args: { id: args.id }
|
|
887
|
+
});
|
|
888
|
+
if (args.file !== void 0 || args.url !== void 0) {
|
|
889
|
+
validateUpload(args);
|
|
890
|
+
printResult(await uploadFile(await resolveCommandContext(options), args, args.id), outputMode);
|
|
891
|
+
process.exit(0);
|
|
892
|
+
}
|
|
893
|
+
validateMetadata(args);
|
|
894
|
+
validateFilename(args.filename);
|
|
895
|
+
if (args.alt === void 0 && args.aiMetadata === void 0 && args.filename === void 0) {
|
|
896
|
+
printError({
|
|
897
|
+
code: "INVALID_ARGS",
|
|
898
|
+
message: "Pass --file, --url, --filename, --alt or --ai-metadata."
|
|
899
|
+
});
|
|
900
|
+
process.exit(2);
|
|
901
|
+
}
|
|
902
|
+
return dispatch({
|
|
903
|
+
...options,
|
|
904
|
+
toolName: "updateFile",
|
|
905
|
+
args: {
|
|
906
|
+
id: args.id,
|
|
907
|
+
alt: args.alt,
|
|
908
|
+
filename: args.filename,
|
|
909
|
+
aiMetadataEnabled: args.aiMetadata === void 0 ? void 0 : args.aiMetadata === "on"
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
} catch (err) {
|
|
913
|
+
const error = asCliError(err);
|
|
914
|
+
printError(error);
|
|
915
|
+
process.exit(error.code === "INVALID_ARGS" ? 2 : 1);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
580
918
|
//#endregion
|
|
581
919
|
//#region src/lib/agent-skills.ts
|
|
582
920
|
function createAgentSkillLinks(targetDir) {
|
|
@@ -584,8 +922,10 @@ function createAgentSkillLinks(targetDir) {
|
|
|
584
922
|
const claudeSkillsDir = path.join(targetDir, ".claude", "skills");
|
|
585
923
|
fs.mkdirSync(agentsSkillsDir, { recursive: true });
|
|
586
924
|
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
587
|
-
|
|
588
|
-
|
|
925
|
+
for (const skill of ["camox", "camoxify"]) {
|
|
926
|
+
fs.symlinkSync(`../../node_modules/camox/skills/${skill}`, path.join(agentsSkillsDir, skill));
|
|
927
|
+
fs.symlinkSync(`../../.agents/skills/${skill}`, path.join(claudeSkillsDir, skill));
|
|
928
|
+
}
|
|
589
929
|
}
|
|
590
930
|
//#endregion
|
|
591
931
|
//#region src/lib/utils.ts
|
|
@@ -656,8 +996,8 @@ function copyDir(src, dest, replacements) {
|
|
|
656
996
|
}
|
|
657
997
|
//#endregion
|
|
658
998
|
//#region src/commands/init.ts
|
|
659
|
-
const parser$
|
|
660
|
-
const handler$
|
|
999
|
+
const parser$7 = command("init", object({ command: constant("init") }));
|
|
1000
|
+
const handler$7 = init;
|
|
661
1001
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
662
1002
|
const ownPkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
663
1003
|
const PNPM_WORKSPACE = `allowBuilds:
|
|
@@ -929,20 +1269,23 @@ src/routeTree.gen.ts
|
|
|
929
1269
|
const projectFlag$1 = optional(option("--project", string({ metavar: "SLUG" })));
|
|
930
1270
|
const jsonFlag$1 = option("--json");
|
|
931
1271
|
const productionFlag$1 = option("--production");
|
|
932
|
-
const parser$
|
|
1272
|
+
const parser$6 = command("layouts", or(command("list", object({
|
|
933
1273
|
command: constant("layouts.list"),
|
|
1274
|
+
cwd: cwdFlag,
|
|
934
1275
|
project: projectFlag$1,
|
|
935
1276
|
production: productionFlag$1,
|
|
936
1277
|
json: jsonFlag$1
|
|
937
1278
|
}))));
|
|
938
|
-
async function handler$
|
|
1279
|
+
async function handler$6(args) {
|
|
939
1280
|
const outputMode = args.json ? "json" : "auto";
|
|
1281
|
+
const cwd = args.cwd;
|
|
940
1282
|
const projectFlag = args.project;
|
|
941
1283
|
const production = args.production;
|
|
942
1284
|
switch (args.command) {
|
|
943
1285
|
case "layouts.list": return dispatch({
|
|
944
1286
|
toolName: "listLayouts",
|
|
945
1287
|
args: {},
|
|
1288
|
+
cwd,
|
|
946
1289
|
projectFlag,
|
|
947
1290
|
production,
|
|
948
1291
|
outputMode
|
|
@@ -951,8 +1294,8 @@ async function handler$5(args) {
|
|
|
951
1294
|
}
|
|
952
1295
|
//#endregion
|
|
953
1296
|
//#region src/commands/login.ts
|
|
954
|
-
const parser$
|
|
955
|
-
const handler$
|
|
1297
|
+
const parser$5 = command("login", object({ command: constant("login") }));
|
|
1298
|
+
const handler$5 = login;
|
|
956
1299
|
async function login() {
|
|
957
1300
|
p.intro("camox login");
|
|
958
1301
|
try {
|
|
@@ -966,26 +1309,29 @@ async function login() {
|
|
|
966
1309
|
}
|
|
967
1310
|
//#endregion
|
|
968
1311
|
//#region src/commands/logout.ts
|
|
969
|
-
const parser$
|
|
970
|
-
|
|
971
|
-
|
|
1312
|
+
const parser$4 = command("logout", object({
|
|
1313
|
+
command: constant("logout"),
|
|
1314
|
+
cwd: cwdFlag
|
|
1315
|
+
}));
|
|
1316
|
+
const handler$4 = logout;
|
|
1317
|
+
function logout(args = {}) {
|
|
972
1318
|
p.intro("camox logout");
|
|
973
1319
|
let authenticationUrl = null;
|
|
974
1320
|
try {
|
|
975
|
-
authenticationUrl = loadRuntime().authenticationUrl;
|
|
1321
|
+
authenticationUrl = loadRuntime(args.cwd).authenticationUrl;
|
|
976
1322
|
} catch (error) {
|
|
977
|
-
if (error instanceof RuntimeMalformedError) {
|
|
1323
|
+
if (error instanceof RuntimeMalformedError || error instanceof RuntimeDirectoryError || args.cwd !== void 0 && error instanceof RuntimeNotFoundError) {
|
|
978
1324
|
log.error(error.message);
|
|
979
1325
|
return;
|
|
980
1326
|
}
|
|
981
1327
|
if (!(error instanceof RuntimeNotFoundError)) throw error;
|
|
982
1328
|
}
|
|
983
|
-
const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl) : readAuthToken();
|
|
1329
|
+
const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl, args.cwd) : readAuthToken();
|
|
984
1330
|
if (!token) {
|
|
985
1331
|
log.error("Not logged in.");
|
|
986
1332
|
return;
|
|
987
1333
|
}
|
|
988
|
-
if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl);
|
|
1334
|
+
if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl, args.cwd);
|
|
989
1335
|
else removeAuthToken();
|
|
990
1336
|
p.log.success(`Logged out from ${token.name}.`);
|
|
991
1337
|
}
|
|
@@ -997,6 +1343,7 @@ const productionFlag = option("--production");
|
|
|
997
1343
|
const liveFlag = option("--live");
|
|
998
1344
|
const list = command("list", object({
|
|
999
1345
|
command: constant("pages.list"),
|
|
1346
|
+
cwd: cwdFlag,
|
|
1000
1347
|
project: projectFlag,
|
|
1001
1348
|
production: productionFlag,
|
|
1002
1349
|
json: jsonFlag
|
|
@@ -1006,6 +1353,7 @@ const get = command("get", object({
|
|
|
1006
1353
|
id: optional(option("--id", integer({ metavar: "ID" }))),
|
|
1007
1354
|
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1008
1355
|
live: liveFlag,
|
|
1356
|
+
cwd: cwdFlag,
|
|
1009
1357
|
project: projectFlag,
|
|
1010
1358
|
production: productionFlag,
|
|
1011
1359
|
json: jsonFlag
|
|
@@ -1015,6 +1363,7 @@ const publish = command("publish", object({
|
|
|
1015
1363
|
id: optional(option("--id", integer({ metavar: "ID" }))),
|
|
1016
1364
|
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1017
1365
|
noLayout: option("--no-layout"),
|
|
1366
|
+
cwd: cwdFlag,
|
|
1018
1367
|
project: projectFlag,
|
|
1019
1368
|
production: productionFlag,
|
|
1020
1369
|
json: jsonFlag
|
|
@@ -1023,6 +1372,7 @@ const unpublish = command("unpublish", object({
|
|
|
1023
1372
|
command: constant("pages.unpublish"),
|
|
1024
1373
|
id: optional(option("--id", integer({ metavar: "ID" }))),
|
|
1025
1374
|
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1375
|
+
cwd: cwdFlag,
|
|
1026
1376
|
project: projectFlag,
|
|
1027
1377
|
production: productionFlag,
|
|
1028
1378
|
json: jsonFlag
|
|
@@ -1031,50 +1381,64 @@ const discardChanges = command("discard-changes", object({
|
|
|
1031
1381
|
command: constant("pages.discard-changes"),
|
|
1032
1382
|
id: optional(option("--id", integer({ metavar: "ID" }))),
|
|
1033
1383
|
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1384
|
+
cwd: cwdFlag,
|
|
1034
1385
|
project: projectFlag,
|
|
1035
1386
|
production: productionFlag,
|
|
1036
1387
|
json: jsonFlag
|
|
1037
1388
|
}));
|
|
1038
|
-
const parser$
|
|
1389
|
+
const parser$3 = command("pages", or(list, get, command("create", object({
|
|
1039
1390
|
command: constant("pages.create"),
|
|
1040
1391
|
nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
|
|
1041
1392
|
pathSegment: option("--path-segment", string({ metavar: "SEGMENT" })),
|
|
1042
1393
|
layoutId: option("--layout-id", integer({ metavar: "ID" })),
|
|
1043
1394
|
parentPageId: optional(option("--parent-page-id", integer({ metavar: "ID" }))),
|
|
1395
|
+
cwd: cwdFlag,
|
|
1044
1396
|
project: projectFlag,
|
|
1045
1397
|
production: productionFlag,
|
|
1046
1398
|
json: jsonFlag
|
|
1047
1399
|
})), command("update", object({
|
|
1048
1400
|
command: constant("pages.update"),
|
|
1049
|
-
id: option("--id", integer({
|
|
1401
|
+
id: optional(option("--id", integer({
|
|
1402
|
+
min: 1,
|
|
1403
|
+
metavar: "ID"
|
|
1404
|
+
}))),
|
|
1405
|
+
path: optional(option("--path", string({ metavar: "PATH" }))),
|
|
1406
|
+
metaTitle: optional(option("--meta-title", string({ metavar: "TEXT" }), { description: message`Set the SEO title. An empty string clears it. Disables automatic SEO for both fields.` })),
|
|
1407
|
+
metaDescription: optional(option("--meta-description", string({ metavar: "TEXT" }), { description: message`Set the SEO description. An empty string clears it. Disables automatic SEO for both fields.` })),
|
|
1408
|
+
aiSeo: optional(option("--ai-seo", choice(["on", "off"]), { description: message`Enable or disable automatic SEO. Enabling schedules asynchronous generation; it cannot be combined with manual metadata. Disabling preserves current metadata.` })),
|
|
1050
1409
|
nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
|
|
1051
1410
|
pathSegment: optional(option("--path-segment", string({ metavar: "SEGMENT" }))),
|
|
1052
1411
|
parentPageId: optional(option("--parent-page-id", integer({ metavar: "ID" }))),
|
|
1412
|
+
cwd: cwdFlag,
|
|
1053
1413
|
project: projectFlag,
|
|
1054
1414
|
production: productionFlag,
|
|
1055
1415
|
json: jsonFlag
|
|
1056
|
-
})), command("set-layout", object({
|
|
1416
|
+
}), { description: message`Update a page draft, including in --production. Pass exactly one of --id or --path and at least one update field. Omitted fields are preserved. Publish separately with pages publish. Example: camox pages update --path /about --meta-title "About us" --meta-description "Meet the team". Read metadata with pages get; add --live for published metadata.` }), command("set-layout", object({
|
|
1057
1417
|
command: constant("pages.set-layout"),
|
|
1058
1418
|
id: option("--id", integer({ metavar: "ID" })),
|
|
1059
1419
|
layoutId: option("--layout-id", integer({ metavar: "ID" })),
|
|
1420
|
+
cwd: cwdFlag,
|
|
1060
1421
|
project: projectFlag,
|
|
1061
1422
|
production: productionFlag,
|
|
1062
1423
|
json: jsonFlag
|
|
1063
1424
|
})), command("delete", object({
|
|
1064
1425
|
command: constant("pages.delete"),
|
|
1065
1426
|
id: option("--id", integer({ metavar: "ID" })),
|
|
1427
|
+
cwd: cwdFlag,
|
|
1066
1428
|
project: projectFlag,
|
|
1067
1429
|
production: productionFlag,
|
|
1068
1430
|
json: jsonFlag
|
|
1069
1431
|
})), publish, unpublish, discardChanges));
|
|
1070
|
-
async function handler$
|
|
1432
|
+
async function handler$3(args) {
|
|
1071
1433
|
const outputMode = args.json ? "json" : "auto";
|
|
1434
|
+
const cwd = args.cwd;
|
|
1072
1435
|
const projectFlag = args.project;
|
|
1073
1436
|
const production = args.production;
|
|
1074
1437
|
switch (args.command) {
|
|
1075
1438
|
case "pages.list": return dispatch({
|
|
1076
1439
|
toolName: "listPages",
|
|
1077
1440
|
args: {},
|
|
1441
|
+
cwd,
|
|
1078
1442
|
projectFlag,
|
|
1079
1443
|
production,
|
|
1080
1444
|
outputMode
|
|
@@ -1093,6 +1457,7 @@ async function handler$2(args) {
|
|
|
1093
1457
|
...args.id != null ? { id: args.id } : { path: args.path },
|
|
1094
1458
|
source: args.live ? "live" : "draft"
|
|
1095
1459
|
},
|
|
1460
|
+
cwd,
|
|
1096
1461
|
projectFlag,
|
|
1097
1462
|
production,
|
|
1098
1463
|
outputMode
|
|
@@ -1105,28 +1470,63 @@ async function handler$2(args) {
|
|
|
1105
1470
|
layoutId: args.layoutId,
|
|
1106
1471
|
parentPageId: args.parentPageId
|
|
1107
1472
|
},
|
|
1473
|
+
cwd,
|
|
1108
1474
|
projectFlag,
|
|
1109
1475
|
production,
|
|
1110
1476
|
outputMode
|
|
1111
1477
|
});
|
|
1112
|
-
case "pages.update":
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1478
|
+
case "pages.update":
|
|
1479
|
+
if (args.id == null === (args.path == null)) {
|
|
1480
|
+
printError({
|
|
1481
|
+
code: "INVALID_ARGS",
|
|
1482
|
+
message: "Pass exactly one of --id or --path."
|
|
1483
|
+
});
|
|
1484
|
+
process.exit(2);
|
|
1485
|
+
}
|
|
1486
|
+
if (args.aiSeo === "on" && (args.metaTitle !== void 0 || args.metaDescription !== void 0)) {
|
|
1487
|
+
printError({
|
|
1488
|
+
code: "INVALID_ARGS",
|
|
1489
|
+
message: "Cannot enable automatic SEO alongside manual metadata."
|
|
1490
|
+
});
|
|
1491
|
+
process.exit(2);
|
|
1492
|
+
}
|
|
1493
|
+
if ([
|
|
1494
|
+
args.nickname,
|
|
1495
|
+
args.pathSegment,
|
|
1496
|
+
args.parentPageId,
|
|
1497
|
+
args.metaTitle,
|
|
1498
|
+
args.metaDescription,
|
|
1499
|
+
args.aiSeo
|
|
1500
|
+
].every((value) => value === void 0)) {
|
|
1501
|
+
printError({
|
|
1502
|
+
code: "INVALID_ARGS",
|
|
1503
|
+
message: "Pass at least one field to update."
|
|
1504
|
+
});
|
|
1505
|
+
process.exit(2);
|
|
1506
|
+
}
|
|
1507
|
+
return dispatch({
|
|
1508
|
+
toolName: "updatePage",
|
|
1509
|
+
args: {
|
|
1510
|
+
...args.id != null ? { id: args.id } : { path: args.path },
|
|
1511
|
+
metaTitle: args.metaTitle,
|
|
1512
|
+
metaDescription: args.metaDescription,
|
|
1513
|
+
aiSeoEnabled: args.aiSeo === void 0 ? void 0 : args.aiSeo === "on",
|
|
1514
|
+
nickname: args.nickname,
|
|
1515
|
+
pathSegment: args.pathSegment,
|
|
1516
|
+
parentPageId: args.parentPageId
|
|
1517
|
+
},
|
|
1518
|
+
cwd,
|
|
1519
|
+
projectFlag,
|
|
1520
|
+
production,
|
|
1521
|
+
outputMode
|
|
1522
|
+
});
|
|
1124
1523
|
case "pages.set-layout": return dispatch({
|
|
1125
1524
|
toolName: "setPageLayout",
|
|
1126
1525
|
args: {
|
|
1127
1526
|
id: args.id,
|
|
1128
1527
|
layoutId: args.layoutId
|
|
1129
1528
|
},
|
|
1529
|
+
cwd,
|
|
1130
1530
|
projectFlag,
|
|
1131
1531
|
production,
|
|
1132
1532
|
outputMode
|
|
@@ -1134,6 +1534,7 @@ async function handler$2(args) {
|
|
|
1134
1534
|
case "pages.delete": return dispatch({
|
|
1135
1535
|
toolName: "deletePage",
|
|
1136
1536
|
args: { id: args.id },
|
|
1537
|
+
cwd,
|
|
1137
1538
|
projectFlag,
|
|
1138
1539
|
production,
|
|
1139
1540
|
outputMode
|
|
@@ -1152,6 +1553,7 @@ async function handler$2(args) {
|
|
|
1152
1553
|
...args.id != null ? { id: args.id } : { path: args.path },
|
|
1153
1554
|
alsoPublishLayout: !args.noLayout
|
|
1154
1555
|
},
|
|
1556
|
+
cwd,
|
|
1155
1557
|
projectFlag,
|
|
1156
1558
|
production,
|
|
1157
1559
|
outputMode
|
|
@@ -1167,6 +1569,7 @@ async function handler$2(args) {
|
|
|
1167
1569
|
return dispatch({
|
|
1168
1570
|
toolName: "unpublishPage",
|
|
1169
1571
|
args: args.id != null ? { id: args.id } : { path: args.path },
|
|
1572
|
+
cwd,
|
|
1170
1573
|
projectFlag,
|
|
1171
1574
|
production,
|
|
1172
1575
|
outputMode
|
|
@@ -1182,6 +1585,7 @@ async function handler$2(args) {
|
|
|
1182
1585
|
return dispatch({
|
|
1183
1586
|
toolName: "discardPageChanges",
|
|
1184
1587
|
args: args.id != null ? { id: args.id } : { path: args.path },
|
|
1588
|
+
cwd,
|
|
1185
1589
|
projectFlag,
|
|
1186
1590
|
production,
|
|
1187
1591
|
outputMode
|
|
@@ -1189,6 +1593,61 @@ async function handler$2(args) {
|
|
|
1189
1593
|
}
|
|
1190
1594
|
}
|
|
1191
1595
|
//#endregion
|
|
1596
|
+
//#region src/commands/preview.ts
|
|
1597
|
+
const parser$2 = command("preview", object({
|
|
1598
|
+
command: constant("preview"),
|
|
1599
|
+
cwd: cwdFlag,
|
|
1600
|
+
url: option("--url", string()),
|
|
1601
|
+
json: option("--json")
|
|
1602
|
+
}), { description: message`Create a single-use sign-in URL for local draft verification. Open it in your automation browser within three minutes. The URL is a credential; do not share it.` });
|
|
1603
|
+
async function handler$2(args) {
|
|
1604
|
+
try {
|
|
1605
|
+
const url = new URL(args.url);
|
|
1606
|
+
if (!["http:", "https:"].includes(url.protocol) || ![
|
|
1607
|
+
"localhost",
|
|
1608
|
+
"127.0.0.1",
|
|
1609
|
+
"[::1]"
|
|
1610
|
+
].includes(url.hostname) || url.username || url.password) throw new Error("Preview requires an http(s) loopback URL without credentials.");
|
|
1611
|
+
const runtime = loadRuntime(args.cwd);
|
|
1612
|
+
const auth = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
|
|
1613
|
+
if (!auth?.email) {
|
|
1614
|
+
printError({
|
|
1615
|
+
code: "NOT_AUTHENTICATED",
|
|
1616
|
+
message: `Run camox login for ${runtime.authenticationUrl} first.`
|
|
1617
|
+
});
|
|
1618
|
+
process.exitCode = 2;
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const response = await fetch(`${normalizeUrl(runtime.apiUrl)}/api/auth/one-time-token/generate`, {
|
|
1622
|
+
headers: { Authorization: `Bearer ${auth.token}` },
|
|
1623
|
+
redirect: "error",
|
|
1624
|
+
signal: AbortSignal.timeout(15e3)
|
|
1625
|
+
});
|
|
1626
|
+
if (!response.ok) throw new Error(`Could not create preview sign-in (${response.status}). Check your session with camox login.`);
|
|
1627
|
+
const data = await response.json();
|
|
1628
|
+
if (typeof data.token !== "string" || !data.token) throw new Error("Authentication backend returned no one-time token.");
|
|
1629
|
+
const target = {
|
|
1630
|
+
projectSlug: runtime.projectSlug,
|
|
1631
|
+
environmentName: `dev:${auth.email}`,
|
|
1632
|
+
apiUrl: normalizeUrl(runtime.apiUrl)
|
|
1633
|
+
};
|
|
1634
|
+
url.searchParams.set("camox-preview", JSON.stringify(target));
|
|
1635
|
+
url.searchParams.set("ott", data.token);
|
|
1636
|
+
printResult({
|
|
1637
|
+
url: url.href,
|
|
1638
|
+
...target,
|
|
1639
|
+
source: "draft",
|
|
1640
|
+
warning: "Single-use sign-in credential, valid for three minutes. Open in your verification browser; do not share or commit."
|
|
1641
|
+
}, args.json ? "json" : "auto");
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
printError({
|
|
1644
|
+
code: "PREVIEW_FAILED",
|
|
1645
|
+
message: error instanceof Error ? error.message : "Could not create preview."
|
|
1646
|
+
});
|
|
1647
|
+
process.exitCode = 2;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
//#endregion
|
|
1192
1651
|
//#region src/commands/release.ts
|
|
1193
1652
|
const parser$1 = command("release", object({ command: constant("release") }));
|
|
1194
1653
|
function handler$1() {
|
|
@@ -1220,14 +1679,22 @@ function handler$1() {
|
|
|
1220
1679
|
//#region src/commands/status.ts
|
|
1221
1680
|
const parser = command("status", object({
|
|
1222
1681
|
command: constant("status"),
|
|
1682
|
+
cwd: cwdFlag,
|
|
1223
1683
|
production: option("--production"),
|
|
1224
1684
|
json: option("--json")
|
|
1225
1685
|
}));
|
|
1226
1686
|
async function handler(args) {
|
|
1227
1687
|
let runtime;
|
|
1228
1688
|
try {
|
|
1229
|
-
runtime = loadRuntime();
|
|
1689
|
+
runtime = loadRuntime(args.cwd);
|
|
1230
1690
|
} catch (err) {
|
|
1691
|
+
if (err instanceof RuntimeDirectoryError) {
|
|
1692
|
+
printError({
|
|
1693
|
+
code: "INVALID_CWD",
|
|
1694
|
+
message: err.message
|
|
1695
|
+
});
|
|
1696
|
+
process.exit(2);
|
|
1697
|
+
}
|
|
1231
1698
|
if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) {
|
|
1232
1699
|
printError({
|
|
1233
1700
|
code: "RUNTIME_NOT_FOUND",
|
|
@@ -1237,7 +1704,7 @@ async function handler(args) {
|
|
|
1237
1704
|
}
|
|
1238
1705
|
throw err;
|
|
1239
1706
|
}
|
|
1240
|
-
const token = readAuthTokenForUrl(runtime.authenticationUrl);
|
|
1707
|
+
const token = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
|
|
1241
1708
|
const environmentName = args.production ? "production" : token?.email ? `dev:${token.email}` : null;
|
|
1242
1709
|
const status = {
|
|
1243
1710
|
projectSlug: runtime.projectSlug,
|
|
@@ -1267,7 +1734,7 @@ async function handler(args) {
|
|
|
1267
1734
|
//#endregion
|
|
1268
1735
|
//#region src/index.ts
|
|
1269
1736
|
const result = runSync(defineProgram({
|
|
1270
|
-
parser: or(parser$
|
|
1737
|
+
parser: or(parser$7, parser$5, parser$4, parser$1, parser, parser$2, parser$3, parser$11, parser$10, parser$6, parser$9, parser$8),
|
|
1271
1738
|
metadata: {
|
|
1272
1739
|
name: "camox",
|
|
1273
1740
|
brief: message`Camox CLI`
|
|
@@ -1275,17 +1742,20 @@ const result = runSync(defineProgram({
|
|
|
1275
1742
|
}), { help: "both" });
|
|
1276
1743
|
switch (result.command) {
|
|
1277
1744
|
case "init":
|
|
1278
|
-
await handler$
|
|
1745
|
+
await handler$7();
|
|
1279
1746
|
break;
|
|
1280
1747
|
case "login":
|
|
1281
|
-
await handler$
|
|
1748
|
+
await handler$5();
|
|
1282
1749
|
break;
|
|
1283
1750
|
case "logout":
|
|
1284
|
-
handler$
|
|
1751
|
+
handler$4(result);
|
|
1285
1752
|
break;
|
|
1286
1753
|
case "release":
|
|
1287
1754
|
handler$1();
|
|
1288
1755
|
break;
|
|
1756
|
+
case "preview":
|
|
1757
|
+
await handler$2(result);
|
|
1758
|
+
break;
|
|
1289
1759
|
case "status":
|
|
1290
1760
|
await handler(result);
|
|
1291
1761
|
break;
|
|
@@ -1298,7 +1768,7 @@ switch (result.command) {
|
|
|
1298
1768
|
case "pages.publish":
|
|
1299
1769
|
case "pages.unpublish":
|
|
1300
1770
|
case "pages.discard-changes":
|
|
1301
|
-
await handler$
|
|
1771
|
+
await handler$3(result);
|
|
1302
1772
|
break;
|
|
1303
1773
|
case "blocks.types":
|
|
1304
1774
|
case "blocks.describe":
|
|
@@ -1308,15 +1778,25 @@ switch (result.command) {
|
|
|
1308
1778
|
case "blocks.edit":
|
|
1309
1779
|
case "blocks.move":
|
|
1310
1780
|
case "blocks.delete":
|
|
1781
|
+
await handler$11(result);
|
|
1782
|
+
break;
|
|
1783
|
+
case "comments.list":
|
|
1784
|
+
case "comments.resolve":
|
|
1785
|
+
await handler$10(result);
|
|
1786
|
+
break;
|
|
1787
|
+
case "files.upload":
|
|
1788
|
+
case "files.update":
|
|
1789
|
+
case "files.get":
|
|
1790
|
+
case "files.list":
|
|
1311
1791
|
await handler$8(result);
|
|
1312
1792
|
break;
|
|
1313
1793
|
case "layouts.list":
|
|
1314
|
-
await handler$
|
|
1794
|
+
await handler$6(result);
|
|
1315
1795
|
break;
|
|
1316
1796
|
case "env.check":
|
|
1317
1797
|
case "env.push":
|
|
1318
1798
|
case "env.pull":
|
|
1319
|
-
await handler$
|
|
1799
|
+
await handler$9(result);
|
|
1320
1800
|
break;
|
|
1321
1801
|
}
|
|
1322
1802
|
//#endregion
|