@anna-ai/cli 0.1.32 → 0.1.34
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-CrBRnzf4.js} +3 -3
- package/dist/{apps-dvknZYI2.js → apps-C_Oln_om.js} +1 -1
- package/dist/apps-cut-C14A3V93.js +159 -0
- package/dist/{apps-destructive-B6RFjokU.js → apps-destructive-a2YBmWFm.js} +2 -2
- package/dist/{apps-discard-hVZxAQyj.js → apps-discard-B49iiOX6.js} +12 -10
- package/dist/{apps-grants-DnXCnkH5.js → apps-grants-lmllFB02.js} +1 -1
- package/dist/apps-publish-B9CnifEx.js +17 -0
- package/dist/{apps-publish-BKqVsS1W.js → apps-publish-qc_E4b_R.js} +7 -7
- package/dist/{apps-push-B3wZr9B4.js → apps-push-DnINd0Cx.js} +14 -12
- package/dist/{apps-release-DZK30eF_.js → apps-release--7R2cmPg.js} +7 -6
- package/dist/{apps-rename-slug-D2BrWasG.js → apps-rename-slug-CGykNHM5.js} +3 -3
- package/dist/{apps-status-Ca6MdXZd.js → apps-status-CcCoYu6i.js} +2 -2
- 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-dy9j-5n8.js} +4 -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 +74 -41
- 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-Ah6J7jYd.js → executa-publish-CGz5wM1Q.js} +20 -4
- package/dist/executa-publish-DHICU6S0.js +11 -0
- 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-CYMtWJU7.js +9 -0
- package/dist/executa-upload-binaries-Do5Sq64E.js +277 -0
- package/dist/{manifest-DOMrcdCK.js → manifest-DlIcwLQs.js} +56 -1
- package/dist/{publish-GpvkaBIx.js → publish-DW_D3uQ4.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-B8KdAh5S.js} +5 -5
- 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/{listing-meta-BlY7XMrw.js → listing-meta-C0HsNjIB.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,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-DlIcwLQs.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-Do5Sq64E.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";
|
|
@@ -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";
|
|
@@ -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-DlIcwLQs.js";
|
|
6
|
+
import "./executas-CvQY_w2U.js";
|
|
7
|
+
import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
|
|
8
|
+
import "./binary-upload-CKMU5Rfw.js";
|
|
9
|
+
import "./executa-upload-binaries-Do5Sq64E.js";
|
|
10
|
+
|
|
11
|
+
export { runExecutaPublish };
|
|
@@ -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,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-DlIcwLQs.js";
|
|
6
|
+
import "./binary-upload-CKMU5Rfw.js";
|
|
7
|
+
import { expandArtifactPath, resolveArtifactPlan, runExecutaUploadBinaries, uploadExecutaBinaries, uploadPlannedParts } from "./executa-upload-binaries-Do5Sq64E.js";
|
|
8
|
+
|
|
9
|
+
export { runExecutaUploadBinaries };
|
|
@@ -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-DlIcwLQs.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 };
|
|
@@ -206,15 +206,19 @@ function parseDistributionProfile(d, ctx) {
|
|
|
206
206
|
const executableName = pickString(d, "executable_name");
|
|
207
207
|
const supportsProtocol = pickBool(d, "supports_protocol");
|
|
208
208
|
const binaryUrls = parseBinaryUrls(d["binary_urls"], ctx);
|
|
209
|
+
const binaryArtifacts = parseBinaryArtifacts(d["binary_artifacts"], ctx);
|
|
209
210
|
const capabilities = parseCapabilities(d["capabilities"], ctx);
|
|
210
211
|
if (PACKAGE_BACKED.has(distType) && !packageName) throw new CliError(`executa.json \`${ctx}.package_name\` is required for \`${ctx}.type: "${distType}"\``, 4);
|
|
211
|
-
if (distType
|
|
212
|
+
if (binaryArtifacts && distType !== "binary") throw new CliError(`executa.json \`${ctx}.binary_artifacts\` is only valid for \`${ctx}.type: "binary"\``, 4);
|
|
213
|
+
if (binaryArtifacts && binaryUrls) throw new CliError(`executa.json \`${ctx}\` declares BOTH \`binary_urls\` and \`binary_artifacts\` — pick exactly one: \`binary_urls\` (a public GET-able source the platform pull-mirrors at cut) or \`binary_artifacts\` (local files pushed directly via \`anna-app executa upload-binaries\`; works for private repos)`, 4);
|
|
214
|
+
if (distType === "binary" && (!binaryUrls || Object.keys(binaryUrls).length === 0) && (!binaryArtifacts || Object.keys(binaryArtifacts).length === 0)) throw new CliError(`executa.json \`${ctx}\` requires exactly one of \`binary_urls\` (non-empty) or \`binary_artifacts\` (non-empty) for \`${ctx}.type: "binary"\``, 4);
|
|
212
215
|
return {
|
|
213
216
|
type: distType,
|
|
214
217
|
packageName,
|
|
215
218
|
executableName,
|
|
216
219
|
supportsProtocol,
|
|
217
220
|
binaryUrls,
|
|
221
|
+
binaryArtifacts,
|
|
218
222
|
capabilities
|
|
219
223
|
};
|
|
220
224
|
}
|
|
@@ -238,6 +242,57 @@ function parseBinaryUrls(value, ctx = "distribution") {
|
|
|
238
242
|
}
|
|
239
243
|
return out;
|
|
240
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Canonical `<os>-<arch>` platform keys accepted for `binary_artifacts`
|
|
247
|
+
* (mirrors ALLOWED_PLATFORMS in matrix-nexus
|
|
248
|
+
* src/services/executa_binary_upload.py — both ends validate independently).
|
|
249
|
+
*/
|
|
250
|
+
const BINARY_ARTIFACT_PLATFORMS = new Set([
|
|
251
|
+
"darwin-arm64",
|
|
252
|
+
"darwin-x86_64",
|
|
253
|
+
"linux-x86_64",
|
|
254
|
+
"linux-arm64",
|
|
255
|
+
"linux-aarch64",
|
|
256
|
+
"windows-x86_64",
|
|
257
|
+
"windows-arm64"
|
|
258
|
+
]);
|
|
259
|
+
const MAX_BINARY_ARTIFACT_PLATFORMS = 8;
|
|
260
|
+
/** Infer the archive format from a path suffix (`.tar.gz`/`.tgz`/`.zip`). */
|
|
261
|
+
function inferArtifactFormat(path) {
|
|
262
|
+
const lower = path.toLowerCase();
|
|
263
|
+
if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) return "tar.gz";
|
|
264
|
+
if (lower.endsWith(".zip")) return "zip";
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
function parseBinaryArtifacts(value, ctx = "distribution") {
|
|
268
|
+
if (value === void 0 || value === null) return void 0;
|
|
269
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new CliError(`executa.json \`${ctx}.binary_artifacts\` must be an object mapping platform → { path, entrypoint?, format? }`, 4);
|
|
270
|
+
const entries = Object.entries(value);
|
|
271
|
+
if (entries.length > MAX_BINARY_ARTIFACT_PLATFORMS) throw new CliError(`executa.json \`${ctx}.binary_artifacts\` declares more than ${MAX_BINARY_ARTIFACT_PLATFORMS} platforms`, 4);
|
|
272
|
+
const out = {};
|
|
273
|
+
for (const [platform, decl] of entries) {
|
|
274
|
+
if (!BINARY_ARTIFACT_PLATFORMS.has(platform)) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` — unknown platform key; expected one of ` + [...BINARY_ARTIFACT_PLATFORMS].join(" | "), 4);
|
|
275
|
+
if (decl === null || typeof decl !== "object" || Array.isArray(decl)) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` must be an object \`{ path, entrypoint?, format? }\``, 4);
|
|
276
|
+
const d = decl;
|
|
277
|
+
const path = pickString(d, "path");
|
|
278
|
+
if (!path || !path.trim()) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` missing required \`path\``, 4);
|
|
279
|
+
const declared = pickString(d, "format");
|
|
280
|
+
let format;
|
|
281
|
+
if (declared !== void 0) if (declared === "tar.gz" || declared === "tgz") format = "tar.gz";
|
|
282
|
+
else if (declared === "zip") format = "zip";
|
|
283
|
+
else throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"].format\` "${declared}" is invalid — expected tar.gz | tgz | zip (bare executables must be archived)`, 4);
|
|
284
|
+
else {
|
|
285
|
+
format = inferArtifactFormat(path);
|
|
286
|
+
if (!format) throw new CliError(`executa.json \`${ctx}.binary_artifacts["${platform}"]\` — cannot infer archive format from "${path}"; use a .tar.gz/.tgz/.zip suffix or set \`format\` explicitly`, 4);
|
|
287
|
+
}
|
|
288
|
+
out[platform] = {
|
|
289
|
+
path,
|
|
290
|
+
entrypoint: pickString(d, "entrypoint"),
|
|
291
|
+
format
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
241
296
|
function parseCapabilities(value, ctx = "distribution") {
|
|
242
297
|
if (value === void 0 || value === null) return void 0;
|
|
243
298
|
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) throw new CliError(`executa.json \`${ctx}.capabilities\` must be an array of strings`, 4);
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import "./apps-DlVsVHQ5.js";
|
|
3
3
|
import { CliError } from "./client-D-_z1ALk.js";
|
|
4
|
-
import { withErrorHandling } from "./_lifecycle-shared-
|
|
5
|
-
import "./
|
|
6
|
-
import "./executas-
|
|
7
|
-
import "./
|
|
8
|
-
import
|
|
9
|
-
import "./
|
|
10
|
-
import "./
|
|
11
|
-
import "./
|
|
12
|
-
import "./app-
|
|
13
|
-
import
|
|
4
|
+
import { withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
5
|
+
import "./executa-cache-BFoUtb4J.js";
|
|
6
|
+
import "./bundled-executas-rSIkaA6-.js";
|
|
7
|
+
import "./manifest-DlIcwLQs.js";
|
|
8
|
+
import "./executas-CvQY_w2U.js";
|
|
9
|
+
import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
|
|
10
|
+
import "./binary-upload-CKMU5Rfw.js";
|
|
11
|
+
import "./executa-upload-binaries-Do5Sq64E.js";
|
|
12
|
+
import "./app-cache-Cr3tKbux.js";
|
|
13
|
+
import "./listing-meta-C0HsNjIB.js";
|
|
14
|
+
import "./app-bundle-upload-CrBRnzf4.js";
|
|
15
|
+
import { runAppsPublish } from "./apps-publish-qc_E4b_R.js";
|
|
14
16
|
import { resolve } from "node:path";
|
|
15
17
|
import { existsSync } from "node:fs";
|
|
16
18
|
import { red, yellow } from "kleur/colors";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
-
import { BridgeRequestError } from "./bridge-
|
|
2
|
+
import { BridgeRequestError } from "./bridge-Dvck0e9c.js";
|
|
3
3
|
import { dirname, join, normalize, resolve } from "node:path";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createReadStream, existsSync, readFileSync, statSync, watch } from "node:fs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
|
-
import { hostOf, requireAccount, withCode } from "./dev-account-
|
|
2
|
+
import { hostOf, requireAccount, withCode } from "./dev-account-CfVdGa9j.js";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import "./client-D-_z1ALk.js";
|
|
3
|
-
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-
|
|
3
|
+
import { resolveClient, withErrorHandling } from "./_lifecycle-shared-RyiRITI5.js";
|
|
4
4
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
5
5
|
|
|
6
6
|
//#region src/api/tokens.ts
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { canonicalHost } from "./credentials-BTv2IfUZ.js";
|
|
2
2
|
import { createApp, findAppBySlug, getApp } from "./apps-DlVsVHQ5.js";
|
|
3
3
|
import { CliError } from "./client-D-_z1ALk.js";
|
|
4
|
-
import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { runExecutaInstall } from "./executa-install-
|
|
8
|
-
import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-
|
|
4
|
+
import { parseExecutaIdOverrides, readExecutasLock, substituteBundledRefs, validateBundledHandles, writeBundleToolIdSidecar, writeExecutasLock } from "./bundled-executas-rSIkaA6-.js";
|
|
5
|
+
import { loadExecutaManifest } from "./manifest-DlIcwLQs.js";
|
|
6
|
+
import { runExecutaPublish } from "./executa-publish-CGz5wM1Q.js";
|
|
7
|
+
import { runExecutaInstall } from "./executa-install-Dz7hBUsn.js";
|
|
8
|
+
import { appCacheMatches, readAppIdentity, writeAppIdentity } from "./app-cache-Cr3tKbux.js";
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
10
10
|
import { dim, green, yellow } from "kleur/colors";
|
|
11
11
|
import { homedir } from "node:os";
|