artifact-cafe 0.0.1 → 0.0.3
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/index.js +962 -275
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,135 +6,121 @@ import { defineCommand, runMain } from "citty";
|
|
|
6
6
|
// src/comments.ts
|
|
7
7
|
import path2 from "path";
|
|
8
8
|
|
|
9
|
-
// ../shared/src/
|
|
9
|
+
// ../shared/src/account.ts
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (p.includes("\\") || p.includes("\0")) return false;
|
|
14
|
-
if (p.startsWith("/") || p.startsWith("~")) return false;
|
|
15
|
-
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
16
|
-
}
|
|
17
|
-
var manifestFileSchema = z.object({
|
|
18
|
-
path: z.string().min(1).max(1024).refine(isSafeArtifactPath, { message: "path must be a safe relative path" }),
|
|
19
|
-
hash: z.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
20
|
-
size: z.number().int().positive(),
|
|
21
|
-
mimeType: z.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" })
|
|
22
|
-
});
|
|
23
|
-
var publishSourceSchema = z.object({
|
|
24
|
-
type: z.literal("cli"),
|
|
25
|
-
agent: z.string().optional(),
|
|
26
|
-
runId: z.string().optional()
|
|
27
|
-
});
|
|
28
|
-
var createPublishSessionSchema = z.object({
|
|
29
|
-
artifactId: z.string().nullable(),
|
|
30
|
-
title: z.string().max(200).optional(),
|
|
31
|
-
entryPath: z.string().optional(),
|
|
32
|
-
files: z.array(manifestFileSchema).min(1).max(1e3),
|
|
33
|
-
source: publishSourceSchema
|
|
34
|
-
});
|
|
35
|
-
var publishSessionResponseSchema = z.object({
|
|
36
|
-
publishSessionId: z.string(),
|
|
37
|
-
artifactId: z.string(),
|
|
38
|
-
versionId: z.string(),
|
|
39
|
-
filesToUpload: z.array(
|
|
40
|
-
z.object({
|
|
41
|
-
path: z.string(),
|
|
42
|
-
uploadUrl: z.string().url(),
|
|
43
|
-
method: z.literal("PUT")
|
|
44
|
-
})
|
|
45
|
-
),
|
|
46
|
-
skippedFiles: z.array(z.string()),
|
|
47
|
-
expiresAt: z.string()
|
|
11
|
+
var createApiKeySchema = z.object({
|
|
12
|
+
name: z.string().trim().min(1).max(60).optional()
|
|
48
13
|
});
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
14
|
+
var createdApiKeySchema = z.object({
|
|
15
|
+
createdAt: z.string(),
|
|
16
|
+
id: z.string(),
|
|
17
|
+
key: z.string(),
|
|
18
|
+
keyPrefix: z.string(),
|
|
19
|
+
name: z.string()
|
|
20
|
+
});
|
|
21
|
+
var apiKeySummarySchema = z.object({
|
|
22
|
+
createdAt: z.string(),
|
|
23
|
+
id: z.string(),
|
|
24
|
+
keyPrefix: z.string(),
|
|
25
|
+
lastUsedAt: z.string().nullable(),
|
|
26
|
+
name: z.string()
|
|
27
|
+
});
|
|
28
|
+
var apiKeyListResponseSchema = z.object({
|
|
29
|
+
keys: z.array(apiKeySummarySchema)
|
|
30
|
+
});
|
|
31
|
+
var revokeApiKeyResponseSchema = z.object({
|
|
32
|
+
revoked: z.literal(true)
|
|
33
|
+
});
|
|
34
|
+
var meResponseSchema = z.object({
|
|
35
|
+
// Best-effort profile field from our own user table; null when unknown.
|
|
36
|
+
email: z.string().nullable(),
|
|
37
|
+
// Present when the request was authenticated with an API key.
|
|
38
|
+
keyName: z.string().optional(),
|
|
39
|
+
userId: z.string()
|
|
57
40
|
});
|
|
58
|
-
var
|
|
41
|
+
var artifactListItemSchema = z.object({
|
|
42
|
+
createdAt: z.string(),
|
|
43
|
+
currentVersionNumber: z.number().int().positive().nullable(),
|
|
59
44
|
id: z.string(),
|
|
45
|
+
openCommentCount: z.number().int().nonnegative(),
|
|
60
46
|
slug: z.string(),
|
|
61
47
|
title: z.string(),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
48
|
+
updatedAt: z.string(),
|
|
49
|
+
url: z.string().url(),
|
|
50
|
+
versionCount: z.number().int().nonnegative()
|
|
51
|
+
});
|
|
52
|
+
var artifactListResponseSchema = z.object({
|
|
53
|
+
artifacts: z.array(artifactListItemSchema)
|
|
68
54
|
});
|
|
69
55
|
|
|
70
56
|
// ../shared/src/comments.ts
|
|
71
57
|
import { z as z2 } from "zod";
|
|
72
58
|
var elementSnapshotSchema = z2.object({
|
|
73
|
-
tag: z2.string().min(1).max(128),
|
|
74
|
-
id: z2.string().max(256).optional(),
|
|
75
59
|
classes: z2.string().max(256).optional(),
|
|
60
|
+
id: z2.string().max(256).optional(),
|
|
61
|
+
tag: z2.string().min(1).max(128),
|
|
76
62
|
text: z2.string().max(200).optional()
|
|
77
63
|
});
|
|
78
64
|
var pointAnchorSchema = z2.object({
|
|
79
|
-
|
|
65
|
+
element: elementSnapshotSchema.optional(),
|
|
80
66
|
// elementPath is the primary anchor; x/y are offsets within the
|
|
81
67
|
// element's box, normalized 0..1 (SPEC.md §8.1).
|
|
82
68
|
elementPath: z2.string().max(2048),
|
|
83
|
-
|
|
84
|
-
y: z2.number().min(0).max(1),
|
|
85
|
-
viewportWidth: z2.number().int().positive().max(1e5),
|
|
69
|
+
type: z2.literal("point"),
|
|
86
70
|
viewportHeight: z2.number().int().positive().max(1e5),
|
|
87
|
-
|
|
71
|
+
viewportWidth: z2.number().int().positive().max(1e5),
|
|
72
|
+
x: z2.number().min(0).max(1),
|
|
73
|
+
y: z2.number().min(0).max(1)
|
|
88
74
|
});
|
|
89
75
|
var textAnchorSchema = z2.object({
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
elementPath: z2.string().max(2048),
|
|
77
|
+
endOffset: z2.number().int().nonnegative().max(1e8),
|
|
92
78
|
prefix: z2.string().max(200).optional(),
|
|
93
|
-
|
|
79
|
+
quote: z2.string().min(1).max(1e3),
|
|
94
80
|
startOffset: z2.number().int().nonnegative().max(1e8),
|
|
95
|
-
|
|
96
|
-
|
|
81
|
+
suffix: z2.string().max(200).optional(),
|
|
82
|
+
type: z2.literal("text")
|
|
97
83
|
});
|
|
98
84
|
var anchorSchema = z2.discriminatedUnion("type", [
|
|
99
85
|
pointAnchorSchema,
|
|
100
86
|
textAnchorSchema
|
|
101
87
|
]);
|
|
102
88
|
var guestSchema = z2.object({
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
displayName: z2.string().min(1).max(64),
|
|
90
|
+
guestId: z2.string().min(16)
|
|
105
91
|
});
|
|
106
92
|
var createCommentThreadSchema = z2.object({
|
|
107
|
-
versionId: z2.string(),
|
|
108
|
-
guest: guestSchema.optional(),
|
|
109
93
|
anchor: anchorSchema,
|
|
110
|
-
body: z2.string().min(1).max(1e4)
|
|
94
|
+
body: z2.string().min(1).max(1e4),
|
|
95
|
+
guest: guestSchema.optional(),
|
|
96
|
+
versionId: z2.string()
|
|
111
97
|
});
|
|
112
98
|
var createCommentMessageSchema = z2.object({
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
body: z2.string().min(1).max(1e4),
|
|
100
|
+
guest: guestSchema.optional()
|
|
115
101
|
});
|
|
116
102
|
var threadActionSchema = z2.object({
|
|
117
103
|
guest: guestSchema.optional()
|
|
118
104
|
});
|
|
119
105
|
var commentAuthorSchema = z2.object({
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
displayName: z2.string(),
|
|
107
|
+
guestId: z2.string().nullable()
|
|
122
108
|
});
|
|
123
109
|
var commentMessageSchema = z2.object({
|
|
124
|
-
id: z2.string(),
|
|
125
|
-
body: z2.string(),
|
|
126
110
|
author: commentAuthorSchema,
|
|
127
|
-
|
|
111
|
+
body: z2.string(),
|
|
112
|
+
createdAt: z2.string(),
|
|
113
|
+
id: z2.string()
|
|
128
114
|
});
|
|
129
115
|
var commentThreadSchema = z2.object({
|
|
130
|
-
id: z2.string(),
|
|
131
|
-
artifactId: z2.string(),
|
|
132
|
-
versionId: z2.string(),
|
|
133
116
|
anchor: anchorSchema,
|
|
134
|
-
|
|
135
|
-
createdBy: commentAuthorSchema,
|
|
117
|
+
artifactId: z2.string(),
|
|
136
118
|
createdAt: z2.string(),
|
|
137
|
-
|
|
119
|
+
createdBy: commentAuthorSchema,
|
|
120
|
+
id: z2.string(),
|
|
121
|
+
messages: z2.array(commentMessageSchema),
|
|
122
|
+
status: z2.enum(["open", "resolved"]),
|
|
123
|
+
versionId: z2.string()
|
|
138
124
|
});
|
|
139
125
|
var commentThreadListSchema = z2.object({
|
|
140
126
|
threads: z2.array(commentThreadSchema)
|
|
@@ -149,23 +135,25 @@ var commentExportReplySchema = z2.object({
|
|
|
149
135
|
createdAt: z2.string()
|
|
150
136
|
});
|
|
151
137
|
var commentExportThreadSchema = z2.object({
|
|
152
|
-
|
|
153
|
-
status: z2.enum(["open", "resolved"]),
|
|
154
|
-
versionNumber: z2.number().int().positive(),
|
|
138
|
+
anchor: anchorSchema,
|
|
155
139
|
author: z2.string(),
|
|
156
140
|
body: z2.string(),
|
|
157
|
-
anchor: anchorSchema,
|
|
158
141
|
createdAt: z2.string(),
|
|
159
|
-
|
|
142
|
+
id: z2.string(),
|
|
143
|
+
replies: z2.array(commentExportReplySchema),
|
|
144
|
+
status: z2.enum(["open", "resolved"]),
|
|
145
|
+
versionNumber: z2.number().int().positive()
|
|
160
146
|
});
|
|
161
147
|
var commentExportSchema = z2.object({
|
|
162
148
|
artifactId: z2.string(),
|
|
149
|
+
comments: z2.array(commentExportThreadSchema),
|
|
163
150
|
// The artifact's current version (null before the first finalize).
|
|
164
|
-
versionNumber: z2.number().int().positive().nullable()
|
|
165
|
-
comments: z2.array(commentExportThreadSchema)
|
|
151
|
+
versionNumber: z2.number().int().positive().nullable()
|
|
166
152
|
});
|
|
167
153
|
function elementLabel(el) {
|
|
168
|
-
if (el.id)
|
|
154
|
+
if (el.id) {
|
|
155
|
+
return `<${el.tag}#${el.id}>`;
|
|
156
|
+
}
|
|
169
157
|
const classes = el.classes ? el.classes.split(" ").slice(0, 2) : [];
|
|
170
158
|
return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
|
|
171
159
|
}
|
|
@@ -174,57 +162,186 @@ function elementLabel(el) {
|
|
|
174
162
|
import { z as z3 } from "zod";
|
|
175
163
|
var apiErrorSchema = z3.object({
|
|
176
164
|
code: z3.string(),
|
|
165
|
+
docsUrl: z3.string().url(),
|
|
177
166
|
message: z3.string(),
|
|
178
|
-
retryAfter: z3.number().int().positive().optional()
|
|
179
|
-
docsUrl: z3.string().url()
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// ../shared/src/versions.ts
|
|
183
|
-
import { z as z4 } from "zod";
|
|
184
|
-
var versionSourceSchema = z4.object({
|
|
185
|
-
type: z4.string(),
|
|
186
|
-
agentName: z4.string().nullable()
|
|
187
|
-
});
|
|
188
|
-
var versionChangesSchema = z4.object({
|
|
189
|
-
added: z4.array(z4.string()),
|
|
190
|
-
changed: z4.array(z4.string()),
|
|
191
|
-
removed: z4.array(z4.string())
|
|
192
|
-
});
|
|
193
|
-
var versionSummarySchema = z4.object({
|
|
194
|
-
id: z4.string(),
|
|
195
|
-
versionNumber: z4.number().int().positive(),
|
|
196
|
-
createdAt: z4.string(),
|
|
197
|
-
entryUrl: z4.string().url(),
|
|
198
|
-
fileCount: z4.number().int().nonnegative(),
|
|
199
|
-
totalSizeBytes: z4.number().int().nonnegative(),
|
|
200
|
-
source: versionSourceSchema,
|
|
201
|
-
isCurrent: z4.boolean(),
|
|
202
|
-
// Absent for the first version — there is no previous version to diff against.
|
|
203
|
-
changes: versionChangesSchema.nullable()
|
|
204
|
-
});
|
|
205
|
-
var versionListResponseSchema = z4.object({
|
|
206
|
-
versions: z4.array(versionSummarySchema)
|
|
167
|
+
retryAfter: z3.number().int().positive().optional()
|
|
207
168
|
});
|
|
208
169
|
|
|
209
170
|
// ../shared/src/owner.ts
|
|
210
|
-
import { z as
|
|
171
|
+
import { z as z4 } from "zod";
|
|
211
172
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
212
|
-
var claimResponseSchema =
|
|
213
|
-
artifactId:
|
|
214
|
-
slug:
|
|
215
|
-
title:
|
|
216
|
-
url:
|
|
217
|
-
});
|
|
218
|
-
var updateArtifactSchema =
|
|
219
|
-
|
|
220
|
-
slug: z5.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
173
|
+
var claimResponseSchema = z4.object({
|
|
174
|
+
artifactId: z4.string(),
|
|
175
|
+
slug: z4.string(),
|
|
176
|
+
title: z4.string(),
|
|
177
|
+
url: z4.string().url()
|
|
178
|
+
});
|
|
179
|
+
var updateArtifactSchema = z4.object({
|
|
180
|
+
slug: z4.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
221
181
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
222
|
-
}).optional()
|
|
182
|
+
}).optional(),
|
|
183
|
+
title: z4.string().min(1).max(200).optional()
|
|
223
184
|
}).refine((body) => body.title !== void 0 || body.slug !== void 0, {
|
|
224
185
|
message: "provide at least one of title, slug"
|
|
225
186
|
});
|
|
226
|
-
var deleteArtifactResponseSchema =
|
|
227
|
-
deleted:
|
|
187
|
+
var deleteArtifactResponseSchema = z4.object({
|
|
188
|
+
deleted: z4.literal(true)
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ../shared/src/protection.ts
|
|
192
|
+
import { z as z5 } from "zod";
|
|
193
|
+
var PASSWORD_MIN = 6;
|
|
194
|
+
var PASSWORD_MAX = 128;
|
|
195
|
+
var gatePasswordSchema = z5.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
196
|
+
var setProtectionSchema = z5.object({
|
|
197
|
+
password: gatePasswordSchema.nullable()
|
|
198
|
+
});
|
|
199
|
+
var protectionResponseSchema = z5.object({
|
|
200
|
+
protected: z5.boolean()
|
|
201
|
+
});
|
|
202
|
+
var unlockSchema = z5.object({
|
|
203
|
+
password: z5.string().min(1).max(1024)
|
|
204
|
+
});
|
|
205
|
+
var unlockResponseSchema = z5.object({
|
|
206
|
+
unlocked: z5.literal(true)
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ../shared/src/publish.ts
|
|
210
|
+
import { z as z6 } from "zod";
|
|
211
|
+
function isSafeArtifactPath(p) {
|
|
212
|
+
if (p.length === 0 || p.length > 1024) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (p.includes("\\") || p.includes("\0")) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
if (p.startsWith("/") || p.startsWith("~")) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
222
|
+
}
|
|
223
|
+
var manifestFileSchema = z6.object({
|
|
224
|
+
hash: z6.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
225
|
+
mimeType: z6.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
226
|
+
path: z6.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
227
|
+
message: "path must be a safe relative path"
|
|
228
|
+
}),
|
|
229
|
+
size: z6.number().int().positive()
|
|
230
|
+
});
|
|
231
|
+
var publishSourceSchema = z6.object({
|
|
232
|
+
agent: z6.string().optional(),
|
|
233
|
+
runId: z6.string().optional(),
|
|
234
|
+
type: z6.literal("cli")
|
|
235
|
+
});
|
|
236
|
+
var createPublishSessionSchema = z6.object({
|
|
237
|
+
artifactId: z6.string().nullable(),
|
|
238
|
+
entryPath: z6.string().optional(),
|
|
239
|
+
files: z6.array(manifestFileSchema).min(1).max(1e3),
|
|
240
|
+
source: publishSourceSchema,
|
|
241
|
+
title: z6.string().max(200).optional()
|
|
242
|
+
});
|
|
243
|
+
var publishSessionResponseSchema = z6.object({
|
|
244
|
+
artifactId: z6.string(),
|
|
245
|
+
expiresAt: z6.string(),
|
|
246
|
+
filesToUpload: z6.array(
|
|
247
|
+
z6.object({
|
|
248
|
+
method: z6.literal("PUT"),
|
|
249
|
+
path: z6.string(),
|
|
250
|
+
uploadUrl: z6.string().url()
|
|
251
|
+
})
|
|
252
|
+
),
|
|
253
|
+
publishSessionId: z6.string(),
|
|
254
|
+
skippedFiles: z6.array(z6.string()),
|
|
255
|
+
versionId: z6.string()
|
|
256
|
+
});
|
|
257
|
+
var finalizeResponseSchema = z6.object({
|
|
258
|
+
artifactId: z6.string(),
|
|
259
|
+
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
260
|
+
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
261
|
+
claimed: z6.boolean(),
|
|
262
|
+
// Returned only on first publish of a new artifact (one-time).
|
|
263
|
+
claimUrl: z6.string().url().optional(),
|
|
264
|
+
publishToken: z6.string().optional(),
|
|
265
|
+
url: z6.string().url(),
|
|
266
|
+
versionId: z6.string(),
|
|
267
|
+
versionNumber: z6.number().int().positive()
|
|
268
|
+
});
|
|
269
|
+
var artifactResponseSchema = z6.object({
|
|
270
|
+
claimed: z6.boolean(),
|
|
271
|
+
currentVersion: z6.object({
|
|
272
|
+
entryUrl: z6.string().url(),
|
|
273
|
+
id: z6.string(),
|
|
274
|
+
versionNumber: z6.number().int().positive()
|
|
275
|
+
}).nullable(),
|
|
276
|
+
id: z6.string(),
|
|
277
|
+
slug: z6.string(),
|
|
278
|
+
title: z6.string()
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// ../shared/src/report.ts
|
|
282
|
+
import { z as z7 } from "zod";
|
|
283
|
+
var reportRequestSchema = z7.object({
|
|
284
|
+
reason: z7.string().max(1e3).optional()
|
|
285
|
+
});
|
|
286
|
+
var reportResponseSchema = z7.object({
|
|
287
|
+
reported: z7.literal(true)
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// ../shared/src/stats.ts
|
|
291
|
+
import { z as z8 } from "zod";
|
|
292
|
+
var avgViewSecondsSchema = z8.number().int().nonnegative().nullable();
|
|
293
|
+
var versionViewStatsSchema = z8.object({
|
|
294
|
+
avgViewSeconds: avgViewSecondsSchema,
|
|
295
|
+
// Views from inside the review shell's iframe vs. direct visits to
|
|
296
|
+
// the render URL. views = embeddedViews + directViews.
|
|
297
|
+
directViews: z8.number().int().nonnegative(),
|
|
298
|
+
embeddedViews: z8.number().int().nonnegative(),
|
|
299
|
+
// Distinct hashed viewer IPs across the version's lifetime.
|
|
300
|
+
uniqueViewers: z8.number().int().nonnegative(),
|
|
301
|
+
versionNumber: z8.number().int().positive(),
|
|
302
|
+
views: z8.number().int().nonnegative()
|
|
303
|
+
});
|
|
304
|
+
var dailyViewsSchema = z8.object({
|
|
305
|
+
avgViewSeconds: avgViewSecondsSchema,
|
|
306
|
+
// UTC day, YYYY-MM-DD.
|
|
307
|
+
day: z8.string(),
|
|
308
|
+
uniqueViewers: z8.number().int().nonnegative(),
|
|
309
|
+
views: z8.number().int().nonnegative()
|
|
310
|
+
});
|
|
311
|
+
var artifactStatsResponseSchema = z8.object({
|
|
312
|
+
avgViewSeconds: avgViewSecondsSchema,
|
|
313
|
+
daily: z8.array(dailyViewsSchema),
|
|
314
|
+
// Distinct hashed viewer IPs across all versions and days.
|
|
315
|
+
totalUniqueViewers: z8.number().int().nonnegative(),
|
|
316
|
+
totalViews: z8.number().int().nonnegative(),
|
|
317
|
+
versions: z8.array(versionViewStatsSchema)
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ../shared/src/versions.ts
|
|
321
|
+
import { z as z9 } from "zod";
|
|
322
|
+
var versionSourceSchema = z9.object({
|
|
323
|
+
agentName: z9.string().nullable(),
|
|
324
|
+
type: z9.string()
|
|
325
|
+
});
|
|
326
|
+
var versionChangesSchema = z9.object({
|
|
327
|
+
added: z9.array(z9.string()),
|
|
328
|
+
changed: z9.array(z9.string()),
|
|
329
|
+
removed: z9.array(z9.string())
|
|
330
|
+
});
|
|
331
|
+
var versionSummarySchema = z9.object({
|
|
332
|
+
// Absent for the first version — there is no previous version to diff against.
|
|
333
|
+
changes: versionChangesSchema.nullable(),
|
|
334
|
+
createdAt: z9.string(),
|
|
335
|
+
entryUrl: z9.string().url(),
|
|
336
|
+
fileCount: z9.number().int().nonnegative(),
|
|
337
|
+
id: z9.string(),
|
|
338
|
+
isCurrent: z9.boolean(),
|
|
339
|
+
source: versionSourceSchema,
|
|
340
|
+
totalSizeBytes: z9.number().int().nonnegative(),
|
|
341
|
+
versionNumber: z9.number().int().positive()
|
|
342
|
+
});
|
|
343
|
+
var versionListResponseSchema = z9.object({
|
|
344
|
+
versions: z9.array(versionSummarySchema)
|
|
228
345
|
});
|
|
229
346
|
|
|
230
347
|
// src/api.ts
|
|
@@ -246,7 +363,8 @@ async function request(url, init, parse) {
|
|
|
246
363
|
res = await fetch(url, init);
|
|
247
364
|
} catch (err) {
|
|
248
365
|
throw new Error(
|
|
249
|
-
`Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})
|
|
366
|
+
`Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})`,
|
|
367
|
+
{ cause: err }
|
|
250
368
|
);
|
|
251
369
|
}
|
|
252
370
|
const body = await res.json().catch(() => null);
|
|
@@ -260,26 +378,45 @@ async function request(url, init, parse) {
|
|
|
260
378
|
parsed.data.retryAfter
|
|
261
379
|
);
|
|
262
380
|
}
|
|
263
|
-
throw new ApiRequestError(
|
|
381
|
+
throw new ApiRequestError(
|
|
382
|
+
res.status,
|
|
383
|
+
"unknown_error",
|
|
384
|
+
`API returned ${res.status}`
|
|
385
|
+
);
|
|
264
386
|
}
|
|
265
387
|
return parse(body);
|
|
266
388
|
}
|
|
267
|
-
function createApiClient(
|
|
268
|
-
const base =
|
|
269
|
-
const headers = {
|
|
270
|
-
|
|
389
|
+
function createApiClient(apiUrl2, publishToken) {
|
|
390
|
+
const base = apiUrl2.replace(/\/$/, "");
|
|
391
|
+
const headers = {
|
|
392
|
+
"content-type": "application/json"
|
|
393
|
+
};
|
|
394
|
+
if (publishToken) {
|
|
395
|
+
headers.authorization = `Bearer ${publishToken}`;
|
|
396
|
+
}
|
|
271
397
|
return {
|
|
272
398
|
async createSession(body) {
|
|
273
399
|
return request(
|
|
274
400
|
`${base}/api/v1/publish`,
|
|
275
|
-
{
|
|
401
|
+
{ body: JSON.stringify(body), headers, method: "POST" },
|
|
276
402
|
(d) => publishSessionResponseSchema.parse(d)
|
|
277
403
|
);
|
|
278
404
|
},
|
|
405
|
+
async exportComments(artifactId, query) {
|
|
406
|
+
const params = new URLSearchParams({
|
|
407
|
+
status: query.status,
|
|
408
|
+
version: query.version
|
|
409
|
+
});
|
|
410
|
+
return request(
|
|
411
|
+
`${base}/api/v1/artifacts/${artifactId}/comments/export?${params}`,
|
|
412
|
+
{ method: "GET" },
|
|
413
|
+
(d) => commentExportSchema.parse(d)
|
|
414
|
+
);
|
|
415
|
+
},
|
|
279
416
|
async finalize(publishSessionId) {
|
|
280
417
|
return request(
|
|
281
418
|
`${base}/api/v1/publish/${publishSessionId}/finalize`,
|
|
282
|
-
{ method: "POST"
|
|
419
|
+
{ headers, method: "POST" },
|
|
283
420
|
(d) => finalizeResponseSchema.parse(d)
|
|
284
421
|
);
|
|
285
422
|
},
|
|
@@ -290,43 +427,75 @@ function createApiClient(apiUrl, publishToken) {
|
|
|
290
427
|
(d) => artifactResponseSchema.parse(d)
|
|
291
428
|
);
|
|
292
429
|
},
|
|
293
|
-
|
|
294
|
-
|
|
430
|
+
// SPEC §6.18 — everything owned by the account this client's API
|
|
431
|
+
// key (or session) belongs to.
|
|
432
|
+
async listArtifacts() {
|
|
295
433
|
return request(
|
|
296
|
-
`${base}/api/v1/artifacts
|
|
297
|
-
{ method: "GET" },
|
|
298
|
-
(d) =>
|
|
434
|
+
`${base}/api/v1/artifacts`,
|
|
435
|
+
{ headers, method: "GET" },
|
|
436
|
+
(d) => artifactListResponseSchema.parse(d)
|
|
437
|
+
);
|
|
438
|
+
},
|
|
439
|
+
// SPEC §6.14 — who the presented credential belongs to. Used by
|
|
440
|
+
// `login` to verify a pasted key and by `whoami`.
|
|
441
|
+
async me() {
|
|
442
|
+
return request(
|
|
443
|
+
`${base}/api/v1/me`,
|
|
444
|
+
{ headers, method: "GET" },
|
|
445
|
+
(d) => meResponseSchema.parse(d)
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
// SPEC §6.16 — set a password (string) or clear it (null). Authorized
|
|
449
|
+
// by the publish token this client was created with.
|
|
450
|
+
async setProtection(artifactId, password) {
|
|
451
|
+
return request(
|
|
452
|
+
`${base}/api/v1/artifacts/${artifactId}/protection`,
|
|
453
|
+
{ body: JSON.stringify({ password }), headers, method: "PUT" },
|
|
454
|
+
(d) => protectionResponseSchema.parse(d)
|
|
299
455
|
);
|
|
300
456
|
}
|
|
301
457
|
};
|
|
302
458
|
}
|
|
303
459
|
async function uploadFile(uploadUrl, content, mimeType) {
|
|
304
460
|
const res = await fetch(uploadUrl, {
|
|
305
|
-
|
|
306
|
-
headers: {
|
|
307
|
-
|
|
461
|
+
body: new Uint8Array(content),
|
|
462
|
+
headers: {
|
|
463
|
+
"content-length": String(content.byteLength),
|
|
464
|
+
"content-type": mimeType
|
|
465
|
+
},
|
|
466
|
+
method: "PUT"
|
|
308
467
|
});
|
|
309
468
|
if (!res.ok) {
|
|
310
|
-
throw new Error(
|
|
469
|
+
throw new Error(
|
|
470
|
+
`Upload failed (${res.status}): ${(await res.text()).slice(0, 200)}`
|
|
471
|
+
);
|
|
311
472
|
}
|
|
312
473
|
}
|
|
313
474
|
|
|
314
475
|
// src/config.ts
|
|
315
|
-
import {
|
|
476
|
+
import {
|
|
477
|
+
appendFileSync,
|
|
478
|
+
existsSync,
|
|
479
|
+
mkdirSync,
|
|
480
|
+
readFileSync,
|
|
481
|
+
writeFileSync
|
|
482
|
+
} from "fs";
|
|
316
483
|
import path from "path";
|
|
317
|
-
import { z as
|
|
318
|
-
var configSchema =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
484
|
+
import { z as z10 } from "zod";
|
|
485
|
+
var configSchema = z10.object({
|
|
486
|
+
apiUrl: z10.string(),
|
|
487
|
+
artifactId: z10.string(),
|
|
488
|
+
publishToken: z10.string(),
|
|
489
|
+
slug: z10.string(),
|
|
490
|
+
url: z10.string()
|
|
324
491
|
});
|
|
325
492
|
var CONFIG_DIR = ".artifactcafe";
|
|
326
493
|
var CONFIG_FILE = "config.json";
|
|
327
494
|
function readConfig(dir) {
|
|
328
495
|
const file = path.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
329
|
-
if (!existsSync(file))
|
|
496
|
+
if (!existsSync(file)) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
330
499
|
try {
|
|
331
500
|
return configSchema.parse(JSON.parse(readFileSync(file, "utf8")));
|
|
332
501
|
} catch {
|
|
@@ -337,24 +506,30 @@ function writeConfig(dir, config) {
|
|
|
337
506
|
mkdirSync(path.join(dir, CONFIG_DIR), { recursive: true });
|
|
338
507
|
writeFileSync(
|
|
339
508
|
path.join(dir, CONFIG_DIR, CONFIG_FILE),
|
|
340
|
-
JSON.stringify(config, null, 2)
|
|
509
|
+
`${JSON.stringify(config, null, 2)}
|
|
510
|
+
`,
|
|
341
511
|
{ mode: 384 }
|
|
342
512
|
);
|
|
343
513
|
ensureGitignored(dir);
|
|
344
514
|
}
|
|
345
515
|
function ensureGitignored(dir) {
|
|
346
516
|
const gitignore = path.join(dir, ".gitignore");
|
|
347
|
-
if (!existsSync(gitignore))
|
|
517
|
+
if (!existsSync(gitignore)) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
348
520
|
const lines = readFileSync(gitignore, "utf8").split(/\r?\n/);
|
|
349
521
|
const present = lines.some((l) => {
|
|
350
522
|
const t = l.trim();
|
|
351
523
|
return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
|
|
352
524
|
});
|
|
353
525
|
if (!present) {
|
|
354
|
-
appendFileSync(
|
|
526
|
+
appendFileSync(
|
|
527
|
+
gitignore,
|
|
528
|
+
`
|
|
355
529
|
# artifact.cafe publish token
|
|
356
530
|
${CONFIG_DIR}/
|
|
357
|
-
`
|
|
531
|
+
`
|
|
532
|
+
);
|
|
358
533
|
}
|
|
359
534
|
}
|
|
360
535
|
|
|
@@ -363,10 +538,14 @@ var STATUSES = ["open", "resolved", "all"];
|
|
|
363
538
|
var VERSIONS = ["current", "all"];
|
|
364
539
|
async function comments(opts) {
|
|
365
540
|
if (!STATUSES.includes(opts.status)) {
|
|
366
|
-
throw new Error(
|
|
541
|
+
throw new Error(
|
|
542
|
+
`--status must be one of: ${STATUSES.join(", ")} (got "${opts.status}").`
|
|
543
|
+
);
|
|
367
544
|
}
|
|
368
545
|
if (!VERSIONS.includes(opts.version)) {
|
|
369
|
-
throw new Error(
|
|
546
|
+
throw new Error(
|
|
547
|
+
`--version must be one of: ${VERSIONS.join(", ")} (got "${opts.version}").`
|
|
548
|
+
);
|
|
370
549
|
}
|
|
371
550
|
const status = opts.status;
|
|
372
551
|
const version = opts.version;
|
|
@@ -377,9 +556,12 @@ async function comments(opts) {
|
|
|
377
556
|
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
378
557
|
);
|
|
379
558
|
}
|
|
380
|
-
const
|
|
381
|
-
const client = createApiClient(
|
|
382
|
-
const data = await client.exportComments(config.artifactId, {
|
|
559
|
+
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
560
|
+
const client = createApiClient(apiUrl2);
|
|
561
|
+
const data = await client.exportComments(config.artifactId, {
|
|
562
|
+
status,
|
|
563
|
+
version
|
|
564
|
+
});
|
|
383
565
|
if (opts.json) {
|
|
384
566
|
console.log(JSON.stringify(data, null, 2));
|
|
385
567
|
return;
|
|
@@ -388,14 +570,19 @@ async function comments(opts) {
|
|
|
388
570
|
}
|
|
389
571
|
function printHuman(data, url, status, version) {
|
|
390
572
|
const statusLabel = status === "all" ? "" : `${status} `;
|
|
391
|
-
|
|
573
|
+
let scope = "across all versions";
|
|
574
|
+
if (version === "current") {
|
|
575
|
+
scope = data.versionNumber === null ? "on the current version" : `on v${data.versionNumber}`;
|
|
576
|
+
}
|
|
392
577
|
if (data.comments.length === 0) {
|
|
393
578
|
console.log(`No ${statusLabel}comments ${scope}.`);
|
|
394
579
|
return;
|
|
395
580
|
}
|
|
396
581
|
const plural = data.comments.length === 1 ? "" : "s";
|
|
397
|
-
console.log(
|
|
398
|
-
|
|
582
|
+
console.log(
|
|
583
|
+
`${data.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
|
|
584
|
+
`
|
|
585
|
+
);
|
|
399
586
|
for (const thread of data.comments) {
|
|
400
587
|
printThread(thread);
|
|
401
588
|
}
|
|
@@ -403,7 +590,9 @@ function printHuman(data, url, status, version) {
|
|
|
403
590
|
function printThread(t) {
|
|
404
591
|
const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
|
|
405
592
|
const date = t.createdAt.slice(0, 10);
|
|
406
|
-
console.log(
|
|
593
|
+
console.log(
|
|
594
|
+
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
595
|
+
);
|
|
407
596
|
if (t.anchor.type === "text") {
|
|
408
597
|
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
409
598
|
} else if (t.anchor.element) {
|
|
@@ -429,73 +618,203 @@ function printCliError(err, json) {
|
|
|
429
618
|
if (json) {
|
|
430
619
|
console.log(
|
|
431
620
|
JSON.stringify({
|
|
432
|
-
error: {
|
|
621
|
+
error: {
|
|
622
|
+
code: err.code,
|
|
623
|
+
message: err.message,
|
|
624
|
+
retryAfter: err.retryAfter
|
|
625
|
+
}
|
|
433
626
|
})
|
|
434
627
|
);
|
|
435
628
|
} else {
|
|
436
629
|
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
437
|
-
if (err.retryAfter)
|
|
630
|
+
if (err.retryAfter) {
|
|
631
|
+
console.error(` Retry in ${err.retryAfter}s.`);
|
|
632
|
+
}
|
|
438
633
|
}
|
|
439
634
|
return;
|
|
440
635
|
}
|
|
441
636
|
const message = err instanceof Error ? err.message : String(err);
|
|
442
|
-
if (json)
|
|
443
|
-
|
|
637
|
+
if (json) {
|
|
638
|
+
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
639
|
+
} else {
|
|
640
|
+
console.error(`\u2717 ${message}`);
|
|
641
|
+
}
|
|
444
642
|
}
|
|
445
643
|
|
|
446
|
-
// src/
|
|
447
|
-
import
|
|
644
|
+
// src/credentials.ts
|
|
645
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
646
|
+
import { homedir } from "os";
|
|
647
|
+
import path3 from "path";
|
|
648
|
+
import { z as z11 } from "zod";
|
|
649
|
+
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
650
|
+
var credentialsSchema = z11.object({
|
|
651
|
+
keys: z11.record(z11.string(), z11.string()),
|
|
652
|
+
version: z11.literal(1)
|
|
653
|
+
});
|
|
654
|
+
function credentialsPath() {
|
|
655
|
+
return path3.join(homedir(), ".artifactcafe", "credentials");
|
|
656
|
+
}
|
|
657
|
+
function apiOrigin(apiUrl2) {
|
|
658
|
+
return new URL(apiUrl2).origin;
|
|
659
|
+
}
|
|
660
|
+
function readCredentials() {
|
|
661
|
+
const file = credentialsPath();
|
|
662
|
+
if (!existsSync2(file)) {
|
|
663
|
+
return { keys: {}, version: 1 };
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
return credentialsSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
|
|
667
|
+
} catch {
|
|
668
|
+
return { keys: {}, version: 1 };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function writeCredentials(credentials) {
|
|
672
|
+
const file = credentialsPath();
|
|
673
|
+
mkdirSync2(path3.dirname(file), { mode: 448, recursive: true });
|
|
674
|
+
writeFileSync2(file, `${JSON.stringify(credentials, null, 2)}
|
|
675
|
+
`, {
|
|
676
|
+
mode: 384
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function storedApiKey(apiUrl2) {
|
|
680
|
+
return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
|
|
681
|
+
}
|
|
682
|
+
function storeApiKey(apiUrl2, key) {
|
|
683
|
+
const credentials = readCredentials();
|
|
684
|
+
credentials.keys[apiOrigin(apiUrl2)] = key;
|
|
685
|
+
writeCredentials(credentials);
|
|
686
|
+
}
|
|
687
|
+
function removeApiKey(apiUrl2) {
|
|
688
|
+
const credentials = readCredentials();
|
|
689
|
+
const origin = apiOrigin(apiUrl2);
|
|
690
|
+
if (!(origin in credentials.keys)) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
delete credentials.keys[origin];
|
|
694
|
+
writeCredentials(credentials);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
function resolveApiKey(apiUrl2, flag) {
|
|
698
|
+
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/list.ts
|
|
702
|
+
async function list(opts) {
|
|
703
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
704
|
+
/\/$/,
|
|
705
|
+
""
|
|
706
|
+
);
|
|
707
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
708
|
+
if (!key) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const data = await createApiClient(apiUrl2, key).listArtifacts();
|
|
714
|
+
if (opts.json) {
|
|
715
|
+
console.log(JSON.stringify(data, null, 2));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (data.artifacts.length === 0) {
|
|
719
|
+
console.log(
|
|
720
|
+
"No artifacts in your account yet \u2014 publish while logged in, or claim one via its claim link."
|
|
721
|
+
);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const plural = data.artifacts.length === 1 ? "" : "s";
|
|
725
|
+
console.log(`${data.artifacts.length} artifact${plural}
|
|
726
|
+
`);
|
|
727
|
+
for (const artifact of data.artifacts) {
|
|
728
|
+
printArtifact(artifact);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function printArtifact(a) {
|
|
732
|
+
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
733
|
+
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
734
|
+
const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
|
|
735
|
+
console.log(`${marker} ${a.title}`);
|
|
736
|
+
console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
|
|
737
|
+
console.log(` ${a.url}
|
|
738
|
+
`);
|
|
739
|
+
}
|
|
740
|
+
function relativeTime(iso) {
|
|
741
|
+
const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
|
|
742
|
+
if (seconds < 60) {
|
|
743
|
+
return "just now";
|
|
744
|
+
}
|
|
745
|
+
if (seconds < 3600) {
|
|
746
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
747
|
+
}
|
|
748
|
+
if (seconds < 86400) {
|
|
749
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
750
|
+
}
|
|
751
|
+
if (seconds < 30 * 86400) {
|
|
752
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
753
|
+
}
|
|
754
|
+
return iso.slice(0, 10);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/login.ts
|
|
758
|
+
import { createInterface } from "readline";
|
|
448
759
|
|
|
449
760
|
// src/publish.ts
|
|
450
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
451
|
-
import path4 from "path";
|
|
452
761
|
import { exec } from "child_process";
|
|
762
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
763
|
+
import path5 from "path";
|
|
453
764
|
|
|
454
765
|
// src/manifest.ts
|
|
455
766
|
import { createHash } from "crypto";
|
|
456
|
-
import { readdirSync, readFileSync as
|
|
457
|
-
import
|
|
767
|
+
import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
768
|
+
import path4 from "path";
|
|
458
769
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
459
770
|
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
460
771
|
function isIgnoredFile(name) {
|
|
461
|
-
if (IGNORED_FILES.has(name))
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (name.startsWith(".
|
|
772
|
+
if (IGNORED_FILES.has(name)) {
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
if (name.endsWith(".pem") || name.endsWith(".key")) {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
if (name.startsWith(".git")) {
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
465
784
|
return false;
|
|
466
785
|
}
|
|
467
786
|
var MIME_TYPES = {
|
|
468
|
-
".
|
|
469
|
-
".htm": "text/html",
|
|
787
|
+
".avif": "image/avif",
|
|
470
788
|
".css": "text/css",
|
|
789
|
+
".gif": "image/gif",
|
|
790
|
+
".htm": "text/html",
|
|
791
|
+
".html": "text/html",
|
|
792
|
+
".ico": "image/x-icon",
|
|
793
|
+
".jpeg": "image/jpeg",
|
|
794
|
+
".jpg": "image/jpeg",
|
|
471
795
|
".js": "text/javascript",
|
|
472
|
-
".mjs": "text/javascript",
|
|
473
796
|
".json": "application/json",
|
|
474
797
|
".map": "application/json",
|
|
475
|
-
".txt": "text/plain",
|
|
476
798
|
".md": "text/markdown",
|
|
477
|
-
".
|
|
478
|
-
".
|
|
799
|
+
".mjs": "text/javascript",
|
|
800
|
+
".mp3": "audio/mpeg",
|
|
801
|
+
".mp4": "video/mp4",
|
|
802
|
+
".otf": "font/otf",
|
|
803
|
+
".pdf": "application/pdf",
|
|
479
804
|
".png": "image/png",
|
|
480
|
-
".
|
|
481
|
-
".
|
|
482
|
-
".
|
|
805
|
+
".svg": "image/svg+xml",
|
|
806
|
+
".ttf": "font/ttf",
|
|
807
|
+
".txt": "text/plain",
|
|
808
|
+
".wasm": "application/wasm",
|
|
809
|
+
".wav": "audio/wav",
|
|
810
|
+
".webm": "video/webm",
|
|
483
811
|
".webp": "image/webp",
|
|
484
|
-
".avif": "image/avif",
|
|
485
|
-
".ico": "image/x-icon",
|
|
486
812
|
".woff": "font/woff",
|
|
487
813
|
".woff2": "font/woff2",
|
|
488
|
-
".
|
|
489
|
-
".otf": "font/otf",
|
|
490
|
-
".pdf": "application/pdf",
|
|
491
|
-
".mp4": "video/mp4",
|
|
492
|
-
".webm": "video/webm",
|
|
493
|
-
".mp3": "audio/mpeg",
|
|
494
|
-
".wav": "audio/wav",
|
|
495
|
-
".wasm": "application/wasm"
|
|
814
|
+
".xml": "application/xml"
|
|
496
815
|
};
|
|
497
816
|
function mimeFor(filePath) {
|
|
498
|
-
return MIME_TYPES[
|
|
817
|
+
return MIME_TYPES[path4.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
499
818
|
}
|
|
500
819
|
function scanDirectory(dir) {
|
|
501
820
|
const files = [];
|
|
@@ -503,7 +822,7 @@ function scanDirectory(dir) {
|
|
|
503
822
|
function walk(current, relPrefix) {
|
|
504
823
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
505
824
|
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
506
|
-
const abs =
|
|
825
|
+
const abs = path4.join(current, entry.name);
|
|
507
826
|
if (entry.isSymbolicLink()) {
|
|
508
827
|
skipped.push(rel);
|
|
509
828
|
continue;
|
|
@@ -516,23 +835,25 @@ function scanDirectory(dir) {
|
|
|
516
835
|
}
|
|
517
836
|
continue;
|
|
518
837
|
}
|
|
519
|
-
if (!entry.isFile())
|
|
838
|
+
if (!entry.isFile()) {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
520
841
|
if (isIgnoredFile(entry.name) || !isSafeArtifactPath(rel)) {
|
|
521
842
|
skipped.push(rel);
|
|
522
843
|
continue;
|
|
523
844
|
}
|
|
524
|
-
const size = statSync(abs)
|
|
845
|
+
const { size } = statSync(abs);
|
|
525
846
|
if (size === 0) {
|
|
526
847
|
skipped.push(rel);
|
|
527
848
|
continue;
|
|
528
849
|
}
|
|
529
|
-
const content =
|
|
850
|
+
const content = readFileSync3(abs);
|
|
530
851
|
files.push({
|
|
531
|
-
|
|
852
|
+
absPath: abs,
|
|
532
853
|
hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
|
|
533
|
-
size,
|
|
534
854
|
mimeType: mimeFor(rel),
|
|
535
|
-
|
|
855
|
+
path: rel,
|
|
856
|
+
size
|
|
536
857
|
});
|
|
537
858
|
}
|
|
538
859
|
}
|
|
@@ -543,96 +864,146 @@ function scanDirectory(dir) {
|
|
|
543
864
|
function resolveEntry(files, explicit) {
|
|
544
865
|
if (explicit) {
|
|
545
866
|
if (!files.some((f) => f.path === explicit)) {
|
|
546
|
-
throw new Error(
|
|
867
|
+
throw new Error(
|
|
868
|
+
`Entry file "${explicit}" not found in the publish folder.`
|
|
869
|
+
);
|
|
547
870
|
}
|
|
548
871
|
return explicit;
|
|
549
872
|
}
|
|
550
|
-
if (files.some((f) => f.path === "index.html"))
|
|
551
|
-
|
|
552
|
-
|
|
873
|
+
if (files.some((f) => f.path === "index.html")) {
|
|
874
|
+
return "index.html";
|
|
875
|
+
}
|
|
876
|
+
const htmlFiles = files.filter(
|
|
877
|
+
(f) => f.path.endsWith(".html") && !f.path.includes("/")
|
|
878
|
+
);
|
|
879
|
+
const [onlyHtmlFile] = htmlFiles;
|
|
880
|
+
if (onlyHtmlFile && htmlFiles.length === 1) {
|
|
881
|
+
return onlyHtmlFile.path;
|
|
882
|
+
}
|
|
553
883
|
throw new Error(
|
|
554
884
|
htmlFiles.length === 0 ? "No entry file found. Add an index.html or pass --entry <file>." : `Multiple HTML files found (${htmlFiles.map((f) => f.path).join(", ")}). Pass --entry <file>.`
|
|
555
885
|
);
|
|
556
886
|
}
|
|
557
887
|
|
|
558
888
|
// src/publish.ts
|
|
559
|
-
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
560
889
|
var UPLOAD_CONCURRENCY = 6;
|
|
561
890
|
async function publish(opts) {
|
|
562
|
-
const dir =
|
|
891
|
+
const dir = path5.resolve(opts.dir);
|
|
563
892
|
const config = readConfig(dir);
|
|
893
|
+
if (opts.password !== void 0) {
|
|
894
|
+
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
895
|
+
if (!parsed.success) {
|
|
896
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
564
899
|
const artifactId = opts.artifact ?? config?.artifactId ?? null;
|
|
565
900
|
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
566
|
-
const
|
|
901
|
+
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
902
|
+
const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
|
|
567
903
|
const { files, skipped } = scanDirectory(dir);
|
|
568
904
|
if (files.length === 0) {
|
|
569
905
|
throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
|
|
570
906
|
}
|
|
571
907
|
const entryPath = resolveEntry(files, opts.entry);
|
|
572
908
|
const log = (msg) => {
|
|
573
|
-
if (!opts.json)
|
|
909
|
+
if (!opts.json) {
|
|
910
|
+
console.log(msg);
|
|
911
|
+
}
|
|
574
912
|
};
|
|
575
913
|
if (skipped.length) {
|
|
576
914
|
log(` ignoring ${skipped.length} item(s): ${preview(skipped)}`);
|
|
577
915
|
}
|
|
578
|
-
const
|
|
579
|
-
const session = await client.createSession({
|
|
916
|
+
const sessionBody = {
|
|
580
917
|
artifactId,
|
|
581
|
-
title: opts.title,
|
|
582
918
|
entryPath,
|
|
583
919
|
files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
|
|
584
920
|
source: {
|
|
585
|
-
type: "cli",
|
|
586
921
|
agent: process.env.ARTIFACT_CAFE_AGENT,
|
|
587
|
-
runId: process.env.ARTIFACT_CAFE_RUN_ID
|
|
922
|
+
runId: process.env.ARTIFACT_CAFE_RUN_ID,
|
|
923
|
+
type: "cli"
|
|
924
|
+
},
|
|
925
|
+
title: opts.title
|
|
926
|
+
};
|
|
927
|
+
let credential = apiKey ?? token;
|
|
928
|
+
let client = createApiClient(apiUrl2, credential);
|
|
929
|
+
let session;
|
|
930
|
+
try {
|
|
931
|
+
session = await client.createSession(sessionBody);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
const keyRefused = apiKey !== void 0 && token !== void 0 && apiKey !== token && artifactId !== null && err instanceof ApiRequestError && (err.status === 401 || err.status === 403);
|
|
934
|
+
if (!keyRefused) {
|
|
935
|
+
throw err;
|
|
588
936
|
}
|
|
589
|
-
|
|
937
|
+
credential = token;
|
|
938
|
+
client = createApiClient(apiUrl2, credential);
|
|
939
|
+
session = await client.createSession(sessionBody);
|
|
940
|
+
}
|
|
590
941
|
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
591
942
|
log(
|
|
592
943
|
` uploading ${session.filesToUpload.length} file(s)` + (session.skippedFiles.length ? `, ${session.skippedFiles.length} unchanged` : "")
|
|
593
944
|
);
|
|
594
945
|
await inBatches(session.filesToUpload, UPLOAD_CONCURRENCY, async (upload) => {
|
|
595
946
|
const file = byPath.get(upload.path);
|
|
596
|
-
if (!file)
|
|
597
|
-
|
|
947
|
+
if (!file) {
|
|
948
|
+
throw new Error(`Server asked for unknown file ${upload.path}`);
|
|
949
|
+
}
|
|
950
|
+
await uploadFile(
|
|
951
|
+
upload.uploadUrl,
|
|
952
|
+
readFileSync4(file.absPath),
|
|
953
|
+
file.mimeType
|
|
954
|
+
);
|
|
598
955
|
});
|
|
599
956
|
const result = await client.finalize(session.publishSessionId);
|
|
600
957
|
if (result.publishToken) {
|
|
601
958
|
writeConfig(dir, {
|
|
959
|
+
apiUrl: apiUrl2,
|
|
602
960
|
artifactId: result.artifactId,
|
|
961
|
+
publishToken: result.publishToken,
|
|
603
962
|
slug: slugFromUrl(result.url),
|
|
604
|
-
url: result.url
|
|
605
|
-
apiUrl,
|
|
606
|
-
publishToken: result.publishToken
|
|
963
|
+
url: result.url
|
|
607
964
|
});
|
|
608
965
|
}
|
|
966
|
+
if (opts.password !== void 0) {
|
|
967
|
+
const protectClient = createApiClient(
|
|
968
|
+
apiUrl2,
|
|
969
|
+
credential ?? result.publishToken
|
|
970
|
+
);
|
|
971
|
+
await protectClient.setProtection(result.artifactId, opts.password);
|
|
972
|
+
}
|
|
609
973
|
if (opts.json) {
|
|
610
974
|
console.log(
|
|
611
975
|
JSON.stringify(
|
|
612
976
|
{
|
|
613
977
|
artifactId: result.artifactId,
|
|
614
|
-
|
|
978
|
+
claimed: result.claimed,
|
|
615
979
|
url: result.url,
|
|
980
|
+
versionNumber: result.versionNumber,
|
|
616
981
|
...result.claimUrl ? { claimUrl: result.claimUrl } : {},
|
|
982
|
+
...opts.password === void 0 ? {} : { protected: true },
|
|
617
983
|
filesUploaded: session.filesToUpload.map((f) => f.path),
|
|
618
|
-
|
|
619
|
-
|
|
984
|
+
ignored: skipped,
|
|
985
|
+
skippedFiles: session.skippedFiles
|
|
620
986
|
},
|
|
621
987
|
null,
|
|
622
988
|
2
|
|
623
989
|
)
|
|
624
990
|
);
|
|
625
991
|
} else if (result.versionNumber === 1) {
|
|
626
|
-
console.log(
|
|
627
|
-
\u2713 Published to artifact.cafe
|
|
628
|
-
`);
|
|
992
|
+
console.log("\n\u2713 Published to artifact.cafe\n");
|
|
629
993
|
console.log(`Review URL:
|
|
630
994
|
${result.url}
|
|
631
995
|
`);
|
|
632
996
|
if (result.claimUrl) {
|
|
633
|
-
console.log(
|
|
997
|
+
console.log(
|
|
998
|
+
`Claim URL (shown once \u2014 claim to keep this artifact):
|
|
634
999
|
${result.claimUrl}
|
|
635
|
-
`
|
|
1000
|
+
`
|
|
1001
|
+
);
|
|
1002
|
+
} else if (result.claimed) {
|
|
1003
|
+
console.log("Claimed by your account \u2014 no expiry.\n");
|
|
1004
|
+
}
|
|
1005
|
+
if (opts.password !== void 0) {
|
|
1006
|
+
console.log("Password protection: on\n");
|
|
636
1007
|
}
|
|
637
1008
|
} else {
|
|
638
1009
|
console.log(`
|
|
@@ -640,6 +1011,9 @@ ${result.claimUrl}
|
|
|
640
1011
|
`);
|
|
641
1012
|
console.log(`${result.url}
|
|
642
1013
|
`);
|
|
1014
|
+
if (opts.password !== void 0) {
|
|
1015
|
+
console.log("Password protection: on\n");
|
|
1016
|
+
}
|
|
643
1017
|
}
|
|
644
1018
|
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
645
1019
|
openInBrowser(result.url);
|
|
@@ -647,12 +1021,19 @@ ${result.claimUrl}
|
|
|
647
1021
|
}
|
|
648
1022
|
async function inBatches(items, concurrency, fn) {
|
|
649
1023
|
let next = 0;
|
|
650
|
-
const workers = Array.from(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1024
|
+
const workers = Array.from(
|
|
1025
|
+
{ length: Math.min(concurrency, items.length) },
|
|
1026
|
+
async () => {
|
|
1027
|
+
while (next < items.length) {
|
|
1028
|
+
const item = items[next];
|
|
1029
|
+
next += 1;
|
|
1030
|
+
if (item === void 0) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
await fn(item);
|
|
1034
|
+
}
|
|
654
1035
|
}
|
|
655
|
-
|
|
1036
|
+
);
|
|
656
1037
|
await Promise.all(workers);
|
|
657
1038
|
}
|
|
658
1039
|
function slugFromUrl(url) {
|
|
@@ -662,14 +1043,133 @@ function preview(items) {
|
|
|
662
1043
|
return items.slice(0, 5).join(", ") + (items.length > 5 ? ", \u2026" : "");
|
|
663
1044
|
}
|
|
664
1045
|
function openInBrowser(url) {
|
|
665
|
-
|
|
1046
|
+
let cmd = "xdg-open";
|
|
1047
|
+
if (process.platform === "darwin") {
|
|
1048
|
+
cmd = "open";
|
|
1049
|
+
} else if (process.platform === "win32") {
|
|
1050
|
+
cmd = "start";
|
|
1051
|
+
}
|
|
666
1052
|
exec(`${cmd} ${JSON.stringify(url)}`, () => {
|
|
667
1053
|
});
|
|
668
1054
|
}
|
|
669
1055
|
|
|
1056
|
+
// src/login.ts
|
|
1057
|
+
function apiUrl() {
|
|
1058
|
+
return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1059
|
+
}
|
|
1060
|
+
function promptSecret(question) {
|
|
1061
|
+
return new Promise((resolve) => {
|
|
1062
|
+
const rl = createInterface({
|
|
1063
|
+
input: process.stdin,
|
|
1064
|
+
output: process.stdout
|
|
1065
|
+
});
|
|
1066
|
+
let muted = false;
|
|
1067
|
+
const raw = rl;
|
|
1068
|
+
raw._writeToOutput = (s) => {
|
|
1069
|
+
if (!muted) {
|
|
1070
|
+
process.stdout.write(s);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
rl.question(question, (answer) => {
|
|
1074
|
+
rl.close();
|
|
1075
|
+
process.stdout.write("\n");
|
|
1076
|
+
resolve(answer);
|
|
1077
|
+
});
|
|
1078
|
+
muted = true;
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
async function login(opts) {
|
|
1082
|
+
const base = apiUrl();
|
|
1083
|
+
const keysUrl = `${base}/cli`;
|
|
1084
|
+
let key = opts.apiKey?.trim();
|
|
1085
|
+
if (!key) {
|
|
1086
|
+
if (!opts.json) {
|
|
1087
|
+
console.log(`
|
|
1088
|
+
Opening ${keysUrl}`);
|
|
1089
|
+
console.log("Create a CLI key there, then paste it below.\n");
|
|
1090
|
+
}
|
|
1091
|
+
if (process.stdout.isTTY) {
|
|
1092
|
+
openInBrowser(keysUrl);
|
|
1093
|
+
}
|
|
1094
|
+
key = (await promptSecret("API key: ")).trim();
|
|
1095
|
+
}
|
|
1096
|
+
if (!key) {
|
|
1097
|
+
throw new Error("No key entered \u2014 nothing was saved.");
|
|
1098
|
+
}
|
|
1099
|
+
if (!key.startsWith("ack_")) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`That doesn't look like an artifact.cafe API key (they start with "ack_").`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
const me = await createApiClient(base, key).me();
|
|
1105
|
+
storeApiKey(base, key);
|
|
1106
|
+
const who = me.email ?? me.userId;
|
|
1107
|
+
if (opts.json) {
|
|
1108
|
+
console.log(
|
|
1109
|
+
JSON.stringify(
|
|
1110
|
+
{ apiUrl: base, email: me.email, loggedIn: true, userId: me.userId },
|
|
1111
|
+
null,
|
|
1112
|
+
2
|
|
1113
|
+
)
|
|
1114
|
+
);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
console.log(`
|
|
1118
|
+
\u2713 Logged in to ${apiOrigin(base)} as ${who}`);
|
|
1119
|
+
console.log(
|
|
1120
|
+
"New publishes are claimed by your account automatically. Try `artifact-cafe list`.\n"
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
async function whoami(opts) {
|
|
1124
|
+
const base = apiUrl();
|
|
1125
|
+
const key = resolveApiKey(base);
|
|
1126
|
+
if (!key) {
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`Not logged in to ${apiOrigin(base)} \u2014 run \`artifact-cafe login\` first.`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
const me = await createApiClient(base, key).me();
|
|
1132
|
+
if (opts.json) {
|
|
1133
|
+
console.log(
|
|
1134
|
+
JSON.stringify(
|
|
1135
|
+
{
|
|
1136
|
+
apiUrl: base,
|
|
1137
|
+
email: me.email,
|
|
1138
|
+
keyName: me.keyName ?? null,
|
|
1139
|
+
userId: me.userId
|
|
1140
|
+
},
|
|
1141
|
+
null,
|
|
1142
|
+
2
|
|
1143
|
+
)
|
|
1144
|
+
);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const keyLabel = me.keyName ? ` (key: ${me.keyName})` : "";
|
|
1148
|
+
console.log(
|
|
1149
|
+
`Logged in to ${apiOrigin(base)} as ${me.email ?? me.userId}${keyLabel}`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
function logout(opts) {
|
|
1153
|
+
const base = apiUrl();
|
|
1154
|
+
const removed = removeApiKey(base);
|
|
1155
|
+
if (opts.json) {
|
|
1156
|
+
console.log(JSON.stringify({ apiUrl: base, loggedOut: removed }, null, 2));
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (!removed) {
|
|
1160
|
+
console.log(`No stored key for ${apiOrigin(base)} \u2014 already logged out.`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
console.log(`\u2713 Logged out of ${apiOrigin(base)} on this machine.`);
|
|
1164
|
+
console.log(
|
|
1165
|
+
`The key itself is still active \u2014 revoke it at ${base}/cli if this machine is shared.`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
670
1169
|
// src/open.ts
|
|
1170
|
+
import path6 from "path";
|
|
671
1171
|
function openArtifact(opts) {
|
|
672
|
-
const dir =
|
|
1172
|
+
const dir = path6.resolve(opts.dir);
|
|
673
1173
|
const config = readConfig(dir);
|
|
674
1174
|
if (!config) {
|
|
675
1175
|
throw new Error(
|
|
@@ -677,29 +1177,90 @@ function openArtifact(opts) {
|
|
|
677
1177
|
);
|
|
678
1178
|
}
|
|
679
1179
|
console.log(config.url);
|
|
680
|
-
if (process.stdout.isTTY)
|
|
1180
|
+
if (process.stdout.isTTY) {
|
|
1181
|
+
openInBrowser(config.url);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/protect.ts
|
|
1186
|
+
import path7 from "path";
|
|
1187
|
+
async function protect(opts) {
|
|
1188
|
+
if (opts.off && opts.password !== void 0) {
|
|
1189
|
+
throw new Error("Pass either --password or --off, not both.");
|
|
1190
|
+
}
|
|
1191
|
+
if (!opts.off && opts.password === void 0) {
|
|
1192
|
+
throw new Error(
|
|
1193
|
+
"Provide --password <password> to set a gate, or --off to remove it."
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
const password = opts.off ? null : opts.password;
|
|
1197
|
+
if (password !== null) {
|
|
1198
|
+
const parsed = gatePasswordSchema.safeParse(password);
|
|
1199
|
+
if (!parsed.success) {
|
|
1200
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
const dir = path7.resolve(opts.dir);
|
|
1204
|
+
const config = readConfig(dir);
|
|
1205
|
+
if (!config) {
|
|
1206
|
+
throw new Error(
|
|
1207
|
+
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
1211
|
+
const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
1212
|
+
const client = createApiClient(apiUrl2, token);
|
|
1213
|
+
const res = await client.setProtection(config.artifactId, password);
|
|
1214
|
+
if (opts.json) {
|
|
1215
|
+
console.log(JSON.stringify({ protected: res.protected }, null, 2));
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
console.log(
|
|
1219
|
+
res.protected ? `
|
|
1220
|
+
\u2713 Password protection on \u2014 ${config.url}
|
|
1221
|
+
` : `
|
|
1222
|
+
\u2713 Password protection removed \u2014 ${config.url}
|
|
1223
|
+
`
|
|
1224
|
+
);
|
|
681
1225
|
}
|
|
682
1226
|
|
|
683
1227
|
// src/index.ts
|
|
684
1228
|
var publish2 = defineCommand({
|
|
685
|
-
meta: { name: "publish", description: "Publish a folder as an artifact" },
|
|
686
1229
|
args: {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1230
|
+
"api-key": {
|
|
1231
|
+
description: "Publish with an account API key (default: stored login)",
|
|
1232
|
+
type: "string"
|
|
1233
|
+
},
|
|
1234
|
+
artifact: {
|
|
1235
|
+
description: "Publish to an existing artifact ID",
|
|
1236
|
+
type: "string"
|
|
1237
|
+
},
|
|
1238
|
+
dir: {
|
|
1239
|
+
default: ".",
|
|
1240
|
+
description: "Directory to publish",
|
|
1241
|
+
type: "positional"
|
|
1242
|
+
},
|
|
1243
|
+
entry: { description: "Entry file (default: auto-detect)", type: "string" },
|
|
1244
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
1245
|
+
"no-open": { description: "Don't open the review URL", type: "boolean" },
|
|
1246
|
+
password: {
|
|
1247
|
+
description: "Password-protect the artifact (min 6 chars)",
|
|
1248
|
+
type: "string"
|
|
1249
|
+
},
|
|
1250
|
+
title: { description: "Artifact title", type: "string" }
|
|
693
1251
|
},
|
|
1252
|
+
meta: { description: "Publish a folder as an artifact", name: "publish" },
|
|
694
1253
|
async run({ args }) {
|
|
695
1254
|
try {
|
|
696
1255
|
await publish({
|
|
1256
|
+
apiKey: args["api-key"],
|
|
1257
|
+
artifact: args.artifact,
|
|
697
1258
|
dir: args.dir,
|
|
698
|
-
title: args.title,
|
|
699
1259
|
entry: args.entry,
|
|
700
|
-
artifact: args.artifact,
|
|
701
1260
|
json: Boolean(args.json),
|
|
702
|
-
open: !args["no-open"]
|
|
1261
|
+
open: !args["no-open"],
|
|
1262
|
+
password: args.password,
|
|
1263
|
+
title: args.title
|
|
703
1264
|
});
|
|
704
1265
|
} catch (err) {
|
|
705
1266
|
printCliError(err, Boolean(args.json));
|
|
@@ -707,13 +1268,104 @@ var publish2 = defineCommand({
|
|
|
707
1268
|
}
|
|
708
1269
|
}
|
|
709
1270
|
});
|
|
1271
|
+
var login2 = defineCommand({
|
|
1272
|
+
args: {
|
|
1273
|
+
"api-key": {
|
|
1274
|
+
description: "Store this key without the browser prompt (agents, CI)",
|
|
1275
|
+
type: "string"
|
|
1276
|
+
},
|
|
1277
|
+
json: { description: "Machine-readable output", type: "boolean" }
|
|
1278
|
+
},
|
|
1279
|
+
meta: {
|
|
1280
|
+
description: "Log in \u2014 mint an API key in the browser and store it",
|
|
1281
|
+
name: "login"
|
|
1282
|
+
},
|
|
1283
|
+
async run({ args }) {
|
|
1284
|
+
try {
|
|
1285
|
+
await login({ apiKey: args["api-key"], json: Boolean(args.json) });
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
printCliError(err, Boolean(args.json));
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
var logout2 = defineCommand({
|
|
1293
|
+
args: {
|
|
1294
|
+
json: { description: "Machine-readable output", type: "boolean" }
|
|
1295
|
+
},
|
|
1296
|
+
meta: {
|
|
1297
|
+
description: "Remove the stored API key for this machine",
|
|
1298
|
+
name: "logout"
|
|
1299
|
+
},
|
|
1300
|
+
run({ args }) {
|
|
1301
|
+
try {
|
|
1302
|
+
logout({ json: Boolean(args.json) });
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
printCliError(err, Boolean(args.json));
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
var whoami2 = defineCommand({
|
|
1310
|
+
args: {
|
|
1311
|
+
json: { description: "Machine-readable output", type: "boolean" }
|
|
1312
|
+
},
|
|
1313
|
+
meta: {
|
|
1314
|
+
description: "Show which account the stored API key belongs to",
|
|
1315
|
+
name: "whoami"
|
|
1316
|
+
},
|
|
1317
|
+
async run({ args }) {
|
|
1318
|
+
try {
|
|
1319
|
+
await whoami({ json: Boolean(args.json) });
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
printCliError(err, Boolean(args.json));
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
var list2 = defineCommand({
|
|
1327
|
+
args: {
|
|
1328
|
+
"api-key": {
|
|
1329
|
+
description: "List with this API key (default: stored login)",
|
|
1330
|
+
type: "string"
|
|
1331
|
+
},
|
|
1332
|
+
json: { description: "Machine-readable output", type: "boolean" }
|
|
1333
|
+
},
|
|
1334
|
+
meta: {
|
|
1335
|
+
description: "List the artifacts your account owns",
|
|
1336
|
+
name: "list"
|
|
1337
|
+
},
|
|
1338
|
+
async run({ args }) {
|
|
1339
|
+
try {
|
|
1340
|
+
await list({ apiKey: args["api-key"], json: Boolean(args.json) });
|
|
1341
|
+
} catch (err) {
|
|
1342
|
+
printCliError(err, Boolean(args.json));
|
|
1343
|
+
process.exit(1);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
710
1347
|
var comments2 = defineCommand({
|
|
711
|
-
meta: { name: "comments", description: "Pull comments for the linked artifact" },
|
|
712
1348
|
args: {
|
|
713
|
-
dir: {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1349
|
+
dir: {
|
|
1350
|
+
default: ".",
|
|
1351
|
+
description: "Published directory",
|
|
1352
|
+
type: "positional"
|
|
1353
|
+
},
|
|
1354
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
1355
|
+
status: {
|
|
1356
|
+
default: "open",
|
|
1357
|
+
description: "Filter: open | resolved | all",
|
|
1358
|
+
type: "string"
|
|
1359
|
+
},
|
|
1360
|
+
version: {
|
|
1361
|
+
default: "current",
|
|
1362
|
+
description: "Filter: current | all",
|
|
1363
|
+
type: "string"
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
meta: {
|
|
1367
|
+
description: "Pull comments for the linked artifact",
|
|
1368
|
+
name: "comments"
|
|
717
1369
|
},
|
|
718
1370
|
async run({ args }) {
|
|
719
1371
|
try {
|
|
@@ -729,11 +1381,44 @@ var comments2 = defineCommand({
|
|
|
729
1381
|
}
|
|
730
1382
|
}
|
|
731
1383
|
});
|
|
1384
|
+
var protect2 = defineCommand({
|
|
1385
|
+
args: {
|
|
1386
|
+
dir: {
|
|
1387
|
+
default: ".",
|
|
1388
|
+
description: "Published directory",
|
|
1389
|
+
type: "positional"
|
|
1390
|
+
},
|
|
1391
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
1392
|
+
off: { description: "Remove the password gate", type: "boolean" },
|
|
1393
|
+
password: { description: "Gate password (min 6 chars)", type: "string" }
|
|
1394
|
+
},
|
|
1395
|
+
meta: {
|
|
1396
|
+
description: "Password-protect the linked artifact (or --off to remove)",
|
|
1397
|
+
name: "protect"
|
|
1398
|
+
},
|
|
1399
|
+
async run({ args }) {
|
|
1400
|
+
try {
|
|
1401
|
+
await protect({
|
|
1402
|
+
dir: args.dir,
|
|
1403
|
+
json: Boolean(args.json),
|
|
1404
|
+
off: Boolean(args.off),
|
|
1405
|
+
password: args.password
|
|
1406
|
+
});
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
printCliError(err, Boolean(args.json));
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
732
1413
|
var open = defineCommand({
|
|
733
|
-
meta: { name: "open", description: "Open the linked artifact's review URL" },
|
|
734
1414
|
args: {
|
|
735
|
-
dir: {
|
|
1415
|
+
dir: {
|
|
1416
|
+
default: ".",
|
|
1417
|
+
description: "Published directory",
|
|
1418
|
+
type: "positional"
|
|
1419
|
+
}
|
|
736
1420
|
},
|
|
1421
|
+
meta: { description: "Open the linked artifact's review URL", name: "open" },
|
|
737
1422
|
run({ args }) {
|
|
738
1423
|
try {
|
|
739
1424
|
openArtifact({ dir: args.dir });
|
|
@@ -743,18 +1428,20 @@ var open = defineCommand({
|
|
|
743
1428
|
}
|
|
744
1429
|
}
|
|
745
1430
|
});
|
|
746
|
-
var login = defineCommand({
|
|
747
|
-
meta: { name: "login", description: "Log in (optional; needed for owner actions)" },
|
|
748
|
-
async run() {
|
|
749
|
-
console.error("login not implemented yet");
|
|
750
|
-
process.exit(1);
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
1431
|
var main = defineCommand({
|
|
754
1432
|
meta: {
|
|
755
|
-
|
|
756
|
-
|
|
1433
|
+
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
1434
|
+
name: "artifact-cafe"
|
|
757
1435
|
},
|
|
758
|
-
subCommands: {
|
|
1436
|
+
subCommands: {
|
|
1437
|
+
comments: comments2,
|
|
1438
|
+
list: list2,
|
|
1439
|
+
login: login2,
|
|
1440
|
+
logout: logout2,
|
|
1441
|
+
open,
|
|
1442
|
+
protect: protect2,
|
|
1443
|
+
publish: publish2,
|
|
1444
|
+
whoami: whoami2
|
|
1445
|
+
}
|
|
759
1446
|
});
|
|
760
1447
|
runMain(main);
|