@anna-ai/cli 0.1.33 → 0.1.35
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/{_lifecycle-shared-CUK2CE76.js → _lifecycle-shared-RyiRITI5.js} +4 -2
- package/dist/{account-bqao7Gp5.js → account-B4rdVDyg.js} +1 -1
- package/dist/{agent-CzPryLi6.js → agent-DAOr_aiE.js} +1 -1
- package/dist/{app-bundle-upload-CqT9KUOl.js → app-bundle-upload-DmVKWAj9.js} +3 -3
- package/dist/{apps-dvknZYI2.js → apps-C_Oln_om.js} +1 -1
- package/dist/apps-cut-MjSe2ph7.js +159 -0
- package/dist/{apps-destructive-B6RFjokU.js → apps-destructive-a2YBmWFm.js} +2 -2
- package/dist/{apps-discard-hVZxAQyj.js → apps-discard-BWLVVMu7.js} +12 -10
- package/dist/{apps-grants-DnXCnkH5.js → apps-grants-lmllFB02.js} +1 -1
- package/dist/{apps-publish-BKqVsS1W.js → apps-publish--EmBa0zN.js} +8 -7
- package/dist/apps-publish-B7reDBe1.js +17 -0
- package/dist/{apps-push-B3wZr9B4.js → apps-push-CkJ1gtpy.js} +16 -12
- package/dist/{apps-release-CyAPmWPU.js → apps-release-J-Sjkoym.js} +3 -3
- package/dist/{apps-rename-slug-D2BrWasG.js → apps-rename-slug-DYrk4Zpt.js} +3 -3
- package/dist/{apps-status-DV8V_y35.js → apps-status-CcCoYu6i.js} +1 -1
- package/dist/{apps-submit-review-DMFQI8JZ.js → apps-submit-review-DNrQc2ws.js} +2 -2
- package/dist/{apps-sync-meta-BOb6J_wz.js → apps-sync-meta-CL2PCMCv.js} +6 -4
- package/dist/{apps-versions-D1lixrmM.js → apps-versions-2BR8xo7C.js} +1 -1
- package/dist/binary-upload-CKMU5Rfw.js +181 -0
- package/dist/bridge-CzeSuRRL.js +3 -0
- package/dist/{bridge-CbjqAm0H.js → bridge-Dvck0e9c.js} +1 -1
- package/dist/{bundled-executas-06keEPP_.js → bundled-executas-Bp9Z6aiR.js} +1 -1
- package/dist/cli.js +80 -44
- package/dist/dev-CZLOk2qE.js +4 -0
- package/dist/{dev-Re6NyNDS.js → dev-GUFWek03.js} +5 -5
- package/dist/{dev-app-cache-Dv1dZ_bw.js → dev-app-cache-B3Wa5t6X.js} +18 -3
- package/dist/{doctor-VrfEkedz.js → doctor-C3Nx8E2L.js} +1 -1
- package/dist/{executa-cache-B0O79Hpb.js → executa-cache-WBkCLic7.js} +1 -1
- package/dist/{executa-destructive-Bz7vWxEO.js → executa-destructive-DCfQ1ez5.js} +3 -3
- package/dist/{executa-dev-B5Uz982M.js → executa-dev-CAw5X64R.js} +8 -8
- package/dist/{executa-install-BW9jjY7u.js → executa-install-Dz7hBUsn.js} +2 -2
- package/dist/executa-install-nOFLBwxv.js +7 -0
- package/dist/executa-publish-B_7k1gOf.js +11 -0
- package/dist/{executa-publish-Ah6J7jYd.js → executa-publish-kh4Ucd3o.js} +21 -5
- package/dist/{executa-reads-BXDJWWw6.js → executa-reads-BmwT_Yjp.js} +2 -2
- package/dist/executa-trusted-publisher-tVwzjI2d.js +91 -0
- package/dist/executa-upload-binaries-DURJTXRI.js +277 -0
- package/dist/executa-upload-binaries-DrwpzQp3.js +9 -0
- package/dist/{listing-meta-BlY7XMrw.js → listing-meta-CtVtyaWu.js} +13 -4
- package/dist/{manifest-DOMrcdCK.js → manifest-CAF3_r1T.js} +88 -11
- package/dist/{publish-GpvkaBIx.js → publish-DwIJWl46.js} +12 -10
- package/dist/{server-BMcGaUTy.js → server-BHWGGZsf.js} +1 -1
- package/dist/{storage-BkF4XyI0.js → storage-aii8EyPC.js} +1 -1
- package/dist/{token-uedeaAy7.js → token-COnYMRB-.js} +1 -1
- package/dist/{working-orchestration-1jzRuk34.js → working-orchestration-D7cW2Bf2.js} +7 -6
- package/package.json +1 -1
- package/dist/apps-cut-8870xpk-.js +0 -86
- package/dist/apps-publish-Djpw5cfG.js +0 -15
- package/dist/bridge-CzHB_IsQ.js +0 -3
- package/dist/dev-C3yWhzAp.js +0 -4
- package/dist/executa-install-Cyid8R7r.js +0 -7
- package/dist/executa-publish-DI_6M_ea.js +0 -9
- /package/dist/{app-cache-BqnTkxUw.js → app-cache-Cr3tKbux.js} +0 -0
- /package/dist/{bundled-executas-_H1A9M50.js → bundled-executas-rSIkaA6-.js} +0 -0
- /package/dist/{confirm-BuT56E_B.js → confirm-BSoOy1X2.js} +0 -0
- /package/dist/{dev-account-DqEFCmVg.js → dev-account-CfVdGa9j.js} +0 -0
- /package/dist/{executa-cache-Do1D7xC0.js → executa-cache-BFoUtb4J.js} +0 -0
- /package/dist/{executa-init-D-kWINZ3.js → executa-init-DKy7DMSD.js} +0 -0
- /package/dist/{executa-register-D5kvSSz_.js → executa-register-CmqKsoAU.js} +0 -0
- /package/dist/{executas-BhFed1B-.js → executas-CvQY_w2U.js} +0 -0
- /package/dist/{fixture-BUKQYR5e.js → fixture-BF1Z0Rry.js} +0 -0
- /package/dist/{host_upload-BVzAqvX1.js → host_upload-DrbQ-vk2.js} +0 -0
- /package/dist/{image-BLPyUbGQ.js → image-B4UrufYH.js} +0 -0
- /package/dist/{login-DhgBgsfx.js → login-DbETuUxy.js} +0 -0
- /package/dist/{logout-VYDkLYAx.js → logout-BzIEf-HU.js} +0 -0
- /package/dist/{runner-BxYjAmih.js → runner-Bka_coxb.js} +0 -0
- /package/dist/{sampling-XBIEYU4A.js → sampling-BpJcd6BC.js} +0 -0
- /package/dist/{whoami-C_x26b_k.js → whoami-Bn38ns8r.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./nexus-root-BlPwOusj.js";
|
|
2
|
-
import { parseExecutaSpec } from "./dev-
|
|
2
|
+
import { parseExecutaSpec } from "./dev-GUFWek03.js";
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
@@ -35,7 +35,7 @@ async function runExecutaDev(opts) {
|
|
|
35
35
|
const oneShot = !!(opts.describe || opts.health || opts.invoke);
|
|
36
36
|
const quiet = oneShot && (opts.json ?? false);
|
|
37
37
|
const { getAccount } = await import("./credentials-Chkoidh5.js");
|
|
38
|
-
const { ensureDevExecutaRegistered } = await import("./dev-app-cache-
|
|
38
|
+
const { ensureDevExecutaRegistered } = await import("./dev-app-cache-B3Wa5t6X.js");
|
|
39
39
|
const needsRealMint = !opts.noSampling && !opts.mockSampling || !opts.noAgent && !opts.mockAgent || !opts.noImage && !opts.mockImage || !opts.noUpload && !opts.mockUpload || opts.storage === "real";
|
|
40
40
|
let effectiveAppSlug = opts.appSlug;
|
|
41
41
|
let autoRegistered = false;
|
|
@@ -57,7 +57,7 @@ async function runExecutaDev(opts) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
const { SamplingBridge } = await import("./sampling-
|
|
60
|
+
const { SamplingBridge } = await import("./sampling-BpJcd6BC.js");
|
|
61
61
|
const simulateUnsupportedResponseFormat = opts.samplingUnsupportedFormat;
|
|
62
62
|
const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
|
|
63
63
|
mode: "mock",
|
|
@@ -69,7 +69,7 @@ async function runExecutaDev(opts) {
|
|
|
69
69
|
appSlug: effectiveAppSlug,
|
|
70
70
|
simulateUnsupportedResponseFormat
|
|
71
71
|
}) : new SamplingBridge({ mode: "off" });
|
|
72
|
-
const { AgentBridge } = await import("./agent-
|
|
72
|
+
const { AgentBridge } = await import("./agent-DAOr_aiE.js");
|
|
73
73
|
const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
|
|
74
74
|
mode: "mock",
|
|
75
75
|
mockFile: opts.mockAgent
|
|
@@ -78,7 +78,7 @@ async function runExecutaDev(opts) {
|
|
|
78
78
|
account: opts.agentAccount ?? opts.samplingAccount,
|
|
79
79
|
appSlug: effectiveAppSlug
|
|
80
80
|
}) : new AgentBridge({ mode: "off" });
|
|
81
|
-
const { StorageBridge } = await import("./storage-
|
|
81
|
+
const { StorageBridge } = await import("./storage-aii8EyPC.js");
|
|
82
82
|
const storageMode = opts.storage ?? (opts.mockStorage ? "mock" : "memory");
|
|
83
83
|
const storage = new StorageBridge({
|
|
84
84
|
mode: storageMode,
|
|
@@ -88,8 +88,8 @@ async function runExecutaDev(opts) {
|
|
|
88
88
|
scopes: opts.storageScopes ? opts.storageScopes.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
89
89
|
pluginName: parsed.tool_id
|
|
90
90
|
});
|
|
91
|
-
const { ExecutaRunner } = await import("./runner-
|
|
92
|
-
const { ImageBridge } = await import("./image-
|
|
91
|
+
const { ExecutaRunner } = await import("./runner-Bka_coxb.js");
|
|
92
|
+
const { ImageBridge } = await import("./image-B4UrufYH.js");
|
|
93
93
|
const image = opts.noImage ? new ImageBridge({ mode: "off" }) : opts.mockImage ? new ImageBridge({
|
|
94
94
|
mode: "mock",
|
|
95
95
|
mockFile: opts.mockImage
|
|
@@ -98,7 +98,7 @@ async function runExecutaDev(opts) {
|
|
|
98
98
|
account: opts.imageAccount ?? opts.samplingAccount,
|
|
99
99
|
appSlug: effectiveAppSlug
|
|
100
100
|
}) : new ImageBridge({ mode: "off" });
|
|
101
|
-
const { HostUploadBridge } = await import("./host_upload-
|
|
101
|
+
const { HostUploadBridge } = await import("./host_upload-DrbQ-vk2.js");
|
|
102
102
|
const hostUpload = opts.noUpload ? new HostUploadBridge({ mode: "off" }) : opts.mockUpload ? new HostUploadBridge({
|
|
103
103
|
mode: "mock",
|
|
104
104
|
mockFile: opts.mockUpload
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readExecutaIdentity } from "./executa-cache-
|
|
2
|
-
import { parseExecutaSpec } from "./dev-
|
|
1
|
+
import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
|
|
2
|
+
import { parseExecutaSpec } from "./dev-GUFWek03.js";
|
|
3
3
|
import { isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import "./client-D-_z1ALk.js";
|
|
3
|
+
import "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import "./executa-cache-BFoUtb4J.js";
|
|
5
|
+
import "./manifest-CAF3_r1T.js";
|
|
6
|
+
import "./executas-CvQY_w2U.js";
|
|
7
|
+
import { runExecutaPublish } from "./executa-publish-kh4Ucd3o.js";
|
|
8
|
+
import "./binary-upload-CKMU5Rfw.js";
|
|
9
|
+
import "./executa-upload-binaries-DURJTXRI.js";
|
|
10
|
+
|
|
11
|
+
export { runExecutaPublish };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import { CliError } from "./client-D-_z1ALk.js";
|
|
3
|
-
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import { executaCacheMatches, invalidateExecutaCache, mintIdempotencyKey, readExecutaIdentity, writeExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
|
|
5
|
+
import { loadExecutaManifest } from "./manifest-CAF3_r1T.js";
|
|
6
|
+
import { commitDraft, createDraft, getMySkill, getMyTool, listToolVersions, publishToolVersion, setSkillVisibility, setToolVisibility, updateMySkill, updateMyTool } from "./executas-CvQY_w2U.js";
|
|
7
|
+
import { uploadExecutaBinaries } from "./executa-upload-binaries-DURJTXRI.js";
|
|
7
8
|
import { join, relative, resolve, sep } from "node:path";
|
|
8
9
|
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
9
10
|
import { bold, cyan, dim, green, yellow } from "kleur/colors";
|
|
@@ -109,7 +110,7 @@ function distributionPayload(d) {
|
|
|
109
110
|
async function runExecutaPublish(opts) {
|
|
110
111
|
return withErrorHandling(async () => {
|
|
111
112
|
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
112
|
-
const manifest = loadExecutaManifest(cwd, opts.manifest);
|
|
113
|
+
const manifest = loadExecutaManifest(cwd, opts.manifest, opts.profile);
|
|
113
114
|
const isSkill = manifest.executa_type === "skill";
|
|
114
115
|
const { client, host, source } = resolveClient({ account: opts.account });
|
|
115
116
|
if (!opts.json && !opts.quiet) console.log(dim(`using PAT from ${source} (host=${host})`));
|
|
@@ -206,6 +207,21 @@ async function runExecutaPublish(opts) {
|
|
|
206
207
|
description: manifest.description,
|
|
207
208
|
...distributionPayload(manifest.distribution)
|
|
208
209
|
});
|
|
210
|
+
if (!isSkill && manifest.distribution?.binaryArtifacts && opts.freeze !== false) {
|
|
211
|
+
const upLog = (msg) => {
|
|
212
|
+
if (!opts.json && !opts.quiet) console.log(msg);
|
|
213
|
+
};
|
|
214
|
+
const up = await uploadExecutaBinaries({
|
|
215
|
+
client,
|
|
216
|
+
host,
|
|
217
|
+
dir: cwd,
|
|
218
|
+
manifest,
|
|
219
|
+
toolId,
|
|
220
|
+
log: upLog
|
|
221
|
+
});
|
|
222
|
+
upLog(green(`✓ binary artifacts published `) + dim(`(${up.uploaded.length} uploaded, ${up.skipped.length} cached)`));
|
|
223
|
+
}
|
|
224
|
+
if (!isSkill && manifest.distribution?.type === "binary" && manifest.distribution?.binaryUrls && !opts.json && !opts.quiet) console.log(yellow("! distribution.binary_urls uses the pull-mirror track: the platform fetches these URLs at cut/release, which requires them to be PUBLICLY downloadable. Private source URLs are deprecated — switch to distribution.binary_artifacts (direct upload) if this source is (or may become) private."));
|
|
209
225
|
const preFreeze = isSkill ? await getMySkill(client, executaId) : await getMyTool(client, executaId);
|
|
210
226
|
const currentVis = preFreeze.visibility ?? "private";
|
|
211
227
|
const targetVis = opts.publish ? "public" : currentVis === "public" ? "public" : "app_bundled";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import { CliError } from "./client-D-_z1ALk.js";
|
|
3
|
-
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-
|
|
4
|
-
import { getMyTool, listMyTools, listToolVersions } from "./executas-
|
|
3
|
+
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import { getMyTool, listMyTools, listToolVersions } from "./executas-CvQY_w2U.js";
|
|
5
5
|
import { bold, cyan, dim, green } from "kleur/colors";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/executa-reads.ts
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { CliError } from "./client-D-_z1ALk.js";
|
|
3
|
+
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
|
|
5
|
+
import { addTrustedPublisher, listTrustedPublishers, removeTrustedPublisher } from "./binary-upload-CKMU5Rfw.js";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { bold, cyan, dim, green } from "kleur/colors";
|
|
8
|
+
|
|
9
|
+
//#region src/commands/executa-trusted-publisher.ts
|
|
10
|
+
function resolveToolId(opts, host) {
|
|
11
|
+
if (opts.toolId) return opts.toolId;
|
|
12
|
+
const dir = resolve(opts.dir ?? process.cwd());
|
|
13
|
+
const identity = readExecutaIdentity(dir);
|
|
14
|
+
if (!identity) throw new CliError(`no .anna/executa.json identity cache in ${dir} — run \`anna-app executa publish\` / \`anna-app apps push\` once, or pass --tool-id`, 4);
|
|
15
|
+
if (identity.host !== canonicalHost(host)) throw new CliError(`identity cache in ${dir} belongs to ${identity.host}, but the current PAT targets ${canonicalHost(host)} — use --account or pass --tool-id`, 4);
|
|
16
|
+
return identity.tool_id;
|
|
17
|
+
}
|
|
18
|
+
function setup(opts) {
|
|
19
|
+
const { client, host } = resolveClient({
|
|
20
|
+
account: opts.account,
|
|
21
|
+
fetchImpl: opts.fetchImpl
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
client,
|
|
25
|
+
host,
|
|
26
|
+
toolId: resolveToolId(opts, host)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function printPublisher(p) {
|
|
30
|
+
console.log(` #${p.id} ${bold(p.repository)} ${cyan(p.workflow)}` + (p.environment ? dim(` env=${p.environment}`) : dim(" env=(any)")) + (p.last_used_at ? dim(` last used ${p.last_used_at}`) : dim(" never used")));
|
|
31
|
+
}
|
|
32
|
+
async function runTrustedPublisherAdd(opts) {
|
|
33
|
+
return withErrorHandling(async () => {
|
|
34
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(opts.repository)) throw new CliError(`--repository must be "owner/repo" (got "${opts.repository}")`, 2);
|
|
35
|
+
const { client, toolId } = setup(opts);
|
|
36
|
+
const created = await addTrustedPublisher(client, toolId, {
|
|
37
|
+
repository: opts.repository,
|
|
38
|
+
workflow: opts.workflow,
|
|
39
|
+
environment: opts.environment
|
|
40
|
+
});
|
|
41
|
+
if (opts.json) {
|
|
42
|
+
console.log(JSON.stringify({
|
|
43
|
+
tool_id: toolId,
|
|
44
|
+
trusted_publisher: created
|
|
45
|
+
}, null, 2));
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
console.log(green(`✓ trusted publisher registered for ${toolId}:`));
|
|
49
|
+
printPublisher(created);
|
|
50
|
+
console.log(dim("→ that workflow can now run ") + cyan("anna-app executa upload-binaries --oidc") + dim(" with `permissions: id-token: write` and no PAT"));
|
|
51
|
+
return 0;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function runTrustedPublisherList(opts) {
|
|
55
|
+
return withErrorHandling(async () => {
|
|
56
|
+
const { client, toolId } = setup(opts);
|
|
57
|
+
const rows = await listTrustedPublishers(client, toolId);
|
|
58
|
+
if (opts.json) {
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
tool_id: toolId,
|
|
61
|
+
trusted_publishers: rows
|
|
62
|
+
}, null, 2));
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
if (rows.length === 0) {
|
|
66
|
+
console.log(dim(`(no trusted publishers registered for ${toolId})`));
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
console.log(bold(`trusted publishers for ${toolId}:`));
|
|
70
|
+
for (const p of rows) printPublisher(p);
|
|
71
|
+
return 0;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async function runTrustedPublisherRemove(opts) {
|
|
75
|
+
return withErrorHandling(async () => {
|
|
76
|
+
const { client, toolId } = setup(opts);
|
|
77
|
+
await removeTrustedPublisher(client, toolId, opts.publisherId);
|
|
78
|
+
if (opts.json) {
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
tool_id: toolId,
|
|
81
|
+
removed: opts.publisherId
|
|
82
|
+
}, null, 2));
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
console.log(green(`✓ removed trusted publisher #${opts.publisherId} from ${toolId}`));
|
|
86
|
+
return 0;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { runTrustedPublisherAdd, runTrustedPublisherList, runTrustedPublisherRemove };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import { CliError } from "./client-D-_z1ALk.js";
|
|
3
|
+
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import { readExecutaIdentity } from "./executa-cache-BFoUtb4J.js";
|
|
5
|
+
import { loadExecutaManifest } from "./manifest-CAF3_r1T.js";
|
|
6
|
+
import { fetchGitHubIdToken, finalizeBinaryUpload, initBinaryUpload, mintUploadSession, mintUploadSessionViaOidc, uploadBinaryPart } from "./binary-upload-CKMU5Rfw.js";
|
|
7
|
+
import { isAbsolute, resolve } from "node:path";
|
|
8
|
+
import { closeSync, createReadStream, existsSync, openSync, readSync, statSync } from "node:fs";
|
|
9
|
+
import { bold, cyan, dim, green, yellow } from "kleur/colors";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
|
|
12
|
+
//#region src/commands/executa-upload-binaries.ts
|
|
13
|
+
const MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024;
|
|
14
|
+
const PART_RETRIES = 3;
|
|
15
|
+
/** Expand `{version}` / `{platform}` / `{tool_id}` placeholders. */
|
|
16
|
+
function expandArtifactPath(template, vars) {
|
|
17
|
+
return template.replaceAll("{version}", vars.version).replaceAll("{platform}", vars.platform).replaceAll("{tool_id}", vars.toolId);
|
|
18
|
+
}
|
|
19
|
+
async function sha256File(path) {
|
|
20
|
+
const hash = createHash("sha256");
|
|
21
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
22
|
+
return hash.digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `distribution.binary_artifacts` into a validated, hashed upload
|
|
26
|
+
* plan. Throws `CliError(4)` on missing files / oversize artifacts.
|
|
27
|
+
*/
|
|
28
|
+
async function resolveArtifactPlan(manifest, dir, toolId) {
|
|
29
|
+
const artifacts = manifest.distribution?.binaryArtifacts;
|
|
30
|
+
if (!artifacts || Object.keys(artifacts).length === 0) throw new CliError("executa.json declares no `distribution.binary_artifacts` — this command pushes LOCAL build artifacts directly to the platform. Add e.g.\n \"distribution\": { \"type\": \"binary\", \"binary_artifacts\": {\n \"darwin-arm64\": { \"path\": \"dist/my-tool-{version}-darwin-arm64.tar.gz\", \"entrypoint\": \"my-tool\" }\n } }\n(projects using public `binary_urls` are pull-mirrored at `apps cut` instead)", 4);
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const [platform, art] of Object.entries(artifacts)) {
|
|
33
|
+
const expanded = expandArtifactPath(art.path, {
|
|
34
|
+
version: manifest.version,
|
|
35
|
+
platform,
|
|
36
|
+
toolId
|
|
37
|
+
});
|
|
38
|
+
const absPath = isAbsolute(expanded) ? expanded : resolve(dir, expanded);
|
|
39
|
+
if (!existsSync(absPath)) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} does not exist — build the artifact first (path placeholders: {version} {platform} {tool_id})`, 4);
|
|
40
|
+
const st = statSync(absPath);
|
|
41
|
+
if (!st.isFile()) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} is not a file`, 4);
|
|
42
|
+
if (st.size <= 0 || st.size > MAX_ARTIFACT_BYTES) throw new CliError(`binary_artifacts["${platform}"]: ${absPath} is ${st.size} bytes — must be 1..${MAX_ARTIFACT_BYTES} (1 GiB)`, 4);
|
|
43
|
+
const filename = absPath.split(/[\\/]/).pop();
|
|
44
|
+
out.push({
|
|
45
|
+
platform,
|
|
46
|
+
absPath,
|
|
47
|
+
decl: {
|
|
48
|
+
filename,
|
|
49
|
+
sha256: await sha256File(absPath),
|
|
50
|
+
byte_size: st.size,
|
|
51
|
+
format: art.format,
|
|
52
|
+
entrypoint: art.entrypoint
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
out.sort((a, b) => a.platform.localeCompare(b.platform));
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
/** Read one part (byte range) of a file into a fresh buffer. */
|
|
60
|
+
function readFilePart(path, start, length) {
|
|
61
|
+
const buf = Buffer.alloc(length);
|
|
62
|
+
const fd = openSync(path, "r");
|
|
63
|
+
try {
|
|
64
|
+
let off = 0;
|
|
65
|
+
while (off < length) {
|
|
66
|
+
const n = readSync(fd, buf, off, length - off, start + off);
|
|
67
|
+
if (n <= 0) break;
|
|
68
|
+
off += n;
|
|
69
|
+
}
|
|
70
|
+
if (off !== length) throw new CliError(`short read on ${path} (${off}/${length} bytes)`, 4);
|
|
71
|
+
return buf;
|
|
72
|
+
} finally {
|
|
73
|
+
closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Drive parts for every platform the init plan marked `upload`.
|
|
78
|
+
* Sequential parts with per-part retry — resume comes from `uploaded_parts`.
|
|
79
|
+
*/
|
|
80
|
+
async function uploadPlannedParts(t, toolId, plan, init, log, retryDelayMs = 500) {
|
|
81
|
+
const outcome = {
|
|
82
|
+
uploadId: init.upload_id,
|
|
83
|
+
skipped: [],
|
|
84
|
+
uploaded: [],
|
|
85
|
+
partsSent: 0
|
|
86
|
+
};
|
|
87
|
+
for (const entry of plan) {
|
|
88
|
+
const asset = init.assets[entry.platform];
|
|
89
|
+
if (!asset) throw new CliError(`server plan is missing platform ${entry.platform}`, 6);
|
|
90
|
+
if (asset.status === "exists") {
|
|
91
|
+
outcome.skipped.push(entry.platform);
|
|
92
|
+
log(dim(`⏭ ${entry.platform}: skipped (content already published)`));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const partSize = asset.part_size;
|
|
96
|
+
const partCount = asset.part_count;
|
|
97
|
+
const already = new Set(asset.uploaded_parts ?? []);
|
|
98
|
+
if (already.size > 0) log(dim(`↩ ${entry.platform}: resuming — ${already.size}/${partCount} part(s) already received`));
|
|
99
|
+
for (let index = 0; index < partCount; index++) {
|
|
100
|
+
if (already.has(index)) continue;
|
|
101
|
+
const start = index * partSize;
|
|
102
|
+
const length = Math.min(partSize, entry.decl.byte_size - start);
|
|
103
|
+
const bytes = readFilePart(entry.absPath, start, length);
|
|
104
|
+
let lastError;
|
|
105
|
+
let sent = false;
|
|
106
|
+
for (let attempt = 1; attempt <= PART_RETRIES; attempt++) try {
|
|
107
|
+
await uploadBinaryPart(t, toolId, init.upload_id, entry.platform, index, bytes);
|
|
108
|
+
sent = true;
|
|
109
|
+
break;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
lastError = e;
|
|
112
|
+
const status = e.status;
|
|
113
|
+
if (status && status < 500 && status !== 429) throw e;
|
|
114
|
+
if (attempt < PART_RETRIES && retryDelayMs > 0) await new Promise((r) => setTimeout(r, retryDelayMs * attempt));
|
|
115
|
+
}
|
|
116
|
+
if (!sent) throw lastError;
|
|
117
|
+
outcome.partsSent += 1;
|
|
118
|
+
log(dim(`↑ ${entry.platform}: part ${index + 1}/${partCount} (${length} bytes)`));
|
|
119
|
+
}
|
|
120
|
+
outcome.uploaded.push(entry.platform);
|
|
121
|
+
}
|
|
122
|
+
return outcome;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Reusable direct-upload flow: hash artifacts → mint upload token → init →
|
|
126
|
+
* push missing parts → finalize. Shared by the standalone
|
|
127
|
+
* `executa upload-binaries` command, `executa publish` (pre-freeze upload)
|
|
128
|
+
* and `apps cut` (bundled-executa pre-upload).
|
|
129
|
+
*
|
|
130
|
+
* Throws `CliError` on any failure; all-`exists` plans finalize instantly
|
|
131
|
+
* (content-addressed idempotency), so re-runs are cheap.
|
|
132
|
+
*/
|
|
133
|
+
async function uploadExecutaBinaries(opts) {
|
|
134
|
+
const log = opts.log ?? (() => {});
|
|
135
|
+
const { host, dir, manifest, toolId } = opts;
|
|
136
|
+
log(dim(`hashing artifacts for ${toolId} (v${manifest.version})…`));
|
|
137
|
+
const plan = await resolveArtifactPlan(manifest, dir, toolId);
|
|
138
|
+
let uploadToken = opts.uploadToken;
|
|
139
|
+
if (!uploadToken) {
|
|
140
|
+
if (!opts.client) throw new CliError("uploadExecutaBinaries needs either a PAT client or a pre-minted uploadToken", 2);
|
|
141
|
+
({upload_token: uploadToken} = await mintUploadSession(opts.client, toolId));
|
|
142
|
+
}
|
|
143
|
+
const transport = {
|
|
144
|
+
host,
|
|
145
|
+
uploadToken,
|
|
146
|
+
fetchImpl: opts.fetchImpl
|
|
147
|
+
};
|
|
148
|
+
const assets = {};
|
|
149
|
+
for (const e of plan) assets[e.platform] = e.decl;
|
|
150
|
+
const init = await initBinaryUpload(transport, toolId, {
|
|
151
|
+
executa_version: manifest.version,
|
|
152
|
+
assets,
|
|
153
|
+
dry_run: opts.dryRun === true
|
|
154
|
+
});
|
|
155
|
+
const planOut = {};
|
|
156
|
+
for (const e of plan) planOut[e.platform] = {
|
|
157
|
+
...e.decl,
|
|
158
|
+
status: init.assets[e.platform]?.status
|
|
159
|
+
};
|
|
160
|
+
if (opts.dryRun) return {
|
|
161
|
+
toolId,
|
|
162
|
+
version: manifest.version,
|
|
163
|
+
uploadId: init.upload_id,
|
|
164
|
+
dryRun: true,
|
|
165
|
+
skipped: plan.filter((e) => init.assets[e.platform]?.status === "exists").map((e) => e.platform),
|
|
166
|
+
uploaded: [],
|
|
167
|
+
plan: planOut,
|
|
168
|
+
assets: {}
|
|
169
|
+
};
|
|
170
|
+
const outcome = await uploadPlannedParts(transport, toolId, plan, init, log);
|
|
171
|
+
const finalized = await finalizeBinaryUpload(transport, toolId, init.upload_id);
|
|
172
|
+
return {
|
|
173
|
+
toolId,
|
|
174
|
+
version: manifest.version,
|
|
175
|
+
uploadId: init.upload_id,
|
|
176
|
+
dryRun: false,
|
|
177
|
+
skipped: outcome.skipped,
|
|
178
|
+
uploaded: outcome.uploaded,
|
|
179
|
+
plan: planOut,
|
|
180
|
+
assets: finalized.assets
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async function runExecutaUploadBinaries(opts) {
|
|
184
|
+
return withErrorHandling(async () => {
|
|
185
|
+
const dir = resolve(opts.dir ?? process.cwd());
|
|
186
|
+
const manifest = loadExecutaManifest(dir);
|
|
187
|
+
const log = (msg) => {
|
|
188
|
+
if (!opts.json) console.log(msg);
|
|
189
|
+
};
|
|
190
|
+
let client;
|
|
191
|
+
let host;
|
|
192
|
+
let uploadToken;
|
|
193
|
+
if (opts.oidc) {
|
|
194
|
+
const rawHost = opts.host ?? process.env.ANNA_APP_HOST;
|
|
195
|
+
if (!rawHost) throw new CliError("--oidc needs the Nexus host — pass --host or set ANNA_APP_HOST", 2);
|
|
196
|
+
host = canonicalHost(rawHost);
|
|
197
|
+
log(dim(`using GitHub OIDC trusted publishing (host=${host})`));
|
|
198
|
+
} else {
|
|
199
|
+
const resolved = resolveClient({
|
|
200
|
+
account: opts.account,
|
|
201
|
+
host: opts.host,
|
|
202
|
+
fetchImpl: opts.fetchImpl
|
|
203
|
+
});
|
|
204
|
+
client = resolved.client;
|
|
205
|
+
host = resolved.host;
|
|
206
|
+
log(dim(`using PAT from ${resolved.source} (host=${host})`));
|
|
207
|
+
}
|
|
208
|
+
let toolId = opts.toolId;
|
|
209
|
+
if (!toolId) {
|
|
210
|
+
const identity = readExecutaIdentity(dir);
|
|
211
|
+
if (!identity) throw new CliError(`no .anna/executa.json identity cache in ${dir} — run \`anna-app executa publish\` (standalone) or \`anna-app apps push\` (bundled) once to mint the executa, or pass --tool-id`, 4);
|
|
212
|
+
if (identity.host !== canonicalHost(host)) throw new CliError(`identity cache in ${dir} belongs to ${identity.host}, but the current target is ${canonicalHost(host)} — use --account/--host or re-publish against this host`, 4);
|
|
213
|
+
toolId = identity.tool_id;
|
|
214
|
+
}
|
|
215
|
+
if (opts.oidc) {
|
|
216
|
+
const idToken = await fetchGitHubIdToken({ fetchImpl: opts.fetchImpl });
|
|
217
|
+
({upload_token: uploadToken} = await mintUploadSessionViaOidc({
|
|
218
|
+
host,
|
|
219
|
+
toolId,
|
|
220
|
+
oidcToken: idToken,
|
|
221
|
+
fetchImpl: opts.fetchImpl
|
|
222
|
+
}));
|
|
223
|
+
log(dim("✓ upload token minted via OIDC exchange (no PAT involved)"));
|
|
224
|
+
}
|
|
225
|
+
const result = await uploadExecutaBinaries({
|
|
226
|
+
client,
|
|
227
|
+
host,
|
|
228
|
+
dir,
|
|
229
|
+
manifest,
|
|
230
|
+
toolId,
|
|
231
|
+
uploadToken,
|
|
232
|
+
dryRun: opts.dryRun === true,
|
|
233
|
+
fetchImpl: opts.fetchImpl,
|
|
234
|
+
log
|
|
235
|
+
});
|
|
236
|
+
if (opts.dryRun) {
|
|
237
|
+
if (opts.json) {
|
|
238
|
+
console.log(JSON.stringify({
|
|
239
|
+
tool_id: toolId,
|
|
240
|
+
version: manifest.version,
|
|
241
|
+
dry_run: true,
|
|
242
|
+
upload_id: result.uploadId,
|
|
243
|
+
assets: result.plan
|
|
244
|
+
}, null, 2));
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
console.log(yellow(`[dry-run] upload plan for ${toolId} v${manifest.version}:`));
|
|
248
|
+
for (const [platform, decl] of Object.entries(result.plan)) {
|
|
249
|
+
const status = decl.status ?? "?";
|
|
250
|
+
console.log(` ${platform}: ${status === "exists" ? green("exists (skip)") : cyan("upload")} ` + dim(`${decl.byte_size} bytes, sha256=${decl.sha256.slice(0, 12)}…`));
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
if (opts.json) {
|
|
255
|
+
console.log(JSON.stringify({
|
|
256
|
+
tool_id: toolId,
|
|
257
|
+
version: manifest.version,
|
|
258
|
+
host,
|
|
259
|
+
upload_id: result.uploadId,
|
|
260
|
+
skipped: result.skipped,
|
|
261
|
+
uploaded: result.uploaded,
|
|
262
|
+
assets: result.assets
|
|
263
|
+
}, null, 2));
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
console.log(green(`✓ ${toolId}: binaries published `) + dim(`(${result.uploaded.length} uploaded, ${result.skipped.length} cached)`));
|
|
267
|
+
for (const [platform, a] of Object.entries(result.assets)) {
|
|
268
|
+
console.log(` ${bold(platform)} ${dim(`sha256=${a.sha256.slice(0, 12)}…`)}`);
|
|
269
|
+
console.log(` ${cyan(a.url)}`);
|
|
270
|
+
}
|
|
271
|
+
console.log(dim("→ binaries are served from the platform CDN; `apps cut` / release will pin these hashes"));
|
|
272
|
+
return 0;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
//#endregion
|
|
277
|
+
export { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "./credentials-BTv2IfUZ.js";
|
|
2
|
+
import "./client-D-_z1ALk.js";
|
|
3
|
+
import "./_lifecycle-shared-RyiRITI5.js";
|
|
4
|
+
import "./executa-cache-BFoUtb4J.js";
|
|
5
|
+
import "./manifest-CAF3_r1T.js";
|
|
6
|
+
import "./binary-upload-CKMU5Rfw.js";
|
|
7
|
+
import { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts } from "./executa-upload-binaries-DURJTXRI.js";
|
|
8
|
+
|
|
9
|
+
export { runExecutaUploadBinaries };
|
|
@@ -27,20 +27,29 @@ function isRemoteAsset(src) {
|
|
|
27
27
|
/**
|
|
28
28
|
* Build the metadata payload from the manifest (only defined fields). The
|
|
29
29
|
* logo is handled separately and never appears here when sourced from a file.
|
|
30
|
+
*
|
|
31
|
+
* Optional listing URLs (cover/homepage/support/privacy) are *clearable*:
|
|
32
|
+
* `loadAppManifest` normalises an explicit `""`/`null` in the local metadata
|
|
33
|
+
* file to the `""` sentinel, which is forwarded on the PATCH so the server
|
|
34
|
+
* value is emptied (the store UI hides falsy links). An absent key stays
|
|
35
|
+
* `undefined` and is omitted from the payload — "leave unchanged".
|
|
30
36
|
*/
|
|
31
37
|
function buildMeta(manifest) {
|
|
32
38
|
const meta = {};
|
|
33
39
|
const add = (key, value) => {
|
|
34
40
|
if (value !== void 0 && value !== null && value !== "") meta[key] = value;
|
|
35
41
|
};
|
|
42
|
+
const addClearable = (key, value) => {
|
|
43
|
+
if (value !== void 0) meta[key] = value;
|
|
44
|
+
};
|
|
36
45
|
add("name", manifest.name);
|
|
37
46
|
add("category", manifest.category);
|
|
38
47
|
add("tagline", manifest.tagline);
|
|
39
48
|
add("description", manifest.description);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
addClearable("cover_url", manifest.coverUrl);
|
|
50
|
+
addClearable("homepage_url", manifest.homepageUrl);
|
|
51
|
+
addClearable("support_url", manifest.supportUrl);
|
|
52
|
+
addClearable("privacy_url", manifest.privacyUrl);
|
|
44
53
|
add("pricing_model", manifest.pricingModel);
|
|
45
54
|
return meta;
|
|
46
55
|
}
|