artifact-cafe 0.0.1 → 0.0.2
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 +557 -304
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,225 +6,262 @@ import { defineCommand, runMain } from "citty";
|
|
|
6
6
|
// src/comments.ts
|
|
7
7
|
import path2 from "path";
|
|
8
8
|
|
|
9
|
-
// ../shared/src/publish.ts
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
function isSafeArtifactPath(p) {
|
|
12
|
-
if (p.length === 0 || p.length > 1024) return false;
|
|
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()
|
|
48
|
-
});
|
|
49
|
-
var finalizeResponseSchema = z.object({
|
|
50
|
-
artifactId: z.string(),
|
|
51
|
-
versionId: z.string(),
|
|
52
|
-
versionNumber: z.number().int().positive(),
|
|
53
|
-
url: z.string().url(),
|
|
54
|
-
// Returned only on first publish of a new artifact (one-time).
|
|
55
|
-
claimUrl: z.string().url().optional(),
|
|
56
|
-
publishToken: z.string().optional()
|
|
57
|
-
});
|
|
58
|
-
var artifactResponseSchema = z.object({
|
|
59
|
-
id: z.string(),
|
|
60
|
-
slug: z.string(),
|
|
61
|
-
title: z.string(),
|
|
62
|
-
currentVersion: z.object({
|
|
63
|
-
id: z.string(),
|
|
64
|
-
versionNumber: z.number().int().positive(),
|
|
65
|
-
entryUrl: z.string().url()
|
|
66
|
-
}).nullable(),
|
|
67
|
-
claimed: z.boolean()
|
|
68
|
-
});
|
|
69
|
-
|
|
70
9
|
// ../shared/src/comments.ts
|
|
71
|
-
import { z
|
|
72
|
-
var elementSnapshotSchema =
|
|
73
|
-
|
|
74
|
-
id:
|
|
75
|
-
|
|
76
|
-
text:
|
|
77
|
-
});
|
|
78
|
-
var pointAnchorSchema =
|
|
79
|
-
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var elementSnapshotSchema = z.object({
|
|
12
|
+
classes: z.string().max(256).optional(),
|
|
13
|
+
id: z.string().max(256).optional(),
|
|
14
|
+
tag: z.string().min(1).max(128),
|
|
15
|
+
text: z.string().max(200).optional()
|
|
16
|
+
});
|
|
17
|
+
var pointAnchorSchema = z.object({
|
|
18
|
+
element: elementSnapshotSchema.optional(),
|
|
80
19
|
// elementPath is the primary anchor; x/y are offsets within the
|
|
81
20
|
// element's box, normalized 0..1 (SPEC.md §8.1).
|
|
82
|
-
elementPath:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
viewportWidth:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
var textAnchorSchema =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
prefix:
|
|
93
|
-
|
|
94
|
-
startOffset:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
var anchorSchema =
|
|
21
|
+
elementPath: z.string().max(2048),
|
|
22
|
+
type: z.literal("point"),
|
|
23
|
+
viewportHeight: z.number().int().positive().max(1e5),
|
|
24
|
+
viewportWidth: z.number().int().positive().max(1e5),
|
|
25
|
+
x: z.number().min(0).max(1),
|
|
26
|
+
y: z.number().min(0).max(1)
|
|
27
|
+
});
|
|
28
|
+
var textAnchorSchema = z.object({
|
|
29
|
+
elementPath: z.string().max(2048),
|
|
30
|
+
endOffset: z.number().int().nonnegative().max(1e8),
|
|
31
|
+
prefix: z.string().max(200).optional(),
|
|
32
|
+
quote: z.string().min(1).max(1e3),
|
|
33
|
+
startOffset: z.number().int().nonnegative().max(1e8),
|
|
34
|
+
suffix: z.string().max(200).optional(),
|
|
35
|
+
type: z.literal("text")
|
|
36
|
+
});
|
|
37
|
+
var anchorSchema = z.discriminatedUnion("type", [
|
|
99
38
|
pointAnchorSchema,
|
|
100
39
|
textAnchorSchema
|
|
101
40
|
]);
|
|
102
|
-
var guestSchema =
|
|
103
|
-
|
|
104
|
-
|
|
41
|
+
var guestSchema = z.object({
|
|
42
|
+
displayName: z.string().min(1).max(64),
|
|
43
|
+
guestId: z.string().min(16)
|
|
105
44
|
});
|
|
106
|
-
var createCommentThreadSchema =
|
|
107
|
-
versionId: z2.string(),
|
|
108
|
-
guest: guestSchema.optional(),
|
|
45
|
+
var createCommentThreadSchema = z.object({
|
|
109
46
|
anchor: anchorSchema,
|
|
110
|
-
body:
|
|
111
|
-
});
|
|
112
|
-
var createCommentMessageSchema = z2.object({
|
|
47
|
+
body: z.string().min(1).max(1e4),
|
|
113
48
|
guest: guestSchema.optional(),
|
|
114
|
-
|
|
49
|
+
versionId: z.string()
|
|
50
|
+
});
|
|
51
|
+
var createCommentMessageSchema = z.object({
|
|
52
|
+
body: z.string().min(1).max(1e4),
|
|
53
|
+
guest: guestSchema.optional()
|
|
115
54
|
});
|
|
116
|
-
var threadActionSchema =
|
|
55
|
+
var threadActionSchema = z.object({
|
|
117
56
|
guest: guestSchema.optional()
|
|
118
57
|
});
|
|
119
|
-
var commentAuthorSchema =
|
|
120
|
-
|
|
121
|
-
|
|
58
|
+
var commentAuthorSchema = z.object({
|
|
59
|
+
displayName: z.string(),
|
|
60
|
+
guestId: z.string().nullable()
|
|
122
61
|
});
|
|
123
|
-
var commentMessageSchema =
|
|
124
|
-
id: z2.string(),
|
|
125
|
-
body: z2.string(),
|
|
62
|
+
var commentMessageSchema = z.object({
|
|
126
63
|
author: commentAuthorSchema,
|
|
127
|
-
|
|
64
|
+
body: z.string(),
|
|
65
|
+
createdAt: z.string(),
|
|
66
|
+
id: z.string()
|
|
128
67
|
});
|
|
129
|
-
var commentThreadSchema =
|
|
130
|
-
id: z2.string(),
|
|
131
|
-
artifactId: z2.string(),
|
|
132
|
-
versionId: z2.string(),
|
|
68
|
+
var commentThreadSchema = z.object({
|
|
133
69
|
anchor: anchorSchema,
|
|
134
|
-
|
|
70
|
+
artifactId: z.string(),
|
|
71
|
+
createdAt: z.string(),
|
|
135
72
|
createdBy: commentAuthorSchema,
|
|
136
|
-
|
|
137
|
-
messages:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
versionNumber: z2.number().int().positive(),
|
|
155
|
-
author: z2.string(),
|
|
156
|
-
body: z2.string(),
|
|
73
|
+
id: z.string(),
|
|
74
|
+
messages: z.array(commentMessageSchema),
|
|
75
|
+
status: z.enum(["open", "resolved"]),
|
|
76
|
+
versionId: z.string()
|
|
77
|
+
});
|
|
78
|
+
var commentThreadListSchema = z.object({
|
|
79
|
+
threads: z.array(commentThreadSchema)
|
|
80
|
+
});
|
|
81
|
+
var commentExportQuerySchema = z.object({
|
|
82
|
+
status: z.enum(["open", "resolved", "all"]).default("open"),
|
|
83
|
+
version: z.enum(["current", "all"]).default("current")
|
|
84
|
+
});
|
|
85
|
+
var commentExportReplySchema = z.object({
|
|
86
|
+
author: z.string(),
|
|
87
|
+
body: z.string(),
|
|
88
|
+
createdAt: z.string()
|
|
89
|
+
});
|
|
90
|
+
var commentExportThreadSchema = z.object({
|
|
157
91
|
anchor: anchorSchema,
|
|
158
|
-
|
|
159
|
-
|
|
92
|
+
author: z.string(),
|
|
93
|
+
body: z.string(),
|
|
94
|
+
createdAt: z.string(),
|
|
95
|
+
id: z.string(),
|
|
96
|
+
replies: z.array(commentExportReplySchema),
|
|
97
|
+
status: z.enum(["open", "resolved"]),
|
|
98
|
+
versionNumber: z.number().int().positive()
|
|
160
99
|
});
|
|
161
|
-
var commentExportSchema =
|
|
162
|
-
artifactId:
|
|
100
|
+
var commentExportSchema = z.object({
|
|
101
|
+
artifactId: z.string(),
|
|
102
|
+
comments: z.array(commentExportThreadSchema),
|
|
163
103
|
// The artifact's current version (null before the first finalize).
|
|
164
|
-
versionNumber:
|
|
165
|
-
comments: z2.array(commentExportThreadSchema)
|
|
104
|
+
versionNumber: z.number().int().positive().nullable()
|
|
166
105
|
});
|
|
167
106
|
function elementLabel(el) {
|
|
168
|
-
if (el.id)
|
|
107
|
+
if (el.id) {
|
|
108
|
+
return `<${el.tag}#${el.id}>`;
|
|
109
|
+
}
|
|
169
110
|
const classes = el.classes ? el.classes.split(" ").slice(0, 2) : [];
|
|
170
111
|
return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
|
|
171
112
|
}
|
|
172
113
|
|
|
173
114
|
// ../shared/src/errors.ts
|
|
115
|
+
import { z as z2 } from "zod";
|
|
116
|
+
var apiErrorSchema = z2.object({
|
|
117
|
+
code: z2.string(),
|
|
118
|
+
docsUrl: z2.string().url(),
|
|
119
|
+
message: z2.string(),
|
|
120
|
+
retryAfter: z2.number().int().positive().optional()
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ../shared/src/owner.ts
|
|
174
124
|
import { z as z3 } from "zod";
|
|
175
|
-
var
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
125
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
126
|
+
var claimResponseSchema = z3.object({
|
|
127
|
+
artifactId: z3.string(),
|
|
128
|
+
slug: z3.string(),
|
|
129
|
+
title: z3.string(),
|
|
130
|
+
url: z3.string().url()
|
|
131
|
+
});
|
|
132
|
+
var updateArtifactSchema = z3.object({
|
|
133
|
+
slug: z3.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
134
|
+
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
135
|
+
}).optional(),
|
|
136
|
+
title: z3.string().min(1).max(200).optional()
|
|
137
|
+
}).refine((body) => body.title !== void 0 || body.slug !== void 0, {
|
|
138
|
+
message: "provide at least one of title, slug"
|
|
139
|
+
});
|
|
140
|
+
var deleteArtifactResponseSchema = z3.object({
|
|
141
|
+
deleted: z3.literal(true)
|
|
180
142
|
});
|
|
181
143
|
|
|
182
|
-
// ../shared/src/
|
|
144
|
+
// ../shared/src/protection.ts
|
|
183
145
|
import { z as z4 } from "zod";
|
|
184
|
-
var
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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()
|
|
146
|
+
var PASSWORD_MIN = 6;
|
|
147
|
+
var PASSWORD_MAX = 128;
|
|
148
|
+
var gatePasswordSchema = z4.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
149
|
+
var setProtectionSchema = z4.object({
|
|
150
|
+
password: gatePasswordSchema.nullable()
|
|
151
|
+
});
|
|
152
|
+
var protectionResponseSchema = z4.object({
|
|
153
|
+
protected: z4.boolean()
|
|
204
154
|
});
|
|
205
|
-
var
|
|
206
|
-
|
|
155
|
+
var unlockSchema = z4.object({
|
|
156
|
+
password: z4.string().min(1).max(1024)
|
|
157
|
+
});
|
|
158
|
+
var unlockResponseSchema = z4.object({
|
|
159
|
+
unlocked: z4.literal(true)
|
|
207
160
|
});
|
|
208
161
|
|
|
209
|
-
// ../shared/src/
|
|
162
|
+
// ../shared/src/publish.ts
|
|
210
163
|
import { z as z5 } from "zod";
|
|
211
|
-
|
|
212
|
-
|
|
164
|
+
function isSafeArtifactPath(p) {
|
|
165
|
+
if (p.length === 0 || p.length > 1024) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (p.includes("\\") || p.includes("\0")) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (p.startsWith("/") || p.startsWith("~")) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
175
|
+
}
|
|
176
|
+
var manifestFileSchema = z5.object({
|
|
177
|
+
hash: z5.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
178
|
+
mimeType: z5.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
179
|
+
path: z5.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
180
|
+
message: "path must be a safe relative path"
|
|
181
|
+
}),
|
|
182
|
+
size: z5.number().int().positive()
|
|
183
|
+
});
|
|
184
|
+
var publishSourceSchema = z5.object({
|
|
185
|
+
agent: z5.string().optional(),
|
|
186
|
+
runId: z5.string().optional(),
|
|
187
|
+
type: z5.literal("cli")
|
|
188
|
+
});
|
|
189
|
+
var createPublishSessionSchema = z5.object({
|
|
190
|
+
artifactId: z5.string().nullable(),
|
|
191
|
+
entryPath: z5.string().optional(),
|
|
192
|
+
files: z5.array(manifestFileSchema).min(1).max(1e3),
|
|
193
|
+
source: publishSourceSchema,
|
|
194
|
+
title: z5.string().max(200).optional()
|
|
195
|
+
});
|
|
196
|
+
var publishSessionResponseSchema = z5.object({
|
|
197
|
+
artifactId: z5.string(),
|
|
198
|
+
expiresAt: z5.string(),
|
|
199
|
+
filesToUpload: z5.array(
|
|
200
|
+
z5.object({
|
|
201
|
+
method: z5.literal("PUT"),
|
|
202
|
+
path: z5.string(),
|
|
203
|
+
uploadUrl: z5.string().url()
|
|
204
|
+
})
|
|
205
|
+
),
|
|
206
|
+
publishSessionId: z5.string(),
|
|
207
|
+
skippedFiles: z5.array(z5.string()),
|
|
208
|
+
versionId: z5.string()
|
|
209
|
+
});
|
|
210
|
+
var finalizeResponseSchema = z5.object({
|
|
213
211
|
artifactId: z5.string(),
|
|
212
|
+
// Returned only on first publish of a new artifact (one-time).
|
|
213
|
+
claimUrl: z5.string().url().optional(),
|
|
214
|
+
publishToken: z5.string().optional(),
|
|
215
|
+
url: z5.string().url(),
|
|
216
|
+
versionId: z5.string(),
|
|
217
|
+
versionNumber: z5.number().int().positive()
|
|
218
|
+
});
|
|
219
|
+
var artifactResponseSchema = z5.object({
|
|
220
|
+
claimed: z5.boolean(),
|
|
221
|
+
currentVersion: z5.object({
|
|
222
|
+
entryUrl: z5.string().url(),
|
|
223
|
+
id: z5.string(),
|
|
224
|
+
versionNumber: z5.number().int().positive()
|
|
225
|
+
}).nullable(),
|
|
226
|
+
id: z5.string(),
|
|
214
227
|
slug: z5.string(),
|
|
215
|
-
title: z5.string()
|
|
216
|
-
url: z5.string().url()
|
|
228
|
+
title: z5.string()
|
|
217
229
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
224
|
-
|
|
230
|
+
|
|
231
|
+
// ../shared/src/report.ts
|
|
232
|
+
import { z as z6 } from "zod";
|
|
233
|
+
var reportRequestSchema = z6.object({
|
|
234
|
+
reason: z6.string().max(1e3).optional()
|
|
235
|
+
});
|
|
236
|
+
var reportResponseSchema = z6.object({
|
|
237
|
+
reported: z6.literal(true)
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ../shared/src/versions.ts
|
|
241
|
+
import { z as z7 } from "zod";
|
|
242
|
+
var versionSourceSchema = z7.object({
|
|
243
|
+
agentName: z7.string().nullable(),
|
|
244
|
+
type: z7.string()
|
|
245
|
+
});
|
|
246
|
+
var versionChangesSchema = z7.object({
|
|
247
|
+
added: z7.array(z7.string()),
|
|
248
|
+
changed: z7.array(z7.string()),
|
|
249
|
+
removed: z7.array(z7.string())
|
|
250
|
+
});
|
|
251
|
+
var versionSummarySchema = z7.object({
|
|
252
|
+
// Absent for the first version — there is no previous version to diff against.
|
|
253
|
+
changes: versionChangesSchema.nullable(),
|
|
254
|
+
createdAt: z7.string(),
|
|
255
|
+
entryUrl: z7.string().url(),
|
|
256
|
+
fileCount: z7.number().int().nonnegative(),
|
|
257
|
+
id: z7.string(),
|
|
258
|
+
isCurrent: z7.boolean(),
|
|
259
|
+
source: versionSourceSchema,
|
|
260
|
+
totalSizeBytes: z7.number().int().nonnegative(),
|
|
261
|
+
versionNumber: z7.number().int().positive()
|
|
225
262
|
});
|
|
226
|
-
var
|
|
227
|
-
|
|
263
|
+
var versionListResponseSchema = z7.object({
|
|
264
|
+
versions: z7.array(versionSummarySchema)
|
|
228
265
|
});
|
|
229
266
|
|
|
230
267
|
// src/api.ts
|
|
@@ -246,7 +283,8 @@ async function request(url, init, parse) {
|
|
|
246
283
|
res = await fetch(url, init);
|
|
247
284
|
} catch (err) {
|
|
248
285
|
throw new Error(
|
|
249
|
-
`Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})
|
|
286
|
+
`Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})`,
|
|
287
|
+
{ cause: err }
|
|
250
288
|
);
|
|
251
289
|
}
|
|
252
290
|
const body = await res.json().catch(() => null);
|
|
@@ -260,26 +298,45 @@ async function request(url, init, parse) {
|
|
|
260
298
|
parsed.data.retryAfter
|
|
261
299
|
);
|
|
262
300
|
}
|
|
263
|
-
throw new ApiRequestError(
|
|
301
|
+
throw new ApiRequestError(
|
|
302
|
+
res.status,
|
|
303
|
+
"unknown_error",
|
|
304
|
+
`API returned ${res.status}`
|
|
305
|
+
);
|
|
264
306
|
}
|
|
265
307
|
return parse(body);
|
|
266
308
|
}
|
|
267
309
|
function createApiClient(apiUrl, publishToken) {
|
|
268
310
|
const base = apiUrl.replace(/\/$/, "");
|
|
269
|
-
const headers = {
|
|
270
|
-
|
|
311
|
+
const headers = {
|
|
312
|
+
"content-type": "application/json"
|
|
313
|
+
};
|
|
314
|
+
if (publishToken) {
|
|
315
|
+
headers.authorization = `Bearer ${publishToken}`;
|
|
316
|
+
}
|
|
271
317
|
return {
|
|
272
318
|
async createSession(body) {
|
|
273
319
|
return request(
|
|
274
320
|
`${base}/api/v1/publish`,
|
|
275
|
-
{
|
|
321
|
+
{ body: JSON.stringify(body), headers, method: "POST" },
|
|
276
322
|
(d) => publishSessionResponseSchema.parse(d)
|
|
277
323
|
);
|
|
278
324
|
},
|
|
325
|
+
async exportComments(artifactId, query) {
|
|
326
|
+
const params = new URLSearchParams({
|
|
327
|
+
status: query.status,
|
|
328
|
+
version: query.version
|
|
329
|
+
});
|
|
330
|
+
return request(
|
|
331
|
+
`${base}/api/v1/artifacts/${artifactId}/comments/export?${params}`,
|
|
332
|
+
{ method: "GET" },
|
|
333
|
+
(d) => commentExportSchema.parse(d)
|
|
334
|
+
);
|
|
335
|
+
},
|
|
279
336
|
async finalize(publishSessionId) {
|
|
280
337
|
return request(
|
|
281
338
|
`${base}/api/v1/publish/${publishSessionId}/finalize`,
|
|
282
|
-
{ method: "POST"
|
|
339
|
+
{ headers, method: "POST" },
|
|
283
340
|
(d) => finalizeResponseSchema.parse(d)
|
|
284
341
|
);
|
|
285
342
|
},
|
|
@@ -290,43 +347,57 @@ function createApiClient(apiUrl, publishToken) {
|
|
|
290
347
|
(d) => artifactResponseSchema.parse(d)
|
|
291
348
|
);
|
|
292
349
|
},
|
|
293
|
-
|
|
294
|
-
|
|
350
|
+
// SPEC §6.16 — set a password (string) or clear it (null). Authorized
|
|
351
|
+
// by the publish token this client was created with.
|
|
352
|
+
async setProtection(artifactId, password) {
|
|
295
353
|
return request(
|
|
296
|
-
`${base}/api/v1/artifacts/${artifactId}/
|
|
297
|
-
{ method: "
|
|
298
|
-
(d) =>
|
|
354
|
+
`${base}/api/v1/artifacts/${artifactId}/protection`,
|
|
355
|
+
{ body: JSON.stringify({ password }), headers, method: "PUT" },
|
|
356
|
+
(d) => protectionResponseSchema.parse(d)
|
|
299
357
|
);
|
|
300
358
|
}
|
|
301
359
|
};
|
|
302
360
|
}
|
|
303
361
|
async function uploadFile(uploadUrl, content, mimeType) {
|
|
304
362
|
const res = await fetch(uploadUrl, {
|
|
305
|
-
|
|
306
|
-
headers: {
|
|
307
|
-
|
|
363
|
+
body: new Uint8Array(content),
|
|
364
|
+
headers: {
|
|
365
|
+
"content-length": String(content.byteLength),
|
|
366
|
+
"content-type": mimeType
|
|
367
|
+
},
|
|
368
|
+
method: "PUT"
|
|
308
369
|
});
|
|
309
370
|
if (!res.ok) {
|
|
310
|
-
throw new Error(
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Upload failed (${res.status}): ${(await res.text()).slice(0, 200)}`
|
|
373
|
+
);
|
|
311
374
|
}
|
|
312
375
|
}
|
|
313
376
|
|
|
314
377
|
// src/config.ts
|
|
315
|
-
import {
|
|
378
|
+
import {
|
|
379
|
+
appendFileSync,
|
|
380
|
+
existsSync,
|
|
381
|
+
mkdirSync,
|
|
382
|
+
readFileSync,
|
|
383
|
+
writeFileSync
|
|
384
|
+
} from "fs";
|
|
316
385
|
import path from "path";
|
|
317
|
-
import { z as
|
|
318
|
-
var configSchema =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
386
|
+
import { z as z8 } from "zod";
|
|
387
|
+
var configSchema = z8.object({
|
|
388
|
+
apiUrl: z8.string(),
|
|
389
|
+
artifactId: z8.string(),
|
|
390
|
+
publishToken: z8.string(),
|
|
391
|
+
slug: z8.string(),
|
|
392
|
+
url: z8.string()
|
|
324
393
|
});
|
|
325
394
|
var CONFIG_DIR = ".artifactcafe";
|
|
326
395
|
var CONFIG_FILE = "config.json";
|
|
327
396
|
function readConfig(dir) {
|
|
328
397
|
const file = path.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
329
|
-
if (!existsSync(file))
|
|
398
|
+
if (!existsSync(file)) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
330
401
|
try {
|
|
331
402
|
return configSchema.parse(JSON.parse(readFileSync(file, "utf8")));
|
|
332
403
|
} catch {
|
|
@@ -337,24 +408,30 @@ function writeConfig(dir, config) {
|
|
|
337
408
|
mkdirSync(path.join(dir, CONFIG_DIR), { recursive: true });
|
|
338
409
|
writeFileSync(
|
|
339
410
|
path.join(dir, CONFIG_DIR, CONFIG_FILE),
|
|
340
|
-
JSON.stringify(config, null, 2)
|
|
411
|
+
`${JSON.stringify(config, null, 2)}
|
|
412
|
+
`,
|
|
341
413
|
{ mode: 384 }
|
|
342
414
|
);
|
|
343
415
|
ensureGitignored(dir);
|
|
344
416
|
}
|
|
345
417
|
function ensureGitignored(dir) {
|
|
346
418
|
const gitignore = path.join(dir, ".gitignore");
|
|
347
|
-
if (!existsSync(gitignore))
|
|
419
|
+
if (!existsSync(gitignore)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
348
422
|
const lines = readFileSync(gitignore, "utf8").split(/\r?\n/);
|
|
349
423
|
const present = lines.some((l) => {
|
|
350
424
|
const t = l.trim();
|
|
351
425
|
return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
|
|
352
426
|
});
|
|
353
427
|
if (!present) {
|
|
354
|
-
appendFileSync(
|
|
428
|
+
appendFileSync(
|
|
429
|
+
gitignore,
|
|
430
|
+
`
|
|
355
431
|
# artifact.cafe publish token
|
|
356
432
|
${CONFIG_DIR}/
|
|
357
|
-
`
|
|
433
|
+
`
|
|
434
|
+
);
|
|
358
435
|
}
|
|
359
436
|
}
|
|
360
437
|
|
|
@@ -363,10 +440,14 @@ var STATUSES = ["open", "resolved", "all"];
|
|
|
363
440
|
var VERSIONS = ["current", "all"];
|
|
364
441
|
async function comments(opts) {
|
|
365
442
|
if (!STATUSES.includes(opts.status)) {
|
|
366
|
-
throw new Error(
|
|
443
|
+
throw new Error(
|
|
444
|
+
`--status must be one of: ${STATUSES.join(", ")} (got "${opts.status}").`
|
|
445
|
+
);
|
|
367
446
|
}
|
|
368
447
|
if (!VERSIONS.includes(opts.version)) {
|
|
369
|
-
throw new Error(
|
|
448
|
+
throw new Error(
|
|
449
|
+
`--version must be one of: ${VERSIONS.join(", ")} (got "${opts.version}").`
|
|
450
|
+
);
|
|
370
451
|
}
|
|
371
452
|
const status = opts.status;
|
|
372
453
|
const version = opts.version;
|
|
@@ -379,7 +460,10 @@ async function comments(opts) {
|
|
|
379
460
|
}
|
|
380
461
|
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
381
462
|
const client = createApiClient(apiUrl);
|
|
382
|
-
const data = await client.exportComments(config.artifactId, {
|
|
463
|
+
const data = await client.exportComments(config.artifactId, {
|
|
464
|
+
status,
|
|
465
|
+
version
|
|
466
|
+
});
|
|
383
467
|
if (opts.json) {
|
|
384
468
|
console.log(JSON.stringify(data, null, 2));
|
|
385
469
|
return;
|
|
@@ -388,14 +472,19 @@ async function comments(opts) {
|
|
|
388
472
|
}
|
|
389
473
|
function printHuman(data, url, status, version) {
|
|
390
474
|
const statusLabel = status === "all" ? "" : `${status} `;
|
|
391
|
-
|
|
475
|
+
let scope = "across all versions";
|
|
476
|
+
if (version === "current") {
|
|
477
|
+
scope = data.versionNumber === null ? "on the current version" : `on v${data.versionNumber}`;
|
|
478
|
+
}
|
|
392
479
|
if (data.comments.length === 0) {
|
|
393
480
|
console.log(`No ${statusLabel}comments ${scope}.`);
|
|
394
481
|
return;
|
|
395
482
|
}
|
|
396
483
|
const plural = data.comments.length === 1 ? "" : "s";
|
|
397
|
-
console.log(
|
|
398
|
-
|
|
484
|
+
console.log(
|
|
485
|
+
`${data.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
|
|
486
|
+
`
|
|
487
|
+
);
|
|
399
488
|
for (const thread of data.comments) {
|
|
400
489
|
printThread(thread);
|
|
401
490
|
}
|
|
@@ -403,7 +492,9 @@ function printHuman(data, url, status, version) {
|
|
|
403
492
|
function printThread(t) {
|
|
404
493
|
const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
|
|
405
494
|
const date = t.createdAt.slice(0, 10);
|
|
406
|
-
console.log(
|
|
495
|
+
console.log(
|
|
496
|
+
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
497
|
+
);
|
|
407
498
|
if (t.anchor.type === "text") {
|
|
408
499
|
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
409
500
|
} else if (t.anchor.element) {
|
|
@@ -429,27 +520,36 @@ function printCliError(err, json) {
|
|
|
429
520
|
if (json) {
|
|
430
521
|
console.log(
|
|
431
522
|
JSON.stringify({
|
|
432
|
-
error: {
|
|
523
|
+
error: {
|
|
524
|
+
code: err.code,
|
|
525
|
+
message: err.message,
|
|
526
|
+
retryAfter: err.retryAfter
|
|
527
|
+
}
|
|
433
528
|
})
|
|
434
529
|
);
|
|
435
530
|
} else {
|
|
436
531
|
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
437
|
-
if (err.retryAfter)
|
|
532
|
+
if (err.retryAfter) {
|
|
533
|
+
console.error(` Retry in ${err.retryAfter}s.`);
|
|
534
|
+
}
|
|
438
535
|
}
|
|
439
536
|
return;
|
|
440
537
|
}
|
|
441
538
|
const message = err instanceof Error ? err.message : String(err);
|
|
442
|
-
if (json)
|
|
443
|
-
|
|
539
|
+
if (json) {
|
|
540
|
+
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
541
|
+
} else {
|
|
542
|
+
console.error(`\u2717 ${message}`);
|
|
543
|
+
}
|
|
444
544
|
}
|
|
445
545
|
|
|
446
546
|
// src/open.ts
|
|
447
547
|
import path5 from "path";
|
|
448
548
|
|
|
449
549
|
// src/publish.ts
|
|
550
|
+
import { exec } from "child_process";
|
|
450
551
|
import { readFileSync as readFileSync3 } from "fs";
|
|
451
552
|
import path4 from "path";
|
|
452
|
-
import { exec } from "child_process";
|
|
453
553
|
|
|
454
554
|
// src/manifest.ts
|
|
455
555
|
import { createHash } from "crypto";
|
|
@@ -458,41 +558,49 @@ import path3 from "path";
|
|
|
458
558
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
459
559
|
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
460
560
|
function isIgnoredFile(name) {
|
|
461
|
-
if (IGNORED_FILES.has(name))
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (name.startsWith(".
|
|
561
|
+
if (IGNORED_FILES.has(name)) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (name.endsWith(".pem") || name.endsWith(".key")) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (name.startsWith(".git")) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
465
573
|
return false;
|
|
466
574
|
}
|
|
467
575
|
var MIME_TYPES = {
|
|
468
|
-
".
|
|
469
|
-
".htm": "text/html",
|
|
576
|
+
".avif": "image/avif",
|
|
470
577
|
".css": "text/css",
|
|
578
|
+
".gif": "image/gif",
|
|
579
|
+
".htm": "text/html",
|
|
580
|
+
".html": "text/html",
|
|
581
|
+
".ico": "image/x-icon",
|
|
582
|
+
".jpeg": "image/jpeg",
|
|
583
|
+
".jpg": "image/jpeg",
|
|
471
584
|
".js": "text/javascript",
|
|
472
|
-
".mjs": "text/javascript",
|
|
473
585
|
".json": "application/json",
|
|
474
586
|
".map": "application/json",
|
|
475
|
-
".txt": "text/plain",
|
|
476
587
|
".md": "text/markdown",
|
|
477
|
-
".
|
|
478
|
-
".
|
|
588
|
+
".mjs": "text/javascript",
|
|
589
|
+
".mp3": "audio/mpeg",
|
|
590
|
+
".mp4": "video/mp4",
|
|
591
|
+
".otf": "font/otf",
|
|
592
|
+
".pdf": "application/pdf",
|
|
479
593
|
".png": "image/png",
|
|
480
|
-
".
|
|
481
|
-
".
|
|
482
|
-
".
|
|
594
|
+
".svg": "image/svg+xml",
|
|
595
|
+
".ttf": "font/ttf",
|
|
596
|
+
".txt": "text/plain",
|
|
597
|
+
".wasm": "application/wasm",
|
|
598
|
+
".wav": "audio/wav",
|
|
599
|
+
".webm": "video/webm",
|
|
483
600
|
".webp": "image/webp",
|
|
484
|
-
".avif": "image/avif",
|
|
485
|
-
".ico": "image/x-icon",
|
|
486
601
|
".woff": "font/woff",
|
|
487
602
|
".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"
|
|
603
|
+
".xml": "application/xml"
|
|
496
604
|
};
|
|
497
605
|
function mimeFor(filePath) {
|
|
498
606
|
return MIME_TYPES[path3.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -516,23 +624,25 @@ function scanDirectory(dir) {
|
|
|
516
624
|
}
|
|
517
625
|
continue;
|
|
518
626
|
}
|
|
519
|
-
if (!entry.isFile())
|
|
627
|
+
if (!entry.isFile()) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
520
630
|
if (isIgnoredFile(entry.name) || !isSafeArtifactPath(rel)) {
|
|
521
631
|
skipped.push(rel);
|
|
522
632
|
continue;
|
|
523
633
|
}
|
|
524
|
-
const size = statSync(abs)
|
|
634
|
+
const { size } = statSync(abs);
|
|
525
635
|
if (size === 0) {
|
|
526
636
|
skipped.push(rel);
|
|
527
637
|
continue;
|
|
528
638
|
}
|
|
529
639
|
const content = readFileSync2(abs);
|
|
530
640
|
files.push({
|
|
531
|
-
|
|
641
|
+
absPath: abs,
|
|
532
642
|
hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
|
|
533
|
-
size,
|
|
534
643
|
mimeType: mimeFor(rel),
|
|
535
|
-
|
|
644
|
+
path: rel,
|
|
645
|
+
size
|
|
536
646
|
});
|
|
537
647
|
}
|
|
538
648
|
}
|
|
@@ -543,13 +653,22 @@ function scanDirectory(dir) {
|
|
|
543
653
|
function resolveEntry(files, explicit) {
|
|
544
654
|
if (explicit) {
|
|
545
655
|
if (!files.some((f) => f.path === explicit)) {
|
|
546
|
-
throw new Error(
|
|
656
|
+
throw new Error(
|
|
657
|
+
`Entry file "${explicit}" not found in the publish folder.`
|
|
658
|
+
);
|
|
547
659
|
}
|
|
548
660
|
return explicit;
|
|
549
661
|
}
|
|
550
|
-
if (files.some((f) => f.path === "index.html"))
|
|
551
|
-
|
|
552
|
-
|
|
662
|
+
if (files.some((f) => f.path === "index.html")) {
|
|
663
|
+
return "index.html";
|
|
664
|
+
}
|
|
665
|
+
const htmlFiles = files.filter(
|
|
666
|
+
(f) => f.path.endsWith(".html") && !f.path.includes("/")
|
|
667
|
+
);
|
|
668
|
+
const [onlyHtmlFile] = htmlFiles;
|
|
669
|
+
if (onlyHtmlFile && htmlFiles.length === 1) {
|
|
670
|
+
return onlyHtmlFile.path;
|
|
671
|
+
}
|
|
553
672
|
throw new Error(
|
|
554
673
|
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
674
|
);
|
|
@@ -561,6 +680,12 @@ var UPLOAD_CONCURRENCY = 6;
|
|
|
561
680
|
async function publish(opts) {
|
|
562
681
|
const dir = path4.resolve(opts.dir);
|
|
563
682
|
const config = readConfig(dir);
|
|
683
|
+
if (opts.password !== void 0) {
|
|
684
|
+
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
685
|
+
if (!parsed.success) {
|
|
686
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
564
689
|
const artifactId = opts.artifact ?? config?.artifactId ?? null;
|
|
565
690
|
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
566
691
|
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
@@ -570,7 +695,9 @@ async function publish(opts) {
|
|
|
570
695
|
}
|
|
571
696
|
const entryPath = resolveEntry(files, opts.entry);
|
|
572
697
|
const log = (msg) => {
|
|
573
|
-
if (!opts.json)
|
|
698
|
+
if (!opts.json) {
|
|
699
|
+
console.log(msg);
|
|
700
|
+
}
|
|
574
701
|
};
|
|
575
702
|
if (skipped.length) {
|
|
576
703
|
log(` ignoring ${skipped.length} item(s): ${preview(skipped)}`);
|
|
@@ -578,14 +705,14 @@ async function publish(opts) {
|
|
|
578
705
|
const client = createApiClient(apiUrl, token);
|
|
579
706
|
const session = await client.createSession({
|
|
580
707
|
artifactId,
|
|
581
|
-
title: opts.title,
|
|
582
708
|
entryPath,
|
|
583
709
|
files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
|
|
584
710
|
source: {
|
|
585
|
-
type: "cli",
|
|
586
711
|
agent: process.env.ARTIFACT_CAFE_AGENT,
|
|
587
|
-
runId: process.env.ARTIFACT_CAFE_RUN_ID
|
|
588
|
-
|
|
712
|
+
runId: process.env.ARTIFACT_CAFE_RUN_ID,
|
|
713
|
+
type: "cli"
|
|
714
|
+
},
|
|
715
|
+
title: opts.title
|
|
589
716
|
});
|
|
590
717
|
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
591
718
|
log(
|
|
@@ -593,46 +720,60 @@ async function publish(opts) {
|
|
|
593
720
|
);
|
|
594
721
|
await inBatches(session.filesToUpload, UPLOAD_CONCURRENCY, async (upload) => {
|
|
595
722
|
const file = byPath.get(upload.path);
|
|
596
|
-
if (!file)
|
|
597
|
-
|
|
723
|
+
if (!file) {
|
|
724
|
+
throw new Error(`Server asked for unknown file ${upload.path}`);
|
|
725
|
+
}
|
|
726
|
+
await uploadFile(
|
|
727
|
+
upload.uploadUrl,
|
|
728
|
+
readFileSync3(file.absPath),
|
|
729
|
+
file.mimeType
|
|
730
|
+
);
|
|
598
731
|
});
|
|
599
732
|
const result = await client.finalize(session.publishSessionId);
|
|
600
733
|
if (result.publishToken) {
|
|
601
734
|
writeConfig(dir, {
|
|
735
|
+
apiUrl,
|
|
602
736
|
artifactId: result.artifactId,
|
|
737
|
+
publishToken: result.publishToken,
|
|
603
738
|
slug: slugFromUrl(result.url),
|
|
604
|
-
url: result.url
|
|
605
|
-
apiUrl,
|
|
606
|
-
publishToken: result.publishToken
|
|
739
|
+
url: result.url
|
|
607
740
|
});
|
|
608
741
|
}
|
|
742
|
+
if (opts.password !== void 0) {
|
|
743
|
+
const protectClient = createApiClient(apiUrl, token ?? result.publishToken);
|
|
744
|
+
await protectClient.setProtection(result.artifactId, opts.password);
|
|
745
|
+
}
|
|
609
746
|
if (opts.json) {
|
|
610
747
|
console.log(
|
|
611
748
|
JSON.stringify(
|
|
612
749
|
{
|
|
613
750
|
artifactId: result.artifactId,
|
|
614
|
-
versionNumber: result.versionNumber,
|
|
615
751
|
url: result.url,
|
|
752
|
+
versionNumber: result.versionNumber,
|
|
616
753
|
...result.claimUrl ? { claimUrl: result.claimUrl } : {},
|
|
754
|
+
...opts.password === void 0 ? {} : { protected: true },
|
|
617
755
|
filesUploaded: session.filesToUpload.map((f) => f.path),
|
|
618
|
-
|
|
619
|
-
|
|
756
|
+
ignored: skipped,
|
|
757
|
+
skippedFiles: session.skippedFiles
|
|
620
758
|
},
|
|
621
759
|
null,
|
|
622
760
|
2
|
|
623
761
|
)
|
|
624
762
|
);
|
|
625
763
|
} else if (result.versionNumber === 1) {
|
|
626
|
-
console.log(
|
|
627
|
-
\u2713 Published to artifact.cafe
|
|
628
|
-
`);
|
|
764
|
+
console.log("\n\u2713 Published to artifact.cafe\n");
|
|
629
765
|
console.log(`Review URL:
|
|
630
766
|
${result.url}
|
|
631
767
|
`);
|
|
632
768
|
if (result.claimUrl) {
|
|
633
|
-
console.log(
|
|
769
|
+
console.log(
|
|
770
|
+
`Claim URL (shown once \u2014 claim to keep this artifact):
|
|
634
771
|
${result.claimUrl}
|
|
635
|
-
`
|
|
772
|
+
`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (opts.password !== void 0) {
|
|
776
|
+
console.log("Password protection: on\n");
|
|
636
777
|
}
|
|
637
778
|
} else {
|
|
638
779
|
console.log(`
|
|
@@ -640,6 +781,9 @@ ${result.claimUrl}
|
|
|
640
781
|
`);
|
|
641
782
|
console.log(`${result.url}
|
|
642
783
|
`);
|
|
784
|
+
if (opts.password !== void 0) {
|
|
785
|
+
console.log("Password protection: on\n");
|
|
786
|
+
}
|
|
643
787
|
}
|
|
644
788
|
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
645
789
|
openInBrowser(result.url);
|
|
@@ -647,12 +791,19 @@ ${result.claimUrl}
|
|
|
647
791
|
}
|
|
648
792
|
async function inBatches(items, concurrency, fn) {
|
|
649
793
|
let next = 0;
|
|
650
|
-
const workers = Array.from(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
794
|
+
const workers = Array.from(
|
|
795
|
+
{ length: Math.min(concurrency, items.length) },
|
|
796
|
+
async () => {
|
|
797
|
+
while (next < items.length) {
|
|
798
|
+
const item = items[next];
|
|
799
|
+
next += 1;
|
|
800
|
+
if (item === void 0) {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
await fn(item);
|
|
804
|
+
}
|
|
654
805
|
}
|
|
655
|
-
|
|
806
|
+
);
|
|
656
807
|
await Promise.all(workers);
|
|
657
808
|
}
|
|
658
809
|
function slugFromUrl(url) {
|
|
@@ -662,7 +813,12 @@ function preview(items) {
|
|
|
662
813
|
return items.slice(0, 5).join(", ") + (items.length > 5 ? ", \u2026" : "");
|
|
663
814
|
}
|
|
664
815
|
function openInBrowser(url) {
|
|
665
|
-
|
|
816
|
+
let cmd = "xdg-open";
|
|
817
|
+
if (process.platform === "darwin") {
|
|
818
|
+
cmd = "open";
|
|
819
|
+
} else if (process.platform === "win32") {
|
|
820
|
+
cmd = "start";
|
|
821
|
+
}
|
|
666
822
|
exec(`${cmd} ${JSON.stringify(url)}`, () => {
|
|
667
823
|
});
|
|
668
824
|
}
|
|
@@ -677,29 +833,85 @@ function openArtifact(opts) {
|
|
|
677
833
|
);
|
|
678
834
|
}
|
|
679
835
|
console.log(config.url);
|
|
680
|
-
if (process.stdout.isTTY)
|
|
836
|
+
if (process.stdout.isTTY) {
|
|
837
|
+
openInBrowser(config.url);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/protect.ts
|
|
842
|
+
import path6 from "path";
|
|
843
|
+
async function protect(opts) {
|
|
844
|
+
if (opts.off && opts.password !== void 0) {
|
|
845
|
+
throw new Error("Pass either --password or --off, not both.");
|
|
846
|
+
}
|
|
847
|
+
if (!opts.off && opts.password === void 0) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
"Provide --password <password> to set a gate, or --off to remove it."
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
const password = opts.off ? null : opts.password;
|
|
853
|
+
if (password !== null) {
|
|
854
|
+
const parsed = gatePasswordSchema.safeParse(password);
|
|
855
|
+
if (!parsed.success) {
|
|
856
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const dir = path6.resolve(opts.dir);
|
|
860
|
+
const config = readConfig(dir);
|
|
861
|
+
if (!config) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
867
|
+
const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
868
|
+
const client = createApiClient(apiUrl, token);
|
|
869
|
+
const res = await client.setProtection(config.artifactId, password);
|
|
870
|
+
if (opts.json) {
|
|
871
|
+
console.log(JSON.stringify({ protected: res.protected }, null, 2));
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
console.log(
|
|
875
|
+
res.protected ? `
|
|
876
|
+
\u2713 Password protection on \u2014 ${config.url}
|
|
877
|
+
` : `
|
|
878
|
+
\u2713 Password protection removed \u2014 ${config.url}
|
|
879
|
+
`
|
|
880
|
+
);
|
|
681
881
|
}
|
|
682
882
|
|
|
683
883
|
// src/index.ts
|
|
684
884
|
var publish2 = defineCommand({
|
|
685
|
-
meta: { name: "publish", description: "Publish a folder as an artifact" },
|
|
686
885
|
args: {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
886
|
+
artifact: {
|
|
887
|
+
description: "Publish to an existing artifact ID",
|
|
888
|
+
type: "string"
|
|
889
|
+
},
|
|
890
|
+
dir: {
|
|
891
|
+
default: ".",
|
|
892
|
+
description: "Directory to publish",
|
|
893
|
+
type: "positional"
|
|
894
|
+
},
|
|
895
|
+
entry: { description: "Entry file (default: auto-detect)", type: "string" },
|
|
896
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
897
|
+
"no-open": { description: "Don't open the review URL", type: "boolean" },
|
|
898
|
+
password: {
|
|
899
|
+
description: "Password-protect the artifact (min 6 chars)",
|
|
900
|
+
type: "string"
|
|
901
|
+
},
|
|
902
|
+
title: { description: "Artifact title", type: "string" }
|
|
693
903
|
},
|
|
904
|
+
meta: { description: "Publish a folder as an artifact", name: "publish" },
|
|
694
905
|
async run({ args }) {
|
|
695
906
|
try {
|
|
696
907
|
await publish({
|
|
908
|
+
artifact: args.artifact,
|
|
697
909
|
dir: args.dir,
|
|
698
|
-
title: args.title,
|
|
699
910
|
entry: args.entry,
|
|
700
|
-
artifact: args.artifact,
|
|
701
911
|
json: Boolean(args.json),
|
|
702
|
-
open: !args["no-open"]
|
|
912
|
+
open: !args["no-open"],
|
|
913
|
+
password: args.password,
|
|
914
|
+
title: args.title
|
|
703
915
|
});
|
|
704
916
|
} catch (err) {
|
|
705
917
|
printCliError(err, Boolean(args.json));
|
|
@@ -708,12 +920,27 @@ var publish2 = defineCommand({
|
|
|
708
920
|
}
|
|
709
921
|
});
|
|
710
922
|
var comments2 = defineCommand({
|
|
711
|
-
meta: { name: "comments", description: "Pull comments for the linked artifact" },
|
|
712
923
|
args: {
|
|
713
|
-
dir: {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
924
|
+
dir: {
|
|
925
|
+
default: ".",
|
|
926
|
+
description: "Published directory",
|
|
927
|
+
type: "positional"
|
|
928
|
+
},
|
|
929
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
930
|
+
status: {
|
|
931
|
+
default: "open",
|
|
932
|
+
description: "Filter: open | resolved | all",
|
|
933
|
+
type: "string"
|
|
934
|
+
},
|
|
935
|
+
version: {
|
|
936
|
+
default: "current",
|
|
937
|
+
description: "Filter: current | all",
|
|
938
|
+
type: "string"
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
meta: {
|
|
942
|
+
description: "Pull comments for the linked artifact",
|
|
943
|
+
name: "comments"
|
|
717
944
|
},
|
|
718
945
|
async run({ args }) {
|
|
719
946
|
try {
|
|
@@ -729,11 +956,44 @@ var comments2 = defineCommand({
|
|
|
729
956
|
}
|
|
730
957
|
}
|
|
731
958
|
});
|
|
959
|
+
var protect2 = defineCommand({
|
|
960
|
+
args: {
|
|
961
|
+
dir: {
|
|
962
|
+
default: ".",
|
|
963
|
+
description: "Published directory",
|
|
964
|
+
type: "positional"
|
|
965
|
+
},
|
|
966
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
967
|
+
off: { description: "Remove the password gate", type: "boolean" },
|
|
968
|
+
password: { description: "Gate password (min 6 chars)", type: "string" }
|
|
969
|
+
},
|
|
970
|
+
meta: {
|
|
971
|
+
description: "Password-protect the linked artifact (or --off to remove)",
|
|
972
|
+
name: "protect"
|
|
973
|
+
},
|
|
974
|
+
async run({ args }) {
|
|
975
|
+
try {
|
|
976
|
+
await protect({
|
|
977
|
+
dir: args.dir,
|
|
978
|
+
json: Boolean(args.json),
|
|
979
|
+
off: Boolean(args.off),
|
|
980
|
+
password: args.password
|
|
981
|
+
});
|
|
982
|
+
} catch (err) {
|
|
983
|
+
printCliError(err, Boolean(args.json));
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
});
|
|
732
988
|
var open = defineCommand({
|
|
733
|
-
meta: { name: "open", description: "Open the linked artifact's review URL" },
|
|
734
989
|
args: {
|
|
735
|
-
dir: {
|
|
990
|
+
dir: {
|
|
991
|
+
default: ".",
|
|
992
|
+
description: "Published directory",
|
|
993
|
+
type: "positional"
|
|
994
|
+
}
|
|
736
995
|
},
|
|
996
|
+
meta: { description: "Open the linked artifact's review URL", name: "open" },
|
|
737
997
|
run({ args }) {
|
|
738
998
|
try {
|
|
739
999
|
openArtifact({ dir: args.dir });
|
|
@@ -743,18 +1003,11 @@ var open = defineCommand({
|
|
|
743
1003
|
}
|
|
744
1004
|
}
|
|
745
1005
|
});
|
|
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
1006
|
var main = defineCommand({
|
|
754
1007
|
meta: {
|
|
755
|
-
|
|
756
|
-
|
|
1008
|
+
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
1009
|
+
name: "artifact-cafe"
|
|
757
1010
|
},
|
|
758
|
-
subCommands: {
|
|
1011
|
+
subCommands: { comments: comments2, open, protect: protect2, publish: publish2 }
|
|
759
1012
|
});
|
|
760
1013
|
runMain(main);
|