Fixes #25
+53
-19
Diff
round #0
+6
packages/cli/CHANGELOG.md
+6
packages/cli/CHANGELOG.md
+41
-17
packages/cli/src/commands/publish.ts
+41
-17
packages/cli/src/commands/publish.ts
···
2
2
import { command, flag } from "cmd-ts";
3
3
import { select, spinner, log } from "@clack/prompts";
4
4
import * as path from "node:path";
5
-
import { loadConfig, loadState, saveState, findConfig } from "../lib/config";
5
+
import { CONFIG_FILENAME, loadConfig, loadState, saveState, findConfig } from "../lib/config";
6
6
import {
7
7
loadCredentials,
8
8
listAllCredentials,
···
17
17
resolveImagePath,
18
18
createBlueskyPost,
19
19
addBskyPostRefToDocument,
20
+
COVER_IMAGE_MAX_SIZE,
20
21
} from "../lib/atproto";
21
22
import {
22
23
scanContentDirectory,
···
52
53
// Load config
53
54
const configPath = await findConfig();
54
55
if (!configPath) {
55
-
log.error("No publisher.config.ts found. Run 'publisher init' first.");
56
+
log.error(`No ${CONFIG_FILENAME} found. Run 'sequoia init' first.`);
56
57
process.exit(1);
57
58
}
58
59
···
261
262
const cutoffDate = new Date();
262
263
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
263
264
265
+
let isValid = true;
264
266
for (const { post, action, reason } of postsToPublish) {
265
267
const icon = action === "create" ? "+" : "~";
266
268
const relativeFilePath = path.relative(configDir, post.filePath);
267
269
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
268
270
271
+
if (post.frontmatter.ogImage) {
272
+
post.coverImagePath = await resolveImagePath(
273
+
post.frontmatter.ogImage,
274
+
imagesDir,
275
+
contentDir,
276
+
);
277
+
}
278
+
269
279
let bskyNote = "";
270
280
if (blueskyEnabled) {
271
281
if (existingBskyPostRef) {
···
292
302
log.message(
293
303
` ${icon} ${post.frontmatter.title} (${reason})${bskyNote}${postUrl}`,
294
304
);
305
+
306
+
const postValid = await validatePost(post);
307
+
isValid &&= postValid;
308
+
}
309
+
310
+
if (!isValid) {
311
+
return;
295
312
}
296
313
297
314
if (dryRun) {
···
329
346
try {
330
347
// Handle cover image upload
331
348
let coverImage: BlobObject | undefined;
332
-
if (post.frontmatter.ogImage) {
333
-
const imagePath = await resolveImagePath(
334
-
post.frontmatter.ogImage,
335
-
imagesDir,
336
-
contentDir,
337
-
);
338
-
339
-
if (imagePath) {
340
-
log.info(` Uploading cover image: ${path.basename(imagePath)}`);
341
-
coverImage = await uploadImage(agent, imagePath);
342
-
if (coverImage) {
343
-
log.info(` Uploaded image blob: ${coverImage.ref.$link}`);
344
-
}
345
-
} else {
346
-
log.warn(` Cover image not found: ${post.frontmatter.ogImage}`);
349
+
if (post.coverImagePath) {
350
+
log.info(` Uploading cover image: ${path.basename(post.coverImagePath)}`);
351
+
coverImage = await uploadImage(agent, post.coverImagePath);
352
+
if (coverImage) {
353
+
log.info(` Uploaded image blob: ${coverImage.ref.$link}`);
347
354
}
355
+
} else {
356
+
log.warn(` Cover image not found: ${post.frontmatter.ogImage}`);
348
357
}
349
358
350
359
// Track atUri, content for state saving, and bskyPostRef
···
372
381
contentForHash = updatedContent;
373
382
publishedCount++;
374
383
} else {
384
+
385
+
// Validate post.
375
386
atUri = post.frontmatter.atUri!;
376
387
await updateDocument(agent, post, atUri, config, coverImage);
377
388
s.stop(`Updated: ${atUri}`);
···
455
466
}
456
467
},
457
468
});
469
+
470
+
async function validatePost(post: BlogPost): Promise<boolean> {
471
+
if (post.coverImagePath) {
472
+
const stat = await fs.stat(post.coverImagePath);
473
+
if (stat.size >= COVER_IMAGE_MAX_SIZE) {
474
+
log.error(` Cover image "${post.coverImagePath}" must be less than 1MB`);
475
+
return false;
476
+
}
477
+
}
478
+
479
+
return true;
480
+
}
481
+
+4
-1
packages/cli/src/lib/atproto.ts
+4
-1
packages/cli/src/lib/atproto.ts
···
14
14
} from "./types";
15
15
import { isAppPasswordCredentials, isOAuthCredentials } from "./types";
16
16
17
+
// https://standard.site/docs/lexicons/document/#optional-properties
18
+
export const COVER_IMAGE_MAX_SIZE = 1024 * 1024 - 1;
19
+
17
20
/**
18
21
* Type guard to check if a record value is a DocumentRecord
19
22
*/
···
189
192
ogImage: string,
190
193
imagesDir: string | undefined,
191
194
contentDir: string,
192
-
): Promise<string | null> {
195
+
): Promise<string | undefined> {
193
196
// Try multiple resolution strategies
194
197
195
198
// 1. If imagesDir is specified, look there
+1
-1
packages/cli/src/lib/config.ts
+1
-1
packages/cli/src/lib/config.ts
History
1 round
0 comments
heaths.dev
submitted
#0
1 commit
expand
collapse
Validate cover image is < 1MB
Fixes #25
merge conflicts detected
expand
collapse
expand
collapse
- packages/cli/CHANGELOG.md:1
- packages/cli/src/commands/publish.ts:2
- packages/cli/src/lib/atproto.ts:14
- packages/cli/src/lib/config.ts:7
- packages/cli/src/lib/types.ts:106