@apicircle/cli 1.0.3 → 1.0.5
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 +4 -0
- package/dist/bin/cli.cjs +1560 -0
- package/dist/bin/cli.cjs.map +1 -0
- package/dist/bin/cli.d.cts +3 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.js +55 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/chunk-B3FBQLFQ.js +84 -0
- package/dist/chunk-B3FBQLFQ.js.map +1 -0
- package/dist/index.cjs +395 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +318 -12
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CLI_PACKAGE_VERSION
|
|
4
|
+
} from "./chunk-B3FBQLFQ.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
7
|
import { Command } from "commander";
|
|
@@ -114,7 +117,7 @@ async function ensureWorkspace(dir) {
|
|
|
114
117
|
linkedWorkspaces: {},
|
|
115
118
|
linkedOverrides: { requests: {}, environmentVars: {} },
|
|
116
119
|
releases: { self: null, perLink: {} },
|
|
117
|
-
globalAssets: { schemas: {}, graphql: {} },
|
|
120
|
+
globalAssets: { schemas: {}, graphql: {}, files: {} },
|
|
118
121
|
mockServers: {},
|
|
119
122
|
meta: { createdAt: now, updatedAt: now, appVersion: "1.0.0" }
|
|
120
123
|
},
|
|
@@ -131,13 +134,14 @@ async function ensureWorkspace(dir) {
|
|
|
131
134
|
retiredBranch: null,
|
|
132
135
|
sync: { lastPulledSnapshot: null, lastPulledSha: null, lastPulledAt: null, dirtyKeys: [] },
|
|
133
136
|
linkedCollections: {},
|
|
137
|
+
attachmentCache: {},
|
|
134
138
|
globalContext: {},
|
|
135
139
|
mockRuntime: { active: {} },
|
|
136
140
|
ui: {
|
|
137
141
|
activeRequestId: null,
|
|
138
142
|
sidebarExpandedSections: [],
|
|
139
|
-
themeId: "
|
|
140
|
-
fontId: "
|
|
143
|
+
themeId: "command-center",
|
|
144
|
+
fontId: "cascadia-code",
|
|
141
145
|
fontSizePercent: FONT_SIZE_PERCENT_DEFAULT
|
|
142
146
|
},
|
|
143
147
|
settings: { validateOnSend: true, monacoConsumesWheel: false },
|
|
@@ -341,7 +345,7 @@ function buildEmptyState(workspaceId, now, withSample) {
|
|
|
341
345
|
linkedWorkspaces: {},
|
|
342
346
|
linkedOverrides: { requests: {}, environmentVars: {} },
|
|
343
347
|
releases: { self: null, perLink: {} },
|
|
344
|
-
globalAssets: { schemas: {}, graphql: {} },
|
|
348
|
+
globalAssets: { schemas: {}, graphql: {}, files: {} },
|
|
345
349
|
mockServers: {},
|
|
346
350
|
meta: { createdAt: now, updatedAt: now, appVersion: "1.0.0" }
|
|
347
351
|
},
|
|
@@ -358,13 +362,14 @@ function buildEmptyState(workspaceId, now, withSample) {
|
|
|
358
362
|
retiredBranch: null,
|
|
359
363
|
sync: { lastPulledSnapshot: null, lastPulledSha: null, lastPulledAt: null, dirtyKeys: [] },
|
|
360
364
|
linkedCollections: {},
|
|
365
|
+
attachmentCache: {},
|
|
361
366
|
globalContext: {},
|
|
362
367
|
mockRuntime: { active: {} },
|
|
363
368
|
ui: {
|
|
364
369
|
activeRequestId: sample?.id ?? null,
|
|
365
370
|
sidebarExpandedSections: [],
|
|
366
|
-
themeId: "
|
|
367
|
-
fontId: "
|
|
371
|
+
themeId: "command-center",
|
|
372
|
+
fontId: "cascadia-code",
|
|
368
373
|
fontSizePercent: 100
|
|
369
374
|
},
|
|
370
375
|
settings: { validateOnSend: true, monacoConsumesWheel: false },
|
|
@@ -535,13 +540,13 @@ function registerImportCommand(program) {
|
|
|
535
540
|
}
|
|
536
541
|
async function readInput(p) {
|
|
537
542
|
if (p === "-") {
|
|
538
|
-
return new Promise((
|
|
543
|
+
return new Promise((resolve7, reject) => {
|
|
539
544
|
let data = "";
|
|
540
545
|
process.stdin.setEncoding("utf-8");
|
|
541
546
|
process.stdin.on("data", (chunk) => {
|
|
542
547
|
data += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
543
548
|
});
|
|
544
|
-
process.stdin.on("end", () =>
|
|
549
|
+
process.stdin.on("end", () => resolve7(data));
|
|
545
550
|
process.stdin.on("error", reject);
|
|
546
551
|
});
|
|
547
552
|
}
|
|
@@ -608,6 +613,274 @@ async function buildSecretsFromCli(options = {}) {
|
|
|
608
613
|
return { byId };
|
|
609
614
|
}
|
|
610
615
|
|
|
616
|
+
// src/util/executionAttachments.ts
|
|
617
|
+
import { createHash } from "crypto";
|
|
618
|
+
import { promises as fs6 } from "fs";
|
|
619
|
+
import * as path6 from "path";
|
|
620
|
+
import {
|
|
621
|
+
collectAttachmentSlots
|
|
622
|
+
} from "@apicircle/core";
|
|
623
|
+
var ATTACHMENTS_DIR = path6.join(".apicircle", "attachments");
|
|
624
|
+
async function prepareExecutionAttachments(workspaceDir, state, plan) {
|
|
625
|
+
const cacheDir = path6.resolve(workspaceDir, ATTACHMENTS_DIR);
|
|
626
|
+
const requirements = collectExecutionAttachmentRequirements(state, plan);
|
|
627
|
+
await fs6.mkdir(cacheDir, { recursive: true });
|
|
628
|
+
let downloaded = 0;
|
|
629
|
+
let alreadyPresent = 0;
|
|
630
|
+
let failed = 0;
|
|
631
|
+
const cache = {
|
|
632
|
+
...state.local.attachmentCache ?? {}
|
|
633
|
+
};
|
|
634
|
+
const entries = [];
|
|
635
|
+
for (const requirement of requirements) {
|
|
636
|
+
const localPath = path6.join(cacheDir, encodeURIComponent(requirement.slotId));
|
|
637
|
+
const present = await hasExpectedFile(localPath, requirement.sha256);
|
|
638
|
+
if (present) {
|
|
639
|
+
alreadyPresent++;
|
|
640
|
+
} else {
|
|
641
|
+
try {
|
|
642
|
+
const bytes = await downloadAttachment(requirement);
|
|
643
|
+
if (!bytes) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Attachment ${attachmentLabel(requirement)} was not found in ${sourceLabel(requirement)}.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
if (requirement.sha256 && sha256Hex(bytes) !== requirement.sha256) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Attachment ${attachmentLabel(requirement)} failed checksum verification.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
await fs6.writeFile(localPath, bytes, { mode: 384 });
|
|
654
|
+
downloaded++;
|
|
655
|
+
} catch (err) {
|
|
656
|
+
failed++;
|
|
657
|
+
throw new Error(
|
|
658
|
+
`Attachment ${attachmentLabel(requirement)} is required by ${requiredByLabel(
|
|
659
|
+
requirement
|
|
660
|
+
)} but could not be downloaded from ${sourceLabel(requirement)}: ${err instanceof Error ? err.message : String(err)}`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
cache[requirement.slotId] = {
|
|
665
|
+
slotId: requirement.slotId,
|
|
666
|
+
filename: requirement.filename ?? requirement.slotId,
|
|
667
|
+
mimeType: requirement.mimeType ?? "application/octet-stream",
|
|
668
|
+
size: requirement.size ?? await fileSize(localPath),
|
|
669
|
+
sha256: requirement.sha256,
|
|
670
|
+
localPath,
|
|
671
|
+
storage: "filesystem",
|
|
672
|
+
source: requirement.source,
|
|
673
|
+
...requirement.linkedWorkspaceId ? { linkedWorkspaceId: requirement.linkedWorkspaceId } : {},
|
|
674
|
+
requiredBy: requirement.requiredBy,
|
|
675
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
676
|
+
};
|
|
677
|
+
entries.push({
|
|
678
|
+
slotId: requirement.slotId,
|
|
679
|
+
filename: requirement.filename ?? requirement.slotId,
|
|
680
|
+
localPath,
|
|
681
|
+
source: requirement.source,
|
|
682
|
+
...requirement.linkedWorkspaceId ? { linkedWorkspaceId: requirement.linkedWorkspaceId } : {},
|
|
683
|
+
requiredBy: requirement.requiredBy
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
const nextState = {
|
|
687
|
+
...state,
|
|
688
|
+
local: {
|
|
689
|
+
...state.local,
|
|
690
|
+
attachmentCache: cache
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
return {
|
|
694
|
+
state: nextState,
|
|
695
|
+
resolveAttachment: createFileAttachmentResolver(nextState),
|
|
696
|
+
summary: {
|
|
697
|
+
total: requirements.length,
|
|
698
|
+
downloaded,
|
|
699
|
+
alreadyPresent,
|
|
700
|
+
failed,
|
|
701
|
+
cacheDir,
|
|
702
|
+
entries
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function createFileAttachmentResolver(state) {
|
|
707
|
+
return async (slotId) => {
|
|
708
|
+
const meta = state.local.attachmentCache?.[slotId];
|
|
709
|
+
if (!meta) return null;
|
|
710
|
+
const bytes = await fs6.readFile(meta.localPath);
|
|
711
|
+
const view = new Uint8Array(bytes);
|
|
712
|
+
const body = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
713
|
+
return {
|
|
714
|
+
blob: new Blob([body], { type: meta.mimeType }),
|
|
715
|
+
filename: meta.filename
|
|
716
|
+
};
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function collectExecutionAttachmentRequirements(state, plan) {
|
|
720
|
+
const seen = /* @__PURE__ */ new Map();
|
|
721
|
+
const localRequestFilter = requestFilterForPlan(plan, null);
|
|
722
|
+
const localCollections = localRequestFilter ? {
|
|
723
|
+
...state.synced.collections,
|
|
724
|
+
requests: Object.fromEntries(
|
|
725
|
+
Object.entries(state.synced.collections.requests).filter(
|
|
726
|
+
([id]) => localRequestFilter.has(id)
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
} : state.synced.collections;
|
|
730
|
+
const workspaceSlots = collectAttachmentSlots({ ...state.synced, collections: localCollections });
|
|
731
|
+
for (const slot of workspaceSlots) {
|
|
732
|
+
const requiredBy = collectRequiredBy(localCollections.requests, slot.slotId);
|
|
733
|
+
if (requiredBy.length === 0) continue;
|
|
734
|
+
addRequirement(seen, {
|
|
735
|
+
...slot,
|
|
736
|
+
source: "workspace",
|
|
737
|
+
repoFullName: state.local.connectedRepo?.fullName ?? void 0,
|
|
738
|
+
branch: state.local.workingBranch?.name ?? void 0,
|
|
739
|
+
publicRepo: state.local.connectedRepo ? !state.local.connectedRepo.isPrivate : false,
|
|
740
|
+
requiredBy
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
for (const [linkedWorkspaceId, snapshot] of Object.entries(state.local.linkedCollections)) {
|
|
744
|
+
const link = state.synced.linkedWorkspaces[linkedWorkspaceId];
|
|
745
|
+
if (!link) continue;
|
|
746
|
+
const linkedRequestFilter = requestFilterForPlan(plan, linkedWorkspaceId);
|
|
747
|
+
if (plan && linkedRequestFilter && linkedRequestFilter.size === 0) continue;
|
|
748
|
+
const linkedCollections = linkedRequestFilter ? {
|
|
749
|
+
...snapshot.collections,
|
|
750
|
+
requests: Object.fromEntries(
|
|
751
|
+
Object.entries(snapshot.collections.requests).filter(
|
|
752
|
+
([id]) => linkedRequestFilter.has(id)
|
|
753
|
+
)
|
|
754
|
+
)
|
|
755
|
+
} : snapshot.collections;
|
|
756
|
+
const linkedSynced = {
|
|
757
|
+
...state.synced,
|
|
758
|
+
collections: linkedCollections,
|
|
759
|
+
environments: snapshot.environments,
|
|
760
|
+
globalAssets: snapshot.globalAssets ?? state.synced.globalAssets
|
|
761
|
+
};
|
|
762
|
+
for (const slot of collectAttachmentSlots(linkedSynced)) {
|
|
763
|
+
const requiredBy = collectRequiredBy(linkedCollections.requests, slot.slotId);
|
|
764
|
+
if (requiredBy.length === 0) continue;
|
|
765
|
+
addRequirement(seen, {
|
|
766
|
+
...slot,
|
|
767
|
+
source: "linked-workspace",
|
|
768
|
+
linkedWorkspaceId,
|
|
769
|
+
repoFullName: link.source.repoFullName,
|
|
770
|
+
branch: link.source.branch,
|
|
771
|
+
publicRepo: link.kind === "public",
|
|
772
|
+
requiredBy
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return [...seen.values()];
|
|
777
|
+
}
|
|
778
|
+
function requestFilterForPlan(plan, linkedWorkspaceId) {
|
|
779
|
+
if (!plan) return null;
|
|
780
|
+
const ids = /* @__PURE__ */ new Set();
|
|
781
|
+
for (const step of plan.steps) {
|
|
782
|
+
if (step.enabled === false) continue;
|
|
783
|
+
if ((step.linkedWorkspaceId ?? null) === linkedWorkspaceId) ids.add(step.requestId);
|
|
784
|
+
}
|
|
785
|
+
return ids;
|
|
786
|
+
}
|
|
787
|
+
function addRequirement(seen, requirement) {
|
|
788
|
+
const existing = seen.get(requirement.slotId);
|
|
789
|
+
if (!existing) {
|
|
790
|
+
seen.set(requirement.slotId, requirement);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
for (const usage of requirement.requiredBy) {
|
|
794
|
+
if (!existing.requiredBy.some((item) => item.requestId === usage.requestId)) {
|
|
795
|
+
existing.requiredBy.push(usage);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function collectRequiredBy(requests, slotId) {
|
|
800
|
+
const requiredBy = [];
|
|
801
|
+
for (const request of Object.values(requests)) {
|
|
802
|
+
if (bodyReferencesSlot(request.body, slotId)) {
|
|
803
|
+
requiredBy.push({ requestId: request.id, requestName: request.name });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return requiredBy;
|
|
807
|
+
}
|
|
808
|
+
function bodyReferencesSlot(body, slotId) {
|
|
809
|
+
if (body.type === "binary") return body.attachment?.slotId === slotId;
|
|
810
|
+
if (body.type !== "form-data") return false;
|
|
811
|
+
return (body.formRows ?? []).some((row) => row.kind === "file" && row.slotId === slotId);
|
|
812
|
+
}
|
|
813
|
+
async function hasExpectedFile(localPath, sha256) {
|
|
814
|
+
try {
|
|
815
|
+
const bytes = await fs6.readFile(localPath);
|
|
816
|
+
if (!sha256) return true;
|
|
817
|
+
return sha256Hex(bytes) === sha256;
|
|
818
|
+
} catch {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async function fileSize(localPath) {
|
|
823
|
+
try {
|
|
824
|
+
return (await fs6.stat(localPath)).size;
|
|
825
|
+
} catch {
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async function downloadAttachment(requirement) {
|
|
830
|
+
if (!requirement.repoFullName || !requirement.branch) return null;
|
|
831
|
+
const [owner, repo] = requirement.repoFullName.split("/", 2);
|
|
832
|
+
if (!owner || !repo) return null;
|
|
833
|
+
const token = resolveGitHubToken(requirement);
|
|
834
|
+
if (!token && !requirement.publicRepo) {
|
|
835
|
+
throw new Error(
|
|
836
|
+
"private linked attachments need a GitHub token (set APICIRCLE_GITHUB_TOKEN or GITHUB_TOKEN)"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const apiPath = [".apicircle", "attachments", requirement.slotId].map(encodeURIComponent).join("/");
|
|
840
|
+
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
841
|
+
repo
|
|
842
|
+
)}/contents/${apiPath}?ref=${encodeURIComponent(requirement.branch)}`;
|
|
843
|
+
const headers = {
|
|
844
|
+
Accept: "application/vnd.github+json",
|
|
845
|
+
"User-Agent": "apicircle-cli",
|
|
846
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
847
|
+
};
|
|
848
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
849
|
+
const res = await fetch(url, { headers, cache: "no-store" });
|
|
850
|
+
if (res.status === 404) return null;
|
|
851
|
+
if (!res.ok) {
|
|
852
|
+
throw new Error(`GitHub returned ${res.status}: ${await res.text()}`);
|
|
853
|
+
}
|
|
854
|
+
const json = await res.json();
|
|
855
|
+
if (json.type !== "file" || typeof json.content !== "string") {
|
|
856
|
+
throw new Error("GitHub response was not a file");
|
|
857
|
+
}
|
|
858
|
+
if (json.encoding !== "base64") {
|
|
859
|
+
throw new Error(`GitHub response used unsupported encoding ${json.encoding ?? "(missing)"}`);
|
|
860
|
+
}
|
|
861
|
+
return new Uint8Array(Buffer.from(json.content.replace(/\n/g, ""), "base64"));
|
|
862
|
+
}
|
|
863
|
+
function resolveGitHubToken(requirement) {
|
|
864
|
+
if (requirement.source === "linked-workspace") {
|
|
865
|
+
return process.env.APICIRCLE_E2E_BOT_PAT_LINK_DEDICATED ?? process.env.APICIRCLE_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.APICIRCLE_E2E_GITHUB_PAT ?? process.env.APICIRCLE_E2E_BOT_PAT ?? "";
|
|
866
|
+
}
|
|
867
|
+
return process.env.APICIRCLE_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.APICIRCLE_E2E_GITHUB_PAT ?? process.env.APICIRCLE_E2E_BOT_PAT ?? "";
|
|
868
|
+
}
|
|
869
|
+
function sha256Hex(bytes) {
|
|
870
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
871
|
+
}
|
|
872
|
+
function attachmentLabel(requirement) {
|
|
873
|
+
return `${requirement.filename ?? requirement.slotId} (${requirement.slotId})`;
|
|
874
|
+
}
|
|
875
|
+
function sourceLabel(requirement) {
|
|
876
|
+
const repo = requirement.repoFullName ?? "local workspace";
|
|
877
|
+
const branch = requirement.branch ? `@${requirement.branch}` : "";
|
|
878
|
+
return `${repo}${branch}`;
|
|
879
|
+
}
|
|
880
|
+
function requiredByLabel(requirement) {
|
|
881
|
+
return requirement.requiredBy.map((item) => item.requestName).join(", ") || "a request";
|
|
882
|
+
}
|
|
883
|
+
|
|
611
884
|
// src/commands/run.ts
|
|
612
885
|
var REPORTERS = ["text", "json", "junit"];
|
|
613
886
|
function registerRunCommand(program) {
|
|
@@ -681,15 +954,27 @@ function registerRunCommand(program) {
|
|
|
681
954
|
const onSigint = () => controller.abort(new Error("aborted by SIGINT"));
|
|
682
955
|
process.on("SIGINT", onSigint);
|
|
683
956
|
if (text) process.stdout.write(formatHeader(ref.plan, actor, withAssertions, opts));
|
|
957
|
+
let prepared;
|
|
958
|
+
try {
|
|
959
|
+
prepared = await prepareExecutionAttachments(dir, state, ref.plan);
|
|
960
|
+
} catch (err) {
|
|
961
|
+
process.off("SIGINT", onSigint);
|
|
962
|
+
fail(err instanceof Error ? err.message : String(err), 1, "attachment");
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (text && prepared.summary.total > 0) {
|
|
966
|
+
process.stdout.write(formatAttachmentPreparation(prepared.summary));
|
|
967
|
+
}
|
|
684
968
|
let result;
|
|
685
969
|
try {
|
|
686
|
-
result = await runPlan(state, ref.id, {
|
|
970
|
+
result = await runPlan(prepared.state, ref.id, {
|
|
687
971
|
withAssertions,
|
|
688
972
|
bail: opts.bail === true,
|
|
689
973
|
env: opts.env,
|
|
690
974
|
secretsById,
|
|
691
975
|
actor,
|
|
692
976
|
signal: controller.signal,
|
|
977
|
+
resolveAttachment: prepared.resolveAttachment,
|
|
693
978
|
authorize: checkRunPermission,
|
|
694
979
|
onStep: text ? (step) => process.stdout.write(formatStepLine(step)) : void 0
|
|
695
980
|
});
|
|
@@ -708,7 +993,7 @@ function registerRunCommand(program) {
|
|
|
708
993
|
if (reporter === "json") {
|
|
709
994
|
process.stdout.write(
|
|
710
995
|
JSON.stringify(
|
|
711
|
-
buildJsonReport(dir, ref.id, ref.plan, actor, result, saved, aborted),
|
|
996
|
+
buildJsonReport(dir, ref.id, ref.plan, actor, result, saved, aborted, prepared.summary),
|
|
712
997
|
null,
|
|
713
998
|
2
|
|
714
999
|
) + "\n"
|
|
@@ -750,6 +1035,26 @@ function formatHeader(plan, actor, withAssertions, opts) {
|
|
|
750
1035
|
)}
|
|
751
1036
|
${kleur4.dim("Run by")} ${actor.name} ${kleur4.dim(`(${actor.kind})`)}
|
|
752
1037
|
|
|
1038
|
+
`;
|
|
1039
|
+
}
|
|
1040
|
+
function formatAttachmentPreparation(summary) {
|
|
1041
|
+
const status = `${summary.downloaded} downloaded, ${summary.alreadyPresent} already local`;
|
|
1042
|
+
const lines = [
|
|
1043
|
+
`${kleur4.bold("Attachments")} ${summary.total} required ${kleur4.dim(
|
|
1044
|
+
`(${status} - ${summary.cacheDir})`
|
|
1045
|
+
)}`
|
|
1046
|
+
];
|
|
1047
|
+
for (const entry2 of summary.entries) {
|
|
1048
|
+
const source = entry2.source === "linked-workspace" ? `linked:${entry2.linkedWorkspaceId ?? "unknown"}` : "workspace";
|
|
1049
|
+
const requiredBy = entry2.requiredBy.map((item) => item.requestName).join(", ");
|
|
1050
|
+
lines.push(
|
|
1051
|
+
` ${kleur4.dim("file")} ${entry2.filename} ${kleur4.dim(
|
|
1052
|
+
`${source} - ${requiredBy} - ${entry2.localPath}`
|
|
1053
|
+
)}`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
return `${lines.join("\n")}
|
|
1057
|
+
|
|
753
1058
|
`;
|
|
754
1059
|
}
|
|
755
1060
|
function formatStepLine(step) {
|
|
@@ -819,7 +1124,7 @@ ${verdict} ${parts.join(kleur4.dim(" \xB7 "))} ${kleur4.dim(
|
|
|
819
1124
|
out += saved ? kleur4.dim("Plan run saved to workspace history.\n") : kleur4.dim("Plan run not saved (--no-save).\n");
|
|
820
1125
|
return out;
|
|
821
1126
|
}
|
|
822
|
-
function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted) {
|
|
1127
|
+
function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted, attachments) {
|
|
823
1128
|
return {
|
|
824
1129
|
workspace,
|
|
825
1130
|
plan: { id: planId, name: plan.name },
|
|
@@ -829,6 +1134,7 @@ function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted)
|
|
|
829
1134
|
aborted,
|
|
830
1135
|
durationMs: result.planRun.durationMs,
|
|
831
1136
|
saved,
|
|
1137
|
+
attachments,
|
|
832
1138
|
counts: tally(result),
|
|
833
1139
|
steps: result.steps.map((s) => ({
|
|
834
1140
|
step: s.stepIndex + 1,
|
|
@@ -1010,7 +1316,7 @@ ${kleur5.dim("Run")} ${kleur5.cyan("apicircle workspaces list")} ${kleur5.dim("t
|
|
|
1010
1316
|
// src/index.ts
|
|
1011
1317
|
function buildProgram() {
|
|
1012
1318
|
const program = new Command();
|
|
1013
|
-
program.name("apicircle").description("Command-line companion to API Circle Studio.").version(
|
|
1319
|
+
program.name("apicircle").description("Command-line companion to API Circle Studio.").version(CLI_PACKAGE_VERSION);
|
|
1014
1320
|
registerMockCommand(program);
|
|
1015
1321
|
registerMcpCommand(program);
|
|
1016
1322
|
registerImportCommand(program);
|