@gobing-ai/knowledge-kit 0.1.12 → 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 +16 -4
- package/package.json +1 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/daily-ai-voice-migration.json +3 -3
- package/plugins/kk/publish-presets/daily-ai-voice.json +4 -4
- package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +2 -2
- package/plugins/kk/scripts/daily-scheduled-run.ts +91 -41
- package/plugins/kk/scripts/daily-stages.ts +52 -9
- 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/workflows/kk-collect.yaml +3 -3
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +12 -12
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +2 -2
- package/plugins/publishings/surfdash-pub/dist/index.js +52 -23
- package/plugins/publishings/surfdash-pub/src/index.ts +69 -24
- package/plugins/publishings/wechatmp-pub/dist/index.js +171 -30
- package/plugins/publishings/wechatmp-pub/src/flow.ts +287 -49
- package/plugins/publishings/weibo-pub/dist/index.js +100 -7
- package/plugins/publishings/weibo-pub/src/index.ts +111 -11
- 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
|
@@ -221,10 +221,10 @@ nodes:
|
|
|
221
221
|
action:
|
|
222
222
|
kind: shell
|
|
223
223
|
options:
|
|
224
|
-
onError: continue
|
|
225
224
|
command: >-
|
|
226
225
|
kk plugin check wechatmp-pub xhs-pub --timeout 60000
|
|
227
226
|
|| echo "session-preflight: a publish channel's saved session was refused — fix it before this run's publish step with: bun plugins/publishings/<channel>-pub/src/profile-check.ts login (wechatmp or xhs)"
|
|
227
|
+
onError: continue
|
|
228
228
|
|
|
229
229
|
- id: extract
|
|
230
230
|
description: >-
|
|
@@ -689,7 +689,7 @@ nodes:
|
|
|
689
689
|
options:
|
|
690
690
|
command: >-
|
|
691
691
|
kk stage daily-voice-audit "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
|
|
692
|
-
|
|
692
|
+
onError: continue
|
|
693
693
|
|
|
694
694
|
- id: duration
|
|
695
695
|
description: "Measured duration gate: under episode_min_minutes the run refuses and closes as no-content (refusal artifact + emptied candidates, exit 0 — 0266); above episode_max_minutes it still fails loud. Duration is authoritative."
|
|
@@ -839,15 +839,6 @@ nodes:
|
|
|
839
839
|
action:
|
|
840
840
|
kind: agent.run
|
|
841
841
|
options:
|
|
842
|
-
# 20261012 hardening: this is the LAST agent step and a translation failure must not abort
|
|
843
|
-
# the overall report/podcast close, yet the step carried no tolerance and no retry sibling — a stochastic
|
|
844
|
-
# executor failure here aborted the DAG *after* surfdash and podcast had published, so
|
|
845
|
-
# publish-classify, mark-published, run-report and ledger never ran for an otherwise complete
|
|
846
|
-
# day. Tolerating the failure is safe because a scaffold is an unpublished draft: the prompt
|
|
847
|
-
# below is what flips `draft: true` → `draft: false`, so an untranslated locale stays off the
|
|
848
|
-
# site, and `daily-publish-completeness` now reads that flag and reports the locale as failed
|
|
849
|
-
# instead of calling a scaffold `done`.
|
|
850
|
-
onError: continue
|
|
851
842
|
role: scribe
|
|
852
843
|
agent: ${vars.agent}
|
|
853
844
|
timeoutMs: 600000
|
|
@@ -863,6 +854,15 @@ nodes:
|
|
|
863
854
|
Series names are fixed product identity, never invented or freely translated: the en series name is "Daily AI Stew" (show "Salty Talk"); the ja series name is "デイリーAIシチュー" (show "咸話咸説"). Never emit 毎日AIブレンド, ごった煮, "Daily AI Medley", "hodgepodge", "Daily AI Briefing", or the zh name 每日AI乱炖 in en/ja prose or link text (a URL keeps whatever it contains). Openers: en "Your Daily AI Stew from Robin, on Salty Talk."; ja "咸話咸説のデイリーAIシチュー、Robin がお届けします。" Listen links: en "[listen to Daily AI Stew · <date>](<url>)", ja "[デイリーAIシチュー · <date> を聴く](<url>)". In each key_points item keep the "Label — sentence" shape: the label before " — " stays a short phrase of at most 14 characters (ja) / 28 (en), never truncated with "…". The local translate-validate stage rewrites known retired names deterministically and fails the publish gate on leftovers.
|
|
864
855
|
Keep frontmatter valid YAML: double-quote any value containing ': '. Read the surfdash translator glossary if present at the surfdash checkout root under .agents/skills/surfdash-translator/glossary.md and apply its terminology.
|
|
865
856
|
Do not wait for operator confirmation. Each written file must end fully in its target language.
|
|
857
|
+
# 20261012 hardening: this is the LAST agent step and a translation failure must not abort
|
|
858
|
+
# the overall report/podcast close, yet the step carried no tolerance and no retry sibling — a stochastic
|
|
859
|
+
# executor failure here aborted the DAG *after* surfdash and podcast had published, so
|
|
860
|
+
# publish-classify, mark-published, run-report and ledger never ran for an otherwise complete
|
|
861
|
+
# day. Tolerating the failure is safe because a scaffold is an unpublished draft: the prompt
|
|
862
|
+
# above is what flips `draft: true` → `draft: false`, so an untranslated locale stays off the
|
|
863
|
+
# site, and `daily-publish-completeness` now reads that flag and reports the locale as failed
|
|
864
|
+
# instead of calling a scaffold `done`.
|
|
865
|
+
onError: continue
|
|
866
866
|
|
|
867
867
|
# 2026-10-24: the locale translation carried `onError: continue` with NO retry sibling, so a
|
|
868
868
|
# failed executor left the en/ja drafts untranslated and the old sync stage could not distinguish
|
|
@@ -880,11 +880,11 @@ nodes:
|
|
|
880
880
|
action:
|
|
881
881
|
kind: agent.run
|
|
882
882
|
options:
|
|
883
|
-
onError: continue
|
|
884
883
|
role: reviewer
|
|
885
884
|
agent: ${vars.agent_retry}
|
|
886
885
|
timeoutMs: 600000
|
|
887
886
|
input: *translate-input
|
|
887
|
+
onError: continue
|
|
888
888
|
|
|
889
889
|
- id: translate-refine
|
|
890
890
|
description: "Run the Surfdash-owned refine pass over zh/en/ja locally before the locale fingerprint is admitted; no CMS, git, or remote side effect"
|
|
@@ -194,10 +194,10 @@ nodes:
|
|
|
194
194
|
action:
|
|
195
195
|
kind: shell
|
|
196
196
|
options:
|
|
197
|
-
onError: continue
|
|
198
197
|
command: >-
|
|
199
198
|
kk stage run publish-matrix-check "${vars.work_dir}" --all-windows
|
|
200
199
|
|| echo "session-preflight: a publish channel's saved session was refused — fix it with: kk bot login <channel>"
|
|
200
|
+
onError: continue
|
|
201
201
|
|
|
202
202
|
- id: extract
|
|
203
203
|
description: >-
|
|
@@ -695,7 +695,7 @@ nodes:
|
|
|
695
695
|
options:
|
|
696
696
|
command: >-
|
|
697
697
|
kk stage daily-voice-audit "${vars.work_dir}" "${vars.run_date}" "${vars.plugins_path}"
|
|
698
|
-
|
|
698
|
+
onError: continue
|
|
699
699
|
|
|
700
700
|
- id: duration
|
|
701
701
|
description: "Measured duration gate: under episode_min_minutes the run refuses and closes as no-content (refusal artifact + emptied candidates + publish-path invalidation, exit 0 — 0266/0329); above episode_max_minutes it still fails loud. Duration is authoritative."
|
|
@@ -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}`);
|
|
@@ -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
|
}
|
|
@@ -846,7 +875,7 @@ async function patchPost(options: SurfdashPubOptions): Promise<Result> {
|
|
|
846
875
|
if (process.env.SURFDASH_AUTO_PUSH !== '0') {
|
|
847
876
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
848
877
|
for (const p of backfilled) commitPaths.add(p);
|
|
849
|
-
const gate = await runSiteGate(surfdashRoot);
|
|
878
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, relative(surfdashRoot, postPath.postPath)]);
|
|
850
879
|
if (!gate.ok) return fail(gate.error);
|
|
851
880
|
}
|
|
852
881
|
const commit = await commitPostPaths(
|
|
@@ -1080,9 +1109,15 @@ async function preparePost(payload: PublishTransportPayload): Promise<PrepareOut
|
|
|
1080
1109
|
return { ok: false, result: failNotSent(`SURFDASH_LOCALE must be one of zh/en/ja, got '${rawLocale}'`) };
|
|
1081
1110
|
}
|
|
1082
1111
|
const locale = rawLocale as SupportedLocale;
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
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('-', '');
|
|
1086
1121
|
const titleSlug = slugify(payload.content.title ?? '');
|
|
1087
1122
|
const slug = titleSlug ? `${dateStamp}-${titleSlug}` : `${dateStamp}-post`;
|
|
1088
1123
|
if (runDate !== '' && !/^\d{8}$/.test(runDate)) {
|
|
@@ -1111,20 +1146,30 @@ async function preparePost(payload: PublishTransportPayload): Promise<PrepareOut
|
|
|
1111
1146
|
|
|
1112
1147
|
const filePath = join(postDir, 'index.md');
|
|
1113
1148
|
try {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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)`);
|
|
1122
1172
|
}
|
|
1123
|
-
const markdown = mapContentToSurfingMarkdown(payload.content, {
|
|
1124
|
-
image: imageFrontmatter,
|
|
1125
|
-
publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined,
|
|
1126
|
-
});
|
|
1127
|
-
await createNodeFileSystem().writeFile(filePath, markdown);
|
|
1128
1173
|
} catch (err: unknown) {
|
|
1129
1174
|
return {
|
|
1130
1175
|
ok: false,
|
|
@@ -1238,9 +1283,9 @@ async function publishPreparedFile(
|
|
|
1238
1283
|
});
|
|
1239
1284
|
}
|
|
1240
1285
|
const backfilled = await runListenLinkBackfill(surfdashRoot);
|
|
1241
|
-
const gate = await runSiteGate(surfdashRoot);
|
|
1242
|
-
if (!gate.ok) return failUncertain(gate.error);
|
|
1243
1286
|
const commitPaths = relPostDirs ?? [relPostDir];
|
|
1287
|
+
const gate = await runSiteGate(surfdashRoot, [...commitPaths, ...backfilled]);
|
|
1288
|
+
if (!gate.ok) return failUncertain(gate.error);
|
|
1244
1289
|
const commitLocales: SupportedLocale[] = relPostDirs === undefined ? [locale] : ['zh', 'en', 'ja'];
|
|
1245
1290
|
const pushed = await pushPostGit(
|
|
1246
1291
|
surfdashRoot,
|