@gobing-ai/knowledge-kit 0.1.9 → 0.1.10
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +4 -0
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +4 -0
- package/plugins/generations/core-facts-gen/dist/index.js +4 -0
- package/plugins/generations/daily-article-gen/dist/index.js +4 -0
- package/plugins/generations/dailynews-gen/dist/index.js +4 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +102 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +2 -4
- package/plugins/generations/gemini-tts/dist/index.js +4 -0
- package/plugins/generations/image-gen/dist/index.js +4 -0
- package/plugins/generations/news-report-gen/dist/index.js +4 -0
- package/plugins/generations/voice-gen/dist/index.js +4 -0
- package/plugins/generations/voxcpm-tts/dist/index.js +4 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +4 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +4 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +11 -3
- package/plugins/ingestions/last30days-ingest/src/index.ts +14 -3
- package/plugins/ingestions/web-search/dist/index.js +4 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-stages.ts +86 -1
- package/plugins/kk/scripts/sentences.ts +134 -0
- package/plugins/kk/scripts/series-names.ts +124 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +37 -3
- package/plugins/kk/workflows/kk-daily-safety.yaml +50 -5
- package/plugins/publishings/bilibili-pub/dist/index.js +4 -0
- package/plugins/publishings/cnblogs-pub/dist/index.js +4 -0
- package/plugins/publishings/csdn-pub/dist/index.js +4 -0
- package/plugins/publishings/csdn-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/emdash-pub/dist/index.js +4 -0
- package/plugins/publishings/infoq-pub/dist/index.js +4 -0
- package/plugins/publishings/infoq-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/jike-pub/dist/index.js +4 -0
- package/plugins/publishings/jike-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +4 -0
- package/plugins/publishings/juejin-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/medium-pub/dist/index.js +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +105 -8
- package/plugins/publishings/podcast-pub/src/seo.ts +2 -4
- package/plugins/publishings/podcast-pub/src/show-notes.ts +3 -8
- package/plugins/publishings/qiita-pub/dist/index.js +4 -0
- package/plugins/publishings/shipinhao-pub/dist/index.js +4 -0
- package/plugins/publishings/sspai-pub/dist/index.js +4 -0
- package/plugins/publishings/substack-pub/dist/index.js +4 -0
- package/plugins/publishings/substack-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +247 -27
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +218 -0
- package/plugins/publishings/surfdash-pub/src/index.ts +76 -29
- package/plugins/publishings/surfdash-pub/src/patch.ts +44 -7
- package/plugins/publishings/threads-pub/dist/index.js +4 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +4 -0
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/weibo-pub/dist/index.js +4 -0
- package/plugins/publishings/weibo-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/x-pub/dist/index.js +4 -0
- package/plugins/publishings/x-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/xhs-pub/dist/index.js +4 -0
- package/plugins/publishings/xhs-pub/dist/profile-check.js +4 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +4 -0
|
@@ -12312,6 +12312,107 @@ async function writeJsonFile(path, value, fs = createNodeFileSystem()) {
|
|
|
12312
12312
|
}
|
|
12313
12313
|
// ../../packages/kk-core/src/render-md.ts
|
|
12314
12314
|
if (false) {}
|
|
12315
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12316
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12317
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12318
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12319
|
+
var OPEN_PAIRS = {
|
|
12320
|
+
"\u300C": "\u300D",
|
|
12321
|
+
"\u300E": "\u300F",
|
|
12322
|
+
"\u201C": "\u201D",
|
|
12323
|
+
"\uFF08": "\uFF09",
|
|
12324
|
+
"(": ")"
|
|
12325
|
+
};
|
|
12326
|
+
var CLOSE_PAIRS = {
|
|
12327
|
+
"\u300D": "\u300C",
|
|
12328
|
+
"\u300F": "\u300E",
|
|
12329
|
+
"\u201D": "\u201C",
|
|
12330
|
+
"\uFF09": "\uFF08",
|
|
12331
|
+
")": "("
|
|
12332
|
+
};
|
|
12333
|
+
function splitSentences(text, options = {}) {
|
|
12334
|
+
if (!text || typeof text !== "string")
|
|
12335
|
+
return [];
|
|
12336
|
+
const results = [];
|
|
12337
|
+
let sentenceStart = 0;
|
|
12338
|
+
let stack = [];
|
|
12339
|
+
let i = 0;
|
|
12340
|
+
const isTerminator = (c) => CJK_TERMINATORS.has(c) || options.cjkOnly !== true && LATIN_TERMINATORS.has(c);
|
|
12341
|
+
while (i < text.length) {
|
|
12342
|
+
const ch = text[i];
|
|
12343
|
+
if (isTerminator(ch)) {
|
|
12344
|
+
let termEnd = i;
|
|
12345
|
+
let hasCjkTerm = false;
|
|
12346
|
+
let hasLatinTerm = false;
|
|
12347
|
+
while (termEnd < text.length) {
|
|
12348
|
+
const termChar = text[termEnd];
|
|
12349
|
+
if (!termChar || !isTerminator(termChar)) {
|
|
12350
|
+
break;
|
|
12351
|
+
}
|
|
12352
|
+
if (CJK_TERMINATORS.has(termChar))
|
|
12353
|
+
hasCjkTerm = true;
|
|
12354
|
+
if (LATIN_TERMINATORS.has(termChar))
|
|
12355
|
+
hasLatinTerm = true;
|
|
12356
|
+
termEnd++;
|
|
12357
|
+
}
|
|
12358
|
+
let closerEnd = termEnd;
|
|
12359
|
+
const tempStack = [...stack];
|
|
12360
|
+
while (closerEnd < text.length) {
|
|
12361
|
+
const closer = text[closerEnd];
|
|
12362
|
+
if (!closer || !ATTACHED_CLOSERS.has(closer)) {
|
|
12363
|
+
break;
|
|
12364
|
+
}
|
|
12365
|
+
const top = tempStack[tempStack.length - 1];
|
|
12366
|
+
if (top !== undefined && top === closer) {
|
|
12367
|
+
tempStack.pop();
|
|
12368
|
+
} else if (CLOSE_PAIRS[closer]) {
|
|
12369
|
+
const idx = tempStack.lastIndexOf(closer);
|
|
12370
|
+
if (idx >= 0)
|
|
12371
|
+
tempStack.splice(idx, 1);
|
|
12372
|
+
}
|
|
12373
|
+
closerEnd++;
|
|
12374
|
+
}
|
|
12375
|
+
let isBoundary = false;
|
|
12376
|
+
if (tempStack.length === 0) {
|
|
12377
|
+
if (hasCjkTerm) {
|
|
12378
|
+
isBoundary = true;
|
|
12379
|
+
} else if (hasLatinTerm) {
|
|
12380
|
+
const nextChar = text[closerEnd];
|
|
12381
|
+
if (closerEnd === text.length || nextChar !== undefined && /\s/.test(nextChar)) {
|
|
12382
|
+
isBoundary = true;
|
|
12383
|
+
}
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
if (isBoundary) {
|
|
12387
|
+
const sentence = text.slice(sentenceStart, closerEnd).trim();
|
|
12388
|
+
if (sentence.length > 0) {
|
|
12389
|
+
results.push(sentence);
|
|
12390
|
+
}
|
|
12391
|
+
sentenceStart = closerEnd;
|
|
12392
|
+
stack = tempStack;
|
|
12393
|
+
i = closerEnd;
|
|
12394
|
+
continue;
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
const openMatch = OPEN_PAIRS[ch];
|
|
12398
|
+
const closeMatch = CLOSE_PAIRS[ch];
|
|
12399
|
+
if (openMatch) {
|
|
12400
|
+
stack.push(openMatch);
|
|
12401
|
+
} else if (closeMatch) {
|
|
12402
|
+
const idx = stack.lastIndexOf(ch);
|
|
12403
|
+
if (idx >= 0)
|
|
12404
|
+
stack.splice(idx, 1);
|
|
12405
|
+
}
|
|
12406
|
+
i++;
|
|
12407
|
+
}
|
|
12408
|
+
if (sentenceStart < text.length) {
|
|
12409
|
+
const tail = text.slice(sentenceStart).trim();
|
|
12410
|
+
if (tail.length > 0) {
|
|
12411
|
+
results.push(tail);
|
|
12412
|
+
}
|
|
12413
|
+
}
|
|
12414
|
+
return results;
|
|
12415
|
+
}
|
|
12315
12416
|
// ../../packages/publish-harness/src/index.ts
|
|
12316
12417
|
class FakeTransportCore {
|
|
12317
12418
|
callLogs = [];
|
|
@@ -12514,8 +12615,8 @@ class MicrofeedClient {
|
|
|
12514
12615
|
}
|
|
12515
12616
|
|
|
12516
12617
|
// ../../plugins/publishings/podcast-pub/src/seo.ts
|
|
12517
|
-
init_dist();
|
|
12518
12618
|
import { readFileSync as readFileSync2 } from "fs";
|
|
12619
|
+
init_dist();
|
|
12519
12620
|
function codePointLength(value) {
|
|
12520
12621
|
return Array.from(value).length;
|
|
12521
12622
|
}
|
|
@@ -12869,11 +12970,7 @@ function clauseFromTitle(title) {
|
|
|
12869
12970
|
return cut.replace(/\s+/g, " ").trim();
|
|
12870
12971
|
}
|
|
12871
12972
|
function firstSentence(text) {
|
|
12872
|
-
|
|
12873
|
-
if (trimmed.length === 0)
|
|
12874
|
-
return "";
|
|
12875
|
-
const parts = trimmed.split(/(?<=[\u3002\uFF01\uFF1F.!?])/);
|
|
12876
|
-
return (parts[0] ?? trimmed).trim();
|
|
12973
|
+
return splitSentences(text.replace(/\s+/g, " ").trim())[0] ?? "";
|
|
12877
12974
|
}
|
|
12878
12975
|
function planItemsFromMetadata(metadata) {
|
|
12879
12976
|
const raw = podcastOf(metadata).planItems;
|
|
@@ -13869,7 +13966,7 @@ function itemBrief(d) {
|
|
|
13869
13966
|
const meta = d.metadata ?? {};
|
|
13870
13967
|
if (typeof meta.brief === "string" && meta.brief.trim().length > 0)
|
|
13871
13968
|
return meta.brief.trim();
|
|
13872
|
-
return (d.body ?? "").trim()
|
|
13969
|
+
return splitSentences((d.body ?? "").trim(), { cjkOnly: true }).slice(0, 2).join("");
|
|
13873
13970
|
}
|
|
13874
13971
|
function renderShowNotesMarkdown(docs, opts, warnings) {
|
|
13875
13972
|
const slugify = (t) => {
|
|
@@ -13937,7 +14034,7 @@ function renderShowNotesMarkdown(docs, opts, warnings) {
|
|
|
13937
14034
|
} else {
|
|
13938
14035
|
docs.forEach((d, i) => {
|
|
13939
14036
|
const body = (d.body ?? "").trim();
|
|
13940
|
-
const sentences = body
|
|
14037
|
+
const sentences = splitSentences(body, { cjkOnly: true }).slice(0, 2).join("");
|
|
13941
14038
|
lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, "", sentences, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""})`, "");
|
|
13942
14039
|
});
|
|
13943
14040
|
lines.push(`\u8BE6\u60C5\u89C1\u672C\u671F\u5B8C\u6574\u6587\u7AE0\uFF1A[ ${opts.title} ](${articleUrl})`, "");
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { splitSentences } from '@gobing-ai/kk-core';
|
|
15
16
|
import { echoError } from '@gobing-ai/ts-utils';
|
|
16
17
|
|
|
17
18
|
/** Code-point length. Microfeed counts the same way for slugs and SEO limits. */
|
|
@@ -506,10 +507,7 @@ export function clauseFromTitle(title: string): string {
|
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
export function firstSentence(text: string): string {
|
|
509
|
-
|
|
510
|
-
if (trimmed.length === 0) return '';
|
|
511
|
-
const parts = trimmed.split(/(?<=[。!?.!?])/);
|
|
512
|
-
return (parts[0] ?? trimmed).trim();
|
|
510
|
+
return splitSentences(text.replace(/\s+/g, ' ').trim())[0] ?? '';
|
|
513
511
|
}
|
|
514
512
|
|
|
515
513
|
export function planItemsFromMetadata(metadata: Record<string, unknown> | undefined): PlanItem[] {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* input is escaped first, and only the tags this module emits reach the API.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type
|
|
14
|
+
import { type Doc, splitSentences } from '@gobing-ai/kk-core';
|
|
15
15
|
import {
|
|
16
16
|
type PlanItem,
|
|
17
17
|
type PodcastSeoConfig,
|
|
@@ -311,9 +311,7 @@ export interface ShowNotesRenderOptions {
|
|
|
311
311
|
function itemBrief(d: Doc): string {
|
|
312
312
|
const meta = d.metadata ?? {};
|
|
313
313
|
if (typeof meta.brief === 'string' && meta.brief.trim().length > 0) return meta.brief.trim();
|
|
314
|
-
return (d.body ?? '')
|
|
315
|
-
.trim()
|
|
316
|
-
.split(/(?<=[。!?])\s*/)
|
|
314
|
+
return splitSentences((d.body ?? '').trim(), { cjkOnly: true })
|
|
317
315
|
.slice(0, 2)
|
|
318
316
|
.join('');
|
|
319
317
|
}
|
|
@@ -407,10 +405,7 @@ export function renderShowNotesMarkdown(docs: Doc[], opts: ShowNotesRenderOption
|
|
|
407
405
|
} else {
|
|
408
406
|
docs.forEach((d, i) => {
|
|
409
407
|
const body = (d.body ?? '').trim();
|
|
410
|
-
const sentences = body
|
|
411
|
-
.split(/(?<=[。!?])\s*/)
|
|
412
|
-
.slice(0, 2)
|
|
413
|
-
.join('');
|
|
408
|
+
const sentences = splitSentences(body, { cjkOnly: true }).slice(0, 2).join('');
|
|
414
409
|
lines.push(
|
|
415
410
|
`## ${i + 1}. ${d.title ?? `条目 ${i + 1}`}`,
|
|
416
411
|
'',
|
|
@@ -12254,6 +12254,10 @@ init_config();
|
|
|
12254
12254
|
|
|
12255
12255
|
// ../../packages/kk-core/src/render-md.ts
|
|
12256
12256
|
if (false) {}
|
|
12257
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12258
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12259
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12260
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12257
12261
|
// ../../packages/publish-harness/src/index.ts
|
|
12258
12262
|
class FakeTransportCore {
|
|
12259
12263
|
callLogs = [];
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20972,6 +20972,10 @@ var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx)
|
|
|
20972
20972
|
});
|
|
20973
20973
|
// ../../packages/kk-core/src/render-md.ts
|
|
20974
20974
|
if (false) {}
|
|
20975
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20976
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20977
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20978
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20975
20979
|
// ../../plugins/publishings/substack-pub/src/index.ts
|
|
20976
20980
|
var CHANNEL = "substack";
|
|
20977
20981
|
var LOGIN_URL_RE = /(\/sign-in|substack\.com\/account\/login)/;
|
|
@@ -12345,6 +12345,10 @@ init_config();
|
|
|
12345
12345
|
|
|
12346
12346
|
// ../../packages/kk-core/src/render-md.ts
|
|
12347
12347
|
if (false) {}
|
|
12348
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12349
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12350
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12351
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12348
12352
|
// ../../plugins/publishings/surfdash-pub/src/index.ts
|
|
12349
12353
|
init_dist();
|
|
12350
12354
|
|
|
@@ -12360,11 +12364,17 @@ function localeIndexPath(postPath, locale) {
|
|
|
12360
12364
|
return;
|
|
12361
12365
|
return `${postPath.slice(0, idx)}${sep}${locale}${sep}${postPath.slice(idx + marker.length)}`;
|
|
12362
12366
|
}
|
|
12367
|
+
var SERIES_NAMES = {
|
|
12368
|
+
zh: "\u6BCF\u65E5AI\u4E71\u7096",
|
|
12369
|
+
en: "Daily AI Stew",
|
|
12370
|
+
ja: "\u30C7\u30A4\u30EA\u30FCAI\u30B7\u30C1\u30E5\u30FC"
|
|
12371
|
+
};
|
|
12363
12372
|
function crossLinkFor(locale, dateIso, showNotesUrl) {
|
|
12373
|
+
const series = SERIES_NAMES[locale];
|
|
12364
12374
|
const links = {
|
|
12365
|
-
zh: `\uD83C\uDFA7 \u672C\u671F\u5DF2\u540C\u6B65\u5236\u4F5C\u4E3A\u64AD\u5BA2\u8282\u76EE\uFF0C[\u70B9\u51FB\u6536\u542C\u300A
|
|
12366
|
-
en: `\uD83C\uDFA7 This episode is also available as a podcast: [listen to
|
|
12367
|
-
ja: `\uD83C\uDFA7 \u672C\u30A8\u30D4\u30BD\u30FC\u30C9\u306F\u30DD\u30C3\u30C9\u30AD\u30E3\u30B9\u30C8\u3067\u3082\u914D\u4FE1\u4E2D\uFF1A[
|
|
12375
|
+
zh: `\uD83C\uDFA7 \u672C\u671F\u5DF2\u540C\u6B65\u5236\u4F5C\u4E3A\u64AD\u5BA2\u8282\u76EE\uFF0C[\u70B9\u51FB\u6536\u542C\u300A${series} \xB7 ${dateIso}\u300B](${showNotesUrl})\u3002`,
|
|
12376
|
+
en: `\uD83C\uDFA7 This episode is also available as a podcast: [listen to ${series} \xB7 ${dateIso}](${showNotesUrl}).`,
|
|
12377
|
+
ja: `\uD83C\uDFA7 \u672C\u30A8\u30D4\u30BD\u30FC\u30C9\u306F\u30DD\u30C3\u30C9\u30AD\u30E3\u30B9\u30C8\u3067\u3082\u914D\u4FE1\u4E2D\uFF1A[${series} \xB7 ${dateIso} \u3092\u8074\u304F](${showNotesUrl})\u3002`
|
|
12368
12378
|
};
|
|
12369
12379
|
return links[locale];
|
|
12370
12380
|
}
|
|
@@ -12402,10 +12412,16 @@ async function patchPostFiles(request) {
|
|
|
12402
12412
|
const end = frontmatterEnd(md);
|
|
12403
12413
|
const fm = end < 0 ? md : md.slice(0, end);
|
|
12404
12414
|
if (request.coverUrl !== "") {
|
|
12415
|
+
if (!/^https:\/\//.test(request.coverUrl)) {
|
|
12416
|
+
throw new Error(`coverUrl must be an absolute https:// URL, got: ${request.coverUrl}`);
|
|
12417
|
+
}
|
|
12405
12418
|
md = upsertFrontmatterKey(md, "image", request.coverUrl);
|
|
12406
12419
|
md = upsertFrontmatterKey(md, "og_image", request.coverUrl);
|
|
12407
12420
|
images[locale] = request.coverUrl;
|
|
12408
|
-
} else if (!/^image:/m.test(fm)
|
|
12421
|
+
} else if (!/^image:/m.test(fm)) {
|
|
12422
|
+
if (!await fs.exists(request.localCover)) {
|
|
12423
|
+
throw new Error(`${locale} post has no cover: coverUrl is empty, the frontmatter has no image key, and localCover does not exist (${request.localCover})`);
|
|
12424
|
+
}
|
|
12409
12425
|
const asset = join(dir, "assets", "cover.png");
|
|
12410
12426
|
await fs.ensureDir(dirname2(asset));
|
|
12411
12427
|
await fs.copy(request.localCover, asset);
|
|
@@ -12414,11 +12430,20 @@ async function patchPostFiles(request) {
|
|
|
12414
12430
|
md = upsertFrontmatterKey(md, "og_image", "./assets/cover.png");
|
|
12415
12431
|
images[locale] = "./assets/cover.png";
|
|
12416
12432
|
}
|
|
12417
|
-
if (request.showNotesUrl !== ""
|
|
12418
|
-
|
|
12433
|
+
if (request.showNotesUrl !== "") {
|
|
12434
|
+
const link = crossLinkFor(locale, request.dateIso, request.showNotesUrl);
|
|
12435
|
+
const lines = md.split(`
|
|
12436
|
+
`);
|
|
12437
|
+
const carriesUrl = lines.some((line) => line.includes(request.showNotesUrl));
|
|
12438
|
+
if (!carriesUrl) {
|
|
12439
|
+
md = `${md.replace(/\s*$/, "")}
|
|
12419
12440
|
|
|
12420
|
-
${
|
|
12441
|
+
${link}
|
|
12421
12442
|
`;
|
|
12443
|
+
} else {
|
|
12444
|
+
md = lines.map((line) => line.includes(request.showNotesUrl) && line.trimStart().startsWith("\uD83C\uDFA7") ? link : line).join(`
|
|
12445
|
+
`);
|
|
12446
|
+
}
|
|
12422
12447
|
}
|
|
12423
12448
|
if (md !== before) {
|
|
12424
12449
|
await fs.writeFile(indexMd, md);
|
|
@@ -12433,6 +12458,172 @@ ${crossLinkFor(locale, request.dateIso, request.showNotesUrl)}
|
|
|
12433
12458
|
return { written, changed, absent, images, slug: basename(dirname2(request.postPath)) };
|
|
12434
12459
|
}
|
|
12435
12460
|
|
|
12461
|
+
// ../../plugins/publishings/surfdash-pub/src/digest-check.ts
|
|
12462
|
+
var DIGEST_TAG = "sub-category:ai-daily";
|
|
12463
|
+
var PODCAST_INTROS = ["\u8FD9\u91CC\u662F\u7531Robin", "\u54B8\u8BDD\u54B8\u8BF4", "This is Robin", "Robin \u304C\u304A\u5C4A\u3051"];
|
|
12464
|
+
var SEO_TITLE_LIMIT = 60;
|
|
12465
|
+
var KEY_LINE = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/;
|
|
12466
|
+
var escapeRe = (token) => token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12467
|
+
function unquote(value) {
|
|
12468
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
12469
|
+
try {
|
|
12470
|
+
return JSON.parse(value);
|
|
12471
|
+
} catch {
|
|
12472
|
+
return value.slice(1, -1);
|
|
12473
|
+
}
|
|
12474
|
+
}
|
|
12475
|
+
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
|
|
12476
|
+
return value.slice(1, -1).replaceAll("''", "'");
|
|
12477
|
+
}
|
|
12478
|
+
return value;
|
|
12479
|
+
}
|
|
12480
|
+
function parseFrontmatterScalars(markdown) {
|
|
12481
|
+
const out = new Map;
|
|
12482
|
+
const lines = markdown.split(/\r?\n/);
|
|
12483
|
+
if (lines[0]?.trim() !== "---")
|
|
12484
|
+
return out;
|
|
12485
|
+
const end = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
12486
|
+
if (end < 0)
|
|
12487
|
+
return out;
|
|
12488
|
+
for (let i = 1;i < end; i++) {
|
|
12489
|
+
const line = lines[i];
|
|
12490
|
+
if (!line)
|
|
12491
|
+
continue;
|
|
12492
|
+
const match = line.match(KEY_LINE);
|
|
12493
|
+
if (!match?.[1] || match[2] === undefined)
|
|
12494
|
+
continue;
|
|
12495
|
+
const key = match[1];
|
|
12496
|
+
const value = match[2].trim();
|
|
12497
|
+
if (value === ">-" || value === ">" || value === ">+" || value === "|-" || value === "|") {
|
|
12498
|
+
const parts = [];
|
|
12499
|
+
let j = i + 1;
|
|
12500
|
+
while (j < end) {
|
|
12501
|
+
const continuation = lines[j];
|
|
12502
|
+
if (!continuation || !/^[ \t]+\S/.test(continuation))
|
|
12503
|
+
break;
|
|
12504
|
+
parts.push(continuation.trim());
|
|
12505
|
+
j++;
|
|
12506
|
+
}
|
|
12507
|
+
out.set(key, parts.join(" "));
|
|
12508
|
+
i = j - 1;
|
|
12509
|
+
continue;
|
|
12510
|
+
}
|
|
12511
|
+
if (value === "") {
|
|
12512
|
+
const items = [];
|
|
12513
|
+
let j = i + 1;
|
|
12514
|
+
while (j < end) {
|
|
12515
|
+
const itemLine = lines[j];
|
|
12516
|
+
if (!itemLine || !/^\s*-\s+/.test(itemLine))
|
|
12517
|
+
break;
|
|
12518
|
+
items.push(unquote(itemLine.replace(/^\s*-\s+/, "").trim()));
|
|
12519
|
+
j++;
|
|
12520
|
+
}
|
|
12521
|
+
if (items.length > 0) {
|
|
12522
|
+
out.set(key, items);
|
|
12523
|
+
i = j - 1;
|
|
12524
|
+
} else {
|
|
12525
|
+
out.set(key, "");
|
|
12526
|
+
}
|
|
12527
|
+
continue;
|
|
12528
|
+
}
|
|
12529
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
12530
|
+
out.set(key, value.slice(1, -1).split(",").map((item) => unquote(item.trim())).filter((item) => item !== ""));
|
|
12531
|
+
continue;
|
|
12532
|
+
}
|
|
12533
|
+
out.set(key, unquote(value));
|
|
12534
|
+
}
|
|
12535
|
+
return out;
|
|
12536
|
+
}
|
|
12537
|
+
function scalar(frontmatter, key) {
|
|
12538
|
+
const value = frontmatter.get(key);
|
|
12539
|
+
if (value === undefined)
|
|
12540
|
+
return;
|
|
12541
|
+
return Array.isArray(value) ? value.join(" ") : value;
|
|
12542
|
+
}
|
|
12543
|
+
function endsAsSentence(text, locale) {
|
|
12544
|
+
return locale === "en" ? /[.!?]["')\u201D\u2019]*$/u.test(text.trimEnd()) : /[\u3002\uFF01\uFF1F][\u300D\u300F\u201D\uFF09]*$/u.test(text.trimEnd());
|
|
12545
|
+
}
|
|
12546
|
+
function checkDigestFrontmatter(markdown, locale) {
|
|
12547
|
+
const frontmatter = parseFrontmatterScalars(markdown);
|
|
12548
|
+
const tags = frontmatter.get("tags");
|
|
12549
|
+
const tagList = Array.isArray(tags) ? tags : typeof tags === "string" && tags !== "" ? [tags] : [];
|
|
12550
|
+
if (!tagList.some((tag) => tag.includes(DIGEST_TAG)))
|
|
12551
|
+
return [];
|
|
12552
|
+
const problems = [];
|
|
12553
|
+
const series = SERIES_NAMES[locale];
|
|
12554
|
+
const titleSep = locale === "en" ? ": " : "\uFF1A";
|
|
12555
|
+
const listSep = locale === "en" ? ", " : "\u3001";
|
|
12556
|
+
const title = scalar(frontmatter, "title");
|
|
12557
|
+
if (title === undefined || title === "") {
|
|
12558
|
+
problems.push("title: missing");
|
|
12559
|
+
} else {
|
|
12560
|
+
const match = title.match(new RegExp(`^${escapeRe(series)} \xB7 (\\d{4}-\\d{2}-\\d{2})${escapeRe(titleSep)}(.+)$`));
|
|
12561
|
+
if (!match) {
|
|
12562
|
+
problems.push(`title: expected "<series> \xB7 YYYY-MM-DD${titleSep}<h1>${listSep}<h2>" for ${series}, got "${title}"`);
|
|
12563
|
+
} else {
|
|
12564
|
+
const rest = match[2] ?? "";
|
|
12565
|
+
const heads = locale === "en" ? rest.split(", ") : rest.split("\u3001");
|
|
12566
|
+
const seps = locale === "en" ? (rest.match(/, /g) ?? []).length : (rest.match(/\u3001/g) ?? []).length;
|
|
12567
|
+
if (seps !== 1 || heads.some((head) => head.trim() === "")) {
|
|
12568
|
+
problems.push(`title: expected exactly two non-empty headlines joined "${listSep}", got "${rest}"`);
|
|
12569
|
+
}
|
|
12570
|
+
}
|
|
12571
|
+
const seoTitle = `${title} | Surfing`;
|
|
12572
|
+
const seoLength = Array.from(seoTitle).length;
|
|
12573
|
+
if (seoLength > SEO_TITLE_LIMIT) {
|
|
12574
|
+
problems.push(`title: "${seoTitle}" is ${seoLength} code points (max ${SEO_TITLE_LIMIT})`);
|
|
12575
|
+
}
|
|
12576
|
+
if (title.includes("\u2026") || title.includes("..."))
|
|
12577
|
+
problems.push('title: contains "\u2026" or "..."');
|
|
12578
|
+
}
|
|
12579
|
+
const description = scalar(frontmatter, "description");
|
|
12580
|
+
const [min, max] = locale === "en" ? [120, 155] : [70, 100];
|
|
12581
|
+
if (description === undefined || description === "") {
|
|
12582
|
+
problems.push("description: missing");
|
|
12583
|
+
} else {
|
|
12584
|
+
const length = Array.from(description).length;
|
|
12585
|
+
if (length < min || length > max) {
|
|
12586
|
+
problems.push(`description: ${length} code points (expected ${min}\u2013${max} for ${locale})`);
|
|
12587
|
+
}
|
|
12588
|
+
if (!endsAsSentence(description, locale)) {
|
|
12589
|
+
problems.push("description: does not end with sentence punctuation");
|
|
12590
|
+
}
|
|
12591
|
+
const lower = description.toLowerCase();
|
|
12592
|
+
const intro = PODCAST_INTROS.find((candidate) => lower.startsWith(candidate.toLowerCase()));
|
|
12593
|
+
if (intro)
|
|
12594
|
+
problems.push(`description: starts with the podcast intro ("${intro}")`);
|
|
12595
|
+
if (description.includes("\u2026") || description.includes("...")) {
|
|
12596
|
+
problems.push('description: contains "\u2026" or "..."');
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12599
|
+
const points = frontmatter.get("key_points");
|
|
12600
|
+
if (!Array.isArray(points) || points.length === 0) {
|
|
12601
|
+
problems.push("key_points: missing");
|
|
12602
|
+
} else {
|
|
12603
|
+
const labelMax = locale === "en" ? 28 : 14;
|
|
12604
|
+
points.forEach((point, index) => {
|
|
12605
|
+
const sep = point.indexOf(" \u2014 ");
|
|
12606
|
+
if (sep < 0) {
|
|
12607
|
+
problems.push(`key_points[${index}]: missing " \u2014 " separator`);
|
|
12608
|
+
return;
|
|
12609
|
+
}
|
|
12610
|
+
const label = point.slice(0, sep);
|
|
12611
|
+
const text = point.slice(sep + 3);
|
|
12612
|
+
const labelLength = Array.from(label).length;
|
|
12613
|
+
if (labelLength === 0 || labelLength > labelMax) {
|
|
12614
|
+
problems.push(`key_points[${index}]: label "${label}" is ${labelLength} code points (max ${labelMax})`);
|
|
12615
|
+
}
|
|
12616
|
+
if (!endsAsSentence(text, locale)) {
|
|
12617
|
+
problems.push(`key_points[${index}]: text does not end with sentence punctuation`);
|
|
12618
|
+
}
|
|
12619
|
+
if (point.includes("\u2026") || point.includes("...")) {
|
|
12620
|
+
problems.push(`key_points[${index}]: contains "\u2026" or "..."`);
|
|
12621
|
+
}
|
|
12622
|
+
});
|
|
12623
|
+
}
|
|
12624
|
+
return problems;
|
|
12625
|
+
}
|
|
12626
|
+
|
|
12436
12627
|
// ../../plugins/publishings/surfdash-pub/src/index.ts
|
|
12437
12628
|
var TARGET = "surfdash";
|
|
12438
12629
|
var OPERATIONS = ["publish", "scaffold-locale", "sync", "patch"];
|
|
@@ -12449,34 +12640,29 @@ function normalizeTag(raw) {
|
|
|
12449
12640
|
}
|
|
12450
12641
|
return slugify2(raw);
|
|
12451
12642
|
}
|
|
12452
|
-
function
|
|
12453
|
-
let title = content.title;
|
|
12454
|
-
if (!title) {
|
|
12455
|
-
const match = content.body.match(/^#\s+(.+)$/m);
|
|
12456
|
-
title = match?.[1] ? match[1].trim() : "Untitled Post";
|
|
12457
|
-
}
|
|
12458
|
-
let description;
|
|
12459
|
-
if (typeof content.metadata?.description === "string") {
|
|
12460
|
-
description = content.metadata.description;
|
|
12461
|
-
} else {
|
|
12462
|
-
const paragraphs = content.body.split(/\n\s*\n/).map((p) => p.replace(/^#+\s+.*$/, "").trim()).filter((p) => p.length > 0);
|
|
12463
|
-
if (paragraphs.length > 0) {
|
|
12464
|
-
description = paragraphs[0];
|
|
12465
|
-
}
|
|
12466
|
-
}
|
|
12643
|
+
function contentTags(content) {
|
|
12467
12644
|
const rawTags = Array.isArray(content.metadata?.tags) ? [...content.metadata.tags] : Array.isArray(content.metadata?.keywords) ? [...content.metadata.keywords] : [];
|
|
12468
12645
|
if (typeof content.metadata?.subCategory === "string" && content.metadata.subCategory.trim().length > 0) {
|
|
12469
12646
|
const subRaw = content.metadata.subCategory.trim();
|
|
12470
12647
|
rawTags.push(/^sub-category\s*:/i.test(subRaw) ? subRaw : `sub-category:${subRaw}`);
|
|
12471
12648
|
}
|
|
12472
|
-
|
|
12649
|
+
return [
|
|
12473
12650
|
...new Set(rawTags.filter((t) => typeof t === "string" && t.trim().length > 0).map((t) => normalizeTag(t)))
|
|
12474
12651
|
].filter((tag) => tag.length > 0);
|
|
12652
|
+
}
|
|
12653
|
+
function mapContentToSurfingMarkdown(content, options = {}) {
|
|
12654
|
+
let title = content.title;
|
|
12655
|
+
if (!title) {
|
|
12656
|
+
const match = content.body.match(/^#\s+(.+)$/m);
|
|
12657
|
+
title = match?.[1] ? match[1].trim() : "Untitled Post";
|
|
12658
|
+
}
|
|
12659
|
+
const description = typeof content.metadata?.description === "string" && content.metadata.description.length > 0 ? content.metadata.description : "";
|
|
12660
|
+
const tags = contentTags(content);
|
|
12475
12661
|
const isoDate = options.publishDate ?? new Date().toISOString().slice(0, 10);
|
|
12476
12662
|
const draft = typeof content.metadata?.published === "boolean" ? !content.metadata.published : false;
|
|
12477
12663
|
const frontmatterObj = {
|
|
12478
12664
|
title,
|
|
12479
|
-
description
|
|
12665
|
+
description,
|
|
12480
12666
|
tags,
|
|
12481
12667
|
publishDate: isoDate,
|
|
12482
12668
|
draft,
|
|
@@ -12489,6 +12675,9 @@ function mapContentToSurfingMarkdown(content, options = {}) {
|
|
|
12489
12675
|
${content.body.trim()}
|
|
12490
12676
|
`;
|
|
12491
12677
|
}
|
|
12678
|
+
function isDigestContent(content) {
|
|
12679
|
+
return contentTags(content).some((tag) => tag.includes(DIGEST_TAG));
|
|
12680
|
+
}
|
|
12492
12681
|
function fail(error) {
|
|
12493
12682
|
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
12494
12683
|
}
|
|
@@ -12549,7 +12738,8 @@ async function spawnCli(cmd, cwd) {
|
|
|
12549
12738
|
const proc = Bun.spawn(cmd, {
|
|
12550
12739
|
cwd,
|
|
12551
12740
|
stdout: "pipe",
|
|
12552
|
-
stderr: "pipe"
|
|
12741
|
+
stderr: "pipe",
|
|
12742
|
+
env: process.env
|
|
12553
12743
|
});
|
|
12554
12744
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
12555
12745
|
proc.exited,
|
|
@@ -12594,6 +12784,17 @@ function requireRefined(filePath) {
|
|
|
12594
12784
|
return;
|
|
12595
12785
|
return `surfdash refine step did not run for ${filePath} (no \`refined: true\`): the publishing pipeline is broken \u2014 the post would ship with generator-supplied SEO metadata`;
|
|
12596
12786
|
}
|
|
12787
|
+
function digestCheckGate(filePath, locale) {
|
|
12788
|
+
const problems = checkDigestFrontmatter(readFileSync2(filePath, "utf-8"), locale);
|
|
12789
|
+
if (problems.length === 0)
|
|
12790
|
+
return;
|
|
12791
|
+
if (process.env.SURFDASH_DIGEST_CHECK === "warn") {
|
|
12792
|
+
for (const problem of problems)
|
|
12793
|
+
echoError(`digest check (warn): ${problem}`);
|
|
12794
|
+
return;
|
|
12795
|
+
}
|
|
12796
|
+
return `digest frontmatter check failed: ${problems.join("; ")}`;
|
|
12797
|
+
}
|
|
12597
12798
|
async function pushPostGit(surfdashRoot, relPostDir, message) {
|
|
12598
12799
|
return commitPostPaths(surfdashRoot, [relPostDir], message);
|
|
12599
12800
|
}
|
|
@@ -12770,6 +12971,12 @@ async function syncPost(options) {
|
|
|
12770
12971
|
if (spawned.exitCode !== 0) {
|
|
12771
12972
|
return fail(`publish-orchestrator sync failed (exit ${spawned.exitCode}): ${withEmdashTokenHint(cliErrorText(spawned))}`);
|
|
12772
12973
|
}
|
|
12974
|
+
const localeInPath = postPath.postPath.match(/[\\/]content[\\/]posts[\\/]articles[\\/](zh|en|ja)[\\/]/);
|
|
12975
|
+
if (localeInPath) {
|
|
12976
|
+
const digestProblem = digestCheckGate(postPath.postPath, localeInPath[1]);
|
|
12977
|
+
if (digestProblem)
|
|
12978
|
+
return fail(digestProblem);
|
|
12979
|
+
}
|
|
12773
12980
|
return ResultSchema.parse({
|
|
12774
12981
|
ok: true,
|
|
12775
12982
|
target: TARGET,
|
|
@@ -12874,12 +13081,18 @@ async function publishPost(payload) {
|
|
|
12874
13081
|
return fail(`post path escapes ${articlesRoot}`);
|
|
12875
13082
|
}
|
|
12876
13083
|
const coverPath = payload.content.metadata?.coverImage;
|
|
12877
|
-
if (typeof coverPath === "string" && existsSync2(coverPath)
|
|
13084
|
+
if (typeof coverPath === "string" && !existsSync2(coverPath)) {
|
|
13085
|
+
return fail(`metadata.coverImage not found: ${coverPath}`);
|
|
13086
|
+
}
|
|
13087
|
+
if (typeof coverPath === "string" && !COVER_EXTS.has(extname(coverPath).toLowerCase())) {
|
|
12878
13088
|
return fail(`metadata.coverImage must be one of ${[...COVER_EXTS].join(", ")}`);
|
|
12879
13089
|
}
|
|
13090
|
+
if (isDigestContent(payload.content) && typeof coverPath !== "string") {
|
|
13091
|
+
return fail("digest post has no cover image (metadata.coverImage)");
|
|
13092
|
+
}
|
|
12880
13093
|
await createNodeFileSystem().ensureDir(postDir);
|
|
12881
13094
|
let imageFrontmatter;
|
|
12882
|
-
if (typeof coverPath === "string"
|
|
13095
|
+
if (typeof coverPath === "string") {
|
|
12883
13096
|
const assetsDir = join2(postDir, "assets");
|
|
12884
13097
|
await createNodeFileSystem().ensureDir(assetsDir);
|
|
12885
13098
|
const coverName = `cover${extname(coverPath).toLowerCase()}`;
|
|
@@ -12900,6 +13113,9 @@ async function publishPost(payload) {
|
|
|
12900
13113
|
const notRefined = requireRefined(filePath);
|
|
12901
13114
|
if (notRefined)
|
|
12902
13115
|
return fail(notRefined);
|
|
13116
|
+
const digestProblem = digestCheckGate(filePath, locale);
|
|
13117
|
+
if (digestProblem)
|
|
13118
|
+
return fail(digestProblem);
|
|
12903
13119
|
const refined = readRefinedSnapshot(filePath);
|
|
12904
13120
|
const baseUrl = (process.env.SURFDASH_URL ?? "https://surfing.salty.vip").replace(/\/$/, "");
|
|
12905
13121
|
const articleUrl = `${baseUrl}${locale === "en" ? "" : `/${locale}`}/posts/${slug}`;
|
|
@@ -12999,9 +13215,13 @@ if (import.meta.main) {
|
|
|
12999
13215
|
process.exit(await main2());
|
|
13000
13216
|
}
|
|
13001
13217
|
export {
|
|
13218
|
+
DIGEST_TAG,
|
|
13002
13219
|
PostsurfingFileCliTransport,
|
|
13003
13220
|
awaitArticleLive,
|
|
13221
|
+
checkDigestFrontmatter,
|
|
13004
13222
|
commitPostPaths,
|
|
13223
|
+
contentTags,
|
|
13224
|
+
digestCheckGate,
|
|
13005
13225
|
fastForwardToOrigin,
|
|
13006
13226
|
getPublishTransport,
|
|
13007
13227
|
main2 as main,
|