@softure-ai/blog 0.1.5 → 0.1.7
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/CHANGELOG.md +22 -0
- package/README.md +142 -15
- package/dist/cli/report.d.ts +17 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +149 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/run.d.ts +13 -6
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +131 -94
- package/dist/cli/run.js.map +1 -1
- package/dist/contract.d.ts +4 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/db/articles.d.ts +10 -1
- package/dist/db/articles.d.ts.map +1 -1
- package/dist/db/articles.js +36 -4
- package/dist/db/articles.js.map +1 -1
- package/dist/db/history.d.ts +33 -0
- package/dist/db/history.d.ts.map +1 -0
- package/dist/db/history.js +66 -0
- package/dist/db/history.js.map +1 -0
- package/dist/db/publish-run.d.ts +11 -0
- package/dist/db/publish-run.d.ts.map +1 -1
- package/dist/db/publish-run.js +14 -4
- package/dist/db/publish-run.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/next/context.js +2 -2
- package/dist/next/context.js.map +1 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +3 -1
- package/dist/options.js.map +1 -1
- package/dist/pages/accept.d.ts +7 -0
- package/dist/pages/accept.d.ts.map +1 -0
- package/dist/pages/accept.js +34 -0
- package/dist/pages/accept.js.map +1 -0
- package/dist/proxy/index.d.ts +15 -0
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +54 -4
- package/dist/proxy/index.js.map +1 -1
- package/dist/quality/catalog.d.ts.map +1 -1
- package/dist/quality/catalog.js +4 -1
- package/dist/quality/catalog.js.map +1 -1
- package/dist/quality/check-article.d.ts.map +1 -1
- package/dist/quality/check-article.js +2 -1
- package/dist/quality/check-article.js.map +1 -1
- package/dist/quality/options.d.ts.map +1 -1
- package/dist/quality/options.js +3 -1
- package/dist/quality/options.js.map +1 -1
- package/dist/quality/rules/blocks.d.ts +8 -1
- package/dist/quality/rules/blocks.d.ts.map +1 -1
- package/dist/quality/rules/blocks.js +26 -0
- package/dist/quality/rules/blocks.js.map +1 -1
- package/dist/render/article-markdown.d.ts +12 -0
- package/dist/render/article-markdown.d.ts.map +1 -0
- package/dist/render/article-markdown.js +21 -0
- package/dist/render/article-markdown.js.map +1 -0
- package/dist/render/index.d.ts +2 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +2 -1
- package/dist/render/index.js.map +1 -1
- package/dist/render/render-article.d.ts +40 -4
- package/dist/render/render-article.d.ts.map +1 -1
- package/dist/render/render-article.js +132 -10
- package/dist/render/render-article.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/sitemap.js +2 -2
- package/dist/sitemap.js.map +1 -1
- package/module.json +1 -1
- package/package.json +7 -3
- package/skill/references/rules.md +2 -1
- package/src/cli/report.ts +182 -0
- package/src/cli/run.ts +132 -100
- package/src/contract.ts +9 -1
- package/src/db/articles.ts +39 -4
- package/src/db/history.ts +83 -0
- package/src/db/publish-run.ts +24 -4
- package/src/index.ts +1 -1
- package/src/next/context.ts +2 -2
- package/src/options.ts +6 -2
- package/src/pages/accept.ts +39 -0
- package/src/proxy/index.ts +62 -4
- package/src/quality/catalog.ts +4 -1
- package/src/quality/check-article.ts +2 -1
- package/src/quality/options.ts +6 -2
- package/src/quality/rules/blocks.ts +26 -1
- package/src/render/article-markdown.ts +34 -0
- package/src/render/index.ts +6 -0
- package/src/render/render-article.ts +170 -16
- package/src/server/index.ts +2 -0
- package/src/sitemap.ts +2 -2
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { computeContentHash, parseArticleFile, type ArticleFileResult, type ParseArticleFileOptions } from "../content/article-file.js";
|
|
2
|
-
export { findArticleBySlug, findSlugRedirect, getPublishedArticle, listArticles, publishArticle, type BlogContext, type ListArticlesFilter, } from "../db/articles.js";
|
|
2
|
+
export { findArticleBySlug, findSlugRedirect, getPublishedArticle, listArticles, publishArticle, type BlogContext, type ListArticlesFilter, type PublishArticleOptions, } from "../db/articles.js";
|
|
3
|
+
export { articleHistorySchema, parseArticleHistory, type ArticleHistory, type ArticleHistoryMap } from "../db/history.js";
|
|
3
4
|
export { runBlogPublish, type ArticleFile, type BlogPublishRun, type PublishedChange, type PublishGate, type PublishProblem, type RunBlogPublishOptions, } from "../db/publish-run.js";
|
|
4
5
|
export { checkArticlesTable } from "./health.js";
|
|
5
6
|
export { getBlogMessages, getBlogOptions, getBlogRefreshPath, getBlogReservedSlugs, getBlogRoutes, getQualitySettings } from "./options.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACxI,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACxI,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1H,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5I,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// not allowed here.
|
|
4
4
|
export { computeContentHash, parseArticleFile } from "../content/article-file.js";
|
|
5
5
|
export { findArticleBySlug, findSlugRedirect, getPublishedArticle, listArticles, publishArticle, } from "../db/articles.js";
|
|
6
|
+
export { articleHistorySchema, parseArticleHistory } from "../db/history.js";
|
|
6
7
|
export { runBlogPublish, } from "../db/publish-run.js";
|
|
7
8
|
export { checkArticlesTable } from "./health.js";
|
|
8
9
|
export { getBlogMessages, getBlogOptions, getBlogRefreshPath, getBlogReservedSlugs, getBlogRoutes, getQualitySettings } from "./options.js";
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,qGAAqG;AACrG,oBAAoB;AACpB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAwD,MAAM,4BAA4B,CAAC;AACxI,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,qGAAqG;AACrG,oBAAoB;AACpB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAwD,MAAM,4BAA4B,CAAC;AACxI,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,cAAc,GAIf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAA+C,MAAM,kBAAkB,CAAC;AAC1H,OAAO,EACL,cAAc,GAOf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5I,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC"}
|
package/dist/sitemap.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// one query on the shared database, not from the pages' Next cache. Crawlers read a sitemap rarely.
|
|
5
5
|
import { systemClock } from "@softure-ai/core";
|
|
6
6
|
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
7
|
-
import {
|
|
7
|
+
import { getConfiguredDatabase } from "@softure-ai/db";
|
|
8
8
|
import { listArticles } from "./db/articles.js";
|
|
9
9
|
import { getBlogSitemapEntries } from "./discovery/sitemap.js";
|
|
10
10
|
import { getBlogOptions, getBlogRoutes } from "./server/options.js";
|
|
@@ -14,7 +14,7 @@ export async function readBlogSitemap(config) {
|
|
|
14
14
|
// Unreachable for a validated config: the module has a database schema.
|
|
15
15
|
throw new Error("@softure-ai/blog: softure.config.ts has no database; the blog sitemap needs one");
|
|
16
16
|
}
|
|
17
|
-
const { db } = await
|
|
17
|
+
const { db } = await getConfiguredDatabase(config.database);
|
|
18
18
|
const texts = await listArticles({ db, clock: systemClock, config });
|
|
19
19
|
const routes = getBlogRoutes(config);
|
|
20
20
|
return getBlogSitemapEntries({
|
package/dist/sitemap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,uGAAuG;AACvG,oGAAoG;AACpG,oGAAoG;AACpG,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,uGAAuG;AACvG,oGAAoG;AACpG,oGAAoG;AACpG,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAyB,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpE,qHAAqH;AACrH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAqB;IACzD,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,wEAAwE;QACxE,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAC;IACrG,CAAC;IACD,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,qBAAqB,CAAC;QAC3B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;QACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;QACnD,MAAM;QACN,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;KACrE,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAsB;IAChD,OAAO,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC,CAAC;AAC7D,CAAC"}
|
package/module.json
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softure-ai/blog",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "SOFTURE AI blog module: articles and glossary terms kept as Markdown files with a strict YAML frontmatter (extendable by the app's own fields), stored in the module's Postgres schema by the softure-blog publish command (dry run by default, all or nothing, content hash, slug history, one pillar per cluster), a server-side Markdown renderer (no raw HTML, safe links, glossary links, block plugins), a text quality gate (softure-blog check, run before every publish) with language rulesets and rule plugins, the blog's pages ready to mount in Next.js (listing, article, glossary, method page, OG image, JSON-LD) with a proxy piece for 301 and 410, an article writing skill (softure-blog skill install) generated from the gate's rules, and discovery: an RSS feed, read next, sitemap entries and an IndexNow submit on publish through the optional @softure-ai/seo, with a rate-limited cache refresh route the publish calls first (through the optional @softure-ai/security).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"*.css"
|
|
9
|
+
],
|
|
8
10
|
"engines": {
|
|
9
11
|
"node": ">=22"
|
|
10
12
|
},
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
},
|
|
16
18
|
"files": [
|
|
17
19
|
"dist",
|
|
20
|
+
"CHANGELOG.md",
|
|
18
21
|
"src",
|
|
19
22
|
"!src/**/*.test.ts",
|
|
20
23
|
"!src/**/*.test.tsx",
|
|
@@ -69,7 +72,6 @@
|
|
|
69
72
|
"dependencies": {
|
|
70
73
|
"@softure-ai/core": "^0.1.0",
|
|
71
74
|
"@softure-ai/db": "^0.1.0",
|
|
72
|
-
"@softure-ai/ui": "^0.1.0",
|
|
73
75
|
"markdown-it": "^15.0.2",
|
|
74
76
|
"markdown-it-footnote": "^4.0.0",
|
|
75
77
|
"yaml": "^2.9.1",
|
|
@@ -78,6 +80,7 @@
|
|
|
78
80
|
"peerDependencies": {
|
|
79
81
|
"@softure-ai/security": "^0.1.0",
|
|
80
82
|
"@softure-ai/seo": "^0.1.0",
|
|
83
|
+
"@softure-ai/ui": "^0.1.0",
|
|
81
84
|
"drizzle-orm": "^0.45.2",
|
|
82
85
|
"next": "^16.0.0",
|
|
83
86
|
"react": "^19.0.0"
|
|
@@ -86,6 +89,7 @@
|
|
|
86
89
|
"@fontsource/inter": "^5.3.0",
|
|
87
90
|
"@softure-ai/security": "^0.1.0",
|
|
88
91
|
"@softure-ai/seo": "^0.1.0",
|
|
92
|
+
"@softure-ai/ui": "^0.1.7",
|
|
89
93
|
"@types/markdown-it": "^14.2.0",
|
|
90
94
|
"@types/markdown-it-footnote": "^3.0.4"
|
|
91
95
|
},
|
|
@@ -31,7 +31,8 @@ rewrite the paragraph around its one thought; do not swap a word for a synonym.
|
|
|
31
31
|
| `length` | | | An article of {{articleWordsMin}} to {{articleWordsMax}} words, a term of {{termWordsMin}} to {{termWordsMax}}. |
|
|
32
32
|
| `footnote-undefined` | | | Define every `[^id]` at the end of the file. |
|
|
33
33
|
| `footnote-unused` | | | Remove a footnote definition nothing refers to, or refer to it. |
|
|
34
|
-
| `block-requires` | | | A
|
|
34
|
+
| `block-requires` | | | A block of the app's block plugins (a fence or a `::directive`) needs its frontmatter keys; add them. |
|
|
35
|
+
| `block-directive` | | | A `::directive` line names a directive the blog renders and writes its attributes as `key="value"` pairs, each key once. |
|
|
35
36
|
|
|
36
37
|
## Links
|
|
37
38
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// How `softure-blog publish` prints its result: for a person (the default) or as a line contract a
|
|
2
|
+
// release script parses (`--format lines`). Both get the same events; only the wording differs.
|
|
3
|
+
//
|
|
4
|
+
// The line contract: every line starts `blog|`, fields are separated by `|`, and a `|` or a line break
|
|
5
|
+
// inside a value becomes a space. Keys are stable English words; a new key may be added, an existing
|
|
6
|
+
// one never changes meaning. A run prints exactly one outcome line: `blog|written`, `blog|dry-run`,
|
|
7
|
+
// `blog|refused` or `blog|failed|<message>`.
|
|
8
|
+
//
|
|
9
|
+
// blog|warning|<subject>|<message>
|
|
10
|
+
// blog|error|<subject>|<message> (before blog|refused)
|
|
11
|
+
// blog|change|<added|changed|unchanged>|<id>|<status/slug before or none>|<status/slug after>
|
|
12
|
+
// blog|moved|<id>|<old slug>|<new slug>
|
|
13
|
+
// blog|imported|<id>|<published_at ISO or none>|<old slugs>
|
|
14
|
+
// blog|summary|<added>|<changed>|<unchanged>
|
|
15
|
+
// blog|cache|<off|skipped|dry-run|refreshed|failed>|…
|
|
16
|
+
// blog|indexnow|<off|skipped|dry-run|submitted|failed>|…
|
|
17
|
+
import type { BlogRefreshOutcome } from "../discovery/refresh.js";
|
|
18
|
+
import { BLOG_REFRESH_SECRET_ENV } from "../discovery/refresh.js";
|
|
19
|
+
import type { BlogIndexNowSubmit } from "../discovery/submit.js";
|
|
20
|
+
import type { BlogPublishRun, PublishedChange, PublishProblem } from "../db/publish-run.js";
|
|
21
|
+
|
|
22
|
+
export interface CliOutput {
|
|
23
|
+
readonly log: (line: string) => void;
|
|
24
|
+
readonly error: (line: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type PublishFormat = "text" | "lines";
|
|
28
|
+
|
|
29
|
+
export interface PublishReporter {
|
|
30
|
+
/** A problem before or outside the run (a file it cannot read, the database); the run's outcome. */
|
|
31
|
+
readonly failed: (message: string) => void;
|
|
32
|
+
readonly run: (run: BlogPublishRun) => void;
|
|
33
|
+
readonly refresh: (outcome: BlogRefreshOutcome) => void;
|
|
34
|
+
readonly indexNow: (submit: BlogIndexNowSubmit) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createPublishReporter(format: PublishFormat, output: CliOutput): PublishReporter {
|
|
38
|
+
return format === "lines" ? createLineReporter(output) : createTextReporter(output);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function countActions(run: Extract<BlogPublishRun, { status: "done" }>): Record<PublishedChange["action"], number> {
|
|
42
|
+
const count = (action: PublishedChange["action"]) => run.changes.filter((change) => change.action === action).length;
|
|
43
|
+
return { added: count("added"), changed: count("changed"), unchanged: count("unchanged") };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatBefore(change: PublishedChange): string {
|
|
47
|
+
return change.statusBefore === null || change.slugBefore === null ? "none" : `${change.statusBefore}/${change.slugBefore}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatDate(date: Date | null): string {
|
|
51
|
+
return date === null ? "none" : date.toISOString();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createTextReporter(output: CliOutput): PublishReporter {
|
|
55
|
+
const formatProblem = (problem: PublishProblem) => `${problem.subject}: ${problem.message}`;
|
|
56
|
+
return {
|
|
57
|
+
failed: (message) => output.error(`softure-blog publish: ${message}`),
|
|
58
|
+
run: (run) => {
|
|
59
|
+
for (const warning of run.warnings) output.error(`warning ${formatProblem(warning)}`);
|
|
60
|
+
if (run.status === "refused") {
|
|
61
|
+
for (const problem of run.problems) output.error(`error ${formatProblem(problem)}`);
|
|
62
|
+
output.error("refused: nothing written; fix the problems above");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const change of run.changes) {
|
|
66
|
+
output.log(`${change.action} ${change.id} ${formatBefore(change)} -> ${change.statusAfter}/${change.slug}`);
|
|
67
|
+
if (change.previousSlug !== null) output.log(`moved ${change.id} ${change.previousSlug} -> ${change.slug}`);
|
|
68
|
+
if (change.imported !== undefined) {
|
|
69
|
+
output.log(`imported ${change.id} published_at=${formatDate(change.imported.publishedAt)} old_slugs=${String(change.imported.oldSlugs)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const counts = countActions(run);
|
|
73
|
+
output.log(`summary: added ${String(counts.added)}, changed ${String(counts.changed)}, unchanged ${String(counts.unchanged)}`);
|
|
74
|
+
output.log(run.committed ? "written" : "dry run: nothing written; pass --commit to write");
|
|
75
|
+
},
|
|
76
|
+
refresh: (outcome) => {
|
|
77
|
+
switch (outcome.kind) {
|
|
78
|
+
case "not_configured":
|
|
79
|
+
output.log(`cache: the running app shows the change within revalidateSeconds (${String(outcome.revalidateSeconds)} s); set ${BLOG_REFRESH_SECRET_ENV} to refresh it now`);
|
|
80
|
+
return;
|
|
81
|
+
case "skipped":
|
|
82
|
+
output.log("cache: no text changed, nothing to refresh");
|
|
83
|
+
return;
|
|
84
|
+
case "dry_run":
|
|
85
|
+
output.log(`cache: dry run, a commit would refresh ${outcome.url}`);
|
|
86
|
+
return;
|
|
87
|
+
case "refreshed":
|
|
88
|
+
output.log(`cache: refreshed ${outcome.url}`);
|
|
89
|
+
return;
|
|
90
|
+
case "failed":
|
|
91
|
+
output.error(`warning cache: ${outcome.reason} (${outcome.code}); the publish is written, the app shows it within ${String(outcome.revalidateSeconds)} s`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
indexNow: ({ paths, outcome }) => {
|
|
96
|
+
switch (outcome.kind) {
|
|
97
|
+
case "not_configured":
|
|
98
|
+
output.log(`indexnow: off, ${outcome.reason}`);
|
|
99
|
+
return;
|
|
100
|
+
case "skipped":
|
|
101
|
+
output.log("indexnow: no public address changed, nothing to submit");
|
|
102
|
+
return;
|
|
103
|
+
case "dry_run":
|
|
104
|
+
output.log(`indexnow: dry run, a commit would submit ${String(outcome.urls.length)} URL(s): ${outcome.urls.join(" ")}`);
|
|
105
|
+
return;
|
|
106
|
+
case "submitted":
|
|
107
|
+
output.log(`indexnow: submitted ${String(outcome.count)} URL(s) (${String(outcome.status)}): ${paths.join(" ")}`);
|
|
108
|
+
return;
|
|
109
|
+
case "failed":
|
|
110
|
+
output.error(`warning indexnow: ${outcome.reason} (${outcome.code}); the publish is written, submit the addresses later: ${paths.join(" ")}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A value inside a contract line: no field separator, no line break. */
|
|
118
|
+
function toField(value: string | number): string {
|
|
119
|
+
return String(value).replace(/[|\r\n]+/g, " ");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function createLineReporter(output: CliOutput): PublishReporter {
|
|
123
|
+
// Every contract line goes to standard output, so `2>&1` is not needed to read them.
|
|
124
|
+
const line = (...fields: readonly (string | number)[]) => output.log(["blog", ...fields.map(toField)].join("|"));
|
|
125
|
+
return {
|
|
126
|
+
failed: (message) => line("failed", message),
|
|
127
|
+
run: (run) => {
|
|
128
|
+
for (const warning of run.warnings) line("warning", warning.subject, warning.message);
|
|
129
|
+
if (run.status === "refused") {
|
|
130
|
+
for (const problem of run.problems) line("error", problem.subject, problem.message);
|
|
131
|
+
line("refused");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
for (const change of run.changes) {
|
|
135
|
+
line("change", change.action, change.id, formatBefore(change), `${change.statusAfter}/${change.slug}`);
|
|
136
|
+
if (change.previousSlug !== null) line("moved", change.id, change.previousSlug, change.slug);
|
|
137
|
+
if (change.imported !== undefined) line("imported", change.id, formatDate(change.imported.publishedAt), change.imported.oldSlugs);
|
|
138
|
+
}
|
|
139
|
+
const counts = countActions(run);
|
|
140
|
+
line("summary", counts.added, counts.changed, counts.unchanged);
|
|
141
|
+
line(run.committed ? "written" : "dry-run");
|
|
142
|
+
},
|
|
143
|
+
refresh: (outcome) => {
|
|
144
|
+
switch (outcome.kind) {
|
|
145
|
+
case "not_configured":
|
|
146
|
+
line("cache", "off", outcome.revalidateSeconds);
|
|
147
|
+
return;
|
|
148
|
+
case "skipped":
|
|
149
|
+
line("cache", "skipped");
|
|
150
|
+
return;
|
|
151
|
+
case "dry_run":
|
|
152
|
+
line("cache", "dry-run", outcome.url);
|
|
153
|
+
return;
|
|
154
|
+
case "refreshed":
|
|
155
|
+
line("cache", "refreshed", outcome.url);
|
|
156
|
+
return;
|
|
157
|
+
case "failed":
|
|
158
|
+
line("cache", "failed", outcome.code, outcome.reason);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
indexNow: ({ paths, outcome }) => {
|
|
163
|
+
switch (outcome.kind) {
|
|
164
|
+
case "not_configured":
|
|
165
|
+
line("indexnow", "off", outcome.reason);
|
|
166
|
+
return;
|
|
167
|
+
case "skipped":
|
|
168
|
+
line("indexnow", "skipped");
|
|
169
|
+
return;
|
|
170
|
+
case "dry_run":
|
|
171
|
+
line("indexnow", "dry-run", outcome.urls.length, outcome.urls.join(" "));
|
|
172
|
+
return;
|
|
173
|
+
case "submitted":
|
|
174
|
+
line("indexnow", "submitted", outcome.count, outcome.status, paths.join(" "));
|
|
175
|
+
return;
|
|
176
|
+
case "failed":
|
|
177
|
+
line("indexnow", "failed", outcome.code, outcome.reason, paths.join(" "));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|