@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.cjs
CHANGED
|
@@ -141,7 +141,7 @@ async function ensureWorkspace(dir) {
|
|
|
141
141
|
linkedWorkspaces: {},
|
|
142
142
|
linkedOverrides: { requests: {}, environmentVars: {} },
|
|
143
143
|
releases: { self: null, perLink: {} },
|
|
144
|
-
globalAssets: { schemas: {}, graphql: {} },
|
|
144
|
+
globalAssets: { schemas: {}, graphql: {}, files: {} },
|
|
145
145
|
mockServers: {},
|
|
146
146
|
meta: { createdAt: now, updatedAt: now, appVersion: "1.0.0" }
|
|
147
147
|
},
|
|
@@ -158,13 +158,14 @@ async function ensureWorkspace(dir) {
|
|
|
158
158
|
retiredBranch: null,
|
|
159
159
|
sync: { lastPulledSnapshot: null, lastPulledSha: null, lastPulledAt: null, dirtyKeys: [] },
|
|
160
160
|
linkedCollections: {},
|
|
161
|
+
attachmentCache: {},
|
|
161
162
|
globalContext: {},
|
|
162
163
|
mockRuntime: { active: {} },
|
|
163
164
|
ui: {
|
|
164
165
|
activeRequestId: null,
|
|
165
166
|
sidebarExpandedSections: [],
|
|
166
|
-
themeId: "
|
|
167
|
-
fontId: "
|
|
167
|
+
themeId: "command-center",
|
|
168
|
+
fontId: "cascadia-code",
|
|
168
169
|
fontSizePercent: import_shared2.FONT_SIZE_PERCENT_DEFAULT
|
|
169
170
|
},
|
|
170
171
|
settings: { validateOnSend: true, monacoConsumesWheel: false },
|
|
@@ -362,7 +363,7 @@ function buildEmptyState(workspaceId, now, withSample) {
|
|
|
362
363
|
linkedWorkspaces: {},
|
|
363
364
|
linkedOverrides: { requests: {}, environmentVars: {} },
|
|
364
365
|
releases: { self: null, perLink: {} },
|
|
365
|
-
globalAssets: { schemas: {}, graphql: {} },
|
|
366
|
+
globalAssets: { schemas: {}, graphql: {}, files: {} },
|
|
366
367
|
mockServers: {},
|
|
367
368
|
meta: { createdAt: now, updatedAt: now, appVersion: "1.0.0" }
|
|
368
369
|
},
|
|
@@ -379,13 +380,14 @@ function buildEmptyState(workspaceId, now, withSample) {
|
|
|
379
380
|
retiredBranch: null,
|
|
380
381
|
sync: { lastPulledSnapshot: null, lastPulledSha: null, lastPulledAt: null, dirtyKeys: [] },
|
|
381
382
|
linkedCollections: {},
|
|
383
|
+
attachmentCache: {},
|
|
382
384
|
globalContext: {},
|
|
383
385
|
mockRuntime: { active: {} },
|
|
384
386
|
ui: {
|
|
385
387
|
activeRequestId: sample?.id ?? null,
|
|
386
388
|
sidebarExpandedSections: [],
|
|
387
|
-
themeId: "
|
|
388
|
-
fontId: "
|
|
389
|
+
themeId: "command-center",
|
|
390
|
+
fontId: "cascadia-code",
|
|
389
391
|
fontSizePercent: 100
|
|
390
392
|
},
|
|
391
393
|
settings: { validateOnSend: true, monacoConsumesWheel: false },
|
|
@@ -552,13 +554,13 @@ function registerImportCommand(program) {
|
|
|
552
554
|
}
|
|
553
555
|
async function readInput(p) {
|
|
554
556
|
if (p === "-") {
|
|
555
|
-
return new Promise((
|
|
557
|
+
return new Promise((resolve7, reject) => {
|
|
556
558
|
let data = "";
|
|
557
559
|
process.stdin.setEncoding("utf-8");
|
|
558
560
|
process.stdin.on("data", (chunk) => {
|
|
559
561
|
data += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
560
562
|
});
|
|
561
|
-
process.stdin.on("end", () =>
|
|
563
|
+
process.stdin.on("end", () => resolve7(data));
|
|
562
564
|
process.stdin.on("error", reject);
|
|
563
565
|
});
|
|
564
566
|
}
|
|
@@ -585,7 +587,7 @@ function blankRequest(partial) {
|
|
|
585
587
|
// src/commands/run.ts
|
|
586
588
|
var os2 = __toESM(require("os"), 1);
|
|
587
589
|
var import_kleur4 = __toESM(require("kleur"), 1);
|
|
588
|
-
var
|
|
590
|
+
var import_core3 = require("@apicircle/core");
|
|
589
591
|
var import_file_backed4 = require("@apicircle/core/workspace/file-backed");
|
|
590
592
|
|
|
591
593
|
// src/util/secrets.ts
|
|
@@ -620,6 +622,272 @@ async function buildSecretsFromCli(options = {}) {
|
|
|
620
622
|
return { byId };
|
|
621
623
|
}
|
|
622
624
|
|
|
625
|
+
// src/util/executionAttachments.ts
|
|
626
|
+
var import_node_crypto = require("crypto");
|
|
627
|
+
var import_node_fs6 = require("fs");
|
|
628
|
+
var path6 = __toESM(require("path"), 1);
|
|
629
|
+
var import_core2 = require("@apicircle/core");
|
|
630
|
+
var ATTACHMENTS_DIR = path6.join(".apicircle", "attachments");
|
|
631
|
+
async function prepareExecutionAttachments(workspaceDir, state, plan) {
|
|
632
|
+
const cacheDir = path6.resolve(workspaceDir, ATTACHMENTS_DIR);
|
|
633
|
+
const requirements = collectExecutionAttachmentRequirements(state, plan);
|
|
634
|
+
await import_node_fs6.promises.mkdir(cacheDir, { recursive: true });
|
|
635
|
+
let downloaded = 0;
|
|
636
|
+
let alreadyPresent = 0;
|
|
637
|
+
let failed = 0;
|
|
638
|
+
const cache = {
|
|
639
|
+
...state.local.attachmentCache ?? {}
|
|
640
|
+
};
|
|
641
|
+
const entries = [];
|
|
642
|
+
for (const requirement of requirements) {
|
|
643
|
+
const localPath = path6.join(cacheDir, encodeURIComponent(requirement.slotId));
|
|
644
|
+
const present = await hasExpectedFile(localPath, requirement.sha256);
|
|
645
|
+
if (present) {
|
|
646
|
+
alreadyPresent++;
|
|
647
|
+
} else {
|
|
648
|
+
try {
|
|
649
|
+
const bytes = await downloadAttachment(requirement);
|
|
650
|
+
if (!bytes) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`Attachment ${attachmentLabel(requirement)} was not found in ${sourceLabel(requirement)}.`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (requirement.sha256 && sha256Hex(bytes) !== requirement.sha256) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`Attachment ${attachmentLabel(requirement)} failed checksum verification.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
await import_node_fs6.promises.writeFile(localPath, bytes, { mode: 384 });
|
|
661
|
+
downloaded++;
|
|
662
|
+
} catch (err) {
|
|
663
|
+
failed++;
|
|
664
|
+
throw new Error(
|
|
665
|
+
`Attachment ${attachmentLabel(requirement)} is required by ${requiredByLabel(
|
|
666
|
+
requirement
|
|
667
|
+
)} but could not be downloaded from ${sourceLabel(requirement)}: ${err instanceof Error ? err.message : String(err)}`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
cache[requirement.slotId] = {
|
|
672
|
+
slotId: requirement.slotId,
|
|
673
|
+
filename: requirement.filename ?? requirement.slotId,
|
|
674
|
+
mimeType: requirement.mimeType ?? "application/octet-stream",
|
|
675
|
+
size: requirement.size ?? await fileSize(localPath),
|
|
676
|
+
sha256: requirement.sha256,
|
|
677
|
+
localPath,
|
|
678
|
+
storage: "filesystem",
|
|
679
|
+
source: requirement.source,
|
|
680
|
+
...requirement.linkedWorkspaceId ? { linkedWorkspaceId: requirement.linkedWorkspaceId } : {},
|
|
681
|
+
requiredBy: requirement.requiredBy,
|
|
682
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
683
|
+
};
|
|
684
|
+
entries.push({
|
|
685
|
+
slotId: requirement.slotId,
|
|
686
|
+
filename: requirement.filename ?? requirement.slotId,
|
|
687
|
+
localPath,
|
|
688
|
+
source: requirement.source,
|
|
689
|
+
...requirement.linkedWorkspaceId ? { linkedWorkspaceId: requirement.linkedWorkspaceId } : {},
|
|
690
|
+
requiredBy: requirement.requiredBy
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
const nextState = {
|
|
694
|
+
...state,
|
|
695
|
+
local: {
|
|
696
|
+
...state.local,
|
|
697
|
+
attachmentCache: cache
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
return {
|
|
701
|
+
state: nextState,
|
|
702
|
+
resolveAttachment: createFileAttachmentResolver(nextState),
|
|
703
|
+
summary: {
|
|
704
|
+
total: requirements.length,
|
|
705
|
+
downloaded,
|
|
706
|
+
alreadyPresent,
|
|
707
|
+
failed,
|
|
708
|
+
cacheDir,
|
|
709
|
+
entries
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function createFileAttachmentResolver(state) {
|
|
714
|
+
return async (slotId) => {
|
|
715
|
+
const meta = state.local.attachmentCache?.[slotId];
|
|
716
|
+
if (!meta) return null;
|
|
717
|
+
const bytes = await import_node_fs6.promises.readFile(meta.localPath);
|
|
718
|
+
const view = new Uint8Array(bytes);
|
|
719
|
+
const body = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
720
|
+
return {
|
|
721
|
+
blob: new Blob([body], { type: meta.mimeType }),
|
|
722
|
+
filename: meta.filename
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function collectExecutionAttachmentRequirements(state, plan) {
|
|
727
|
+
const seen = /* @__PURE__ */ new Map();
|
|
728
|
+
const localRequestFilter = requestFilterForPlan(plan, null);
|
|
729
|
+
const localCollections = localRequestFilter ? {
|
|
730
|
+
...state.synced.collections,
|
|
731
|
+
requests: Object.fromEntries(
|
|
732
|
+
Object.entries(state.synced.collections.requests).filter(
|
|
733
|
+
([id]) => localRequestFilter.has(id)
|
|
734
|
+
)
|
|
735
|
+
)
|
|
736
|
+
} : state.synced.collections;
|
|
737
|
+
const workspaceSlots = (0, import_core2.collectAttachmentSlots)({ ...state.synced, collections: localCollections });
|
|
738
|
+
for (const slot of workspaceSlots) {
|
|
739
|
+
const requiredBy = collectRequiredBy(localCollections.requests, slot.slotId);
|
|
740
|
+
if (requiredBy.length === 0) continue;
|
|
741
|
+
addRequirement(seen, {
|
|
742
|
+
...slot,
|
|
743
|
+
source: "workspace",
|
|
744
|
+
repoFullName: state.local.connectedRepo?.fullName ?? void 0,
|
|
745
|
+
branch: state.local.workingBranch?.name ?? void 0,
|
|
746
|
+
publicRepo: state.local.connectedRepo ? !state.local.connectedRepo.isPrivate : false,
|
|
747
|
+
requiredBy
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
for (const [linkedWorkspaceId, snapshot] of Object.entries(state.local.linkedCollections)) {
|
|
751
|
+
const link = state.synced.linkedWorkspaces[linkedWorkspaceId];
|
|
752
|
+
if (!link) continue;
|
|
753
|
+
const linkedRequestFilter = requestFilterForPlan(plan, linkedWorkspaceId);
|
|
754
|
+
if (plan && linkedRequestFilter && linkedRequestFilter.size === 0) continue;
|
|
755
|
+
const linkedCollections = linkedRequestFilter ? {
|
|
756
|
+
...snapshot.collections,
|
|
757
|
+
requests: Object.fromEntries(
|
|
758
|
+
Object.entries(snapshot.collections.requests).filter(
|
|
759
|
+
([id]) => linkedRequestFilter.has(id)
|
|
760
|
+
)
|
|
761
|
+
)
|
|
762
|
+
} : snapshot.collections;
|
|
763
|
+
const linkedSynced = {
|
|
764
|
+
...state.synced,
|
|
765
|
+
collections: linkedCollections,
|
|
766
|
+
environments: snapshot.environments,
|
|
767
|
+
globalAssets: snapshot.globalAssets ?? state.synced.globalAssets
|
|
768
|
+
};
|
|
769
|
+
for (const slot of (0, import_core2.collectAttachmentSlots)(linkedSynced)) {
|
|
770
|
+
const requiredBy = collectRequiredBy(linkedCollections.requests, slot.slotId);
|
|
771
|
+
if (requiredBy.length === 0) continue;
|
|
772
|
+
addRequirement(seen, {
|
|
773
|
+
...slot,
|
|
774
|
+
source: "linked-workspace",
|
|
775
|
+
linkedWorkspaceId,
|
|
776
|
+
repoFullName: link.source.repoFullName,
|
|
777
|
+
branch: link.source.branch,
|
|
778
|
+
publicRepo: link.kind === "public",
|
|
779
|
+
requiredBy
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return [...seen.values()];
|
|
784
|
+
}
|
|
785
|
+
function requestFilterForPlan(plan, linkedWorkspaceId) {
|
|
786
|
+
if (!plan) return null;
|
|
787
|
+
const ids = /* @__PURE__ */ new Set();
|
|
788
|
+
for (const step of plan.steps) {
|
|
789
|
+
if (step.enabled === false) continue;
|
|
790
|
+
if ((step.linkedWorkspaceId ?? null) === linkedWorkspaceId) ids.add(step.requestId);
|
|
791
|
+
}
|
|
792
|
+
return ids;
|
|
793
|
+
}
|
|
794
|
+
function addRequirement(seen, requirement) {
|
|
795
|
+
const existing = seen.get(requirement.slotId);
|
|
796
|
+
if (!existing) {
|
|
797
|
+
seen.set(requirement.slotId, requirement);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
for (const usage of requirement.requiredBy) {
|
|
801
|
+
if (!existing.requiredBy.some((item) => item.requestId === usage.requestId)) {
|
|
802
|
+
existing.requiredBy.push(usage);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function collectRequiredBy(requests, slotId) {
|
|
807
|
+
const requiredBy = [];
|
|
808
|
+
for (const request of Object.values(requests)) {
|
|
809
|
+
if (bodyReferencesSlot(request.body, slotId)) {
|
|
810
|
+
requiredBy.push({ requestId: request.id, requestName: request.name });
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return requiredBy;
|
|
814
|
+
}
|
|
815
|
+
function bodyReferencesSlot(body, slotId) {
|
|
816
|
+
if (body.type === "binary") return body.attachment?.slotId === slotId;
|
|
817
|
+
if (body.type !== "form-data") return false;
|
|
818
|
+
return (body.formRows ?? []).some((row) => row.kind === "file" && row.slotId === slotId);
|
|
819
|
+
}
|
|
820
|
+
async function hasExpectedFile(localPath, sha256) {
|
|
821
|
+
try {
|
|
822
|
+
const bytes = await import_node_fs6.promises.readFile(localPath);
|
|
823
|
+
if (!sha256) return true;
|
|
824
|
+
return sha256Hex(bytes) === sha256;
|
|
825
|
+
} catch {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async function fileSize(localPath) {
|
|
830
|
+
try {
|
|
831
|
+
return (await import_node_fs6.promises.stat(localPath)).size;
|
|
832
|
+
} catch {
|
|
833
|
+
return 0;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function downloadAttachment(requirement) {
|
|
837
|
+
if (!requirement.repoFullName || !requirement.branch) return null;
|
|
838
|
+
const [owner, repo] = requirement.repoFullName.split("/", 2);
|
|
839
|
+
if (!owner || !repo) return null;
|
|
840
|
+
const token = resolveGitHubToken(requirement);
|
|
841
|
+
if (!token && !requirement.publicRepo) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
"private linked attachments need a GitHub token (set APICIRCLE_GITHUB_TOKEN or GITHUB_TOKEN)"
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
const apiPath = [".apicircle", "attachments", requirement.slotId].map(encodeURIComponent).join("/");
|
|
847
|
+
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
848
|
+
repo
|
|
849
|
+
)}/contents/${apiPath}?ref=${encodeURIComponent(requirement.branch)}`;
|
|
850
|
+
const headers = {
|
|
851
|
+
Accept: "application/vnd.github+json",
|
|
852
|
+
"User-Agent": "apicircle-cli",
|
|
853
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
854
|
+
};
|
|
855
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
856
|
+
const res = await fetch(url, { headers, cache: "no-store" });
|
|
857
|
+
if (res.status === 404) return null;
|
|
858
|
+
if (!res.ok) {
|
|
859
|
+
throw new Error(`GitHub returned ${res.status}: ${await res.text()}`);
|
|
860
|
+
}
|
|
861
|
+
const json = await res.json();
|
|
862
|
+
if (json.type !== "file" || typeof json.content !== "string") {
|
|
863
|
+
throw new Error("GitHub response was not a file");
|
|
864
|
+
}
|
|
865
|
+
if (json.encoding !== "base64") {
|
|
866
|
+
throw new Error(`GitHub response used unsupported encoding ${json.encoding ?? "(missing)"}`);
|
|
867
|
+
}
|
|
868
|
+
return new Uint8Array(Buffer.from(json.content.replace(/\n/g, ""), "base64"));
|
|
869
|
+
}
|
|
870
|
+
function resolveGitHubToken(requirement) {
|
|
871
|
+
if (requirement.source === "linked-workspace") {
|
|
872
|
+
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 ?? "";
|
|
873
|
+
}
|
|
874
|
+
return process.env.APICIRCLE_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.APICIRCLE_E2E_GITHUB_PAT ?? process.env.APICIRCLE_E2E_BOT_PAT ?? "";
|
|
875
|
+
}
|
|
876
|
+
function sha256Hex(bytes) {
|
|
877
|
+
return (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
|
|
878
|
+
}
|
|
879
|
+
function attachmentLabel(requirement) {
|
|
880
|
+
return `${requirement.filename ?? requirement.slotId} (${requirement.slotId})`;
|
|
881
|
+
}
|
|
882
|
+
function sourceLabel(requirement) {
|
|
883
|
+
const repo = requirement.repoFullName ?? "local workspace";
|
|
884
|
+
const branch = requirement.branch ? `@${requirement.branch}` : "";
|
|
885
|
+
return `${repo}${branch}`;
|
|
886
|
+
}
|
|
887
|
+
function requiredByLabel(requirement) {
|
|
888
|
+
return requirement.requiredBy.map((item) => item.requestName).join(", ") || "a request";
|
|
889
|
+
}
|
|
890
|
+
|
|
623
891
|
// src/commands/run.ts
|
|
624
892
|
var REPORTERS = ["text", "json", "junit"];
|
|
625
893
|
function registerRunCommand(program) {
|
|
@@ -661,7 +929,7 @@ function registerRunCommand(program) {
|
|
|
661
929
|
fail(`no workspace found at ${dir} (expected workspace.synced.json)`);
|
|
662
930
|
return;
|
|
663
931
|
}
|
|
664
|
-
const ref = (0,
|
|
932
|
+
const ref = (0, import_core3.resolvePlanRef)(state.synced, planRef);
|
|
665
933
|
if (!ref.ok) {
|
|
666
934
|
fail(ref.error);
|
|
667
935
|
if (ref.available.length > 0) {
|
|
@@ -693,21 +961,33 @@ function registerRunCommand(program) {
|
|
|
693
961
|
const onSigint = () => controller.abort(new Error("aborted by SIGINT"));
|
|
694
962
|
process.on("SIGINT", onSigint);
|
|
695
963
|
if (text) process.stdout.write(formatHeader(ref.plan, actor, withAssertions, opts));
|
|
964
|
+
let prepared;
|
|
965
|
+
try {
|
|
966
|
+
prepared = await prepareExecutionAttachments(dir, state, ref.plan);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
process.off("SIGINT", onSigint);
|
|
969
|
+
fail(err instanceof Error ? err.message : String(err), 1, "attachment");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (text && prepared.summary.total > 0) {
|
|
973
|
+
process.stdout.write(formatAttachmentPreparation(prepared.summary));
|
|
974
|
+
}
|
|
696
975
|
let result;
|
|
697
976
|
try {
|
|
698
|
-
result = await (0,
|
|
977
|
+
result = await (0, import_core3.runPlan)(prepared.state, ref.id, {
|
|
699
978
|
withAssertions,
|
|
700
979
|
bail: opts.bail === true,
|
|
701
980
|
env: opts.env,
|
|
702
981
|
secretsById,
|
|
703
982
|
actor,
|
|
704
983
|
signal: controller.signal,
|
|
984
|
+
resolveAttachment: prepared.resolveAttachment,
|
|
705
985
|
authorize: checkRunPermission,
|
|
706
986
|
onStep: text ? (step) => process.stdout.write(formatStepLine(step)) : void 0
|
|
707
987
|
});
|
|
708
988
|
} catch (err) {
|
|
709
989
|
process.off("SIGINT", onSigint);
|
|
710
|
-
if (err instanceof
|
|
990
|
+
if (err instanceof import_core3.PlanRunDeniedError) {
|
|
711
991
|
fail(err.message, 3, "denied");
|
|
712
992
|
return;
|
|
713
993
|
}
|
|
@@ -720,7 +1000,7 @@ function registerRunCommand(program) {
|
|
|
720
1000
|
if (reporter === "json") {
|
|
721
1001
|
process.stdout.write(
|
|
722
1002
|
JSON.stringify(
|
|
723
|
-
buildJsonReport(dir, ref.id, ref.plan, actor, result, saved, aborted),
|
|
1003
|
+
buildJsonReport(dir, ref.id, ref.plan, actor, result, saved, aborted, prepared.summary),
|
|
724
1004
|
null,
|
|
725
1005
|
2
|
|
726
1006
|
) + "\n"
|
|
@@ -746,7 +1026,7 @@ function resolveActor(local, override) {
|
|
|
746
1026
|
if (username) return { kind: "os", name: username };
|
|
747
1027
|
} catch {
|
|
748
1028
|
}
|
|
749
|
-
return
|
|
1029
|
+
return import_core3.ANONYMOUS_ACTOR;
|
|
750
1030
|
}
|
|
751
1031
|
function checkRunPermission(_ctx) {
|
|
752
1032
|
}
|
|
@@ -762,6 +1042,26 @@ function formatHeader(plan, actor, withAssertions, opts) {
|
|
|
762
1042
|
)}
|
|
763
1043
|
${import_kleur4.default.dim("Run by")} ${actor.name} ${import_kleur4.default.dim(`(${actor.kind})`)}
|
|
764
1044
|
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
function formatAttachmentPreparation(summary) {
|
|
1048
|
+
const status = `${summary.downloaded} downloaded, ${summary.alreadyPresent} already local`;
|
|
1049
|
+
const lines = [
|
|
1050
|
+
`${import_kleur4.default.bold("Attachments")} ${summary.total} required ${import_kleur4.default.dim(
|
|
1051
|
+
`(${status} - ${summary.cacheDir})`
|
|
1052
|
+
)}`
|
|
1053
|
+
];
|
|
1054
|
+
for (const entry2 of summary.entries) {
|
|
1055
|
+
const source = entry2.source === "linked-workspace" ? `linked:${entry2.linkedWorkspaceId ?? "unknown"}` : "workspace";
|
|
1056
|
+
const requiredBy = entry2.requiredBy.map((item) => item.requestName).join(", ");
|
|
1057
|
+
lines.push(
|
|
1058
|
+
` ${import_kleur4.default.dim("file")} ${entry2.filename} ${import_kleur4.default.dim(
|
|
1059
|
+
`${source} - ${requiredBy} - ${entry2.localPath}`
|
|
1060
|
+
)}`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return `${lines.join("\n")}
|
|
1064
|
+
|
|
765
1065
|
`;
|
|
766
1066
|
}
|
|
767
1067
|
function formatStepLine(step) {
|
|
@@ -831,7 +1131,7 @@ ${verdict} ${parts.join(import_kleur4.default.dim(" \xB7 "))} ${import_kleur4.
|
|
|
831
1131
|
out += saved ? import_kleur4.default.dim("Plan run saved to workspace history.\n") : import_kleur4.default.dim("Plan run not saved (--no-save).\n");
|
|
832
1132
|
return out;
|
|
833
1133
|
}
|
|
834
|
-
function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted) {
|
|
1134
|
+
function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted, attachments) {
|
|
835
1135
|
return {
|
|
836
1136
|
workspace,
|
|
837
1137
|
plan: { id: planId, name: plan.name },
|
|
@@ -841,6 +1141,7 @@ function buildJsonReport(workspace, planId, plan, actor, result, saved, aborted)
|
|
|
841
1141
|
aborted,
|
|
842
1142
|
durationMs: result.planRun.durationMs,
|
|
843
1143
|
saved,
|
|
1144
|
+
attachments,
|
|
844
1145
|
counts: tally(result),
|
|
845
1146
|
steps: result.steps.map((s) => ({
|
|
846
1147
|
step: s.stepIndex + 1,
|
|
@@ -1019,10 +1320,88 @@ ${import_kleur5.default.dim("Run")} ${import_kleur5.default.cyan("apicircle work
|
|
|
1019
1320
|
});
|
|
1020
1321
|
}
|
|
1021
1322
|
|
|
1323
|
+
// package.json
|
|
1324
|
+
var package_default = {
|
|
1325
|
+
name: "@apicircle/cli",
|
|
1326
|
+
version: "1.0.5",
|
|
1327
|
+
private: false,
|
|
1328
|
+
type: "module",
|
|
1329
|
+
description: "Command-line interface for API Circle Studio. Run mock servers, drive the MCP server, and import OpenAPI / Postman / Insomnia collections from any terminal.",
|
|
1330
|
+
license: "SEE LICENSE IN LICENSE",
|
|
1331
|
+
repository: {
|
|
1332
|
+
type: "git",
|
|
1333
|
+
url: "git+https://github.com/apicircle/studio.git",
|
|
1334
|
+
directory: "packages/cli"
|
|
1335
|
+
},
|
|
1336
|
+
homepage: "https://github.com/apicircle/studio#readme",
|
|
1337
|
+
bugs: "https://github.com/apicircle/studio/issues",
|
|
1338
|
+
engines: {
|
|
1339
|
+
node: ">=20"
|
|
1340
|
+
},
|
|
1341
|
+
main: "./src/index.ts",
|
|
1342
|
+
types: "./src/index.ts",
|
|
1343
|
+
bin: {
|
|
1344
|
+
apicircle: "./dist/bin/cli.cjs"
|
|
1345
|
+
},
|
|
1346
|
+
exports: {
|
|
1347
|
+
".": "./src/index.ts"
|
|
1348
|
+
},
|
|
1349
|
+
files: [
|
|
1350
|
+
"dist"
|
|
1351
|
+
],
|
|
1352
|
+
publishConfig: {
|
|
1353
|
+
main: "./dist/index.cjs",
|
|
1354
|
+
module: "./dist/index.js",
|
|
1355
|
+
types: "./dist/index.d.cts",
|
|
1356
|
+
exports: {
|
|
1357
|
+
".": {
|
|
1358
|
+
import: {
|
|
1359
|
+
types: "./dist/index.d.ts",
|
|
1360
|
+
default: "./dist/index.js"
|
|
1361
|
+
},
|
|
1362
|
+
require: {
|
|
1363
|
+
types: "./dist/index.d.cts",
|
|
1364
|
+
default: "./dist/index.cjs"
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
scripts: {
|
|
1370
|
+
build: "tsup",
|
|
1371
|
+
check: "tsc --noEmit",
|
|
1372
|
+
test: "vitest run",
|
|
1373
|
+
clean: "rm -rf dist node_modules"
|
|
1374
|
+
},
|
|
1375
|
+
dependencies: {
|
|
1376
|
+
"@apicircle/core": "workspace:*",
|
|
1377
|
+
"@apicircle/mcp-server": "workspace:*",
|
|
1378
|
+
"@apicircle/mock-server-core": "workspace:*",
|
|
1379
|
+
"@apicircle/shared": "workspace:*",
|
|
1380
|
+
commander: "^12.0.0",
|
|
1381
|
+
kleur: "^4.1.5"
|
|
1382
|
+
},
|
|
1383
|
+
devDependencies: {
|
|
1384
|
+
"@types/node": "^20.0.0",
|
|
1385
|
+
tsup: "^8.3.0",
|
|
1386
|
+
typescript: "^5.4.0",
|
|
1387
|
+
vitest: "^2.0.0"
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// src/packageVersion.ts
|
|
1392
|
+
function readPackageVersion() {
|
|
1393
|
+
const version = package_default.version;
|
|
1394
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
1395
|
+
throw new Error("Unable to read @apicircle/cli package version");
|
|
1396
|
+
}
|
|
1397
|
+
return version;
|
|
1398
|
+
}
|
|
1399
|
+
var CLI_PACKAGE_VERSION = readPackageVersion();
|
|
1400
|
+
|
|
1022
1401
|
// src/index.ts
|
|
1023
1402
|
function buildProgram() {
|
|
1024
1403
|
const program = new import_commander.Command();
|
|
1025
|
-
program.name("apicircle").description("Command-line companion to API Circle Studio.").version(
|
|
1404
|
+
program.name("apicircle").description("Command-line companion to API Circle Studio.").version(CLI_PACKAGE_VERSION);
|
|
1026
1405
|
registerMockCommand(program);
|
|
1027
1406
|
registerMcpCommand(program);
|
|
1028
1407
|
registerImportCommand(program);
|