@gobing-ai/knowledge-kit 0.1.10 → 0.1.11
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 +128 -25
- package/package.json +1 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1057 -108
- package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +168 -121
- package/plugins/kk/workflows/kk-daily-safety.yaml +129 -50
- package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +2 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/infoq-pub/dist/index.js +2 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/jike-pub/dist/index.js +2 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/juejin-pub/dist/index.js +2 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/podcast-pub/dist/index.js +46 -8
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +2 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +2 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +460 -52
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
- package/plugins/publishings/surfdash-pub/src/index.ts +589 -64
- package/plugins/publishings/wechatmp-pub/dist/index.js +933 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +1125 -85
- package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
- package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
- package/plugins/publishings/weibo-pub/dist/index.js +2 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/dist/index.js +2 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xhs-pub/dist/index.js +2 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
|
@@ -12070,6 +12070,7 @@ class FakeFileCliTransport {
|
|
|
12070
12070
|
var init_src = () => {};
|
|
12071
12071
|
|
|
12072
12072
|
// ../../plugins/publishings/surfdash-pub/src/index.ts
|
|
12073
|
+
import { createHash } from "crypto";
|
|
12073
12074
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
12074
12075
|
import { copyFile, cp, writeFile } from "fs/promises";
|
|
12075
12076
|
import { dirname as dirname3, extname, isAbsolute, join as join2, relative, resolve, sep as sep2 } from "path";
|
|
@@ -12463,7 +12464,47 @@ var DIGEST_TAG = "sub-category:ai-daily";
|
|
|
12463
12464
|
var PODCAST_INTROS = ["\u8FD9\u91CC\u662F\u7531Robin", "\u54B8\u8BDD\u54B8\u8BF4", "This is Robin", "Robin \u304C\u304A\u5C4A\u3051"];
|
|
12464
12465
|
var SEO_TITLE_LIMIT = 60;
|
|
12465
12466
|
var KEY_LINE = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/;
|
|
12467
|
+
var BLOCK_SCALAR = /^[>|][+-]?$/;
|
|
12466
12468
|
var escapeRe = (token) => token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12469
|
+
function leadingIndent(line) {
|
|
12470
|
+
return line.match(/^[ \t]*/)?.[0].length ?? 0;
|
|
12471
|
+
}
|
|
12472
|
+
function readBlockScalar(lines, start, end, parentIndent, indicator) {
|
|
12473
|
+
const content = [];
|
|
12474
|
+
let j = start;
|
|
12475
|
+
while (j < end) {
|
|
12476
|
+
const line = lines[j] ?? "";
|
|
12477
|
+
if (line.trim() === "") {
|
|
12478
|
+
content.push("");
|
|
12479
|
+
j++;
|
|
12480
|
+
continue;
|
|
12481
|
+
}
|
|
12482
|
+
if (leadingIndent(line) <= parentIndent)
|
|
12483
|
+
break;
|
|
12484
|
+
content.push(line);
|
|
12485
|
+
j++;
|
|
12486
|
+
}
|
|
12487
|
+
if (content.length === 0)
|
|
12488
|
+
return { value: "", nextIndex: j };
|
|
12489
|
+
const normalized = content.map((line) => line === "" ? "" : line.slice(Math.min(parentIndent, leadingIndent(line))));
|
|
12490
|
+
try {
|
|
12491
|
+
const parsed = Bun.YAML.parse([`value: ${indicator}`, ...normalized, "sentinel: null"].join(`
|
|
12492
|
+
`));
|
|
12493
|
+
if (typeof parsed.value === "string")
|
|
12494
|
+
return { value: parsed.value, nextIndex: j };
|
|
12495
|
+
} catch {}
|
|
12496
|
+
const folded = indicator.startsWith(">");
|
|
12497
|
+
const parts = [];
|
|
12498
|
+
for (const line of normalized) {
|
|
12499
|
+
if (line !== "")
|
|
12500
|
+
parts.push(line.trim());
|
|
12501
|
+
}
|
|
12502
|
+
return {
|
|
12503
|
+
value: parts.join(folded ? " " : `
|
|
12504
|
+
`),
|
|
12505
|
+
nextIndex: j
|
|
12506
|
+
};
|
|
12507
|
+
}
|
|
12467
12508
|
function unquote(value) {
|
|
12468
12509
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
12469
12510
|
try {
|
|
@@ -12494,28 +12535,33 @@ function parseFrontmatterScalars(markdown) {
|
|
|
12494
12535
|
continue;
|
|
12495
12536
|
const key = match[1];
|
|
12496
12537
|
const value = match[2].trim();
|
|
12497
|
-
if (value
|
|
12498
|
-
const
|
|
12499
|
-
|
|
12500
|
-
|
|
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;
|
|
12538
|
+
if (BLOCK_SCALAR.test(value)) {
|
|
12539
|
+
const block = readBlockScalar(lines, i + 1, end, leadingIndent(line), value);
|
|
12540
|
+
out.set(key, block.value);
|
|
12541
|
+
i = block.nextIndex - 1;
|
|
12509
12542
|
continue;
|
|
12510
12543
|
}
|
|
12511
12544
|
if (value === "") {
|
|
12512
12545
|
const items = [];
|
|
12546
|
+
let listIndent;
|
|
12513
12547
|
let j = i + 1;
|
|
12514
12548
|
while (j < end) {
|
|
12515
12549
|
const itemLine = lines[j];
|
|
12516
12550
|
if (!itemLine || !/^\s*-\s+/.test(itemLine))
|
|
12517
12551
|
break;
|
|
12518
|
-
|
|
12552
|
+
const itemIndent = leadingIndent(itemLine);
|
|
12553
|
+
if (listIndent === undefined)
|
|
12554
|
+
listIndent = itemIndent;
|
|
12555
|
+
if (itemIndent !== listIndent)
|
|
12556
|
+
break;
|
|
12557
|
+
const raw = itemLine.replace(/^\s*-\s+/, "").trim();
|
|
12558
|
+
if (BLOCK_SCALAR.test(raw)) {
|
|
12559
|
+
const block = readBlockScalar(lines, j + 1, end, itemIndent, raw);
|
|
12560
|
+
items.push(block.value);
|
|
12561
|
+
j = block.nextIndex;
|
|
12562
|
+
continue;
|
|
12563
|
+
}
|
|
12564
|
+
items.push(unquote(raw));
|
|
12519
12565
|
j++;
|
|
12520
12566
|
}
|
|
12521
12567
|
if (items.length > 0) {
|
|
@@ -12626,7 +12672,15 @@ function checkDigestFrontmatter(markdown, locale) {
|
|
|
12626
12672
|
|
|
12627
12673
|
// ../../plugins/publishings/surfdash-pub/src/index.ts
|
|
12628
12674
|
var TARGET = "surfdash";
|
|
12629
|
-
var OPERATIONS = [
|
|
12675
|
+
var OPERATIONS = [
|
|
12676
|
+
"publish",
|
|
12677
|
+
"prepare",
|
|
12678
|
+
"refine-prepared",
|
|
12679
|
+
"publish-prepared",
|
|
12680
|
+
"scaffold-locale",
|
|
12681
|
+
"sync",
|
|
12682
|
+
"patch"
|
|
12683
|
+
];
|
|
12630
12684
|
var SCAFFOLD_LOCALES = ["en", "ja"];
|
|
12631
12685
|
var COVER_EXTS = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
|
|
12632
12686
|
function slugify2(str) {
|
|
@@ -12681,6 +12735,15 @@ function isDigestContent(content) {
|
|
|
12681
12735
|
function fail(error) {
|
|
12682
12736
|
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
12683
12737
|
}
|
|
12738
|
+
function failWithDeliveryState(error, deliveryState) {
|
|
12739
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error, metadata: { deliveryState } });
|
|
12740
|
+
}
|
|
12741
|
+
function failNotSent(error) {
|
|
12742
|
+
return failWithDeliveryState(error, "not-sent");
|
|
12743
|
+
}
|
|
12744
|
+
function failUncertain(error) {
|
|
12745
|
+
return failWithDeliveryState(error, "uncertain");
|
|
12746
|
+
}
|
|
12684
12747
|
async function writePublishResult(outputPath, result) {
|
|
12685
12748
|
const outDir = dirname3(outputPath);
|
|
12686
12749
|
if (outDir && outDir !== ".") {
|
|
@@ -12785,6 +12848,8 @@ function requireRefined(filePath) {
|
|
|
12785
12848
|
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`;
|
|
12786
12849
|
}
|
|
12787
12850
|
function digestCheckGate(filePath, locale) {
|
|
12851
|
+
if (process.env.SURFDASH_DIGEST_CHECK === "off")
|
|
12852
|
+
return;
|
|
12788
12853
|
const problems = checkDigestFrontmatter(readFileSync2(filePath, "utf-8"), locale);
|
|
12789
12854
|
if (problems.length === 0)
|
|
12790
12855
|
return;
|
|
@@ -12795,8 +12860,69 @@ function digestCheckGate(filePath, locale) {
|
|
|
12795
12860
|
}
|
|
12796
12861
|
return `digest frontmatter check failed: ${problems.join("; ")}`;
|
|
12797
12862
|
}
|
|
12798
|
-
|
|
12799
|
-
return
|
|
12863
|
+
function parsePorcelainPaths(stdout) {
|
|
12864
|
+
return stdout.split(`
|
|
12865
|
+
`).map((line) => line.trimEnd()).filter((line) => line.length > 3).map((line) => {
|
|
12866
|
+
const raw = line.slice(3).trim();
|
|
12867
|
+
const arrowIdx = raw.indexOf(" -> ");
|
|
12868
|
+
const target = arrowIdx >= 0 ? raw.slice(arrowIdx + 4).trim() : raw;
|
|
12869
|
+
if (target.startsWith('"') && target.endsWith('"')) {
|
|
12870
|
+
return target.slice(1, -1);
|
|
12871
|
+
}
|
|
12872
|
+
return target;
|
|
12873
|
+
}).filter((p) => p.length > 0);
|
|
12874
|
+
}
|
|
12875
|
+
async function runListenLinkBackfill(surfdashRoot) {
|
|
12876
|
+
if (process.env.SURFDASH_LISTEN_BACKFILL === "off") {
|
|
12877
|
+
return [];
|
|
12878
|
+
}
|
|
12879
|
+
const before = await spawnCli(["git", "status", "--porcelain", "--", "content/posts"], surfdashRoot);
|
|
12880
|
+
const beforePaths = new Set(parsePorcelainPaths(before.stdout));
|
|
12881
|
+
const spawned = await spawnCli(["bun", "run", "sd", "seo", "listen-link", "--backfill", "--write"], surfdashRoot);
|
|
12882
|
+
if (spawned.exitCode !== 0) {
|
|
12883
|
+
echoError(`listen-link backfill: ${cliErrorText(spawned)}`);
|
|
12884
|
+
}
|
|
12885
|
+
const after = await spawnCli(["git", "status", "--porcelain", "--", "content/posts"], surfdashRoot);
|
|
12886
|
+
const afterPaths = parsePorcelainPaths(after.stdout);
|
|
12887
|
+
return [...new Set(afterPaths.filter((p) => !beforePaths.has(p)))];
|
|
12888
|
+
}
|
|
12889
|
+
var SITE_GATE_COMMANDS = [
|
|
12890
|
+
["bun", "run", "sd", "check", "digest"],
|
|
12891
|
+
["bun", "run", "check:content"]
|
|
12892
|
+
];
|
|
12893
|
+
function extractFailingPostHint(text) {
|
|
12894
|
+
const match = text.match(/(?:content[\\/])?posts[\\/][^\s:)]+/);
|
|
12895
|
+
if (!match)
|
|
12896
|
+
return;
|
|
12897
|
+
return match[0].replace(/[.,;]+$/, "");
|
|
12898
|
+
}
|
|
12899
|
+
async function runSiteGate(surfdashRoot) {
|
|
12900
|
+
if (process.env.SURFDASH_DIGEST_CHECK === "off") {
|
|
12901
|
+
return { ok: true };
|
|
12902
|
+
}
|
|
12903
|
+
for (const cmd of SITE_GATE_COMMANDS) {
|
|
12904
|
+
const spawned = await spawnCli([...cmd], surfdashRoot);
|
|
12905
|
+
if (spawned.exitCode !== 0) {
|
|
12906
|
+
const output = cliErrorText(spawned);
|
|
12907
|
+
const postHint = extractFailingPostHint(output);
|
|
12908
|
+
const hint = postHint ? ` [failing post: ${postHint}]` : "";
|
|
12909
|
+
const error = `site gate failed: ${cmd.join(" ")} (exit ${spawned.exitCode})${hint}: ${output}`;
|
|
12910
|
+
if (process.env.SURFDASH_DIGEST_CHECK === "warn") {
|
|
12911
|
+
for (const line of error.split(`
|
|
12912
|
+
`)) {
|
|
12913
|
+
echoError(`site gate (warn): ${line}`);
|
|
12914
|
+
}
|
|
12915
|
+
continue;
|
|
12916
|
+
}
|
|
12917
|
+
return { ok: false, error };
|
|
12918
|
+
}
|
|
12919
|
+
}
|
|
12920
|
+
return { ok: true };
|
|
12921
|
+
}
|
|
12922
|
+
async function pushPostGit(surfdashRoot, relPostDir, message, extraPaths = []) {
|
|
12923
|
+
const rawPaths = Array.isArray(relPostDir) ? [...relPostDir, ...extraPaths] : [relPostDir, ...extraPaths];
|
|
12924
|
+
const paths = [...new Set(rawPaths)];
|
|
12925
|
+
return commitPostPaths(surfdashRoot, paths, message);
|
|
12800
12926
|
}
|
|
12801
12927
|
async function assertMainBranch(surfdashRoot) {
|
|
12802
12928
|
const branch = await spawnCli(["git", "rev-parse", "--abbrev-ref", "HEAD"], surfdashRoot);
|
|
@@ -12912,7 +13038,19 @@ async function scaffoldLocale(options) {
|
|
|
12912
13038
|
return fail(`options.postPath must be a zh post path containing a "${sep2}zh${sep2}" segment: ${postPath.postPath}`);
|
|
12913
13039
|
}
|
|
12914
13040
|
try {
|
|
12915
|
-
const
|
|
13041
|
+
const force = options.force === true;
|
|
13042
|
+
const spawned = await spawnCli([
|
|
13043
|
+
"bun",
|
|
13044
|
+
"run",
|
|
13045
|
+
"sd",
|
|
13046
|
+
"translate",
|
|
13047
|
+
postPath.postPath,
|
|
13048
|
+
"--source",
|
|
13049
|
+
"zh",
|
|
13050
|
+
"--target",
|
|
13051
|
+
target.locale,
|
|
13052
|
+
...force ? ["--force"] : []
|
|
13053
|
+
], surfdashRoot);
|
|
12916
13054
|
if (spawned.exitCode !== 0) {
|
|
12917
13055
|
return fail(`sd translate failed (exit ${spawned.exitCode}): ${cliErrorText(spawned)}`);
|
|
12918
13056
|
}
|
|
@@ -13030,6 +13168,14 @@ async function patchPost(options) {
|
|
|
13030
13168
|
commitPaths.add(relative(surfdashRoot, assetsDir));
|
|
13031
13169
|
}
|
|
13032
13170
|
}
|
|
13171
|
+
if (process.env.SURFDASH_AUTO_PUSH !== "0") {
|
|
13172
|
+
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
13173
|
+
for (const p of backfilled)
|
|
13174
|
+
commitPaths.add(p);
|
|
13175
|
+
const gate = await runSiteGate(surfdashRoot);
|
|
13176
|
+
if (!gate.ok)
|
|
13177
|
+
return fail(gate.error);
|
|
13178
|
+
}
|
|
13033
13179
|
const commit = await commitPostPaths(surfdashRoot, [...commitPaths], `feat: cover + podcast cross-link for ${outcome.slug} (kk daily patch)`, process.env.SURFDASH_AUTO_PUSH !== "0");
|
|
13034
13180
|
if (!commit.ok)
|
|
13035
13181
|
return fail(`patch: ${commit.error}`);
|
|
@@ -13056,94 +13202,347 @@ async function patchPost(options) {
|
|
|
13056
13202
|
metadata
|
|
13057
13203
|
});
|
|
13058
13204
|
}
|
|
13059
|
-
|
|
13060
|
-
|
|
13205
|
+
function fileSha256(filePath) {
|
|
13206
|
+
return createHash("sha256").update(readFileSync2(filePath)).digest("hex");
|
|
13207
|
+
}
|
|
13208
|
+
function preparedLocaleIdentity(postPath, runDate) {
|
|
13209
|
+
const enPath = deriveLocaleDraftPath(postPath, "en");
|
|
13210
|
+
const jaPath = deriveLocaleDraftPath(postPath, "ja");
|
|
13211
|
+
if (enPath === undefined || jaPath === undefined) {
|
|
13212
|
+
return { ok: false, error: `prepared zh post path must contain a "/zh/" locale segment: ${postPath}` };
|
|
13213
|
+
}
|
|
13214
|
+
const localePaths = { zh: postPath, en: enPath, ja: jaPath };
|
|
13215
|
+
const localeDigests = {};
|
|
13216
|
+
for (const locale of ["zh", "en", "ja"]) {
|
|
13217
|
+
const path = localePaths[locale];
|
|
13218
|
+
if (!existsSync2(path))
|
|
13219
|
+
return { ok: false, error: `${locale} locale file is missing: ${path}` };
|
|
13220
|
+
try {
|
|
13221
|
+
localeDigests[locale] = fileSha256(path);
|
|
13222
|
+
} catch (error) {
|
|
13223
|
+
return {
|
|
13224
|
+
ok: false,
|
|
13225
|
+
error: `${locale} locale file cannot be fingerprinted: ${error instanceof Error ? error.message : String(error)}`
|
|
13226
|
+
};
|
|
13227
|
+
}
|
|
13228
|
+
}
|
|
13229
|
+
const identity = { runDate, postPath, localePaths, localeDigests };
|
|
13230
|
+
return {
|
|
13231
|
+
ok: true,
|
|
13232
|
+
value: { ...identity, fingerprint: createHash("sha256").update(JSON.stringify(identity)).digest("hex") }
|
|
13233
|
+
};
|
|
13234
|
+
}
|
|
13235
|
+
function resolvePreparedLocaleSet(options) {
|
|
13236
|
+
const postPath = resolvePostPath(options);
|
|
13237
|
+
if ("error" in postPath)
|
|
13238
|
+
return { ok: false, result: failNotSent(postPath.error) };
|
|
13061
13239
|
const surfdashRoot = resolveSurfdashRoot();
|
|
13062
13240
|
if (!surfdashRoot) {
|
|
13063
|
-
return
|
|
13241
|
+
return {
|
|
13242
|
+
ok: false,
|
|
13243
|
+
result: failNotSent("SURFDASH_ROOT is required when POSTSURFING_BIN is not a path inside the surfdash checkout")
|
|
13244
|
+
};
|
|
13245
|
+
}
|
|
13246
|
+
const outsideRoot = postPathOutsideRoot(postPath.postPath, surfdashRoot);
|
|
13247
|
+
if (outsideRoot)
|
|
13248
|
+
return { ok: false, result: failNotSent(outsideRoot) };
|
|
13249
|
+
if (!existsSync2(postPath.postPath))
|
|
13250
|
+
return { ok: false, result: failNotSent(`prepared post does not exist: ${postPath.postPath}`) };
|
|
13251
|
+
const runDate = options.runDate;
|
|
13252
|
+
if (typeof runDate !== "string" || !/^\d{8}$/.test(runDate)) {
|
|
13253
|
+
return { ok: false, result: failNotSent(`options.runDate must be YYYYMMDD, got '${String(runDate)}'`) };
|
|
13064
13254
|
}
|
|
13065
|
-
const
|
|
13066
|
-
|
|
13067
|
-
|
|
13255
|
+
const articlesRoot = resolve(surfdashRoot, "content", "posts", "articles");
|
|
13256
|
+
const relativePost = relative(articlesRoot, postPath.postPath);
|
|
13257
|
+
const parts = relativePost.split(sep2);
|
|
13258
|
+
const slug = parts[1];
|
|
13259
|
+
if (parts.length !== 3 || parts[0] !== "zh" || parts[2] !== "index.md" || slug === undefined || slug === "") {
|
|
13260
|
+
return {
|
|
13261
|
+
ok: false,
|
|
13262
|
+
result: failNotSent(`options.postPath must be content/posts/articles/zh/<slug>/index.md: ${postPath.postPath}`)
|
|
13263
|
+
};
|
|
13264
|
+
}
|
|
13265
|
+
const enPath = deriveLocaleDraftPath(postPath.postPath, "en");
|
|
13266
|
+
const jaPath = deriveLocaleDraftPath(postPath.postPath, "ja");
|
|
13267
|
+
if (enPath === undefined || jaPath === undefined) {
|
|
13268
|
+
return {
|
|
13269
|
+
ok: false,
|
|
13270
|
+
result: failNotSent(`prepared zh post path must contain a "/zh/" locale segment: ${postPath.postPath}`)
|
|
13271
|
+
};
|
|
13068
13272
|
}
|
|
13273
|
+
const localePaths = { zh: postPath.postPath, en: enPath, ja: jaPath };
|
|
13274
|
+
for (const locale of ["zh", "en", "ja"]) {
|
|
13275
|
+
if (!existsSync2(localePaths[locale])) {
|
|
13276
|
+
return { ok: false, result: failNotSent(`${locale} locale file is missing: ${localePaths[locale]}`) };
|
|
13277
|
+
}
|
|
13278
|
+
}
|
|
13279
|
+
return { ok: true, value: { surfdashRoot, runDate, postPath: postPath.postPath, slug, localePaths } };
|
|
13280
|
+
}
|
|
13281
|
+
function resolvePreparedPost(options) {
|
|
13282
|
+
const resolved = resolvePreparedLocaleSet(options);
|
|
13283
|
+
if (!resolved.ok)
|
|
13284
|
+
return resolved;
|
|
13285
|
+
const { surfdashRoot, runDate, postPath: preparedPath, slug } = resolved.value;
|
|
13286
|
+
const expectedPostSha256 = options.expectedPostSha256;
|
|
13287
|
+
if (typeof expectedPostSha256 !== "string" || !/^[0-9a-f]{64}$/.test(expectedPostSha256)) {
|
|
13288
|
+
return { ok: false, result: failNotSent("options.expectedPostSha256 must be a lowercase SHA-256 digest") };
|
|
13289
|
+
}
|
|
13290
|
+
const expectedLocaleFingerprint = options.expectedLocaleFingerprint;
|
|
13291
|
+
if (typeof expectedLocaleFingerprint !== "string" || !/^[0-9a-f]{64}$/.test(expectedLocaleFingerprint)) {
|
|
13292
|
+
return {
|
|
13293
|
+
ok: false,
|
|
13294
|
+
result: failNotSent("options.expectedLocaleFingerprint must be a lowercase SHA-256 digest")
|
|
13295
|
+
};
|
|
13296
|
+
}
|
|
13297
|
+
const identity = preparedLocaleIdentity(preparedPath, runDate);
|
|
13298
|
+
if (!identity.ok)
|
|
13299
|
+
return { ok: false, result: failNotSent(identity.error) };
|
|
13300
|
+
if (identity.value.localeDigests.zh !== expectedPostSha256) {
|
|
13301
|
+
return {
|
|
13302
|
+
ok: false,
|
|
13303
|
+
result: failNotSent(`prepared zh post bytes changed after validation: ${preparedPath}`)
|
|
13304
|
+
};
|
|
13305
|
+
}
|
|
13306
|
+
if (identity.value.fingerprint !== expectedLocaleFingerprint) {
|
|
13307
|
+
return {
|
|
13308
|
+
ok: false,
|
|
13309
|
+
result: failNotSent("prepared locale files changed after validation; refresh the locale gate before publishing")
|
|
13310
|
+
};
|
|
13311
|
+
}
|
|
13312
|
+
const baseUrl = (process.env.SURFDASH_URL ?? "https://surfing.salty.vip").replace(/\/$/, "");
|
|
13313
|
+
return {
|
|
13314
|
+
ok: true,
|
|
13315
|
+
prepared: {
|
|
13316
|
+
surfdashRoot,
|
|
13317
|
+
locale: "zh",
|
|
13318
|
+
dateStamp: runDate,
|
|
13319
|
+
slug,
|
|
13320
|
+
filePath: preparedPath,
|
|
13321
|
+
relPostDir: join2("content", "posts", "articles", "zh", slug),
|
|
13322
|
+
relPostDirs: ["zh", "en", "ja"].map((locale) => join2("content", "posts", "articles", locale, slug)),
|
|
13323
|
+
articleUrl: `${baseUrl}/zh/posts/${slug}`
|
|
13324
|
+
},
|
|
13325
|
+
identity: identity.value
|
|
13326
|
+
};
|
|
13327
|
+
}
|
|
13328
|
+
async function preparePost(payload) {
|
|
13329
|
+
const surfdashRoot = resolveSurfdashRoot();
|
|
13330
|
+
if (!surfdashRoot) {
|
|
13331
|
+
return {
|
|
13332
|
+
ok: false,
|
|
13333
|
+
result: failNotSent("SURFDASH_ROOT is required when POSTSURFING_BIN is not a path inside the surfdash checkout")
|
|
13334
|
+
};
|
|
13335
|
+
}
|
|
13336
|
+
const rawLocale = process.env.SURFDASH_LOCALE ?? "zh";
|
|
13337
|
+
if (!["zh", "en", "ja"].includes(rawLocale)) {
|
|
13338
|
+
return { ok: false, result: failNotSent(`SURFDASH_LOCALE must be one of zh/en/ja, got '${rawLocale}'`) };
|
|
13339
|
+
}
|
|
13340
|
+
const locale = rawLocale;
|
|
13069
13341
|
const runDate = typeof payload.content.metadata?.runDate === "string" ? payload.content.metadata.runDate : "";
|
|
13070
13342
|
const today = new Date().toISOString().slice(0, 10).replaceAll("-", "");
|
|
13071
13343
|
const dateStamp = runDate || today;
|
|
13072
13344
|
const titleSlug = slugify2(payload.content.title ?? "");
|
|
13073
13345
|
const slug = titleSlug ? `${dateStamp}-${titleSlug}` : `${dateStamp}-post`;
|
|
13074
13346
|
if (runDate !== "" && !/^\d{8}$/.test(runDate)) {
|
|
13075
|
-
return
|
|
13347
|
+
return { ok: false, result: failNotSent(`metadata.runDate must be YYYYMMDD, got '${runDate}'`) };
|
|
13076
13348
|
}
|
|
13077
13349
|
const articlesRoot = resolve(surfdashRoot, "content", "posts", "articles");
|
|
13078
13350
|
const localeRoot = resolve(articlesRoot, locale);
|
|
13079
13351
|
const postDir = resolve(localeRoot, slug);
|
|
13080
13352
|
if (dirname3(localeRoot) !== articlesRoot || dirname3(postDir) !== localeRoot) {
|
|
13081
|
-
return
|
|
13353
|
+
return { ok: false, result: failNotSent(`post path escapes ${articlesRoot}`) };
|
|
13082
13354
|
}
|
|
13083
13355
|
const coverPath = payload.content.metadata?.coverImage;
|
|
13084
13356
|
if (typeof coverPath === "string" && !existsSync2(coverPath)) {
|
|
13085
|
-
return
|
|
13357
|
+
return { ok: false, result: failNotSent(`metadata.coverImage not found: ${coverPath}`) };
|
|
13086
13358
|
}
|
|
13087
13359
|
if (typeof coverPath === "string" && !COVER_EXTS.has(extname(coverPath).toLowerCase())) {
|
|
13088
|
-
return
|
|
13360
|
+
return {
|
|
13361
|
+
ok: false,
|
|
13362
|
+
result: failNotSent(`metadata.coverImage must be one of ${[...COVER_EXTS].join(", ")}`)
|
|
13363
|
+
};
|
|
13089
13364
|
}
|
|
13090
13365
|
if (isDigestContent(payload.content) && typeof coverPath !== "string") {
|
|
13091
|
-
return
|
|
13366
|
+
return { ok: false, result: failNotSent("digest post has no cover image (metadata.coverImage)") };
|
|
13092
13367
|
}
|
|
13093
|
-
await createNodeFileSystem().ensureDir(postDir);
|
|
13094
|
-
let imageFrontmatter;
|
|
13095
|
-
if (typeof coverPath === "string") {
|
|
13096
|
-
const assetsDir = join2(postDir, "assets");
|
|
13097
|
-
await createNodeFileSystem().ensureDir(assetsDir);
|
|
13098
|
-
const coverName = `cover${extname(coverPath).toLowerCase()}`;
|
|
13099
|
-
await copyFile(coverPath, join2(assetsDir, coverName));
|
|
13100
|
-
imageFrontmatter = `./assets/${coverName}`;
|
|
13101
|
-
}
|
|
13102
|
-
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
13103
|
-
image: imageFrontmatter,
|
|
13104
|
-
publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined
|
|
13105
|
-
});
|
|
13106
13368
|
const filePath = join2(postDir, "index.md");
|
|
13107
13369
|
try {
|
|
13370
|
+
await createNodeFileSystem().ensureDir(postDir);
|
|
13371
|
+
let imageFrontmatter;
|
|
13372
|
+
if (typeof coverPath === "string") {
|
|
13373
|
+
const assetsDir = join2(postDir, "assets");
|
|
13374
|
+
await createNodeFileSystem().ensureDir(assetsDir);
|
|
13375
|
+
const coverName = `cover${extname(coverPath).toLowerCase()}`;
|
|
13376
|
+
await copyFile(coverPath, join2(assetsDir, coverName));
|
|
13377
|
+
imageFrontmatter = `./assets/${coverName}`;
|
|
13378
|
+
}
|
|
13379
|
+
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
13380
|
+
image: imageFrontmatter,
|
|
13381
|
+
publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined
|
|
13382
|
+
});
|
|
13108
13383
|
await createNodeFileSystem().writeFile(filePath, markdown);
|
|
13384
|
+
} catch (err) {
|
|
13385
|
+
return {
|
|
13386
|
+
ok: false,
|
|
13387
|
+
result: failNotSent(`Surfdash post preparation failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
13388
|
+
};
|
|
13389
|
+
}
|
|
13390
|
+
const baseUrl = (process.env.SURFDASH_URL ?? "https://surfing.salty.vip").replace(/\/$/, "");
|
|
13391
|
+
const articleUrl = `${baseUrl}${locale === "en" ? "" : `/${locale}`}/posts/${slug}`;
|
|
13392
|
+
return {
|
|
13393
|
+
ok: true,
|
|
13394
|
+
prepared: {
|
|
13395
|
+
surfdashRoot,
|
|
13396
|
+
locale,
|
|
13397
|
+
dateStamp,
|
|
13398
|
+
slug,
|
|
13399
|
+
filePath,
|
|
13400
|
+
relPostDir: join2("content", "posts", "articles", locale, slug),
|
|
13401
|
+
articleUrl
|
|
13402
|
+
}
|
|
13403
|
+
};
|
|
13404
|
+
}
|
|
13405
|
+
async function preparePostResult(payload) {
|
|
13406
|
+
const outcome = await preparePost(payload);
|
|
13407
|
+
if (!outcome.ok)
|
|
13408
|
+
return outcome.result;
|
|
13409
|
+
return ResultSchema.parse({
|
|
13410
|
+
ok: true,
|
|
13411
|
+
target: TARGET,
|
|
13412
|
+
message: `Prepared ${outcome.prepared.locale} post at ${outcome.prepared.filePath}`,
|
|
13413
|
+
metadata: {
|
|
13414
|
+
postPath: outcome.prepared.filePath,
|
|
13415
|
+
articleUrl: outcome.prepared.articleUrl,
|
|
13416
|
+
dateStamp: outcome.prepared.dateStamp,
|
|
13417
|
+
slug: outcome.prepared.slug
|
|
13418
|
+
}
|
|
13419
|
+
});
|
|
13420
|
+
}
|
|
13421
|
+
async function publishPreparedFile(prepared, identity) {
|
|
13422
|
+
const postsurfingBin = process.env.POSTSURFING_BIN ?? "postsurfing";
|
|
13423
|
+
const { surfdashRoot, locale, dateStamp, filePath, relPostDir, relPostDirs, articleUrl } = prepared;
|
|
13424
|
+
const identityMetadata = identity ? {
|
|
13425
|
+
runDate: identity.runDate,
|
|
13426
|
+
localeFingerprint: identity.fingerprint,
|
|
13427
|
+
...identity.postSha256 ? { postSha256: identity.postSha256 } : {}
|
|
13428
|
+
} : {};
|
|
13429
|
+
try {
|
|
13109
13430
|
const spawned = await spawnCli([postsurfingBin, "publish", filePath], surfdashRoot);
|
|
13110
13431
|
if (spawned.exitCode !== 0) {
|
|
13111
|
-
return
|
|
13432
|
+
return failUncertain(`postsurfing CLI failed (exit ${spawned.exitCode}): ${withEmdashTokenHint(cliErrorText(spawned))}`);
|
|
13433
|
+
}
|
|
13434
|
+
if (identity !== undefined) {
|
|
13435
|
+
const afterRemote = preparedLocaleIdentity(filePath, identity.runDate);
|
|
13436
|
+
if (!afterRemote.ok)
|
|
13437
|
+
return failUncertain(`prepared locale files changed after remote publish: ${afterRemote.error}`);
|
|
13438
|
+
if (afterRemote.value.fingerprint !== identity.fingerprint || identity.postSha256 !== undefined && afterRemote.value.localeDigests.zh !== identity.postSha256) {
|
|
13439
|
+
return failUncertain("prepared locale files changed after remote publish; reconcile before retrying");
|
|
13440
|
+
}
|
|
13112
13441
|
}
|
|
13113
13442
|
const notRefined = requireRefined(filePath);
|
|
13114
13443
|
if (notRefined)
|
|
13115
|
-
return
|
|
13444
|
+
return failUncertain(notRefined);
|
|
13116
13445
|
const digestProblem = digestCheckGate(filePath, locale);
|
|
13117
13446
|
if (digestProblem)
|
|
13118
|
-
return
|
|
13447
|
+
return failUncertain(digestProblem);
|
|
13119
13448
|
const refined = readRefinedSnapshot(filePath);
|
|
13120
|
-
const baseUrl = (process.env.SURFDASH_URL ?? "https://surfing.salty.vip").replace(/\/$/, "");
|
|
13121
|
-
const articleUrl = `${baseUrl}${locale === "en" ? "" : `/${locale}`}/posts/${slug}`;
|
|
13122
13449
|
if (process.env.SURFDASH_AUTO_PUSH === "0") {
|
|
13123
13450
|
return ResultSchema.parse({
|
|
13124
13451
|
ok: true,
|
|
13125
13452
|
target: TARGET,
|
|
13126
13453
|
url: articleUrl,
|
|
13127
13454
|
message: "Published via postsurfing CLI (auto-push disabled)",
|
|
13128
|
-
metadata: {
|
|
13455
|
+
metadata: {
|
|
13456
|
+
postPath: filePath,
|
|
13457
|
+
...identityMetadata,
|
|
13458
|
+
refinedTitle: refined?.title,
|
|
13459
|
+
refinedCanonical: refined?.canonical
|
|
13460
|
+
}
|
|
13129
13461
|
});
|
|
13130
13462
|
}
|
|
13131
|
-
const
|
|
13132
|
-
const
|
|
13463
|
+
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
13464
|
+
const gate = await runSiteGate(surfdashRoot);
|
|
13465
|
+
if (!gate.ok)
|
|
13466
|
+
return failUncertain(gate.error);
|
|
13467
|
+
const commitPaths = relPostDirs ?? [relPostDir];
|
|
13468
|
+
const commitLocales = relPostDirs === undefined ? [locale] : ["zh", "en", "ja"];
|
|
13469
|
+
const pushed = await pushPostGit(surfdashRoot, commitPaths, `content: daily AI news ${dateStamp} (${commitLocales.join(", ")})`, backfilled);
|
|
13133
13470
|
if (!pushed.ok)
|
|
13134
|
-
return
|
|
13471
|
+
return failUncertain(pushed.error);
|
|
13135
13472
|
const live = await awaitArticleLive(articleUrl);
|
|
13136
13473
|
return ResultSchema.parse({
|
|
13137
13474
|
ok: true,
|
|
13138
13475
|
target: TARGET,
|
|
13139
13476
|
url: articleUrl,
|
|
13140
13477
|
message: live ? "Published, committed, pushed, and live on the site" : "Published and pushed; URL not live within the verify window \u2014 check the surfdash deploy workflow",
|
|
13141
|
-
metadata: {
|
|
13478
|
+
metadata: {
|
|
13479
|
+
postPath: filePath,
|
|
13480
|
+
...identityMetadata,
|
|
13481
|
+
gitCommit: pushed.hash,
|
|
13482
|
+
live,
|
|
13483
|
+
refinedTitle: refined?.title
|
|
13484
|
+
}
|
|
13142
13485
|
});
|
|
13143
13486
|
} catch (err) {
|
|
13144
|
-
return
|
|
13487
|
+
return failUncertain(`Postsurfing transport error: ${err instanceof Error ? err.message : String(err)}`);
|
|
13145
13488
|
}
|
|
13146
13489
|
}
|
|
13490
|
+
async function publishPost(payload) {
|
|
13491
|
+
const outcome = await preparePost(payload);
|
|
13492
|
+
if (!outcome.ok)
|
|
13493
|
+
return outcome.result;
|
|
13494
|
+
return publishPreparedFile(outcome.prepared);
|
|
13495
|
+
}
|
|
13496
|
+
async function refinePreparedPost(options) {
|
|
13497
|
+
const resolved = resolvePreparedLocaleSet(options);
|
|
13498
|
+
if (!resolved.ok)
|
|
13499
|
+
return resolved.result;
|
|
13500
|
+
const orchestrator = join2(resolved.value.surfdashRoot, "scripts", "publish-orchestrator.ts");
|
|
13501
|
+
if (!existsSync2(orchestrator))
|
|
13502
|
+
return failNotSent(`Surfdash refine orchestrator is missing: ${orchestrator}`);
|
|
13503
|
+
try {
|
|
13504
|
+
for (const locale of ["zh", "en", "ja"]) {
|
|
13505
|
+
const filePath = resolved.value.localePaths[locale];
|
|
13506
|
+
const spawned = await spawnCli([process.execPath, orchestrator, "refine", filePath], resolved.value.surfdashRoot);
|
|
13507
|
+
if (spawned.exitCode !== 0) {
|
|
13508
|
+
return failNotSent(`Surfdash refine failed for ${locale} (exit ${spawned.exitCode}): ${cliErrorText(spawned)}`);
|
|
13509
|
+
}
|
|
13510
|
+
const notRefined = requireRefined(filePath);
|
|
13511
|
+
if (notRefined)
|
|
13512
|
+
return failNotSent(notRefined);
|
|
13513
|
+
const digestProblem = digestCheckGate(filePath, locale);
|
|
13514
|
+
if (digestProblem)
|
|
13515
|
+
return failNotSent(digestProblem);
|
|
13516
|
+
}
|
|
13517
|
+
} catch (error) {
|
|
13518
|
+
return failNotSent(`Surfdash refine transport error: ${error instanceof Error ? error.message : String(error)}`);
|
|
13519
|
+
}
|
|
13520
|
+
const identity = preparedLocaleIdentity(resolved.value.postPath, resolved.value.runDate);
|
|
13521
|
+
if (!identity.ok)
|
|
13522
|
+
return failNotSent(identity.error);
|
|
13523
|
+
return ResultSchema.parse({
|
|
13524
|
+
ok: true,
|
|
13525
|
+
target: TARGET,
|
|
13526
|
+
message: "Refined zh/en/ja via the Surfdash-owned orchestrator",
|
|
13527
|
+
metadata: {
|
|
13528
|
+
postPath: resolved.value.postPath,
|
|
13529
|
+
runDate: resolved.value.runDate,
|
|
13530
|
+
localePaths: identity.value.localePaths,
|
|
13531
|
+
localeDigests: identity.value.localeDigests,
|
|
13532
|
+
localeFingerprint: identity.value.fingerprint
|
|
13533
|
+
}
|
|
13534
|
+
});
|
|
13535
|
+
}
|
|
13536
|
+
async function publishPreparedPost(options) {
|
|
13537
|
+
const outcome = resolvePreparedPost(options);
|
|
13538
|
+
if (!outcome.ok)
|
|
13539
|
+
return outcome.result;
|
|
13540
|
+
return publishPreparedFile(outcome.prepared, {
|
|
13541
|
+
runDate: outcome.identity.runDate,
|
|
13542
|
+
fingerprint: outcome.identity.fingerprint,
|
|
13543
|
+
postSha256: outcome.identity.localeDigests.zh
|
|
13544
|
+
});
|
|
13545
|
+
}
|
|
13147
13546
|
|
|
13148
13547
|
class PostsurfingFileCliTransport {
|
|
13149
13548
|
kind = "file-cli";
|
|
@@ -13153,6 +13552,12 @@ class PostsurfingFileCliTransport {
|
|
|
13153
13552
|
if ("error" in routed)
|
|
13154
13553
|
return fail(routed.error);
|
|
13155
13554
|
switch (routed.operation) {
|
|
13555
|
+
case "prepare":
|
|
13556
|
+
return preparePostResult(payload);
|
|
13557
|
+
case "refine-prepared":
|
|
13558
|
+
return refinePreparedPost(options);
|
|
13559
|
+
case "publish-prepared":
|
|
13560
|
+
return publishPreparedPost(options);
|
|
13156
13561
|
case "scaffold-locale":
|
|
13157
13562
|
return scaffoldLocale(options);
|
|
13158
13563
|
case "sync":
|
|
@@ -13222,6 +13627,7 @@ export {
|
|
|
13222
13627
|
commitPostPaths,
|
|
13223
13628
|
contentTags,
|
|
13224
13629
|
digestCheckGate,
|
|
13630
|
+
extractFailingPostHint,
|
|
13225
13631
|
fastForwardToOrigin,
|
|
13226
13632
|
getPublishTransport,
|
|
13227
13633
|
main2 as main,
|
|
@@ -13232,6 +13638,8 @@ export {
|
|
|
13232
13638
|
pushPostGit,
|
|
13233
13639
|
readRefinedSnapshot,
|
|
13234
13640
|
requireRefined,
|
|
13641
|
+
runListenLinkBackfill,
|
|
13642
|
+
runSiteGate,
|
|
13235
13643
|
slugify2 as slugify,
|
|
13236
13644
|
withEmdashTokenHint
|
|
13237
13645
|
};
|