@enactprotocol/api 2.0.0
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/package.json +34 -0
- package/src/attestations.ts +461 -0
- package/src/auth.ts +293 -0
- package/src/client.ts +349 -0
- package/src/download.ts +298 -0
- package/src/index.ts +109 -0
- package/src/publish.ts +316 -0
- package/src/search.ts +147 -0
- package/src/trust.ts +203 -0
- package/src/types.ts +468 -0
- package/src/utils.ts +86 -0
package/src/download.ts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool download functionality
|
|
3
|
+
* Implements bundle download and tool info retrieval
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { EnactApiClient } from "./client";
|
|
7
|
+
import type { ToolMetadata, ToolVersionDetails } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Download options
|
|
11
|
+
*/
|
|
12
|
+
export interface DownloadOptions {
|
|
13
|
+
/** Tool name (e.g., "alice/utils/greeter") */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Tool version (e.g., "1.2.0") */
|
|
16
|
+
version: string;
|
|
17
|
+
/** Verify bundle hash after download */
|
|
18
|
+
verify?: boolean | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Download result
|
|
23
|
+
*/
|
|
24
|
+
export interface DownloadResult {
|
|
25
|
+
/** Downloaded bundle data */
|
|
26
|
+
data: ArrayBuffer;
|
|
27
|
+
/** Bundle hash (sha256) */
|
|
28
|
+
hash: string;
|
|
29
|
+
/** Content length in bytes */
|
|
30
|
+
size: number;
|
|
31
|
+
/** Content type */
|
|
32
|
+
contentType: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool info (metadata) v2
|
|
37
|
+
*/
|
|
38
|
+
export interface ToolInfo {
|
|
39
|
+
/** Tool name */
|
|
40
|
+
name: string;
|
|
41
|
+
/** Tool description */
|
|
42
|
+
description: string;
|
|
43
|
+
/** Tool tags */
|
|
44
|
+
tags: string[];
|
|
45
|
+
/** SPDX license */
|
|
46
|
+
license: string;
|
|
47
|
+
/** Author info */
|
|
48
|
+
author: {
|
|
49
|
+
username: string;
|
|
50
|
+
avatarUrl?: string | undefined;
|
|
51
|
+
};
|
|
52
|
+
/** Repository URL */
|
|
53
|
+
repository?: string | undefined;
|
|
54
|
+
/** Creation timestamp */
|
|
55
|
+
createdAt: Date;
|
|
56
|
+
/** Last update timestamp */
|
|
57
|
+
updatedAt: Date;
|
|
58
|
+
/** Latest version */
|
|
59
|
+
latestVersion: string;
|
|
60
|
+
/** All available versions (paginated) */
|
|
61
|
+
versions: Array<{
|
|
62
|
+
version: string;
|
|
63
|
+
publishedAt: Date;
|
|
64
|
+
downloads: number;
|
|
65
|
+
bundleHash: string;
|
|
66
|
+
yanked: boolean;
|
|
67
|
+
}>;
|
|
68
|
+
/** Total number of versions */
|
|
69
|
+
versionsTotal: number;
|
|
70
|
+
/** Total downloads */
|
|
71
|
+
totalDownloads: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Version-specific tool info v2
|
|
76
|
+
*/
|
|
77
|
+
export interface ToolVersionInfo {
|
|
78
|
+
/** Tool name */
|
|
79
|
+
name: string;
|
|
80
|
+
/** Version */
|
|
81
|
+
version: string;
|
|
82
|
+
/** Description */
|
|
83
|
+
description: string;
|
|
84
|
+
/** License */
|
|
85
|
+
license: string;
|
|
86
|
+
/** Whether yanked */
|
|
87
|
+
yanked: boolean;
|
|
88
|
+
/** Yank reason */
|
|
89
|
+
yankReason?: string | undefined;
|
|
90
|
+
/** Replacement version */
|
|
91
|
+
yankReplacement?: string | undefined;
|
|
92
|
+
/** Yanked timestamp */
|
|
93
|
+
yankedAt?: Date | undefined;
|
|
94
|
+
/** Full manifest */
|
|
95
|
+
manifest: Record<string, unknown>;
|
|
96
|
+
/** Bundle info */
|
|
97
|
+
bundle: {
|
|
98
|
+
hash: string;
|
|
99
|
+
size: number;
|
|
100
|
+
downloadUrl: string;
|
|
101
|
+
};
|
|
102
|
+
/** Attestations */
|
|
103
|
+
attestations: Array<{
|
|
104
|
+
auditor: string;
|
|
105
|
+
auditorProvider: string;
|
|
106
|
+
signedAt: Date;
|
|
107
|
+
rekorLogId: string;
|
|
108
|
+
verified: boolean;
|
|
109
|
+
}>;
|
|
110
|
+
/** Published by */
|
|
111
|
+
publishedBy: {
|
|
112
|
+
username: string;
|
|
113
|
+
avatarUrl?: string | undefined;
|
|
114
|
+
};
|
|
115
|
+
/** Publication timestamp */
|
|
116
|
+
publishedAt: Date;
|
|
117
|
+
/** Download count */
|
|
118
|
+
downloads: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Convert raw tool metadata to ToolInfo
|
|
123
|
+
*/
|
|
124
|
+
function toToolInfo(raw: ToolMetadata): ToolInfo {
|
|
125
|
+
return {
|
|
126
|
+
name: raw.name,
|
|
127
|
+
description: raw.description,
|
|
128
|
+
tags: raw.tags,
|
|
129
|
+
license: raw.license,
|
|
130
|
+
author: {
|
|
131
|
+
username: raw.author.username,
|
|
132
|
+
avatarUrl: raw.author.avatar_url,
|
|
133
|
+
},
|
|
134
|
+
repository: raw.repository,
|
|
135
|
+
createdAt: new Date(raw.created_at),
|
|
136
|
+
updatedAt: new Date(raw.updated_at),
|
|
137
|
+
latestVersion: raw.latest_version,
|
|
138
|
+
versions: raw.versions.map((v) => ({
|
|
139
|
+
version: v.version,
|
|
140
|
+
publishedAt: new Date(v.published_at),
|
|
141
|
+
downloads: v.downloads,
|
|
142
|
+
bundleHash: v.bundle_hash,
|
|
143
|
+
yanked: v.yanked,
|
|
144
|
+
})),
|
|
145
|
+
versionsTotal: raw.versions_total,
|
|
146
|
+
totalDownloads: raw.total_downloads,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Convert raw version details to ToolVersionInfo
|
|
152
|
+
*/
|
|
153
|
+
function toToolVersionInfo(raw: ToolVersionDetails): ToolVersionInfo {
|
|
154
|
+
return {
|
|
155
|
+
name: raw.name,
|
|
156
|
+
version: raw.version,
|
|
157
|
+
description: raw.description,
|
|
158
|
+
license: raw.license,
|
|
159
|
+
yanked: raw.yanked,
|
|
160
|
+
yankReason: raw.yank_reason,
|
|
161
|
+
yankReplacement: raw.yank_replacement,
|
|
162
|
+
yankedAt: raw.yanked_at ? new Date(raw.yanked_at) : undefined,
|
|
163
|
+
manifest: raw.manifest,
|
|
164
|
+
bundle: {
|
|
165
|
+
hash: raw.bundle.hash,
|
|
166
|
+
size: raw.bundle.size,
|
|
167
|
+
downloadUrl: raw.bundle.download_url,
|
|
168
|
+
},
|
|
169
|
+
attestations: raw.attestations.map((a) => ({
|
|
170
|
+
auditor: a.auditor,
|
|
171
|
+
auditorProvider: a.auditor_provider,
|
|
172
|
+
signedAt: new Date(a.signed_at),
|
|
173
|
+
rekorLogId: a.rekor_log_id,
|
|
174
|
+
verified: a.verification?.verified ?? false,
|
|
175
|
+
})),
|
|
176
|
+
publishedBy: {
|
|
177
|
+
username: raw.published_by.username,
|
|
178
|
+
avatarUrl: raw.published_by.avatar_url,
|
|
179
|
+
},
|
|
180
|
+
publishedAt: new Date(raw.published_at),
|
|
181
|
+
downloads: raw.downloads,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get tool metadata
|
|
187
|
+
*
|
|
188
|
+
* @param client - API client instance
|
|
189
|
+
* @param name - Tool name (e.g., "alice/utils/greeter")
|
|
190
|
+
* @returns Tool metadata
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const info = await getToolInfo(client, "alice/utils/greeter");
|
|
195
|
+
* console.log(info.latestVersion);
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export async function getToolInfo(client: EnactApiClient, name: string): Promise<ToolInfo> {
|
|
199
|
+
const response = await client.get<ToolMetadata>(`/tools/${name}`);
|
|
200
|
+
return toToolInfo(response.data);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get version-specific tool information
|
|
205
|
+
*
|
|
206
|
+
* @param client - API client instance
|
|
207
|
+
* @param name - Tool name
|
|
208
|
+
* @param version - Tool version
|
|
209
|
+
* @returns Version-specific tool info
|
|
210
|
+
*/
|
|
211
|
+
export async function getToolVersion(
|
|
212
|
+
client: EnactApiClient,
|
|
213
|
+
name: string,
|
|
214
|
+
version: string
|
|
215
|
+
): Promise<ToolVersionInfo> {
|
|
216
|
+
const response = await client.get<ToolVersionDetails>(`/tools/${name}/versions/${version}`);
|
|
217
|
+
return toToolVersionInfo(response.data);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get attestations for a tool version
|
|
222
|
+
*
|
|
223
|
+
* @param client - API client instance
|
|
224
|
+
* @param name - Tool name
|
|
225
|
+
* @param version - Tool version
|
|
226
|
+
* @returns Attestation list
|
|
227
|
+
*/
|
|
228
|
+
export async function getAttestations(
|
|
229
|
+
client: EnactApiClient,
|
|
230
|
+
name: string,
|
|
231
|
+
version: string
|
|
232
|
+
): Promise<
|
|
233
|
+
Array<{
|
|
234
|
+
auditor: string;
|
|
235
|
+
auditorProvider: string;
|
|
236
|
+
signedAt: Date;
|
|
237
|
+
rekorLogId: string;
|
|
238
|
+
verified: boolean;
|
|
239
|
+
}>
|
|
240
|
+
> {
|
|
241
|
+
const response = await client.get<ToolVersionDetails>(`/tools/${name}/versions/${version}`);
|
|
242
|
+
return response.data.attestations.map((a) => ({
|
|
243
|
+
auditor: a.auditor,
|
|
244
|
+
auditorProvider: a.auditor_provider,
|
|
245
|
+
signedAt: new Date(a.signed_at),
|
|
246
|
+
rekorLogId: a.rekor_log_id,
|
|
247
|
+
verified: a.verification?.verified ?? false,
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Download a tool bundle (v2 with yank handling)
|
|
253
|
+
*
|
|
254
|
+
* @param client - API client instance
|
|
255
|
+
* @param options - Download options
|
|
256
|
+
* @returns Downloaded bundle data with metadata
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const result = await downloadBundle(client, {
|
|
261
|
+
* name: "alice/utils/greeter",
|
|
262
|
+
* version: "1.2.0",
|
|
263
|
+
* verify: true
|
|
264
|
+
* });
|
|
265
|
+
* await Bun.write("bundle.tar.gz", result.data);
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export async function downloadBundle(
|
|
269
|
+
client: EnactApiClient,
|
|
270
|
+
options: DownloadOptions & { acknowledgeYanked?: boolean }
|
|
271
|
+
): Promise<DownloadResult> {
|
|
272
|
+
const { name, version, acknowledgeYanked } = options;
|
|
273
|
+
|
|
274
|
+
// Build query params for yanked version acknowledgment
|
|
275
|
+
const params = acknowledgeYanked ? "?acknowledge_yanked=true" : "";
|
|
276
|
+
const response = await client.download(`/tools/${name}/versions/${version}/download${params}`);
|
|
277
|
+
|
|
278
|
+
const data = await response.arrayBuffer();
|
|
279
|
+
const contentType = response.headers.get("Content-Type") ?? "application/gzip";
|
|
280
|
+
const size = data.byteLength;
|
|
281
|
+
|
|
282
|
+
// Extract hash from ETag or compute it
|
|
283
|
+
let hash = response.headers.get("ETag")?.replace(/"/g, "") ?? "";
|
|
284
|
+
|
|
285
|
+
// If verify is true and we need to compute hash, do it
|
|
286
|
+
if (options.verify && !hash.startsWith("sha256:")) {
|
|
287
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
288
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
289
|
+
hash = `sha256:${hashArray.map((b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
data,
|
|
294
|
+
hash,
|
|
295
|
+
size,
|
|
296
|
+
contentType,
|
|
297
|
+
};
|
|
298
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @enactprotocol/api - Registry API client for Enact tools (v2)
|
|
3
|
+
*
|
|
4
|
+
* This package provides HTTP client functionality for interacting with
|
|
5
|
+
* the Enact registry v2, including:
|
|
6
|
+
* - OAuth authentication
|
|
7
|
+
* - Tool search and discovery
|
|
8
|
+
* - Bundle download with yank handling
|
|
9
|
+
* - Tool publishing (multipart upload)
|
|
10
|
+
* - Attestation management
|
|
11
|
+
* - Trust configuration
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const VERSION = "0.2.0";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
export * from "./types";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Client
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export { EnactApiClient, createApiClient } from "./client";
|
|
29
|
+
export type { ApiClientOptions } from "./client";
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Search
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
export { searchTools } from "./search";
|
|
36
|
+
export type { SearchOptions, SearchResult, SearchResponse } from "./search";
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Download
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
export { downloadBundle, getToolInfo, getToolVersion, getAttestations } from "./download";
|
|
43
|
+
export type { DownloadOptions, DownloadResult, ToolInfo, ToolVersionInfo } from "./download";
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Publish
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
publishTool,
|
|
51
|
+
createBundle,
|
|
52
|
+
submitAttestation,
|
|
53
|
+
yankVersion,
|
|
54
|
+
unyankVersion,
|
|
55
|
+
deleteTool,
|
|
56
|
+
} from "./publish";
|
|
57
|
+
export type {
|
|
58
|
+
PublishOptions,
|
|
59
|
+
PublishResult,
|
|
60
|
+
BundleInfo,
|
|
61
|
+
SubmitAttestationOptions,
|
|
62
|
+
AttestationResult,
|
|
63
|
+
} from "./publish";
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Auth (v2 OAuth)
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
initiateLogin,
|
|
71
|
+
exchangeCodeForToken,
|
|
72
|
+
refreshAccessToken,
|
|
73
|
+
getCurrentUser,
|
|
74
|
+
authenticate,
|
|
75
|
+
logout,
|
|
76
|
+
getAuthStatus,
|
|
77
|
+
getUserProfile,
|
|
78
|
+
submitFeedback,
|
|
79
|
+
} from "./auth";
|
|
80
|
+
export type { AuthResult, AuthStatus } from "./auth";
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Attestations (v2)
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
getAttestations as getAttestationList,
|
|
88
|
+
submitAttestation as submitAttestationToRegistry,
|
|
89
|
+
revokeAttestation,
|
|
90
|
+
hasAttestation,
|
|
91
|
+
getAttestationBundle,
|
|
92
|
+
verifyAttestationLocally,
|
|
93
|
+
verifyAllAttestations,
|
|
94
|
+
hasTrustedAttestation,
|
|
95
|
+
} from "./attestations";
|
|
96
|
+
export type { AttestationListResponse, VerifiedAuditor } from "./attestations";
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Trust (v2)
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
getUserTrust,
|
|
104
|
+
updateMyTrust,
|
|
105
|
+
addTrustedAuditor,
|
|
106
|
+
removeTrustedAuditor,
|
|
107
|
+
userTrustsAuditor,
|
|
108
|
+
getMyTrustedAuditors,
|
|
109
|
+
} from "./trust";
|
package/src/publish.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool publishing functionality (v2)
|
|
3
|
+
* Implements bundle creation and publishing to registry
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { EnactApiClient } from "./client";
|
|
7
|
+
import type { AttestationResponse, PublishResponse } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Publish options
|
|
11
|
+
*/
|
|
12
|
+
export interface PublishOptions {
|
|
13
|
+
/** Tool name (e.g., "alice/utils/greeter") */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Tool version (e.g., "1.2.0") */
|
|
16
|
+
version: string;
|
|
17
|
+
/** Bundle data (tar.gz) */
|
|
18
|
+
bundle: ArrayBuffer | Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Publish result
|
|
23
|
+
*/
|
|
24
|
+
export interface PublishResult {
|
|
25
|
+
/** Tool name */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Published version */
|
|
28
|
+
version: string;
|
|
29
|
+
/** Publication timestamp */
|
|
30
|
+
publishedAt: Date;
|
|
31
|
+
/** Bundle hash */
|
|
32
|
+
bundleHash: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bundle info (for createBundle)
|
|
37
|
+
*/
|
|
38
|
+
export interface BundleInfo {
|
|
39
|
+
/** Bundle data */
|
|
40
|
+
data: Uint8Array;
|
|
41
|
+
/** SHA-256 hash */
|
|
42
|
+
hash: string;
|
|
43
|
+
/** Size in bytes */
|
|
44
|
+
size: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Attestation submission options (v2)
|
|
49
|
+
*/
|
|
50
|
+
export interface SubmitAttestationOptions {
|
|
51
|
+
/** Tool name */
|
|
52
|
+
name: string;
|
|
53
|
+
/** Tool version */
|
|
54
|
+
version: string;
|
|
55
|
+
/** Sigstore bundle (full bundle object) */
|
|
56
|
+
sigstoreBundle: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Attestation submission result (v2)
|
|
61
|
+
*/
|
|
62
|
+
export interface AttestationResult {
|
|
63
|
+
/** Auditor email */
|
|
64
|
+
auditor: string;
|
|
65
|
+
/** OAuth provider */
|
|
66
|
+
auditorProvider: string;
|
|
67
|
+
/** Signing timestamp */
|
|
68
|
+
signedAt: Date;
|
|
69
|
+
/** Rekor log ID */
|
|
70
|
+
rekorLogId: string;
|
|
71
|
+
/** Rekor log index */
|
|
72
|
+
rekorLogIndex?: number | undefined;
|
|
73
|
+
/** Verification result */
|
|
74
|
+
verification: {
|
|
75
|
+
verified: boolean;
|
|
76
|
+
verifiedAt: Date;
|
|
77
|
+
rekorVerified: boolean;
|
|
78
|
+
certificateVerified: boolean;
|
|
79
|
+
signatureVerified: boolean;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a bundle from tool directory
|
|
85
|
+
*
|
|
86
|
+
* This is a placeholder that would typically:
|
|
87
|
+
* 1. Read all files from the tool directory
|
|
88
|
+
* 2. Create a tar.gz archive
|
|
89
|
+
* 3. Compute SHA-256 hash
|
|
90
|
+
*
|
|
91
|
+
* @param toolDir - Path to tool directory
|
|
92
|
+
* @returns Bundle info with data and hash
|
|
93
|
+
*/
|
|
94
|
+
export async function createBundle(toolDir: string): Promise<BundleInfo> {
|
|
95
|
+
// This would use @enactprotocol/shared utilities to:
|
|
96
|
+
// 1. Read files from toolDir
|
|
97
|
+
// 2. Create tarball
|
|
98
|
+
// 3. Compute hash
|
|
99
|
+
|
|
100
|
+
// For now, this is a placeholder that throws
|
|
101
|
+
// The actual implementation would integrate with the shared package
|
|
102
|
+
throw new Error(`createBundle not yet implemented for: ${toolDir}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Publish a tool to the registry (v2 - multipart upload)
|
|
107
|
+
*
|
|
108
|
+
* @param client - API client instance (must be authenticated)
|
|
109
|
+
* @param options - Publish options with manifest and bundle
|
|
110
|
+
* @returns Publish result
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const bundle = await createBundle("./my-tool");
|
|
115
|
+
* const result = await publishTool(client, {
|
|
116
|
+
* name: "alice/utils/greeter",
|
|
117
|
+
* manifest: { enact: "2.0.0", name: "alice/utils/greeter", version: "1.2.0", ... },
|
|
118
|
+
* bundle: bundle.data,
|
|
119
|
+
* readme: "# My Tool\n\nDescription..."
|
|
120
|
+
* });
|
|
121
|
+
* console.log(`Published: ${result.bundleHash}`);
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export async function publishTool(
|
|
125
|
+
client: EnactApiClient,
|
|
126
|
+
options: {
|
|
127
|
+
name: string;
|
|
128
|
+
manifest: Record<string, unknown>;
|
|
129
|
+
bundle: ArrayBuffer | Uint8Array;
|
|
130
|
+
readme?: string | undefined;
|
|
131
|
+
}
|
|
132
|
+
): Promise<PublishResult> {
|
|
133
|
+
const { name, manifest, bundle, readme } = options;
|
|
134
|
+
|
|
135
|
+
// Create FormData for multipart upload
|
|
136
|
+
const formData = new FormData();
|
|
137
|
+
|
|
138
|
+
// Add manifest as JSON
|
|
139
|
+
formData.append("manifest", JSON.stringify(manifest));
|
|
140
|
+
|
|
141
|
+
// Add bundle as file
|
|
142
|
+
const bundleBlob =
|
|
143
|
+
bundle instanceof ArrayBuffer
|
|
144
|
+
? new Blob([bundle], { type: "application/gzip" })
|
|
145
|
+
: new Blob([bundle], { type: "application/gzip" });
|
|
146
|
+
formData.append("bundle", bundleBlob, "bundle.tar.gz");
|
|
147
|
+
|
|
148
|
+
// Add optional readme
|
|
149
|
+
if (readme) {
|
|
150
|
+
formData.append("readme", readme);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Make multipart request (v2 endpoint is POST /tools/{name})
|
|
154
|
+
const response = await fetch(`${client.getBaseUrl()}/tools/${name}`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
Authorization: client.getAuthToken() ? `Bearer ${client.getAuthToken()}` : "",
|
|
158
|
+
"User-Agent": client.getUserAgent() || "enact-cli/0.1.0",
|
|
159
|
+
},
|
|
160
|
+
body: formData,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
const errorText = await response.text();
|
|
165
|
+
throw new Error(`Publish failed: ${response.status} - ${errorText}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const json = (await response.json()) as { data: PublishResponse } | PublishResponse;
|
|
169
|
+
// Handle both wrapped and unwrapped responses
|
|
170
|
+
const data = "data" in json ? json.data : json;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
name: data.name,
|
|
174
|
+
version: data.version,
|
|
175
|
+
publishedAt: new Date(data.published_at),
|
|
176
|
+
bundleHash: data.bundle_hash,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Submit an attestation for a tool version (v2)
|
|
182
|
+
*
|
|
183
|
+
* @param client - API client instance (must be authenticated via OIDC)
|
|
184
|
+
* @param options - Attestation options
|
|
185
|
+
* @returns Attestation result
|
|
186
|
+
*/
|
|
187
|
+
export async function submitAttestation(
|
|
188
|
+
client: EnactApiClient,
|
|
189
|
+
options: SubmitAttestationOptions
|
|
190
|
+
): Promise<AttestationResult> {
|
|
191
|
+
const { name, version, sigstoreBundle } = options;
|
|
192
|
+
|
|
193
|
+
const response = await client.post<AttestationResponse>(
|
|
194
|
+
`/tools/${name}/versions/${version}/attestations`,
|
|
195
|
+
{
|
|
196
|
+
bundle: sigstoreBundle,
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
auditor: response.data.auditor,
|
|
202
|
+
auditorProvider: response.data.auditor_provider,
|
|
203
|
+
signedAt: new Date(response.data.signed_at),
|
|
204
|
+
rekorLogId: response.data.rekor_log_id,
|
|
205
|
+
rekorLogIndex: response.data.rekor_log_index,
|
|
206
|
+
verification: {
|
|
207
|
+
verified: response.data.verification.verified,
|
|
208
|
+
verifiedAt: new Date(response.data.verification.verified_at),
|
|
209
|
+
rekorVerified: response.data.verification.rekor_verified,
|
|
210
|
+
certificateVerified: response.data.verification.certificate_verified,
|
|
211
|
+
signatureVerified: response.data.verification.signature_verified,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Yank a tool version (v2)
|
|
218
|
+
*
|
|
219
|
+
* Yanked versions remain downloadable but are excluded from version listings
|
|
220
|
+
* by default and show warnings to users.
|
|
221
|
+
*
|
|
222
|
+
* @param client - API client instance (must be authenticated and owner)
|
|
223
|
+
* @param name - Tool name
|
|
224
|
+
* @param version - Version to yank
|
|
225
|
+
* @param options - Yank options (reason, replacement)
|
|
226
|
+
* @returns Yank result
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const result = await yankVersion(client, "alice/utils/greeter", "1.1.0", {
|
|
231
|
+
* reason: "Security vulnerability CVE-2025-1234",
|
|
232
|
+
* replacementVersion: "1.2.0"
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export async function yankVersion(
|
|
237
|
+
client: EnactApiClient,
|
|
238
|
+
name: string,
|
|
239
|
+
version: string,
|
|
240
|
+
options?: {
|
|
241
|
+
reason?: string | undefined;
|
|
242
|
+
replacementVersion?: string | undefined;
|
|
243
|
+
}
|
|
244
|
+
): Promise<{
|
|
245
|
+
yanked: true;
|
|
246
|
+
version: string;
|
|
247
|
+
reason?: string | undefined;
|
|
248
|
+
replacementVersion?: string | undefined;
|
|
249
|
+
yankedAt: Date;
|
|
250
|
+
}> {
|
|
251
|
+
const response = await client.post<{
|
|
252
|
+
yanked: true;
|
|
253
|
+
version: string;
|
|
254
|
+
reason?: string | undefined;
|
|
255
|
+
replacement_version?: string | undefined;
|
|
256
|
+
yanked_at: string;
|
|
257
|
+
}>(`/tools/${name}/versions/${version}/yank`, {
|
|
258
|
+
reason: options?.reason,
|
|
259
|
+
replacement_version: options?.replacementVersion,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
yanked: response.data.yanked,
|
|
264
|
+
version: response.data.version,
|
|
265
|
+
reason: response.data.reason,
|
|
266
|
+
replacementVersion: response.data.replacement_version,
|
|
267
|
+
yankedAt: new Date(response.data.yanked_at),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Unyank a tool version (v2)
|
|
273
|
+
*
|
|
274
|
+
* Restore a previously yanked version.
|
|
275
|
+
*
|
|
276
|
+
* @param client - API client instance (must be authenticated and owner)
|
|
277
|
+
* @param name - Tool name
|
|
278
|
+
* @param version - Version to unyank
|
|
279
|
+
* @returns Unyank result
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```ts
|
|
283
|
+
* const result = await unyankVersion(client, "alice/utils/greeter", "1.1.0");
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export async function unyankVersion(
|
|
287
|
+
client: EnactApiClient,
|
|
288
|
+
name: string,
|
|
289
|
+
version: string
|
|
290
|
+
): Promise<{
|
|
291
|
+
yanked: false;
|
|
292
|
+
version: string;
|
|
293
|
+
unyankedAt: Date;
|
|
294
|
+
}> {
|
|
295
|
+
const response = await client.post<{
|
|
296
|
+
yanked: false;
|
|
297
|
+
version: string;
|
|
298
|
+
unyanked_at: string;
|
|
299
|
+
}>(`/tools/${name}/versions/${version}/unyank`);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
yanked: response.data.yanked,
|
|
303
|
+
version: response.data.version,
|
|
304
|
+
unyankedAt: new Date(response.data.unyanked_at),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Delete a tool from the registry
|
|
310
|
+
*
|
|
311
|
+
* @param client - API client instance (must be authenticated and owner)
|
|
312
|
+
* @param name - Tool name to delete
|
|
313
|
+
*/
|
|
314
|
+
export async function deleteTool(client: EnactApiClient, name: string): Promise<void> {
|
|
315
|
+
await client.delete(`/tools/${name}`);
|
|
316
|
+
}
|