@gobing-ai/knowledge-kit 0.1.11 → 0.1.13
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 +2372 -1722
- package/package.json +1 -1
- package/plugins/generations/image-gen/README.md +108 -0
- package/plugins/generations/image-gen/assets/sample/cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/dist/index.js +102 -58
- package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +5 -21
- package/plugins/generations/image-gen/scripts/gen-samples.ts +208 -0
- package/plugins/generations/image-gen/src/compose/chromium.ts +2 -3
- package/plugins/generations/image-gen/src/presets.ts +6 -3
- package/plugins/generations/image-gen/src/providers/errors.ts +16 -1
- package/plugins/generations/image-gen/src/providers/minimax.ts +46 -17
- package/plugins/ingestions/web-search/src/index.ts +1 -1
- package/plugins/kk/README.md +18 -0
- package/plugins/kk/commands/image-extract.md +1 -0
- package/plugins/kk/commands/image-generate.md +1 -0
- package/plugins/kk/commands/tell-me.md +1 -0
- package/plugins/kk/commands/workflow-run.md +7 -1
- package/plugins/kk/decisions/extract/it-topics.yaml +24 -0
- package/plugins/kk/decisions/kk-itc.yaml +6 -4
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/daily-ai-voice-migration.json +146 -0
- package/plugins/kk/publish-presets/daily-ai-voice.json +15 -7
- package/plugins/kk/schedules/kk-collect.grok-bot.md.tmpl +69 -0
- package/plugins/kk/schedules/kk-daily-ai-voice3.cron.tmpl +18 -0
- package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +15 -5
- package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +9 -4
- package/plugins/kk/scripts/daily-scheduled-run.ts +91 -41
- package/plugins/kk/scripts/daily-stages.ts +394 -53
- package/plugins/kk/scripts/kk-workflow-stages.ts +190 -0
- package/plugins/kk/scripts/safety-artifact-check.ts +70 -28
- package/plugins/kk/scripts/voice3-publish-window.ts +202 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +1 -0
- package/plugins/kk/skills/audio-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/content-judge/SKILL.md +1 -0
- package/plugins/kk/skills/content-safety/SKILL.md +1 -0
- package/plugins/kk/skills/content-safety/references/platforms/podcast-salty.md +41 -0
- package/plugins/kk/skills/content-safety/references/platforms/surfdash.md +36 -0
- package/plugins/kk/skills/content-safety/references/platforms/xhs.md +39 -0
- package/plugins/kk/skills/explain-things/SKILL.md +1 -0
- package/plugins/kk/skills/image-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/itc-generating/SKILL.md +1 -0
- package/plugins/kk/skills/publish/SKILL.md +1 -0
- package/plugins/kk/skills/smart-seo/SKILL.md +1 -0
- package/plugins/kk/skills/storm-research/SKILL.md +1 -0
- package/plugins/kk/skills/taste-unslop/SKILL.md +1 -0
- package/plugins/kk/skills/topic/SKILL.md +1 -0
- package/plugins/kk/skills/web-automation/SKILL.md +1 -0
- package/plugins/kk/skills/xhs-images/SKILL.md +1 -0
- package/plugins/kk/workflows/kk-collect.yaml +3 -3
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +21 -19
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +163 -63
- package/plugins/kk/workflows/kk-daily-publish3.yaml +82 -26
- package/plugins/kk/workflows/kk-daily-safety.yaml +39 -31
- package/plugins/kk/workflows/kk-itc-author.yaml +75 -0
- package/plugins/kk/workflows/kk-itc-publish-tail.yaml +25 -3
- package/plugins/kk/workflows/kk-itc.yaml +36 -4
- package/plugins/publishings/linkedin-pub/dist/index.js +21333 -0
- package/plugins/publishings/linkedin-pub/dist/profile-check.js +21005 -0
- package/plugins/publishings/linkedin-pub/package.json +19 -0
- package/plugins/publishings/linkedin-pub/plugin.json +12 -0
- package/plugins/publishings/linkedin-pub/src/index.ts +705 -0
- package/plugins/publishings/linkedin-pub/src/profile-check.ts +17 -0
- package/plugins/publishings/linkedin-pub/tsconfig.json +4 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +53 -24
- package/plugins/publishings/surfdash-pub/src/index.ts +72 -25
- package/plugins/publishings/threads-pub/dist/index.js +7 -0
- package/plugins/publishings/threads-pub/src/index.ts +12 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +176 -32
- package/plugins/publishings/wechatmp-pub/src/flow.ts +298 -52
- package/plugins/publishings/weibo-pub/dist/index.js +100 -7
- package/plugins/publishings/weibo-pub/src/index.ts +111 -11
- package/plugins/publishings/x-pub/dist/index.js +7 -0
- package/plugins/publishings/x-pub/src/index.ts +11 -0
- package/plugins/publishings/xhs-pub/dist/index.js +164 -12
- package/plugins/publishings/xhs-pub/dist/profile-check.js +11 -1
- package/plugins/publishings/xhs-pub/src/flow.ts +194 -23
- package/plugins/publishings/xhs-pub/src/page.ts +26 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkedIn profile-session capability (0328 R2).
|
|
3
|
+
*
|
|
4
|
+
* `login` opens the feed in a headed browser and resolves only once LinkedIn's readiness predicate
|
|
5
|
+
* passes; `check` reopens the profile headlessly and reports `verified` or `refused` without
|
|
6
|
+
* posting. Arg parsing, profile precedence (`--profile` > `LINKEDIN_PROFILE_DIR` >
|
|
7
|
+
* `KK_BROWSER_PROFILE_DIR` > `~/.config/kk/browser-profiles/linkedin`, ADR-042) and the exit codes
|
|
8
|
+
* (0 verified, 1 refused, 2 usage error) all live in the shared `runProfileCli`, so the publish
|
|
9
|
+
* preflight (`kk plugin check linkedin-pub` and the publish-matrix probe) and this CLI apply one
|
|
10
|
+
* gate.
|
|
11
|
+
*/
|
|
12
|
+
import { runProfileCli } from '@gobing-ai/publish-harness/browser';
|
|
13
|
+
import { LINKEDIN_CONFIG } from './index';
|
|
14
|
+
|
|
15
|
+
if (import.meta.main) {
|
|
16
|
+
process.exit(await runProfileCli(LINKEDIN_CONFIG, Bun.argv.slice(2)));
|
|
17
|
+
}
|
|
@@ -12798,11 +12798,14 @@ function resolveSurfdashRoot() {
|
|
|
12798
12798
|
return process.env.SURFDASH_ROOT ?? (postsurfingBin.includes("/") ? resolve(dirname3(dirname3(postsurfingBin))) : undefined);
|
|
12799
12799
|
}
|
|
12800
12800
|
async function spawnCli(cmd, cwd) {
|
|
12801
|
+
const env = { ...process.env };
|
|
12802
|
+
for (const key of ["SPUR_WORKFLOW_RUN_ACTIVE", "SPUR_EXPECTED_DEFINITION_DIGEST"])
|
|
12803
|
+
delete env[key];
|
|
12801
12804
|
const proc = Bun.spawn(cmd, {
|
|
12802
12805
|
cwd,
|
|
12803
12806
|
stdout: "pipe",
|
|
12804
12807
|
stderr: "pipe",
|
|
12805
|
-
env
|
|
12808
|
+
env
|
|
12806
12809
|
});
|
|
12807
12810
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
12808
12811
|
proc.exited,
|
|
@@ -12896,7 +12899,30 @@ function extractFailingPostHint(text) {
|
|
|
12896
12899
|
return;
|
|
12897
12900
|
return match[0].replace(/[.,;]+$/, "");
|
|
12898
12901
|
}
|
|
12899
|
-
|
|
12902
|
+
function extractPostDates(paths) {
|
|
12903
|
+
const dates = new Set;
|
|
12904
|
+
for (const p of paths) {
|
|
12905
|
+
const m = p.replaceAll("\\", "/").match(/(?:^|\/)posts\/articles\/(?:zh|en|ja)\/(\d{8})-/);
|
|
12906
|
+
if (m?.[1])
|
|
12907
|
+
dates.add(m[1]);
|
|
12908
|
+
}
|
|
12909
|
+
return dates;
|
|
12910
|
+
}
|
|
12911
|
+
function isUnrelatedDatePostFailure(output, ownedPaths) {
|
|
12912
|
+
const ownedDates = extractPostDates(ownedPaths);
|
|
12913
|
+
if (ownedDates.size === 0)
|
|
12914
|
+
return false;
|
|
12915
|
+
const failingHints = [...output.matchAll(/(?:content[\\/])?posts[\\/][^\s:)]+/g)].map((m) => m[0].replace(/[.,;]+$/, ""));
|
|
12916
|
+
if (failingHints.length === 0)
|
|
12917
|
+
return false;
|
|
12918
|
+
for (const hint of failingHints) {
|
|
12919
|
+
const m = hint.replaceAll("\\", "/").match(/(?:^|\/)posts\/articles\/(?:zh|en|ja)\/(\d{8})-/);
|
|
12920
|
+
if (!m?.[1] || ownedDates.has(m[1]))
|
|
12921
|
+
return false;
|
|
12922
|
+
}
|
|
12923
|
+
return true;
|
|
12924
|
+
}
|
|
12925
|
+
async function runSiteGate(surfdashRoot, ownedPaths = []) {
|
|
12900
12926
|
if (process.env.SURFDASH_DIGEST_CHECK === "off") {
|
|
12901
12927
|
return { ok: true };
|
|
12902
12928
|
}
|
|
@@ -12907,7 +12933,7 @@ async function runSiteGate(surfdashRoot) {
|
|
|
12907
12933
|
const postHint = extractFailingPostHint(output);
|
|
12908
12934
|
const hint = postHint ? ` [failing post: ${postHint}]` : "";
|
|
12909
12935
|
const error = `site gate failed: ${cmd.join(" ")} (exit ${spawned.exitCode})${hint}: ${output}`;
|
|
12910
|
-
if (process.env.SURFDASH_DIGEST_CHECK === "warn") {
|
|
12936
|
+
if (process.env.SURFDASH_DIGEST_CHECK === "warn" || isUnrelatedDatePostFailure(output, ownedPaths)) {
|
|
12911
12937
|
for (const line of error.split(`
|
|
12912
12938
|
`)) {
|
|
12913
12939
|
echoError(`site gate (warn): ${line}`);
|
|
@@ -13079,7 +13105,7 @@ function normalizeLocaleFrontmatter(markdown) {
|
|
|
13079
13105
|
const close = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
13080
13106
|
if (close < 0)
|
|
13081
13107
|
return markdown;
|
|
13082
|
-
const head = lines.slice(1, close).filter((l) => !/^canonical\s
|
|
13108
|
+
const head = lines.slice(1, close).filter((l) => !/^canonical\s*:.*\/zh\//.test(l)).map((l) => /^draft\s*:/.test(l) ? "draft: false" : l);
|
|
13083
13109
|
return [lines[0], ...head, ...lines.slice(close)].join(`
|
|
13084
13110
|
`);
|
|
13085
13111
|
}
|
|
@@ -13172,7 +13198,7 @@ async function patchPost(options) {
|
|
|
13172
13198
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
13173
13199
|
for (const p of backfilled)
|
|
13174
13200
|
commitPaths.add(p);
|
|
13175
|
-
const gate = await runSiteGate(surfdashRoot);
|
|
13201
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, relative(surfdashRoot, postPath.postPath)]);
|
|
13176
13202
|
if (!gate.ok)
|
|
13177
13203
|
return fail(gate.error);
|
|
13178
13204
|
}
|
|
@@ -13338,9 +13364,8 @@ async function preparePost(payload) {
|
|
|
13338
13364
|
return { ok: false, result: failNotSent(`SURFDASH_LOCALE must be one of zh/en/ja, got '${rawLocale}'`) };
|
|
13339
13365
|
}
|
|
13340
13366
|
const locale = rawLocale;
|
|
13341
|
-
const runDate = typeof payload.content.metadata?.runDate === "string" ? payload.content.metadata.runDate : "";
|
|
13342
|
-
const
|
|
13343
|
-
const dateStamp = runDate || today;
|
|
13367
|
+
const runDate = typeof payload.content.metadata?.runDate === "string" && payload.content.metadata.runDate !== "" ? payload.content.metadata.runDate : typeof payload.content.metadata?.date === "string" ? payload.content.metadata.date.replaceAll("-", "") : "";
|
|
13368
|
+
const dateStamp = runDate || new Date().toISOString().slice(0, 10).replaceAll("-", "");
|
|
13344
13369
|
const titleSlug = slugify2(payload.content.title ?? "");
|
|
13345
13370
|
const slug = titleSlug ? `${dateStamp}-${titleSlug}` : `${dateStamp}-post`;
|
|
13346
13371
|
if (runDate !== "" && !/^\d{8}$/.test(runDate)) {
|
|
@@ -13367,20 +13392,24 @@ async function preparePost(payload) {
|
|
|
13367
13392
|
}
|
|
13368
13393
|
const filePath = join2(postDir, "index.md");
|
|
13369
13394
|
try {
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13395
|
+
if (readRefinedSnapshot(filePath) === undefined) {
|
|
13396
|
+
await createNodeFileSystem().ensureDir(postDir);
|
|
13397
|
+
let imageFrontmatter;
|
|
13398
|
+
if (typeof coverPath === "string") {
|
|
13399
|
+
const assetsDir = join2(postDir, "assets");
|
|
13400
|
+
await createNodeFileSystem().ensureDir(assetsDir);
|
|
13401
|
+
const coverName = `cover${extname(coverPath).toLowerCase()}`;
|
|
13402
|
+
await copyFile(coverPath, join2(assetsDir, coverName));
|
|
13403
|
+
imageFrontmatter = `./assets/${coverName}`;
|
|
13404
|
+
}
|
|
13405
|
+
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
13406
|
+
image: imageFrontmatter,
|
|
13407
|
+
publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined
|
|
13408
|
+
});
|
|
13409
|
+
await createNodeFileSystem().writeFile(filePath, markdown);
|
|
13410
|
+
} else {
|
|
13411
|
+
echoError(`surfdash-pub: keeping existing refined post at ${filePath} (not re-minting)`);
|
|
13412
|
+
}
|
|
13384
13413
|
} catch (err) {
|
|
13385
13414
|
return {
|
|
13386
13415
|
ok: false,
|
|
@@ -13461,10 +13490,10 @@ async function publishPreparedFile(prepared, identity) {
|
|
|
13461
13490
|
});
|
|
13462
13491
|
}
|
|
13463
13492
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
13464
|
-
const
|
|
13493
|
+
const commitPaths = relPostDirs ?? [relPostDir];
|
|
13494
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, ...backfilled]);
|
|
13465
13495
|
if (!gate.ok)
|
|
13466
13496
|
return failUncertain(gate.error);
|
|
13467
|
-
const commitPaths = relPostDirs ?? [relPostDir];
|
|
13468
13497
|
const commitLocales = relPostDirs === undefined ? [locale] : ["zh", "en", "ja"];
|
|
13469
13498
|
const pushed = await pushPostGit(surfdashRoot, commitPaths, `content: daily AI news ${dateStamp} (${commitLocales.join(", ")})`, backfilled);
|
|
13470
13499
|
if (!pushed.ok)
|
|
@@ -316,11 +316,16 @@ interface CliSpawnResult {
|
|
|
316
316
|
|
|
317
317
|
/** One spawn helper for every CLI-backed operation (postsurfing publish / sd translate / orchestrator sync). */
|
|
318
318
|
async function spawnCli(cmd: string[], cwd: string): Promise<CliSpawnResult> {
|
|
319
|
+
// postsurfing runs a nested spur workflow in the surfdash repo; a kk publish3 step shell carries
|
|
320
|
+
// the engine's single-flight guard vars, and the guard would refuse that nested run. Same
|
|
321
|
+
// escape the kk-daily-ai-voice3 safety node uses for its intentional nesting.
|
|
322
|
+
const env = { ...process.env };
|
|
323
|
+
for (const key of ['SPUR_WORKFLOW_RUN_ACTIVE', 'SPUR_EXPECTED_DEFINITION_DIGEST']) delete env[key];
|
|
319
324
|
const proc = Bun.spawn(cmd, {
|
|
320
325
|
cwd,
|
|
321
326
|
stdout: 'pipe',
|
|
322
327
|
stderr: 'pipe',
|
|
323
|
-
env
|
|
328
|
+
env,
|
|
324
329
|
});
|
|
325
330
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
326
331
|
proc.exited,
|
|
@@ -471,17 +476,41 @@ export function extractFailingPostHint(text: string): string | undefined {
|
|
|
471
476
|
return match[0].replace(/[.,;]+$/, '');
|
|
472
477
|
}
|
|
473
478
|
|
|
479
|
+
function extractPostDates(paths: readonly string[]): Set<string> {
|
|
480
|
+
const dates = new Set<string>();
|
|
481
|
+
for (const p of paths) {
|
|
482
|
+
const m = p.replaceAll('\\', '/').match(/(?:^|\/)posts\/articles\/(?:zh|en|ja)\/(\d{8})-/);
|
|
483
|
+
if (m?.[1]) dates.add(m[1]);
|
|
484
|
+
}
|
|
485
|
+
return dates;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function isUnrelatedDatePostFailure(output: string, ownedPaths: readonly string[]): boolean {
|
|
489
|
+
const ownedDates = extractPostDates(ownedPaths);
|
|
490
|
+
if (ownedDates.size === 0) return false;
|
|
491
|
+
const failingHints = [...output.matchAll(/(?:content[\\/])?posts[\\/][^\s:)]+/g)].map((m) =>
|
|
492
|
+
m[0].replace(/[.,;]+$/, ''),
|
|
493
|
+
);
|
|
494
|
+
if (failingHints.length === 0) return false;
|
|
495
|
+
for (const hint of failingHints) {
|
|
496
|
+
const m = hint.replaceAll('\\', '/').match(/(?:^|\/)posts\/articles\/(?:zh|en|ja)\/(\d{8})-/);
|
|
497
|
+
if (!m?.[1] || ownedDates.has(m[1])) return false;
|
|
498
|
+
}
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
|
|
474
502
|
/**
|
|
475
503
|
* Runs the surfdash site gates (`bun run sd check digest` then `bun run check:content`)
|
|
476
504
|
* with cwd = `surfdashRoot` via `spawnCli`.
|
|
477
505
|
*
|
|
478
506
|
* If either command exits non-zero:
|
|
479
|
-
* - With `SURFDASH_DIGEST_CHECK=warn
|
|
480
|
-
*
|
|
507
|
+
* - With `SURFDASH_DIGEST_CHECK=warn` (or when every failing post belongs to an unrelated date
|
|
508
|
+
* outside `ownedPaths`): echoes each line of the failure via `echoError` prefixed with
|
|
509
|
+
* `site gate (warn):` and continues.
|
|
481
510
|
* - With `SURFDASH_DIGEST_CHECK=off`: skips the gate completely.
|
|
482
511
|
* - Default (unset or any other value): fails loud with command, trimmed output, and post hint.
|
|
483
512
|
*/
|
|
484
|
-
export async function runSiteGate(surfdashRoot: string): Promise<SiteGateOutcome> {
|
|
513
|
+
export async function runSiteGate(surfdashRoot: string, ownedPaths: readonly string[] = []): Promise<SiteGateOutcome> {
|
|
485
514
|
if (process.env.SURFDASH_DIGEST_CHECK === 'off') {
|
|
486
515
|
return { ok: true };
|
|
487
516
|
}
|
|
@@ -493,7 +522,7 @@ export async function runSiteGate(surfdashRoot: string): Promise<SiteGateOutcome
|
|
|
493
522
|
const postHint = extractFailingPostHint(output);
|
|
494
523
|
const hint = postHint ? ` [failing post: ${postHint}]` : '';
|
|
495
524
|
const error = `site gate failed: ${cmd.join(' ')} (exit ${spawned.exitCode})${hint}: ${output}`;
|
|
496
|
-
if (process.env.SURFDASH_DIGEST_CHECK === 'warn') {
|
|
525
|
+
if (process.env.SURFDASH_DIGEST_CHECK === 'warn' || isUnrelatedDatePostFailure(output, ownedPaths)) {
|
|
497
526
|
for (const line of error.split('\n')) {
|
|
498
527
|
echoError(`site gate (warn): ${line}`);
|
|
499
528
|
}
|
|
@@ -735,7 +764,9 @@ export function normalizeLocaleFrontmatter(markdown: string): string {
|
|
|
735
764
|
if (close < 0) return markdown;
|
|
736
765
|
const head = lines
|
|
737
766
|
.slice(1, close)
|
|
738
|
-
|
|
767
|
+
// Only the zh scaffold's canonical is stale; a sibling's own (refined) canonical must survive,
|
|
768
|
+
// or the orchestrator re-dumps the frontmatter and the byte-exact drift gate fails.
|
|
769
|
+
.filter((l) => !/^canonical\s*:.*\/zh\//.test(l))
|
|
739
770
|
.map((l) => (/^draft\s*:/.test(l) ? 'draft: false' : l));
|
|
740
771
|
return [lines[0], ...head, ...lines.slice(close)].join('\n');
|
|
741
772
|
}
|
|
@@ -844,7 +875,7 @@ async function patchPost(options: SurfdashPubOptions): Promise<Result> {
|
|
|
844
875
|
if (process.env.SURFDASH_AUTO_PUSH !== '0') {
|
|
845
876
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
846
877
|
for (const p of backfilled) commitPaths.add(p);
|
|
847
|
-
const gate = await runSiteGate(surfdashRoot);
|
|
878
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, relative(surfdashRoot, postPath.postPath)]);
|
|
848
879
|
if (!gate.ok) return fail(gate.error);
|
|
849
880
|
}
|
|
850
881
|
const commit = await commitPostPaths(
|
|
@@ -1078,9 +1109,15 @@ async function preparePost(payload: PublishTransportPayload): Promise<PrepareOut
|
|
|
1078
1109
|
return { ok: false, result: failNotSent(`SURFDASH_LOCALE must be one of zh/en/ja, got '${rawLocale}'`) };
|
|
1079
1110
|
}
|
|
1080
1111
|
const locale = rawLocale as SupportedLocale;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
const
|
|
1112
|
+
// dateStamp source: explicit metadata.runDate, else the payload's own metadata.date —
|
|
1113
|
+
// never the wall clock, which around midnight local time mints `tomorrow` slugs.
|
|
1114
|
+
const runDate =
|
|
1115
|
+
typeof payload.content.metadata?.runDate === 'string' && payload.content.metadata.runDate !== ''
|
|
1116
|
+
? payload.content.metadata.runDate
|
|
1117
|
+
: typeof payload.content.metadata?.date === 'string'
|
|
1118
|
+
? payload.content.metadata.date.replaceAll('-', '')
|
|
1119
|
+
: '';
|
|
1120
|
+
const dateStamp = runDate || new Date().toISOString().slice(0, 10).replaceAll('-', '');
|
|
1084
1121
|
const titleSlug = slugify(payload.content.title ?? '');
|
|
1085
1122
|
const slug = titleSlug ? `${dateStamp}-${titleSlug}` : `${dateStamp}-post`;
|
|
1086
1123
|
if (runDate !== '' && !/^\d{8}$/.test(runDate)) {
|
|
@@ -1109,20 +1146,30 @@ async function preparePost(payload: PublishTransportPayload): Promise<PrepareOut
|
|
|
1109
1146
|
|
|
1110
1147
|
const filePath = join(postDir, 'index.md');
|
|
1111
1148
|
try {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1149
|
+
// A post the site's own refine step already marked is not ours to clobber: re-minting it
|
|
1150
|
+
// replaces the refined canonical frontmatter (refine owns `refined`, `seo_title`, …) with
|
|
1151
|
+
// generator defaults and flips an already-published day back to a draft. Keep the file and
|
|
1152
|
+
// reuse it; only genuinely fresh days (no marker) get minted (2026-11-05 tier-0 rerun).
|
|
1153
|
+
if (readRefinedSnapshot(filePath) === undefined) {
|
|
1154
|
+
await createNodeFileSystem().ensureDir(postDir);
|
|
1155
|
+
let imageFrontmatter: string | undefined;
|
|
1156
|
+
if (typeof coverPath === 'string') {
|
|
1157
|
+
const assetsDir = join(postDir, 'assets');
|
|
1158
|
+
await createNodeFileSystem().ensureDir(assetsDir);
|
|
1159
|
+
const coverName = `cover${extname(coverPath).toLowerCase()}`;
|
|
1160
|
+
await copyFile(coverPath, join(assetsDir, coverName));
|
|
1161
|
+
imageFrontmatter = `./assets/${coverName}`;
|
|
1162
|
+
}
|
|
1163
|
+
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
1164
|
+
image: imageFrontmatter,
|
|
1165
|
+
publishDate: runDate
|
|
1166
|
+
? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}`
|
|
1167
|
+
: undefined,
|
|
1168
|
+
});
|
|
1169
|
+
await createNodeFileSystem().writeFile(filePath, markdown);
|
|
1170
|
+
} else {
|
|
1171
|
+
echoError(`surfdash-pub: keeping existing refined post at ${filePath} (not re-minting)`);
|
|
1120
1172
|
}
|
|
1121
|
-
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
1122
|
-
image: imageFrontmatter,
|
|
1123
|
-
publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined,
|
|
1124
|
-
});
|
|
1125
|
-
await createNodeFileSystem().writeFile(filePath, markdown);
|
|
1126
1173
|
} catch (err: unknown) {
|
|
1127
1174
|
return {
|
|
1128
1175
|
ok: false,
|
|
@@ -1236,9 +1283,9 @@ async function publishPreparedFile(
|
|
|
1236
1283
|
});
|
|
1237
1284
|
}
|
|
1238
1285
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
1239
|
-
const gate = await runSiteGate(surfdashRoot);
|
|
1240
|
-
if (!gate.ok) return failUncertain(gate.error);
|
|
1241
1286
|
const commitPaths = relPostDirs ?? [relPostDir];
|
|
1287
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, ...backfilled]);
|
|
1288
|
+
if (!gate.ok) return failUncertain(gate.error);
|
|
1242
1289
|
const commitLocales: SupportedLocale[] = relPostDirs === undefined ? [locale] : ['zh', 'en', 'ja'];
|
|
1243
1290
|
const pushed = await pushPostGit(
|
|
1244
1291
|
surfdashRoot,
|
|
@@ -12391,6 +12391,13 @@ class ThreadsPostApiTransport {
|
|
|
12391
12391
|
const unsupported = refuseUnsupportedOptions(payload.options, payload.content);
|
|
12392
12392
|
if (unsupported !== undefined)
|
|
12393
12393
|
return fail(unsupported);
|
|
12394
|
+
const requestedStatus = payload.content.metadata?.publishStatus;
|
|
12395
|
+
if (requestedStatus === "draft") {
|
|
12396
|
+
return fail("threads post has no draft: the Threads API publishes immediately \u2014 plan this target dummy/off or launch it live");
|
|
12397
|
+
}
|
|
12398
|
+
if (requestedStatus !== undefined && requestedStatus !== "public") {
|
|
12399
|
+
return fail(`unknown publishStatus '${String(requestedStatus)}' (expected absent, 'draft' or 'public')`);
|
|
12400
|
+
}
|
|
12394
12401
|
const missing = TOKEN_ENVS.filter((key) => {
|
|
12395
12402
|
const value = process.env[key];
|
|
12396
12403
|
return !value || value.trim().length === 0;
|
|
@@ -146,6 +146,18 @@ export class ThreadsPostApiTransport implements PublishTransport {
|
|
|
146
146
|
const unsupported = refuseUnsupportedOptions(payload.options, payload.content);
|
|
147
147
|
if (unsupported !== undefined) return fail(unsupported);
|
|
148
148
|
|
|
149
|
+
// 0330 R2: the post API publishes immediately — there is no draft. Intercept before the
|
|
150
|
+
// credential lookup so a planned draft never reaches Threads, and fail closed on unknown modes.
|
|
151
|
+
const requestedStatus = payload.content.metadata?.publishStatus;
|
|
152
|
+
if (requestedStatus === 'draft') {
|
|
153
|
+
return fail(
|
|
154
|
+
'threads post has no draft: the Threads API publishes immediately — plan this target dummy/off or launch it live',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (requestedStatus !== undefined && requestedStatus !== 'public') {
|
|
158
|
+
return fail(`unknown publishStatus '${String(requestedStatus)}' (expected absent, 'draft' or 'public')`);
|
|
159
|
+
}
|
|
160
|
+
|
|
149
161
|
const missing = TOKEN_ENVS.filter((key) => {
|
|
150
162
|
const value = process.env[key];
|
|
151
163
|
return !value || value.trim().length === 0;
|