sequoia-cli 0.1.2-beta.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +295 -160
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67266,18 +67266,10 @@ function parseFrontmatter(content, mapping) {
|
|
|
67266
67266
|
const raw = {};
|
|
67267
67267
|
const lines = frontmatterStr.split(`
|
|
67268
67268
|
`);
|
|
67269
|
-
|
|
67270
|
-
while (i < lines.length) {
|
|
67271
|
-
const line = lines[i];
|
|
67272
|
-
if (line === undefined) {
|
|
67273
|
-
i++;
|
|
67274
|
-
continue;
|
|
67275
|
-
}
|
|
67269
|
+
for (const line of lines) {
|
|
67276
67270
|
const sepIndex = line.indexOf(separator);
|
|
67277
|
-
if (sepIndex === -1)
|
|
67278
|
-
i++;
|
|
67271
|
+
if (sepIndex === -1)
|
|
67279
67272
|
continue;
|
|
67280
|
-
}
|
|
67281
67273
|
const key = line.slice(0, sepIndex).trim();
|
|
67282
67274
|
let value = line.slice(sepIndex + 1).trim();
|
|
67283
67275
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
@@ -67286,36 +67278,6 @@ function parseFrontmatter(content, mapping) {
|
|
|
67286
67278
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
67287
67279
|
const arrayContent = value.slice(1, -1);
|
|
67288
67280
|
raw[key] = arrayContent.split(",").map((item) => item.trim().replace(/^["']|["']$/g, ""));
|
|
67289
|
-
} else if (value === "" && !isToml) {
|
|
67290
|
-
const arrayItems = [];
|
|
67291
|
-
let j2 = i + 1;
|
|
67292
|
-
while (j2 < lines.length) {
|
|
67293
|
-
const nextLine = lines[j2];
|
|
67294
|
-
if (nextLine === undefined) {
|
|
67295
|
-
j2++;
|
|
67296
|
-
continue;
|
|
67297
|
-
}
|
|
67298
|
-
const listMatch = nextLine.match(/^\s+-\s*(.*)$/);
|
|
67299
|
-
if (listMatch && listMatch[1] !== undefined) {
|
|
67300
|
-
let itemValue = listMatch[1].trim();
|
|
67301
|
-
if (itemValue.startsWith('"') && itemValue.endsWith('"') || itemValue.startsWith("'") && itemValue.endsWith("'")) {
|
|
67302
|
-
itemValue = itemValue.slice(1, -1);
|
|
67303
|
-
}
|
|
67304
|
-
arrayItems.push(itemValue);
|
|
67305
|
-
j2++;
|
|
67306
|
-
} else if (nextLine.trim() === "") {
|
|
67307
|
-
j2++;
|
|
67308
|
-
} else {
|
|
67309
|
-
break;
|
|
67310
|
-
}
|
|
67311
|
-
}
|
|
67312
|
-
if (arrayItems.length > 0) {
|
|
67313
|
-
raw[key] = arrayItems;
|
|
67314
|
-
i = j2;
|
|
67315
|
-
continue;
|
|
67316
|
-
} else {
|
|
67317
|
-
raw[key] = value;
|
|
67318
|
-
}
|
|
67319
67281
|
} else if (value === "true") {
|
|
67320
67282
|
raw[key] = true;
|
|
67321
67283
|
} else if (value === "false") {
|
|
@@ -67323,7 +67285,6 @@ function parseFrontmatter(content, mapping) {
|
|
|
67323
67285
|
} else {
|
|
67324
67286
|
raw[key] = value;
|
|
67325
67287
|
}
|
|
67326
|
-
i++;
|
|
67327
67288
|
}
|
|
67328
67289
|
const frontmatter = {};
|
|
67329
67290
|
const titleField = mapping?.title || "title";
|
|
@@ -67348,37 +67309,17 @@ function parseFrontmatter(content, mapping) {
|
|
|
67348
67309
|
frontmatter.ogImage = raw[coverField] || raw.ogImage;
|
|
67349
67310
|
const tagsField = mapping?.tags || "tags";
|
|
67350
67311
|
frontmatter.tags = raw[tagsField] || raw.tags;
|
|
67312
|
+
const draftField = mapping?.draft || "draft";
|
|
67313
|
+
const draftValue = raw[draftField] ?? raw.draft;
|
|
67314
|
+
if (draftValue !== undefined) {
|
|
67315
|
+
frontmatter.draft = draftValue === true || draftValue === "true";
|
|
67316
|
+
}
|
|
67351
67317
|
frontmatter.atUri = raw.atUri;
|
|
67352
|
-
return { frontmatter, body
|
|
67318
|
+
return { frontmatter, body };
|
|
67353
67319
|
}
|
|
67354
67320
|
function getSlugFromFilename(filename) {
|
|
67355
67321
|
return filename.replace(/\.mdx?$/, "").toLowerCase().replace(/\s+/g, "-");
|
|
67356
67322
|
}
|
|
67357
|
-
function getSlugFromOptions(relativePath, rawFrontmatter, options = {}) {
|
|
67358
|
-
const { slugSource = "filename", slugField = "slug", removeIndexFromSlug = false } = options;
|
|
67359
|
-
let slug;
|
|
67360
|
-
switch (slugSource) {
|
|
67361
|
-
case "path":
|
|
67362
|
-
slug = relativePath.replace(/\.mdx?$/, "").toLowerCase().replace(/\s+/g, "-");
|
|
67363
|
-
break;
|
|
67364
|
-
case "frontmatter":
|
|
67365
|
-
const frontmatterValue = rawFrontmatter[slugField] || rawFrontmatter.slug || rawFrontmatter.url;
|
|
67366
|
-
if (frontmatterValue && typeof frontmatterValue === "string") {
|
|
67367
|
-
slug = frontmatterValue.replace(/^\//, "").toLowerCase().replace(/\s+/g, "-");
|
|
67368
|
-
} else {
|
|
67369
|
-
slug = getSlugFromFilename(path3.basename(relativePath));
|
|
67370
|
-
}
|
|
67371
|
-
break;
|
|
67372
|
-
case "filename":
|
|
67373
|
-
default:
|
|
67374
|
-
slug = getSlugFromFilename(path3.basename(relativePath));
|
|
67375
|
-
break;
|
|
67376
|
-
}
|
|
67377
|
-
if (removeIndexFromSlug) {
|
|
67378
|
-
slug = slug.replace(/\/_?index$/, "");
|
|
67379
|
-
}
|
|
67380
|
-
return slug;
|
|
67381
|
-
}
|
|
67382
67323
|
async function getContentHash(content) {
|
|
67383
67324
|
const encoder = new TextEncoder;
|
|
67384
67325
|
const data = encoder.encode(content);
|
|
@@ -67394,23 +67335,7 @@ function shouldIgnore(relativePath, ignorePatterns) {
|
|
|
67394
67335
|
}
|
|
67395
67336
|
return false;
|
|
67396
67337
|
}
|
|
67397
|
-
async function scanContentDirectory(contentDir,
|
|
67398
|
-
let options;
|
|
67399
|
-
if (frontmatterMappingOrOptions && (("slugSource" in frontmatterMappingOrOptions) || ("frontmatterMapping" in frontmatterMappingOrOptions) || ("ignorePatterns" in frontmatterMappingOrOptions))) {
|
|
67400
|
-
options = frontmatterMappingOrOptions;
|
|
67401
|
-
} else {
|
|
67402
|
-
options = {
|
|
67403
|
-
frontmatterMapping: frontmatterMappingOrOptions,
|
|
67404
|
-
ignorePatterns
|
|
67405
|
-
};
|
|
67406
|
-
}
|
|
67407
|
-
const {
|
|
67408
|
-
frontmatterMapping,
|
|
67409
|
-
ignorePatterns: ignore = [],
|
|
67410
|
-
slugSource,
|
|
67411
|
-
slugField,
|
|
67412
|
-
removeIndexFromSlug
|
|
67413
|
-
} = options;
|
|
67338
|
+
async function scanContentDirectory(contentDir, frontmatterMapping, ignorePatterns = []) {
|
|
67414
67339
|
const patterns = ["**/*.md", "**/*.mdx"];
|
|
67415
67340
|
const posts = [];
|
|
67416
67341
|
for (const pattern of patterns) {
|
|
@@ -67419,25 +67344,21 @@ async function scanContentDirectory(contentDir, frontmatterMappingOrOptions, ign
|
|
|
67419
67344
|
absolute: false
|
|
67420
67345
|
});
|
|
67421
67346
|
for (const relativePath of files) {
|
|
67422
|
-
if (shouldIgnore(relativePath,
|
|
67347
|
+
if (shouldIgnore(relativePath, ignorePatterns)) {
|
|
67423
67348
|
continue;
|
|
67424
67349
|
}
|
|
67425
67350
|
const filePath = path3.join(contentDir, relativePath);
|
|
67426
67351
|
const rawContent = await fs2.readFile(filePath, "utf-8");
|
|
67427
67352
|
try {
|
|
67428
|
-
const { frontmatter, body
|
|
67429
|
-
const
|
|
67430
|
-
|
|
67431
|
-
slugField,
|
|
67432
|
-
removeIndexFromSlug
|
|
67433
|
-
});
|
|
67353
|
+
const { frontmatter, body } = parseFrontmatter(rawContent, frontmatterMapping);
|
|
67354
|
+
const filename = path3.basename(relativePath);
|
|
67355
|
+
const slug = getSlugFromFilename(filename);
|
|
67434
67356
|
posts.push({
|
|
67435
67357
|
filePath,
|
|
67436
67358
|
slug,
|
|
67437
67359
|
frontmatter,
|
|
67438
67360
|
content: body,
|
|
67439
|
-
rawContent
|
|
67440
|
-
rawFrontmatter
|
|
67361
|
+
rawContent
|
|
67441
67362
|
});
|
|
67442
67363
|
} catch (error) {
|
|
67443
67364
|
console.error(`Error parsing ${relativePath}:`, error);
|
|
@@ -67580,13 +67501,8 @@ async function resolveImagePath(ogImage, imagesDir, contentDir) {
|
|
|
67580
67501
|
async function createDocument(agent, post, config, coverImage) {
|
|
67581
67502
|
const pathPrefix = config.pathPrefix || "/posts";
|
|
67582
67503
|
const postPath = `${pathPrefix}/${post.slug}`;
|
|
67504
|
+
const textContent = stripMarkdownForText(post.content);
|
|
67583
67505
|
const publishDate = new Date(post.frontmatter.publishDate);
|
|
67584
|
-
let textContent;
|
|
67585
|
-
if (config.textContentField && post.rawFrontmatter?.[config.textContentField]) {
|
|
67586
|
-
textContent = String(post.rawFrontmatter[config.textContentField]);
|
|
67587
|
-
} else {
|
|
67588
|
-
textContent = stripMarkdownForText(post.content);
|
|
67589
|
-
}
|
|
67590
67506
|
const record = {
|
|
67591
67507
|
$type: "site.standard.document",
|
|
67592
67508
|
title: post.frontmatter.title,
|
|
@@ -67596,9 +67512,6 @@ async function createDocument(agent, post, config, coverImage) {
|
|
|
67596
67512
|
publishedAt: publishDate.toISOString(),
|
|
67597
67513
|
canonicalUrl: `${config.siteUrl}${postPath}`
|
|
67598
67514
|
};
|
|
67599
|
-
if (post.frontmatter.description) {
|
|
67600
|
-
record.description = post.frontmatter.description;
|
|
67601
|
-
}
|
|
67602
67515
|
if (coverImage) {
|
|
67603
67516
|
record.coverImage = coverImage;
|
|
67604
67517
|
}
|
|
@@ -67620,13 +67533,8 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
67620
67533
|
const [, , collection, rkey] = uriMatch;
|
|
67621
67534
|
const pathPrefix = config.pathPrefix || "/posts";
|
|
67622
67535
|
const postPath = `${pathPrefix}/${post.slug}`;
|
|
67536
|
+
const textContent = stripMarkdownForText(post.content);
|
|
67623
67537
|
const publishDate = new Date(post.frontmatter.publishDate);
|
|
67624
|
-
let textContent;
|
|
67625
|
-
if (config.textContentField && post.rawFrontmatter?.[config.textContentField]) {
|
|
67626
|
-
textContent = String(post.rawFrontmatter[config.textContentField]);
|
|
67627
|
-
} else {
|
|
67628
|
-
textContent = stripMarkdownForText(post.content);
|
|
67629
|
-
}
|
|
67630
67538
|
const record = {
|
|
67631
67539
|
$type: "site.standard.document",
|
|
67632
67540
|
title: post.frontmatter.title,
|
|
@@ -67636,9 +67544,6 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
67636
67544
|
publishedAt: publishDate.toISOString(),
|
|
67637
67545
|
canonicalUrl: `${config.siteUrl}${postPath}`
|
|
67638
67546
|
};
|
|
67639
|
-
if (post.frontmatter.description) {
|
|
67640
|
-
record.description = post.frontmatter.description;
|
|
67641
|
-
}
|
|
67642
67547
|
if (coverImage) {
|
|
67643
67548
|
record.coverImage = coverImage;
|
|
67644
67549
|
}
|
|
@@ -67652,6 +67557,16 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
67652
67557
|
record
|
|
67653
67558
|
});
|
|
67654
67559
|
}
|
|
67560
|
+
function parseAtUri(atUri) {
|
|
67561
|
+
const match2 = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
67562
|
+
if (!match2)
|
|
67563
|
+
return null;
|
|
67564
|
+
return {
|
|
67565
|
+
did: match2[1],
|
|
67566
|
+
collection: match2[2],
|
|
67567
|
+
rkey: match2[3]
|
|
67568
|
+
};
|
|
67569
|
+
}
|
|
67655
67570
|
async function listDocuments(agent, publicationUri) {
|
|
67656
67571
|
const documents = [];
|
|
67657
67572
|
let cursor;
|
|
@@ -67706,6 +67621,129 @@ async function createPublication(agent, options) {
|
|
|
67706
67621
|
});
|
|
67707
67622
|
return response.data.uri;
|
|
67708
67623
|
}
|
|
67624
|
+
function countGraphemes(str) {
|
|
67625
|
+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
67626
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
67627
|
+
return [...segmenter.segment(str)].length;
|
|
67628
|
+
}
|
|
67629
|
+
return [...str].length;
|
|
67630
|
+
}
|
|
67631
|
+
function truncateToGraphemes(str, maxGraphemes) {
|
|
67632
|
+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
67633
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
67634
|
+
const segments = [...segmenter.segment(str)];
|
|
67635
|
+
if (segments.length <= maxGraphemes)
|
|
67636
|
+
return str;
|
|
67637
|
+
return segments.slice(0, maxGraphemes - 3).map((s) => s.segment).join("") + "...";
|
|
67638
|
+
}
|
|
67639
|
+
const chars = [...str];
|
|
67640
|
+
if (chars.length <= maxGraphemes)
|
|
67641
|
+
return str;
|
|
67642
|
+
return chars.slice(0, maxGraphemes - 3).join("") + "...";
|
|
67643
|
+
}
|
|
67644
|
+
async function createBlueskyPost(agent, options) {
|
|
67645
|
+
const { title, description, canonicalUrl, coverImage, publishedAt } = options;
|
|
67646
|
+
const MAX_GRAPHEMES = 300;
|
|
67647
|
+
let postText;
|
|
67648
|
+
const urlPart = `
|
|
67649
|
+
|
|
67650
|
+
${canonicalUrl}`;
|
|
67651
|
+
const urlGraphemes = countGraphemes(urlPart);
|
|
67652
|
+
if (description) {
|
|
67653
|
+
const fullText = `${title}
|
|
67654
|
+
|
|
67655
|
+
${description}${urlPart}`;
|
|
67656
|
+
if (countGraphemes(fullText) <= MAX_GRAPHEMES) {
|
|
67657
|
+
postText = fullText;
|
|
67658
|
+
} else {
|
|
67659
|
+
const availableForDesc = MAX_GRAPHEMES - countGraphemes(title) - countGraphemes(`
|
|
67660
|
+
|
|
67661
|
+
`) - urlGraphemes - countGraphemes(`
|
|
67662
|
+
|
|
67663
|
+
`);
|
|
67664
|
+
if (availableForDesc > 10) {
|
|
67665
|
+
const truncatedDesc = truncateToGraphemes(description, availableForDesc);
|
|
67666
|
+
postText = `${title}
|
|
67667
|
+
|
|
67668
|
+
${truncatedDesc}${urlPart}`;
|
|
67669
|
+
} else {
|
|
67670
|
+
postText = `${title}${urlPart}`;
|
|
67671
|
+
}
|
|
67672
|
+
}
|
|
67673
|
+
} else {
|
|
67674
|
+
postText = `${title}${urlPart}`;
|
|
67675
|
+
}
|
|
67676
|
+
if (countGraphemes(postText) > MAX_GRAPHEMES) {
|
|
67677
|
+
postText = truncateToGraphemes(postText, MAX_GRAPHEMES);
|
|
67678
|
+
}
|
|
67679
|
+
const encoder = new TextEncoder;
|
|
67680
|
+
const urlStartInText = postText.lastIndexOf(canonicalUrl);
|
|
67681
|
+
const beforeUrl = postText.substring(0, urlStartInText);
|
|
67682
|
+
const byteStart = encoder.encode(beforeUrl).length;
|
|
67683
|
+
const byteEnd = byteStart + encoder.encode(canonicalUrl).length;
|
|
67684
|
+
const facets = [
|
|
67685
|
+
{
|
|
67686
|
+
index: {
|
|
67687
|
+
byteStart,
|
|
67688
|
+
byteEnd
|
|
67689
|
+
},
|
|
67690
|
+
features: [
|
|
67691
|
+
{
|
|
67692
|
+
$type: "app.bsky.richtext.facet#link",
|
|
67693
|
+
uri: canonicalUrl
|
|
67694
|
+
}
|
|
67695
|
+
]
|
|
67696
|
+
}
|
|
67697
|
+
];
|
|
67698
|
+
const embed = {
|
|
67699
|
+
$type: "app.bsky.embed.external",
|
|
67700
|
+
external: {
|
|
67701
|
+
uri: canonicalUrl,
|
|
67702
|
+
title: title.substring(0, 500),
|
|
67703
|
+
description: (description || "").substring(0, 1000)
|
|
67704
|
+
}
|
|
67705
|
+
};
|
|
67706
|
+
if (coverImage) {
|
|
67707
|
+
embed.external.thumb = coverImage;
|
|
67708
|
+
}
|
|
67709
|
+
const record = {
|
|
67710
|
+
$type: "app.bsky.feed.post",
|
|
67711
|
+
text: postText,
|
|
67712
|
+
facets,
|
|
67713
|
+
embed,
|
|
67714
|
+
createdAt: new Date(publishedAt).toISOString()
|
|
67715
|
+
};
|
|
67716
|
+
const response = await agent.com.atproto.repo.createRecord({
|
|
67717
|
+
repo: agent.session.did,
|
|
67718
|
+
collection: "app.bsky.feed.post",
|
|
67719
|
+
record
|
|
67720
|
+
});
|
|
67721
|
+
return {
|
|
67722
|
+
uri: response.data.uri,
|
|
67723
|
+
cid: response.data.cid
|
|
67724
|
+
};
|
|
67725
|
+
}
|
|
67726
|
+
async function addBskyPostRefToDocument(agent, documentAtUri, bskyPostRef) {
|
|
67727
|
+
const parsed = parseAtUri(documentAtUri);
|
|
67728
|
+
if (!parsed) {
|
|
67729
|
+
throw new Error(`Invalid document URI: ${documentAtUri}`);
|
|
67730
|
+
}
|
|
67731
|
+
const existingRecord = await agent.com.atproto.repo.getRecord({
|
|
67732
|
+
repo: parsed.did,
|
|
67733
|
+
collection: parsed.collection,
|
|
67734
|
+
rkey: parsed.rkey
|
|
67735
|
+
});
|
|
67736
|
+
const updatedRecord = {
|
|
67737
|
+
...existingRecord.data.value,
|
|
67738
|
+
bskyPostRef
|
|
67739
|
+
};
|
|
67740
|
+
await agent.com.atproto.repo.putRecord({
|
|
67741
|
+
repo: parsed.did,
|
|
67742
|
+
collection: parsed.collection,
|
|
67743
|
+
rkey: parsed.rkey,
|
|
67744
|
+
record: updatedRecord
|
|
67745
|
+
});
|
|
67746
|
+
}
|
|
67709
67747
|
|
|
67710
67748
|
// src/lib/prompts.ts
|
|
67711
67749
|
function exitOnCancel(value) {
|
|
@@ -67918,17 +67956,8 @@ function generateConfigTemplate(options) {
|
|
|
67918
67956
|
if (options.ignore && options.ignore.length > 0) {
|
|
67919
67957
|
config.ignore = options.ignore;
|
|
67920
67958
|
}
|
|
67921
|
-
if (options.
|
|
67922
|
-
config.
|
|
67923
|
-
}
|
|
67924
|
-
if (options.slugField && options.slugField !== "slug") {
|
|
67925
|
-
config.slugField = options.slugField;
|
|
67926
|
-
}
|
|
67927
|
-
if (options.removeIndexFromSlug) {
|
|
67928
|
-
config.removeIndexFromSlug = options.removeIndexFromSlug;
|
|
67929
|
-
}
|
|
67930
|
-
if (options.textContentField) {
|
|
67931
|
-
config.textContentField = options.textContentField;
|
|
67959
|
+
if (options.bluesky) {
|
|
67960
|
+
config.bluesky = options.bluesky;
|
|
67932
67961
|
}
|
|
67933
67962
|
return JSON.stringify(config, null, 2);
|
|
67934
67963
|
}
|
|
@@ -68044,6 +68073,11 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68044
68073
|
message: "Field name for tags:",
|
|
68045
68074
|
defaultValue: "tags",
|
|
68046
68075
|
placeholder: "tags, categories, keywords, etc."
|
|
68076
|
+
}),
|
|
68077
|
+
draftField: () => Qt({
|
|
68078
|
+
message: "Field name for draft status:",
|
|
68079
|
+
defaultValue: "draft",
|
|
68080
|
+
placeholder: "draft, private, hidden, etc."
|
|
68047
68081
|
})
|
|
68048
68082
|
}, { onCancel });
|
|
68049
68083
|
const fieldMappings = [
|
|
@@ -68051,7 +68085,8 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68051
68085
|
["description", frontmatterConfig.descField, "description"],
|
|
68052
68086
|
["publishDate", frontmatterConfig.dateField, "publishDate"],
|
|
68053
68087
|
["coverImage", frontmatterConfig.coverField, "ogImage"],
|
|
68054
|
-
["tags", frontmatterConfig.tagsField, "tags"]
|
|
68088
|
+
["tags", frontmatterConfig.tagsField, "tags"],
|
|
68089
|
+
["draft", frontmatterConfig.draftField, "draft"]
|
|
68055
68090
|
];
|
|
68056
68091
|
const builtMapping = fieldMappings.reduce((acc, [key, value, defaultValue]) => {
|
|
68057
68092
|
if (value !== defaultValue) {
|
|
@@ -68139,6 +68174,35 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68139
68174
|
}
|
|
68140
68175
|
publicationUri = uri;
|
|
68141
68176
|
}
|
|
68177
|
+
const enableBluesky = await Mt2({
|
|
68178
|
+
message: "Enable automatic Bluesky posting when publishing?",
|
|
68179
|
+
initialValue: false
|
|
68180
|
+
});
|
|
68181
|
+
if (enableBluesky === Symbol.for("cancel")) {
|
|
68182
|
+
onCancel();
|
|
68183
|
+
}
|
|
68184
|
+
let blueskyConfig;
|
|
68185
|
+
if (enableBluesky) {
|
|
68186
|
+
const maxAgeDaysInput = await Qt({
|
|
68187
|
+
message: "Maximum age (in days) for posts to be shared on Bluesky:",
|
|
68188
|
+
defaultValue: "7",
|
|
68189
|
+
placeholder: "7",
|
|
68190
|
+
validate: (value) => {
|
|
68191
|
+
const num = parseInt(value, 10);
|
|
68192
|
+
if (isNaN(num) || num < 1) {
|
|
68193
|
+
return "Please enter a positive number";
|
|
68194
|
+
}
|
|
68195
|
+
}
|
|
68196
|
+
});
|
|
68197
|
+
if (maxAgeDaysInput === Symbol.for("cancel")) {
|
|
68198
|
+
onCancel();
|
|
68199
|
+
}
|
|
68200
|
+
const maxAgeDays = parseInt(maxAgeDaysInput, 10);
|
|
68201
|
+
blueskyConfig = {
|
|
68202
|
+
enabled: true,
|
|
68203
|
+
...maxAgeDays !== 7 && { maxAgeDays }
|
|
68204
|
+
};
|
|
68205
|
+
}
|
|
68142
68206
|
const pdsUrl = credentials?.pdsUrl;
|
|
68143
68207
|
const configContent = generateConfigTemplate({
|
|
68144
68208
|
siteUrl: siteConfig.siteUrl,
|
|
@@ -68149,7 +68213,8 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68149
68213
|
pathPrefix: siteConfig.pathPrefix || "/posts",
|
|
68150
68214
|
publicationUri,
|
|
68151
68215
|
pdsUrl,
|
|
68152
|
-
frontmatter: frontmatterMapping
|
|
68216
|
+
frontmatter: frontmatterMapping,
|
|
68217
|
+
bluesky: blueskyConfig
|
|
68153
68218
|
});
|
|
68154
68219
|
const configPath = path6.join(process.cwd(), "sequoia.json");
|
|
68155
68220
|
await fs5.writeFile(configPath, configContent);
|
|
@@ -68216,24 +68281,38 @@ var injectCommand = import_cmd_ts3.command({
|
|
|
68216
68281
|
const resolvedOutputDir = path7.isAbsolute(outputDir) ? outputDir : path7.join(configDir, outputDir);
|
|
68217
68282
|
R2.info(`Scanning for HTML files in: ${resolvedOutputDir}`);
|
|
68218
68283
|
const state = await loadState(configDir);
|
|
68219
|
-
const
|
|
68284
|
+
const genericFilenames = new Set([
|
|
68285
|
+
"+page",
|
|
68286
|
+
"index",
|
|
68287
|
+
"_index",
|
|
68288
|
+
"page",
|
|
68289
|
+
"readme"
|
|
68290
|
+
]);
|
|
68291
|
+
const pathToAtUri = new Map;
|
|
68220
68292
|
for (const [filePath, postState] of Object.entries(state.posts)) {
|
|
68221
|
-
if (postState.atUri
|
|
68222
|
-
|
|
68223
|
-
|
|
68224
|
-
|
|
68225
|
-
|
|
68293
|
+
if (postState.atUri) {
|
|
68294
|
+
let basename4 = path7.basename(filePath, path7.extname(filePath));
|
|
68295
|
+
if (genericFilenames.has(basename4.toLowerCase())) {
|
|
68296
|
+
const pathParts = filePath.split(/[/\\]/).filter((p) => p && !(p.startsWith("(") && p.endsWith(")")));
|
|
68297
|
+
if (pathParts.length >= 2) {
|
|
68298
|
+
const slug = pathParts[pathParts.length - 2];
|
|
68299
|
+
if (slug && slug !== "." && slug !== "content" && slug !== "routes" && slug !== "src") {
|
|
68300
|
+
basename4 = slug;
|
|
68301
|
+
}
|
|
68302
|
+
}
|
|
68303
|
+
}
|
|
68304
|
+
pathToAtUri.set(basename4, postState.atUri);
|
|
68305
|
+
const dirName = path7.basename(path7.dirname(filePath));
|
|
68306
|
+
if (dirName !== "." && dirName !== "content" && !(dirName.startsWith("(") && dirName.endsWith(")"))) {
|
|
68307
|
+
pathToAtUri.set(`${dirName}/${basename4}`, postState.atUri);
|
|
68226
68308
|
}
|
|
68227
|
-
} else if (postState.atUri) {
|
|
68228
|
-
const basename4 = path7.basename(filePath, path7.extname(filePath));
|
|
68229
|
-
slugToAtUri.set(basename4.toLowerCase(), postState.atUri);
|
|
68230
68309
|
}
|
|
68231
68310
|
}
|
|
68232
|
-
if (
|
|
68311
|
+
if (pathToAtUri.size === 0) {
|
|
68233
68312
|
R2.warn("No published posts found in state. Run 'sequoia publish' first.");
|
|
68234
68313
|
return;
|
|
68235
68314
|
}
|
|
68236
|
-
R2.info(`Found ${
|
|
68315
|
+
R2.info(`Found ${pathToAtUri.size} published posts in state`);
|
|
68237
68316
|
const htmlFiles = await glob("**/*.html", {
|
|
68238
68317
|
cwd: resolvedOutputDir,
|
|
68239
68318
|
absolute: false
|
|
@@ -68252,16 +68331,19 @@ var injectCommand = import_cmd_ts3.command({
|
|
|
68252
68331
|
const htmlDir = path7.dirname(relativePath);
|
|
68253
68332
|
const htmlBasename = path7.basename(relativePath, ".html");
|
|
68254
68333
|
let atUri;
|
|
68255
|
-
atUri =
|
|
68334
|
+
atUri = pathToAtUri.get(htmlBasename);
|
|
68256
68335
|
if (!atUri && htmlBasename === "index" && htmlDir !== ".") {
|
|
68257
|
-
|
|
68336
|
+
const slug = path7.basename(htmlDir);
|
|
68337
|
+
atUri = pathToAtUri.get(slug);
|
|
68258
68338
|
if (!atUri) {
|
|
68259
|
-
const
|
|
68260
|
-
|
|
68339
|
+
const parentDir = path7.dirname(htmlDir);
|
|
68340
|
+
if (parentDir !== ".") {
|
|
68341
|
+
atUri = pathToAtUri.get(`${path7.basename(parentDir)}/${slug}`);
|
|
68342
|
+
}
|
|
68261
68343
|
}
|
|
68262
68344
|
}
|
|
68263
68345
|
if (!atUri && htmlDir !== ".") {
|
|
68264
|
-
atUri =
|
|
68346
|
+
atUri = pathToAtUri.get(`${htmlDir}/${htmlBasename}`);
|
|
68265
68347
|
}
|
|
68266
68348
|
if (!atUri) {
|
|
68267
68349
|
skippedCount++;
|
|
@@ -68361,16 +68443,15 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68361
68443
|
const state = await loadState(configDir);
|
|
68362
68444
|
const s = Ie();
|
|
68363
68445
|
s.start("Scanning for posts...");
|
|
68364
|
-
const posts = await scanContentDirectory(contentDir,
|
|
68365
|
-
frontmatterMapping: config.frontmatter,
|
|
68366
|
-
ignorePatterns: config.ignore,
|
|
68367
|
-
slugSource: config.slugSource,
|
|
68368
|
-
slugField: config.slugField,
|
|
68369
|
-
removeIndexFromSlug: config.removeIndexFromSlug
|
|
68370
|
-
});
|
|
68446
|
+
const posts = await scanContentDirectory(contentDir, config.frontmatter, config.ignore);
|
|
68371
68447
|
s.stop(`Found ${posts.length} posts`);
|
|
68372
68448
|
const postsToPublish = [];
|
|
68449
|
+
const draftPosts = [];
|
|
68373
68450
|
for (const post of posts) {
|
|
68451
|
+
if (post.frontmatter.draft) {
|
|
68452
|
+
draftPosts.push(post);
|
|
68453
|
+
continue;
|
|
68454
|
+
}
|
|
68374
68455
|
const contentHash = await getContentHash(post.rawContent);
|
|
68375
68456
|
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68376
68457
|
const postState = state.posts[relativeFilePath];
|
|
@@ -68394,6 +68475,9 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68394
68475
|
});
|
|
68395
68476
|
}
|
|
68396
68477
|
}
|
|
68478
|
+
if (draftPosts.length > 0) {
|
|
68479
|
+
R2.info(`Skipping ${draftPosts.length} draft post${draftPosts.length === 1 ? "" : "s"}`);
|
|
68480
|
+
}
|
|
68397
68481
|
if (postsToPublish.length === 0) {
|
|
68398
68482
|
R2.success("All posts are up to date. Nothing to publish.");
|
|
68399
68483
|
return;
|
|
@@ -68401,11 +68485,34 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68401
68485
|
R2.info(`
|
|
68402
68486
|
${postsToPublish.length} posts to publish:
|
|
68403
68487
|
`);
|
|
68488
|
+
const blueskyEnabled = config.bluesky?.enabled ?? false;
|
|
68489
|
+
const maxAgeDays = config.bluesky?.maxAgeDays ?? 7;
|
|
68490
|
+
const cutoffDate = new Date;
|
|
68491
|
+
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
68404
68492
|
for (const { post, action, reason } of postsToPublish) {
|
|
68405
68493
|
const icon = action === "create" ? "+" : "~";
|
|
68406
|
-
|
|
68494
|
+
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68495
|
+
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
68496
|
+
let bskyNote = "";
|
|
68497
|
+
if (blueskyEnabled) {
|
|
68498
|
+
if (existingBskyPostRef) {
|
|
68499
|
+
bskyNote = " [bsky: exists]";
|
|
68500
|
+
} else {
|
|
68501
|
+
const publishDate = new Date(post.frontmatter.publishDate);
|
|
68502
|
+
if (publishDate < cutoffDate) {
|
|
68503
|
+
bskyNote = ` [bsky: skipped, older than ${maxAgeDays} days]`;
|
|
68504
|
+
} else {
|
|
68505
|
+
bskyNote = " [bsky: will post]";
|
|
68506
|
+
}
|
|
68507
|
+
}
|
|
68508
|
+
}
|
|
68509
|
+
R2.message(` ${icon} ${post.frontmatter.title} (${reason})${bskyNote}`);
|
|
68407
68510
|
}
|
|
68408
68511
|
if (dryRun) {
|
|
68512
|
+
if (blueskyEnabled) {
|
|
68513
|
+
R2.info(`
|
|
68514
|
+
Bluesky posting: enabled (max age: ${maxAgeDays} days)`);
|
|
68515
|
+
}
|
|
68409
68516
|
R2.info(`
|
|
68410
68517
|
Dry run complete. No changes made.`);
|
|
68411
68518
|
return;
|
|
@@ -68423,6 +68530,7 @@ Dry run complete. No changes made.`);
|
|
|
68423
68530
|
let publishedCount = 0;
|
|
68424
68531
|
let updatedCount = 0;
|
|
68425
68532
|
let errorCount = 0;
|
|
68533
|
+
let bskyPostCount = 0;
|
|
68426
68534
|
for (const { post, action } of postsToPublish) {
|
|
68427
68535
|
s.start(`Publishing: ${post.frontmatter.title}`);
|
|
68428
68536
|
try {
|
|
@@ -68441,6 +68549,9 @@ Dry run complete. No changes made.`);
|
|
|
68441
68549
|
}
|
|
68442
68550
|
let atUri;
|
|
68443
68551
|
let contentForHash;
|
|
68552
|
+
let bskyPostRef;
|
|
68553
|
+
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68554
|
+
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
68444
68555
|
if (action === "create") {
|
|
68445
68556
|
atUri = await createDocument(agent, post, config, coverImage);
|
|
68446
68557
|
s.stop(`Created: ${atUri}`);
|
|
@@ -68456,13 +68567,41 @@ Dry run complete. No changes made.`);
|
|
|
68456
68567
|
contentForHash = post.rawContent;
|
|
68457
68568
|
updatedCount++;
|
|
68458
68569
|
}
|
|
68570
|
+
if (blueskyEnabled) {
|
|
68571
|
+
if (existingBskyPostRef) {
|
|
68572
|
+
R2.info(` Bluesky post already exists, skipping`);
|
|
68573
|
+
bskyPostRef = existingBskyPostRef;
|
|
68574
|
+
} else {
|
|
68575
|
+
const publishDate = new Date(post.frontmatter.publishDate);
|
|
68576
|
+
if (publishDate < cutoffDate) {
|
|
68577
|
+
R2.info(` Post is older than ${maxAgeDays} days, skipping Bluesky post`);
|
|
68578
|
+
} else {
|
|
68579
|
+
try {
|
|
68580
|
+
const pathPrefix = config.pathPrefix || "/posts";
|
|
68581
|
+
const canonicalUrl = `${config.siteUrl}${pathPrefix}/${post.slug}`;
|
|
68582
|
+
bskyPostRef = await createBlueskyPost(agent, {
|
|
68583
|
+
title: post.frontmatter.title,
|
|
68584
|
+
description: post.frontmatter.description,
|
|
68585
|
+
canonicalUrl,
|
|
68586
|
+
coverImage,
|
|
68587
|
+
publishedAt: post.frontmatter.publishDate
|
|
68588
|
+
});
|
|
68589
|
+
await addBskyPostRefToDocument(agent, atUri, bskyPostRef);
|
|
68590
|
+
R2.info(` Created Bluesky post: ${bskyPostRef.uri}`);
|
|
68591
|
+
bskyPostCount++;
|
|
68592
|
+
} catch (bskyError) {
|
|
68593
|
+
const errorMsg = bskyError instanceof Error ? bskyError.message : String(bskyError);
|
|
68594
|
+
R2.warn(` Failed to create Bluesky post: ${errorMsg}`);
|
|
68595
|
+
}
|
|
68596
|
+
}
|
|
68597
|
+
}
|
|
68598
|
+
}
|
|
68459
68599
|
const contentHash = await getContentHash(contentForHash);
|
|
68460
|
-
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68461
68600
|
state.posts[relativeFilePath] = {
|
|
68462
68601
|
contentHash,
|
|
68463
68602
|
atUri,
|
|
68464
68603
|
lastPublished: new Date().toISOString(),
|
|
68465
|
-
|
|
68604
|
+
bskyPostRef
|
|
68466
68605
|
};
|
|
68467
68606
|
} catch (error) {
|
|
68468
68607
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -68476,6 +68615,9 @@ Dry run complete. No changes made.`);
|
|
|
68476
68615
|
---`);
|
|
68477
68616
|
R2.info(`Published: ${publishedCount}`);
|
|
68478
68617
|
R2.info(`Updated: ${updatedCount}`);
|
|
68618
|
+
if (bskyPostCount > 0) {
|
|
68619
|
+
R2.info(`Bluesky posts: ${bskyPostCount}`);
|
|
68620
|
+
}
|
|
68479
68621
|
if (errorCount > 0) {
|
|
68480
68622
|
R2.warn(`Errors: ${errorCount}`);
|
|
68481
68623
|
}
|
|
@@ -68549,18 +68691,11 @@ var syncCommand = import_cmd_ts5.command({
|
|
|
68549
68691
|
}
|
|
68550
68692
|
const contentDir = path9.isAbsolute(config.contentDir) ? config.contentDir : path9.join(configDir, config.contentDir);
|
|
68551
68693
|
s.start("Scanning local content...");
|
|
68552
|
-
const localPosts = await scanContentDirectory(contentDir,
|
|
68553
|
-
frontmatterMapping: config.frontmatter,
|
|
68554
|
-
ignorePatterns: config.ignore,
|
|
68555
|
-
slugSource: config.slugSource,
|
|
68556
|
-
slugField: config.slugField,
|
|
68557
|
-
removeIndexFromSlug: config.removeIndexFromSlug
|
|
68558
|
-
});
|
|
68694
|
+
const localPosts = await scanContentDirectory(contentDir, config.frontmatter);
|
|
68559
68695
|
s.stop(`Found ${localPosts.length} local posts`);
|
|
68560
|
-
const pathPrefix = config.pathPrefix || "/posts";
|
|
68561
68696
|
const postsByPath = new Map;
|
|
68562
68697
|
for (const post of localPosts) {
|
|
68563
|
-
const postPath =
|
|
68698
|
+
const postPath = `/posts/${post.slug}`;
|
|
68564
68699
|
postsByPath.set(postPath, post);
|
|
68565
68700
|
}
|
|
68566
68701
|
const state = await loadState(configDir);
|
|
@@ -68658,7 +68793,7 @@ Publish evergreen content to the ATmosphere
|
|
|
68658
68793
|
|
|
68659
68794
|
> https://tangled.org/stevedylan.dev/sequoia
|
|
68660
68795
|
`,
|
|
68661
|
-
version: "0.
|
|
68796
|
+
version: "0.2.0",
|
|
68662
68797
|
cmds: {
|
|
68663
68798
|
auth: authCommand,
|
|
68664
68799
|
init: initCommand,
|