@xera-ai/cli 0.12.0 → 0.12.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/dist/index.js
CHANGED
|
@@ -210,21 +210,34 @@ async function runChecks(cwd) {
|
|
|
210
210
|
checks.push({ name: "xera skills present", ok: false, message: "run `xera init`" });
|
|
211
211
|
} else {
|
|
212
212
|
const required = [
|
|
213
|
-
"xera-run
|
|
214
|
-
"xera-fetch
|
|
215
|
-
"xera-feature
|
|
216
|
-
"xera-script
|
|
217
|
-
"xera-exec
|
|
218
|
-
"xera-report
|
|
219
|
-
"xera-promote
|
|
213
|
+
"xera-run",
|
|
214
|
+
"xera-fetch",
|
|
215
|
+
"xera-feature",
|
|
216
|
+
"xera-script",
|
|
217
|
+
"xera-exec",
|
|
218
|
+
"xera-report",
|
|
219
|
+
"xera-promote"
|
|
220
220
|
];
|
|
221
|
-
const missing =
|
|
221
|
+
const missing = [];
|
|
222
|
+
const legacyFlat = [];
|
|
223
|
+
for (const base of required) {
|
|
224
|
+
if (existsSync(join(skillsDir, base, "SKILL.md")))
|
|
225
|
+
continue;
|
|
226
|
+
if (existsSync(join(skillsDir, `${base}.md`))) {
|
|
227
|
+
legacyFlat.push(base);
|
|
228
|
+
} else {
|
|
229
|
+
missing.push(base);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
222
232
|
const skillsCheck = {
|
|
223
233
|
name: "xera skills present",
|
|
224
|
-
ok: missing.length === 0
|
|
234
|
+
ok: missing.length === 0 && legacyFlat.length === 0
|
|
225
235
|
};
|
|
226
|
-
if (missing.length)
|
|
227
|
-
skillsCheck.message = `missing: ${missing.join(", ")}`;
|
|
236
|
+
if (missing.length) {
|
|
237
|
+
skillsCheck.message = `missing: ${missing.map((b) => `${b}/SKILL.md`).join(", ")}`;
|
|
238
|
+
} else if (legacyFlat.length) {
|
|
239
|
+
skillsCheck.message = `legacy flat layout in .claude/skills/ \u2014 run \`xera init --update\` to migrate to <name>/SKILL.md (Claude Code Skill tool requires the directory layout)`;
|
|
240
|
+
}
|
|
228
241
|
checks.push(skillsCheck);
|
|
229
242
|
}
|
|
230
243
|
return checks;
|
|
@@ -261,9 +274,16 @@ async function doctorCommand(opts) {
|
|
|
261
274
|
}
|
|
262
275
|
|
|
263
276
|
// src/commands/init.ts
|
|
264
|
-
import {
|
|
277
|
+
import {
|
|
278
|
+
appendFileSync,
|
|
279
|
+
existsSync as existsSync3,
|
|
280
|
+
mkdirSync as mkdirSync2,
|
|
281
|
+
readdirSync as readdirSync2,
|
|
282
|
+
readFileSync as readFileSync3,
|
|
283
|
+
writeFileSync as writeFileSync2
|
|
284
|
+
} from "fs";
|
|
265
285
|
import { createRequire } from "module";
|
|
266
|
-
import { join as join3 } from "path";
|
|
286
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
267
287
|
import * as p from "@clack/prompts";
|
|
268
288
|
import { generateKey } from "@xera-ai/core";
|
|
269
289
|
import pc2 from "picocolors";
|
|
@@ -293,17 +313,6 @@ function scaffoldFile(targetPath, templateName, vars) {
|
|
|
293
313
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
294
314
|
writeFileSync(targetPath, render(tmpl, vars));
|
|
295
315
|
}
|
|
296
|
-
function copyDir(src, dest) {
|
|
297
|
-
mkdirSync(dest, { recursive: true });
|
|
298
|
-
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
299
|
-
const s = join2(src, entry.name);
|
|
300
|
-
const d = join2(dest, entry.name);
|
|
301
|
-
if (entry.isDirectory())
|
|
302
|
-
copyDir(s, d);
|
|
303
|
-
else
|
|
304
|
-
writeFileSync(d, readFileSync2(s));
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
316
|
|
|
308
317
|
// src/commands/init.ts
|
|
309
318
|
var require2 = createRequire(import.meta.url);
|
|
@@ -446,15 +455,22 @@ async function initCommand(opts) {
|
|
|
446
455
|
writeFileSync2(gitignorePath, `${gitignoreAdditions.trim()}
|
|
447
456
|
`);
|
|
448
457
|
}
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
const skillsPkgPath = require2.resolve("@xera-ai/skills/package.json");
|
|
459
|
+
const skillsSrcDir = join3(skillsPkgPath, "..");
|
|
460
|
+
const SKILL_IGNORE = new Set(["package.json", "version.json", "CHANGELOG.md"]);
|
|
461
|
+
for (const name of readdirSync2(skillsSrcDir)) {
|
|
462
|
+
if (SKILL_IGNORE.has(name))
|
|
463
|
+
continue;
|
|
464
|
+
if (!name.endsWith(".md"))
|
|
465
|
+
continue;
|
|
466
|
+
const content = readFileSync3(join3(skillsSrcDir, name));
|
|
467
|
+
const base = name.replace(/\.md$/, "");
|
|
468
|
+
const skillFile = join3(cwd, ".claude/skills", base, "SKILL.md");
|
|
469
|
+
mkdirSync2(dirname2(skillFile), { recursive: true });
|
|
470
|
+
writeFileSync2(skillFile, content);
|
|
471
|
+
const cmdFile = join3(cwd, ".claude/commands", name);
|
|
472
|
+
mkdirSync2(dirname2(cmdFile), { recursive: true });
|
|
473
|
+
writeFileSync2(cmdFile, content);
|
|
458
474
|
}
|
|
459
475
|
const pkgPath = join3(cwd, "package.json");
|
|
460
476
|
const pkg = existsSync3(pkgPath) ? JSON.parse(readFileSync3(pkgPath, "utf8")) : { name: "xera-project", private: true, type: "module" };
|
|
@@ -496,22 +512,25 @@ async function initCommand(opts) {
|
|
|
496
512
|
writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2));
|
|
497
513
|
const nextSteps = shape === "api" ? `
|
|
498
514
|
Next:
|
|
499
|
-
1)
|
|
500
|
-
|
|
515
|
+
1) Copy .env.example to .env and set your auth credentials:
|
|
516
|
+
cp .env.example .env
|
|
517
|
+
# then edit .env to set USER_BEARER_TOKEN=...
|
|
501
518
|
2) Run pre-authentication:
|
|
502
519
|
bun run xera:auth-setup
|
|
503
520
|
3) Start testing:
|
|
504
521
|
Open Claude Code in this directory and run: /xera-run <TICKET>
|
|
505
522
|
` : shape === "mixed" ? `
|
|
506
523
|
Next:
|
|
507
|
-
1)
|
|
524
|
+
1) Copy .env.example to .env and set credentials (both web logins and API tokens):
|
|
525
|
+
cp .env.example .env
|
|
508
526
|
2) Run pre-authentication:
|
|
509
527
|
bun run xera:auth-setup
|
|
510
528
|
3) Start testing:
|
|
511
529
|
Open Claude Code in this directory and run: /xera-run <TICKET>
|
|
512
530
|
` : `
|
|
513
531
|
Next:
|
|
514
|
-
1)
|
|
532
|
+
1) Copy .env.example to .env and set your Jira credentials:
|
|
533
|
+
cp .env.example .env
|
|
515
534
|
2) Run pre-authentication:
|
|
516
535
|
bun run xera:auth-setup
|
|
517
536
|
3) Start testing:
|
|
@@ -525,18 +544,97 @@ Next:
|
|
|
525
544
|
import {
|
|
526
545
|
copyFileSync,
|
|
527
546
|
existsSync as existsSync4,
|
|
528
|
-
mkdirSync as
|
|
529
|
-
readdirSync as
|
|
547
|
+
mkdirSync as mkdirSync3,
|
|
548
|
+
readdirSync as readdirSync3,
|
|
530
549
|
readFileSync as readFileSync4,
|
|
550
|
+
unlinkSync,
|
|
531
551
|
writeFileSync as writeFileSync3
|
|
532
552
|
} from "fs";
|
|
533
553
|
import { createRequire as createRequire2 } from "module";
|
|
534
|
-
import { dirname as
|
|
554
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
535
555
|
import * as p2 from "@clack/prompts";
|
|
536
556
|
import pc3 from "picocolors";
|
|
537
557
|
var require3 = createRequire2(import.meta.url);
|
|
538
558
|
var CLI_VERSION2 = require3("../package.json").version;
|
|
539
|
-
|
|
559
|
+
function detectAdaptersFromConfig(cwd) {
|
|
560
|
+
const configPath = join4(cwd, "xera.config.ts");
|
|
561
|
+
if (!existsSync4(configPath))
|
|
562
|
+
return null;
|
|
563
|
+
const cfg = readFileSync4(configPath, "utf8");
|
|
564
|
+
const m = cfg.match(/adapters:\s*\[([^\]]+)\]/);
|
|
565
|
+
if (!m)
|
|
566
|
+
return null;
|
|
567
|
+
return (m[1].match(/'(\w+)'/g) ?? []).map((s) => s.slice(1, -1));
|
|
568
|
+
}
|
|
569
|
+
function adaptersForShape(shape) {
|
|
570
|
+
if (shape === "web")
|
|
571
|
+
return ["web"];
|
|
572
|
+
if (shape === "api")
|
|
573
|
+
return ["http"];
|
|
574
|
+
return ["web", "http"];
|
|
575
|
+
}
|
|
576
|
+
function renderHttpConfigSnippet(opts) {
|
|
577
|
+
const baseUrl = opts.apiBaseUrl ?? "https://api.staging.example.com";
|
|
578
|
+
const strategy = opts.authStrategy ?? "bearer";
|
|
579
|
+
const roles = (opts.httpRoles ?? "user").split(",").map((s) => s.trim()).filter(Boolean);
|
|
580
|
+
const rolesBlock = roles.map((r) => ` ${r}: { tokenEnv: '${r.toUpperCase().replace(/-/g, "_")}_BEARER_TOKEN' },`).join(`
|
|
581
|
+
`);
|
|
582
|
+
const specLine = opts.openapiPath ? ` spec: '${opts.openapiPath}',
|
|
583
|
+
` : "";
|
|
584
|
+
return [
|
|
585
|
+
` http: {`,
|
|
586
|
+
` baseUrl: { staging: '${baseUrl}' },`,
|
|
587
|
+
` defaultEnv: 'staging',`,
|
|
588
|
+
`${specLine} auth: {`,
|
|
589
|
+
` strategy: '${strategy}',`,
|
|
590
|
+
` roles: {`,
|
|
591
|
+
rolesBlock,
|
|
592
|
+
` },`,
|
|
593
|
+
` },`,
|
|
594
|
+
` },`
|
|
595
|
+
].join(`
|
|
596
|
+
`);
|
|
597
|
+
}
|
|
598
|
+
function renderWebConfigSnippet(opts) {
|
|
599
|
+
const baseUrl = opts.stagingUrl ?? "https://staging.example.com";
|
|
600
|
+
const roles = (opts.roles ?? "admin,regular").split(",").map((s) => s.trim()).filter(Boolean);
|
|
601
|
+
const authBlock = opts.authEnabled === false ? "" : ` auth: {
|
|
602
|
+
strategy: 'storageState',
|
|
603
|
+
setupScript: './shared/auth-setup.ts',
|
|
604
|
+
roles: {
|
|
605
|
+
${roles.map((r) => ` ${r}: { envEmail: 'TEST_${r.toUpperCase().replace(/-/g, "_")}_EMAIL', envPassword: 'TEST_${r.toUpperCase().replace(/-/g, "_")}_PWD' },`).join(`
|
|
606
|
+
`)}
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
`;
|
|
610
|
+
return [
|
|
611
|
+
` web: {`,
|
|
612
|
+
` baseUrl: { staging: '${baseUrl}' },`,
|
|
613
|
+
` defaultEnv: 'staging',`,
|
|
614
|
+
authBlock + ` },`
|
|
615
|
+
].join(`
|
|
616
|
+
`);
|
|
617
|
+
}
|
|
618
|
+
var HTTP_AUTH_SETUP_SNIPPET = `import { defineHttpAuthSetup, presetHttpAuth } from '@xera-ai/http';
|
|
619
|
+
|
|
620
|
+
export const http = defineHttpAuthSetup(async (request, role, creds) => {
|
|
621
|
+
return presetHttpAuth({
|
|
622
|
+
request,
|
|
623
|
+
role,
|
|
624
|
+
config: (globalThis as Record<string, unknown>).__XERA_HTTP_CONFIG__ as never,
|
|
625
|
+
});
|
|
626
|
+
});`;
|
|
627
|
+
var WEB_AUTH_SETUP_SNIPPET = `import { defineAuthSetup } from '@xera-ai/web';
|
|
628
|
+
|
|
629
|
+
export const web = defineAuthSetup(async (page, _role, creds) => {
|
|
630
|
+
await page.goto('/login');
|
|
631
|
+
await page.getByLabel('Email').fill(creds.email);
|
|
632
|
+
await page.getByLabel('Password').fill(creds.password);
|
|
633
|
+
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
634
|
+
await page.waitForURL(/.*\\/dashboard/);
|
|
635
|
+
return { expiresAt: Date.now() + 8 * 3600 * 1000 };
|
|
636
|
+
});`;
|
|
637
|
+
async function initUpdateCommand(opts) {
|
|
540
638
|
const cwd = process.cwd();
|
|
541
639
|
p2.intro(pc3.cyan("xera init --update"));
|
|
542
640
|
const pkgPath = join4(cwd, "package.json");
|
|
@@ -566,7 +664,7 @@ async function initUpdateCommand(_opts) {
|
|
|
566
664
|
pkg.scripts["xera:disputes"] = "xera-internal disputes";
|
|
567
665
|
writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2));
|
|
568
666
|
const wfDir = join4(cwd, ".github/workflows");
|
|
569
|
-
|
|
667
|
+
mkdirSync3(wfDir, { recursive: true });
|
|
570
668
|
try {
|
|
571
669
|
const cliPkgPath = require3.resolve("@xera-ai/cli/package.json");
|
|
572
670
|
const cliTplPath = join4(cliPkgPath, "..", "templates/xera-graph.yml.template");
|
|
@@ -577,28 +675,47 @@ async function initUpdateCommand(_opts) {
|
|
|
577
675
|
}
|
|
578
676
|
const skillsSrc = require3.resolve("@xera-ai/skills/package.json");
|
|
579
677
|
const newSkillsDir = join4(skillsSrc, "..");
|
|
580
|
-
const
|
|
581
|
-
for (const name of
|
|
678
|
+
const SKILL_IGNORE = new Set(["package.json", "version.json", "CHANGELOG.md"]);
|
|
679
|
+
for (const name of readdirSync3(newSkillsDir)) {
|
|
680
|
+
if (SKILL_IGNORE.has(name))
|
|
681
|
+
continue;
|
|
582
682
|
if (!name.endsWith(".md"))
|
|
583
683
|
continue;
|
|
584
684
|
const newContent = readFileSync4(join4(newSkillsDir, name), "utf8");
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
685
|
+
const base = name.replace(/\.md$/, "");
|
|
686
|
+
const skillPath = join4(cwd, ".claude/skills", base, "SKILL.md");
|
|
687
|
+
const legacyFlatSkillPath = join4(cwd, ".claude/skills", name);
|
|
688
|
+
const cmdPath = join4(cwd, ".claude/commands", name);
|
|
689
|
+
let migratedLegacy = false;
|
|
690
|
+
if (existsSync4(legacyFlatSkillPath) && !existsSync4(skillPath)) {
|
|
691
|
+
const legacyContent = readFileSync4(legacyFlatSkillPath, "utf8");
|
|
692
|
+
mkdirSync3(dirname3(skillPath), { recursive: true });
|
|
693
|
+
writeFileSync3(skillPath, legacyContent);
|
|
694
|
+
unlinkSync(legacyFlatSkillPath);
|
|
695
|
+
migratedLegacy = true;
|
|
696
|
+
}
|
|
697
|
+
const targets = [];
|
|
698
|
+
for (const path of [skillPath, cmdPath]) {
|
|
699
|
+
if (!existsSync4(path)) {
|
|
700
|
+
targets.push({ path, state: "missing" });
|
|
701
|
+
} else {
|
|
702
|
+
const content = readFileSync4(path, "utf8");
|
|
703
|
+
targets.push({ path, state: content === newContent ? "same" : "diff" });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (targets.every((s) => s.state === "missing")) {
|
|
707
|
+
for (const { path } of targets) {
|
|
708
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
595
709
|
writeFileSync3(path, newContent);
|
|
596
710
|
}
|
|
597
711
|
p2.log.info(`+ ${name}`);
|
|
598
712
|
continue;
|
|
599
713
|
}
|
|
600
|
-
if (
|
|
601
|
-
|
|
714
|
+
if (targets.every((s) => s.state === "same")) {
|
|
715
|
+
if (migratedLegacy)
|
|
716
|
+
p2.log.success(`migrated ${name} to .claude/skills/${base}/SKILL.md`);
|
|
717
|
+
else
|
|
718
|
+
p2.log.info(`= ${name}`);
|
|
602
719
|
continue;
|
|
603
720
|
}
|
|
604
721
|
const choice = await p2.select({
|
|
@@ -609,15 +726,62 @@ async function initUpdateCommand(_opts) {
|
|
|
609
726
|
]
|
|
610
727
|
});
|
|
611
728
|
if (choice === "overwrite") {
|
|
612
|
-
for (const { path } of
|
|
613
|
-
|
|
729
|
+
for (const { path } of targets) {
|
|
730
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
614
731
|
writeFileSync3(path, newContent);
|
|
615
732
|
}
|
|
616
733
|
p2.log.success(`overwrote ${name}`);
|
|
617
734
|
} else {
|
|
735
|
+
if (migratedLegacy)
|
|
736
|
+
p2.log.success(`migrated ${name} to .claude/skills/${base}/SKILL.md`);
|
|
618
737
|
p2.log.warn(`kept local ${name}`);
|
|
619
738
|
}
|
|
620
739
|
}
|
|
740
|
+
const hasShapeFlags = opts.apiBaseUrl !== undefined || opts.openapiPath !== undefined || opts.authStrategy !== undefined || opts.httpRoles !== undefined || opts.stagingUrl !== undefined || opts.authEnabled !== undefined || opts.roles !== undefined;
|
|
741
|
+
if (opts.shape !== undefined) {
|
|
742
|
+
const current = detectAdaptersFromConfig(cwd);
|
|
743
|
+
if (current === null) {
|
|
744
|
+
p2.log.warn("--shape was provided but could not detect existing adapters in xera.config.ts. Skipping shape check.");
|
|
745
|
+
} else {
|
|
746
|
+
const requested = adaptersForShape(opts.shape);
|
|
747
|
+
const missing = requested.filter((a) => !current.includes(a));
|
|
748
|
+
const extra = current.filter((a) => !requested.includes(a));
|
|
749
|
+
if (missing.length === 0 && extra.length === 0) {
|
|
750
|
+
p2.log.info(`shape '${opts.shape}' already matches existing adapters [${current.join(", ")}]`);
|
|
751
|
+
} else {
|
|
752
|
+
if (missing.length > 0) {
|
|
753
|
+
const lines = [
|
|
754
|
+
`--shape ${opts.shape} requested but xera.config.ts has adapters: [${current.join(", ")}]`,
|
|
755
|
+
`Missing adapter(s): ${missing.join(", ")}`,
|
|
756
|
+
``,
|
|
757
|
+
`init --update is non-destructive \u2014 it will NOT modify xera.config.ts or shared/auth-setup.ts.`,
|
|
758
|
+
`To complete the upgrade, hand-edit both files:`,
|
|
759
|
+
``,
|
|
760
|
+
`1. In xera.config.ts, change \`adapters: [${current.map((a) => `'${a}'`).join(", ")}]\` to \`adapters: [${requested.map((a) => `'${a}'`).join(", ")}]\` and add this block inside defineConfig({...}):`,
|
|
761
|
+
``
|
|
762
|
+
];
|
|
763
|
+
for (const a of missing) {
|
|
764
|
+
lines.push(a === "http" ? renderHttpConfigSnippet(opts) : renderWebConfigSnippet(opts));
|
|
765
|
+
lines.push("");
|
|
766
|
+
}
|
|
767
|
+
lines.push(`2. In shared/auth-setup.ts, add this export:`);
|
|
768
|
+
lines.push("");
|
|
769
|
+
for (const a of missing) {
|
|
770
|
+
lines.push(a === "http" ? HTTP_AUTH_SETUP_SNIPPET : WEB_AUTH_SETUP_SNIPPET);
|
|
771
|
+
lines.push("");
|
|
772
|
+
}
|
|
773
|
+
lines.push(`3. Add ${missing.includes("http") ? `\`@xera-ai/http\`` : ""}${missing.includes("http") && missing.includes("web") ? " and " : ""}${missing.includes("web") ? `\`@xera-ai/web\`` : ""} to dependencies (re-run \`xera init --update\` after editing to bump versions), then run \`xera doctor\` to verify.`);
|
|
774
|
+
p2.log.warn(lines.join(`
|
|
775
|
+
`));
|
|
776
|
+
}
|
|
777
|
+
if (extra.length > 0) {
|
|
778
|
+
p2.log.warn(`--shape ${opts.shape} would remove adapter(s) [${extra.join(", ")}] from xera.config.ts, but init --update never removes config. Remove the block(s) by hand if intended.`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
} else if (hasShapeFlags) {
|
|
783
|
+
p2.log.warn("Shape-related flags (--au/--as/--hr/--su/--ro/--op/--auth-enabled) are ignored by init --update without --shape. To change shape, pass --shape <web|api|mixed> alongside.");
|
|
784
|
+
}
|
|
621
785
|
p2.outro(pc3.green("Update complete. Run `xera doctor` to verify."));
|
|
622
786
|
}
|
|
623
787
|
|
|
@@ -670,7 +834,38 @@ async function main() {
|
|
|
670
834
|
cli.usage("<command> [options]");
|
|
671
835
|
cli.command("init", "Scaffold a new xera project in the current directory").option("--update", "Non-destructive refresh of an existing project").option("-y, --yes", "Accept all defaults (non-interactive)").option("--shape <shape>", "Project shape: web | api | mixed").option("--ju, --jira-base-url <url>", "Jira workspace URL").option("--pk, --project-keys <keys>", "Jira project key(s), comma-separated").option("--sf, --story-field <field>", "Jira field id for user story (default: description)").option("--ac, --ac-field <field>", "Jira field id for acceptance criteria").option("--su, --staging-url <url>", "Web app staging URL").option("--auth-enabled", "App requires login to test most pages").option("--no-auth-enabled", "App does not require login").option("--ro, --roles <roles>", "Test user roles, comma-separated (default: admin,regular)").option("--au, --api-base-url <url>", "API base URL").option("--op, --openapi-path <path>", "OpenAPI spec path or URL").option("--as, --auth-strategy <strategy>", `API auth strategy: ${VALID_AUTH_STRATEGIES.join(" | ")}`).option("--hr, --http-roles <roles>", "HTTP test roles, comma-separated (default: user)").example("xera init").example("xera init -y --shape web").example("xera init -y --shape api --pk MYPROJ --ju https://myco.atlassian.net --au https://api.staging.example.com --as bearer").example("xera init -y --shape mixed --pk PROJ --ju https://myco.atlassian.net --su https://staging.example.com --au https://api.staging.example.com").action(async (opts) => {
|
|
672
836
|
if (opts.update) {
|
|
673
|
-
|
|
837
|
+
const updateOpts = { yes: !!opts.yes };
|
|
838
|
+
if (opts.shape !== undefined) {
|
|
839
|
+
if (!VALID_SHAPES.includes(opts.shape)) {
|
|
840
|
+
console.error(pc4.red(`
|
|
841
|
+
error: --shape must be one of: ${VALID_SHAPES.join(", ")}
|
|
842
|
+
`));
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
updateOpts.shape = opts.shape;
|
|
846
|
+
}
|
|
847
|
+
if (opts.authStrategy !== undefined) {
|
|
848
|
+
if (!VALID_AUTH_STRATEGIES.includes(opts.authStrategy)) {
|
|
849
|
+
console.error(pc4.red(`
|
|
850
|
+
error: --auth-strategy must be one of: ${VALID_AUTH_STRATEGIES.join(", ")}
|
|
851
|
+
`));
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
updateOpts.authStrategy = opts.authStrategy;
|
|
855
|
+
}
|
|
856
|
+
if (opts.apiBaseUrl !== undefined)
|
|
857
|
+
updateOpts.apiBaseUrl = opts.apiBaseUrl;
|
|
858
|
+
if (opts.openapiPath !== undefined)
|
|
859
|
+
updateOpts.openapiPath = opts.openapiPath;
|
|
860
|
+
if (opts.httpRoles !== undefined)
|
|
861
|
+
updateOpts.httpRoles = opts.httpRoles;
|
|
862
|
+
if (opts.stagingUrl !== undefined)
|
|
863
|
+
updateOpts.stagingUrl = opts.stagingUrl;
|
|
864
|
+
if (opts.authEnabled !== undefined)
|
|
865
|
+
updateOpts.authEnabled = opts.authEnabled;
|
|
866
|
+
if (opts.roles !== undefined)
|
|
867
|
+
updateOpts.roles = opts.roles;
|
|
868
|
+
await initUpdateCommand(updateOpts);
|
|
674
869
|
return;
|
|
675
870
|
}
|
|
676
871
|
const initOpts = { yes: !!opts.yes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xera-ai/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xera": "./bin/xera"
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@xera-ai/core": "^0.12.
|
|
19
|
-
"@xera-ai/skills": "^0.12.
|
|
18
|
+
"@xera-ai/core": "^0.12.2",
|
|
19
|
+
"@xera-ai/skills": "^0.12.2",
|
|
20
20
|
"@clack/prompts": "1.4.0",
|
|
21
21
|
"cac": "7.0.0",
|
|
22
22
|
"picocolors": "1.1.1"
|
|
@@ -11,8 +11,8 @@ export default defineConfig({
|
|
|
11
11
|
},
|
|
12
12
|
},
|
|
13
13
|
http: {
|
|
14
|
-
baseUrl: {
|
|
15
|
-
defaultEnv: '
|
|
14
|
+
baseUrl: { staging: '{{apiBaseUrl}}' },
|
|
15
|
+
defaultEnv: 'staging',
|
|
16
16
|
{{#if openapiPath}}spec: '{{openapiPath}}',{{/if}}
|
|
17
17
|
auth: {
|
|
18
18
|
strategy: '{{authStrategy}}',
|
|
@@ -11,8 +11,8 @@ export default defineConfig({
|
|
|
11
11
|
},
|
|
12
12
|
},
|
|
13
13
|
web: {
|
|
14
|
-
baseUrl: {
|
|
15
|
-
defaultEnv: '
|
|
14
|
+
baseUrl: { staging: '{{stagingUrl}}' },
|
|
15
|
+
defaultEnv: 'staging',
|
|
16
16
|
{{#if authEnabled}}auth: {
|
|
17
17
|
strategy: 'storageState',
|
|
18
18
|
setupScript: './shared/auth-setup.ts',
|
|
@@ -23,8 +23,8 @@ export default defineConfig({
|
|
|
23
23
|
},{{/if}}
|
|
24
24
|
},
|
|
25
25
|
http: {
|
|
26
|
-
baseUrl: {
|
|
27
|
-
defaultEnv: '
|
|
26
|
+
baseUrl: { staging: '{{apiBaseUrl}}' },
|
|
27
|
+
defaultEnv: 'staging',
|
|
28
28
|
{{#if openapiPath}}spec: '{{openapiPath}}',{{/if}}
|
|
29
29
|
auth: {
|
|
30
30
|
strategy: '{{authStrategy}}',
|