@softure-ai/blog 0.1.6 → 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 +12 -0
- package/README.md +135 -10
- 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 +12 -5
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +121 -87
- 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/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 +52 -2
- 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/module.json +1 -1
- package/package.json +1 -1
- package/skill/references/rules.md +2 -1
- package/src/cli/report.ts +182 -0
- package/src/cli/run.ts +121 -91
- 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/options.ts +6 -2
- package/src/pages/accept.ts +39 -0
- package/src/proxy/index.ts +60 -2
- 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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
|
|
|
4
4
|
production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
|
|
5
5
|
"Definition of done"). Versions before the first one below are described in their GitHub Releases (`blog@x.y.z`).
|
|
6
6
|
|
|
7
|
+
## 0.1.7
|
|
8
|
+
|
|
9
|
+
- Block plugins with `syntax: "directive"` render top-level `::name{key="value"}` lines with parsed
|
|
10
|
+
`attributes`; the quality gate checks their `requires` and reports an unknown directive or unreadable
|
|
11
|
+
attributes (`block-directive`). `ArticleBlock` and `FoundBlock` carry `syntax` and `attributes`.
|
|
12
|
+
- `createBlogMarkdown` (`/proxy`) answers an article or term asked for with `Accept: text/markdown` with
|
|
13
|
+
`toArticleMarkdown`; a block plugin may give its Markdown form (`markdown`).
|
|
14
|
+
- `softure-blog publish --stdin` reads one file (`--name`) or a JSON bundle (files and an optional
|
|
15
|
+
history) from standard input; `--format lines` prints a stable `blog|<key>|…` contract.
|
|
16
|
+
- `publish --history <file.json>` (and `runBlogPublish({ history })`) imports `published_at`, `updated_at`
|
|
17
|
+
and old slugs on the first publish of each article, for an app moving its existing blog in.
|
|
18
|
+
|
|
7
19
|
## 0.1.6
|
|
8
20
|
|
|
9
21
|
- Adapters and commands use the configured database handle.
|
package/README.md
CHANGED
|
@@ -11,12 +11,15 @@ editor and no CMS, a text changes only through a commit and `softure-blog publis
|
|
|
11
11
|
- `blog.articles` and `blog.slug_history` with database constraints for every invariant that fits one.
|
|
12
12
|
- `softure-blog publish`: a dry run by default; with `--commit`, all files or none; unchanged files are
|
|
13
13
|
skipped by their content hash; a slug change keeps the old slug as a redirect; one pillar per cluster.
|
|
14
|
+
Files from paths or from standard input (`--stdin`, for a release through an ssh pipe), a line
|
|
15
|
+
contract for scripts (`--format lines`), and the dates and old slugs of an existing blog imported on
|
|
16
|
+
the first publish (`--history`).
|
|
14
17
|
- Read functions for the pages: `getPublishedArticle`, `findArticleBySlug`, `findSlugRedirect`,
|
|
15
18
|
`listArticles`.
|
|
16
19
|
- `renderArticle(markdown, options)`: the body as safe HTML on the server (no raw HTML, safe link
|
|
17
20
|
schemes only, marked external links, images under the app's image policy), heading ids and an
|
|
18
21
|
optional table of contents, glossary links on the first mention of a term, block plugins for the
|
|
19
|
-
app's own fenced blocks, reading time.
|
|
22
|
+
app's own fenced blocks and `::directive{…}` lines, reading time.
|
|
20
23
|
- Pages, each mounted with one re-export line (`@softure-ai/blog/next`): the listing grouped by cluster
|
|
21
24
|
with the pillar first, an article (dates, summary, contents, FAQ, sources, signature, disclaimer,
|
|
22
25
|
`BlogPosting`/`BreadcrumbList`/`FAQPage` JSON-LD), the glossary index and a term page (`DefinedTerm`,
|
|
@@ -24,7 +27,8 @@ editor and no CMS, a text changes only through a commit and `softure-blog publis
|
|
|
24
27
|
Their canonical, Open Graph, JSON-LD and feed URLs follow `@softure-ai/seo`'s origin, host and
|
|
25
28
|
trailing-slash rule when the app lists `seo()` (core's `getSiteUrls`), and `appOrigin` otherwise.
|
|
26
29
|
- `createBlogRedirects` (`@softure-ai/blog/proxy`): 301 from an old slug, 410 for a withdrawn text,
|
|
27
|
-
in the app's `proxy.ts
|
|
30
|
+
in the app's `proxy.ts`; `createBlogMarkdown` answers an article or term asked for with
|
|
31
|
+
`Accept: text/markdown` with its Markdown (`toArticleMarkdown`), for agents.
|
|
28
32
|
- `@softure-ai/blog/styles.css`: the pages and the rendered body on the `--sft-*` tokens.
|
|
29
33
|
- Discovery: an RSS 2.0 feed (`serveBlogRss`), "read next" under every article (its cluster first, the
|
|
30
34
|
pillar on top), and, with `@softure-ai/seo` (optional): sitemap entries with each text's real
|
|
@@ -280,7 +284,8 @@ The quality gate resolves internal links through `quality.paths` (default `/blog
|
|
|
280
284
|
The commands:
|
|
281
285
|
|
|
282
286
|
```bash
|
|
283
|
-
softure-blog publish [<path>...] [--commit] [--withdraw] [--no-indexnow] [--app-url <origin>]
|
|
287
|
+
softure-blog publish [<path>...] [--commit] [--withdraw] [--no-indexnow] [--app-url <origin>]
|
|
288
|
+
[--stdin [--name <slug>.md]] [--history <file.json>] [--format text|lines] [--config <file>]
|
|
284
289
|
softure-blog check [<path>...] [--external] [--today <YYYY-MM-DD>] [--config <file>]
|
|
285
290
|
softure-blog skill install [--dir <path>] [--command <cmd>] [--check] [--config <file>]
|
|
286
291
|
```
|
|
@@ -300,6 +305,13 @@ softure-blog skill install [--dir <path>] [--command <cmd>] [--check] [--config
|
|
|
300
305
|
listing or the glossary of its kind) as canonical URLs on seo's origin; a dry run prints them;
|
|
301
306
|
`--no-indexnow` skips the submit (e.g. a local or CI database). A failed submit is a warning: the
|
|
302
307
|
publish stays written and the exit code stays 0.
|
|
308
|
+
- `--stdin` reads the files from standard input instead of paths: with `--name <slug>.md`, the one file's
|
|
309
|
+
text; without it, a JSON bundle `{"files":[{"name":"<slug>.md","text":"…"}]}` (a whole folder, and
|
|
310
|
+
optionally `"history"`, the content of a `--history` file, so a container needs no file for it). A
|
|
311
|
+
release that publishes inside a container through an ssh gateway pipes the content in, so the image
|
|
312
|
+
needs no copy of `content/`. `--stdin` takes no paths; `--withdraw` with it needs `--name`.
|
|
313
|
+
- `--history <file.json>` imports the earlier life of an existing blog (see "Moving an existing blog in").
|
|
314
|
+
- `--format lines` prints the line contract below instead of the text for people.
|
|
303
315
|
- Exit codes: 0 done, 1 refused or failed (nothing written), 2 usage error.
|
|
304
316
|
|
|
305
317
|
Output, one line per text, then a summary:
|
|
@@ -312,6 +324,71 @@ summary: added 1, changed 1, unchanged 12
|
|
|
312
324
|
dry run: nothing written; pass --commit to write
|
|
313
325
|
```
|
|
314
326
|
|
|
327
|
+
**The line contract** (`--format lines`), for a release script that greps the output. Every line goes to
|
|
328
|
+
standard output and starts `blog|`; fields are separated by `|`, and a `|` or a line break inside a value
|
|
329
|
+
becomes a space. Keys are stable: a new one may be added, an existing one never changes meaning. A run
|
|
330
|
+
prints exactly one outcome line: `blog|written`, `blog|dry-run`, `blog|refused` or `blog|failed|<message>`.
|
|
331
|
+
|
|
332
|
+
```text
|
|
333
|
+
blog|warning|<subject>|<message>
|
|
334
|
+
blog|error|<subject>|<message>
|
|
335
|
+
blog|change|<added|changed|unchanged>|<id>|<status/slug before, or none>|<status/slug after>
|
|
336
|
+
blog|moved|<id>|<old slug>|<new slug>
|
|
337
|
+
blog|imported|<id>|<published_at ISO, or none>|<old slugs>
|
|
338
|
+
blog|summary|<added>|<changed>|<unchanged>
|
|
339
|
+
blog|written
|
|
340
|
+
blog|cache|off|<revalidateSeconds> | skipped | dry-run|<url> | refreshed|<url> | failed|<code>|<reason>
|
|
341
|
+
blog|indexnow|off|<reason> | skipped | dry-run|<count>|<urls> | submitted|<count>|<status>|<paths> | failed|<code>|<reason>|<paths>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# A release step: the content goes in on stdin, the contract comes back.
|
|
346
|
+
node -e 'const fs = require("fs"); const dir = "content/blog";
|
|
347
|
+
const files = fs.readdirSync(dir).filter((n) => n.endsWith(".md") && n !== "README.md").sort()
|
|
348
|
+
.map((name) => ({ name, text: fs.readFileSync(`${dir}/${name}`, "utf8") }));
|
|
349
|
+
process.stdout.write(JSON.stringify({ files }))' > bundle.json
|
|
350
|
+
OUT="$(ssh deploy@host 'docker compose exec -T app node blog.cjs publish --stdin --commit --format lines' < bundle.json)"
|
|
351
|
+
grep -q '^blog|written$' <<< "$OUT" || { grep '^blog|\(error\|failed\)' <<< "$OUT"; exit 1; }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Moving an existing blog in.** An app that already published its texts from its own tables keeps their
|
|
355
|
+
`published_at` (often only in its database, not in the files), their `updated_at` and their old slugs
|
|
356
|
+
with `--history <file.json>`. On the first publish of each article (no row in `blog.articles` yet), the
|
|
357
|
+
article takes `published_at` from the history unless its file sets one, `updated_at` from the history,
|
|
358
|
+
and its old slugs enter `blog.slug_history` (301s keep working); the run prints `imported` for it. An
|
|
359
|
+
article that already has a row ignores its entry, so the same file can stay in a release script; an
|
|
360
|
+
entry for an article outside the run is a warning. An old slug another article holds refuses the run.
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"articles": [
|
|
365
|
+
{
|
|
366
|
+
"id": "index-funds",
|
|
367
|
+
"published_at": "2026-03-01T08:00:00+01:00",
|
|
368
|
+
"updated_at": "2026-06-15T10:30:00Z",
|
|
369
|
+
"old_slugs": [{ "slug": "what-is-an-index-fund", "changed_at": "2026-04-01T00:00:00Z" }]
|
|
370
|
+
},
|
|
371
|
+
{ "id": "draft-text", "published_at": null }
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`published_at` is required (`null` for a text never published), `updated_at` needs it, ids and slugs are
|
|
377
|
+
kebab-case, each article and old slug appears once. Timestamps keep millisecond precision. One query over
|
|
378
|
+
an app's own tables (here `blog_articles(id, published_at, updated_at)` and
|
|
379
|
+
`blog_slug_history(old_slug, article_id, changed_at)`) writes the file:
|
|
380
|
+
|
|
381
|
+
```sql
|
|
382
|
+
\copy (SELECT json_build_object('articles', coalesce(json_agg(json_build_object(
|
|
383
|
+
'id', a.id, 'published_at', a.published_at, 'updated_at', a.updated_at,
|
|
384
|
+
'old_slugs', coalesce((SELECT json_agg(json_build_object('slug', h.old_slug, 'changed_at', h.changed_at))
|
|
385
|
+
FROM blog_slug_history h WHERE h.article_id = a.id), '[]'::json))), '[]'::json))
|
|
386
|
+
FROM blog_articles a) TO 'history.json'
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`publishArticle(ctx, input, { history })` and `runBlogPublish(ctx, files, { history })` (with
|
|
390
|
+
`parseArticleHistory(json)`) are the same import without the command line.
|
|
391
|
+
|
|
315
392
|
Like `softure migrate`, the bin loads `softure.config.(ts|mts|js|mjs)` with Node and opens
|
|
316
393
|
`database.handle` when the config sets one, otherwise `database.url`. When Node cannot load the config (path aliases, a bundled container), call
|
|
317
394
|
`runBlogCli` from an app script:
|
|
@@ -459,9 +536,56 @@ const chartBlock: BlockPlugin = {
|
|
|
459
536
|
|
|
460
537
|
Plugin output is the app's own code and is trusted as is: escape what goes into its HTML. A plugin
|
|
461
538
|
that throws fails the render (a bug, not content). Without the plugin the same fence renders as a code
|
|
462
|
-
block. `findArticleBlocks(markdown, plugins)` lists the blocks a text uses with their line
|
|
463
|
-
`requires`, without rendering. When any block returns a node, `html` is `null`: render
|
|
464
|
-
order (`html` segments as HTML, `node` segments as they are).
|
|
539
|
+
block. `findArticleBlocks(markdown, plugins)` lists the blocks a text uses with their line, syntax,
|
|
540
|
+
attributes and `requires`, without rendering. When any block returns a node, `html` is `null`: render
|
|
541
|
+
`segments` in order (`html` segments as HTML, `node` segments as they are).
|
|
542
|
+
|
|
543
|
+
**Directive plugins.** A plugin with `syntax: "directive"` renders a top-level line `::name{…}` instead
|
|
544
|
+
of a fence, the way many Markdown blogs embed a chart or a tool:
|
|
545
|
+
|
|
546
|
+
```md
|
|
547
|
+
Paragraph before.
|
|
548
|
+
::chart{type="wealth" scenario="w=35&d=300000" title="Your wealth"}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
const chartDirective: BlockPlugin = {
|
|
553
|
+
type: "chart",
|
|
554
|
+
syntax: "directive",
|
|
555
|
+
requires: ["current_as_of"],
|
|
556
|
+
render: ({ attributes, article }) =>
|
|
557
|
+
attributes === null
|
|
558
|
+
? { kind: "html", html: '<figure class="chart-error">…</figure>' }
|
|
559
|
+
: { kind: "html", html: renderChart(attributes, article) },
|
|
560
|
+
markdown: ({ attributes }) => chartAsTable(attributes), // optional, for Accept: text/markdown
|
|
561
|
+
};
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
- The line stands alone (it may follow a paragraph line directly); the braces are optional; values are
|
|
565
|
+
double-quoted and hold no `"`; each key appears once. `attributes` holds them, `info` the raw text
|
|
566
|
+
inside the braces, `content` the whole line. Attributes that cannot be read reach the plugin as `null`
|
|
567
|
+
(show an error frame) and the gate refuses them.
|
|
568
|
+
- Only top-level lines count: a directive in a list, a quote, a fence or indented code stays text, and
|
|
569
|
+
so does a name no directive plugin registers. One type may have a fence plugin and a directive plugin.
|
|
570
|
+
- Register the same plugins in `quality.blocks`: the gate reports a directive's missing `requires`, and
|
|
571
|
+
with any directive plugin registered `block-directive` reports a `::name` line no plugin renders (a
|
|
572
|
+
typo would show as a paragraph) or one whose attributes cannot be read.
|
|
573
|
+
- `parseDirectiveLine` and `parseDirectiveAttributes` (`@softure-ai/blog/server`) are the parser.
|
|
574
|
+
|
|
575
|
+
**Markdown for agents.** `createBlogMarkdown(config)` (`@softure-ai/blog/proxy`) answers a GET or HEAD
|
|
576
|
+
of a published article or term whose `Accept` names `text/markdown` with at least the weight of
|
|
577
|
+
`text/html` (never `*/*`: browsers and crawlers keep the page) with `toArticleMarkdown(article)`: the
|
|
578
|
+
title, description, the day the facts were checked, the summary, the stored body, sources and FAQ.
|
|
579
|
+
Plugin blocks keep their source unless the plugin has `markdown`. The answer carries `Vary: Accept` and
|
|
580
|
+
`cache-control: private`, so a shared cache never hands Markdown to a browser. Put it before the redirects:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
const blogMarkdown = createBlogMarkdown(softureConfig);
|
|
584
|
+
const blogRedirects = createBlogRedirects(softureConfig);
|
|
585
|
+
export async function proxy(request: NextRequest) {
|
|
586
|
+
return (await blogMarkdown(request)) ?? (await blogRedirects(request)) ?? NextResponse.next();
|
|
587
|
+
}
|
|
588
|
+
```
|
|
465
589
|
|
|
466
590
|
## 5. Migrations and tables
|
|
467
591
|
|
|
@@ -567,8 +691,8 @@ export const tickerPlugin: QualityPlugin = {
|
|
|
567
691
|
plugins in `quality.blocks` (type, info, fence line, `requires`), `today` and the language `ruleset` (for
|
|
568
692
|
its number notation); the text helpers (`toProse`, `splitSentences`, `findSignificantNumbers`, …) are
|
|
569
693
|
exported from `@softure-ai/blog/server`.
|
|
570
|
-
- `renderArticle({ blocks })` and `blog({ blocks })`: block plugins for the app's fenced blocks
|
|
571
|
-
(
|
|
694
|
+
- `renderArticle({ blocks })` and `blog({ blocks })`: block plugins for the app's fenced blocks and
|
|
695
|
+
`::directive` lines (an engine chart), with an optional Markdown form for agents.
|
|
572
696
|
- The pages' `cta` and `afterArticle` slots: the app's call to action and blocks (a waitlist form).
|
|
573
697
|
|
|
574
698
|
## 11. GDPR
|
|
@@ -580,13 +704,14 @@ Articles hold editorial content, no personal data: nothing to export or delete.
|
|
|
580
704
|
- Two runs that rename one article away from a slug and give it to another at the same moment can leave the
|
|
581
705
|
slug both current and in the slug history (BF-12).
|
|
582
706
|
- The content hash is part of the contract: a field added later enters it only when present.
|
|
583
|
-
- No `--stdin` (a deploy transport).
|
|
584
707
|
- The refresh route expires the cache of the instance that answers it. With several instances and Next's
|
|
585
708
|
default (in-memory) cache handler, the others show a publish after `revalidateSeconds`; a shared cache
|
|
586
709
|
handler covers them.
|
|
587
710
|
- The renderer has no raw HTML and no figures: an image has no caption, and the app hosts and sizes its
|
|
588
711
|
images itself (no `next/image`). A plugin fence inside a list or a quote stays a code
|
|
589
|
-
block (a block node cannot sit inside a list's HTML)
|
|
712
|
+
block (a block node cannot sit inside a list's HTML), and a `::directive` there stays text; the gate
|
|
713
|
+
does not report a registered directive in such a place.
|
|
714
|
+
- Only leaf directives (`::name{…}`, one line): no container (`:::name … :::`) or inline (`:name[…]`) ones.
|
|
590
715
|
- The gate reads Markdown line by line (blocks, not a syntax tree): enough for the rules, not a
|
|
591
716
|
renderer. Fenced code and HTML comments are skipped.
|
|
592
717
|
- **Adopting FIRE_TRACKER's gate:** `language: "pl"`, `ymyl: { ownCalculationMark }` with its calculation
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BlogRefreshOutcome } from "../discovery/refresh.js";
|
|
2
|
+
import type { BlogIndexNowSubmit } from "../discovery/submit.js";
|
|
3
|
+
import type { BlogPublishRun } from "../db/publish-run.js";
|
|
4
|
+
export interface CliOutput {
|
|
5
|
+
readonly log: (line: string) => void;
|
|
6
|
+
readonly error: (line: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export type PublishFormat = "text" | "lines";
|
|
9
|
+
export interface PublishReporter {
|
|
10
|
+
/** A problem before or outside the run (a file it cannot read, the database); the run's outcome. */
|
|
11
|
+
readonly failed: (message: string) => void;
|
|
12
|
+
readonly run: (run: BlogPublishRun) => void;
|
|
13
|
+
readonly refresh: (outcome: BlogRefreshOutcome) => void;
|
|
14
|
+
readonly indexNow: (submit: BlogIndexNowSubmit) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function createPublishReporter(format: PublishFormat, output: CliOutput): PublishReporter;
|
|
17
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/cli/report.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,sBAAsB,CAAC;AAE5F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,GAAG,eAAe,CAE/F"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { BLOG_REFRESH_SECRET_ENV } from "../discovery/refresh.js";
|
|
2
|
+
export function createPublishReporter(format, output) {
|
|
3
|
+
return format === "lines" ? createLineReporter(output) : createTextReporter(output);
|
|
4
|
+
}
|
|
5
|
+
function countActions(run) {
|
|
6
|
+
const count = (action) => run.changes.filter((change) => change.action === action).length;
|
|
7
|
+
return { added: count("added"), changed: count("changed"), unchanged: count("unchanged") };
|
|
8
|
+
}
|
|
9
|
+
function formatBefore(change) {
|
|
10
|
+
return change.statusBefore === null || change.slugBefore === null ? "none" : `${change.statusBefore}/${change.slugBefore}`;
|
|
11
|
+
}
|
|
12
|
+
function formatDate(date) {
|
|
13
|
+
return date === null ? "none" : date.toISOString();
|
|
14
|
+
}
|
|
15
|
+
function createTextReporter(output) {
|
|
16
|
+
const formatProblem = (problem) => `${problem.subject}: ${problem.message}`;
|
|
17
|
+
return {
|
|
18
|
+
failed: (message) => output.error(`softure-blog publish: ${message}`),
|
|
19
|
+
run: (run) => {
|
|
20
|
+
for (const warning of run.warnings)
|
|
21
|
+
output.error(`warning ${formatProblem(warning)}`);
|
|
22
|
+
if (run.status === "refused") {
|
|
23
|
+
for (const problem of run.problems)
|
|
24
|
+
output.error(`error ${formatProblem(problem)}`);
|
|
25
|
+
output.error("refused: nothing written; fix the problems above");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const change of run.changes) {
|
|
29
|
+
output.log(`${change.action} ${change.id} ${formatBefore(change)} -> ${change.statusAfter}/${change.slug}`);
|
|
30
|
+
if (change.previousSlug !== null)
|
|
31
|
+
output.log(`moved ${change.id} ${change.previousSlug} -> ${change.slug}`);
|
|
32
|
+
if (change.imported !== undefined) {
|
|
33
|
+
output.log(`imported ${change.id} published_at=${formatDate(change.imported.publishedAt)} old_slugs=${String(change.imported.oldSlugs)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const counts = countActions(run);
|
|
37
|
+
output.log(`summary: added ${String(counts.added)}, changed ${String(counts.changed)}, unchanged ${String(counts.unchanged)}`);
|
|
38
|
+
output.log(run.committed ? "written" : "dry run: nothing written; pass --commit to write");
|
|
39
|
+
},
|
|
40
|
+
refresh: (outcome) => {
|
|
41
|
+
switch (outcome.kind) {
|
|
42
|
+
case "not_configured":
|
|
43
|
+
output.log(`cache: the running app shows the change within revalidateSeconds (${String(outcome.revalidateSeconds)} s); set ${BLOG_REFRESH_SECRET_ENV} to refresh it now`);
|
|
44
|
+
return;
|
|
45
|
+
case "skipped":
|
|
46
|
+
output.log("cache: no text changed, nothing to refresh");
|
|
47
|
+
return;
|
|
48
|
+
case "dry_run":
|
|
49
|
+
output.log(`cache: dry run, a commit would refresh ${outcome.url}`);
|
|
50
|
+
return;
|
|
51
|
+
case "refreshed":
|
|
52
|
+
output.log(`cache: refreshed ${outcome.url}`);
|
|
53
|
+
return;
|
|
54
|
+
case "failed":
|
|
55
|
+
output.error(`warning cache: ${outcome.reason} (${outcome.code}); the publish is written, the app shows it within ${String(outcome.revalidateSeconds)} s`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
indexNow: ({ paths, outcome }) => {
|
|
60
|
+
switch (outcome.kind) {
|
|
61
|
+
case "not_configured":
|
|
62
|
+
output.log(`indexnow: off, ${outcome.reason}`);
|
|
63
|
+
return;
|
|
64
|
+
case "skipped":
|
|
65
|
+
output.log("indexnow: no public address changed, nothing to submit");
|
|
66
|
+
return;
|
|
67
|
+
case "dry_run":
|
|
68
|
+
output.log(`indexnow: dry run, a commit would submit ${String(outcome.urls.length)} URL(s): ${outcome.urls.join(" ")}`);
|
|
69
|
+
return;
|
|
70
|
+
case "submitted":
|
|
71
|
+
output.log(`indexnow: submitted ${String(outcome.count)} URL(s) (${String(outcome.status)}): ${paths.join(" ")}`);
|
|
72
|
+
return;
|
|
73
|
+
case "failed":
|
|
74
|
+
output.error(`warning indexnow: ${outcome.reason} (${outcome.code}); the publish is written, submit the addresses later: ${paths.join(" ")}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** A value inside a contract line: no field separator, no line break. */
|
|
81
|
+
function toField(value) {
|
|
82
|
+
return String(value).replace(/[|\r\n]+/g, " ");
|
|
83
|
+
}
|
|
84
|
+
function createLineReporter(output) {
|
|
85
|
+
// Every contract line goes to standard output, so `2>&1` is not needed to read them.
|
|
86
|
+
const line = (...fields) => output.log(["blog", ...fields.map(toField)].join("|"));
|
|
87
|
+
return {
|
|
88
|
+
failed: (message) => line("failed", message),
|
|
89
|
+
run: (run) => {
|
|
90
|
+
for (const warning of run.warnings)
|
|
91
|
+
line("warning", warning.subject, warning.message);
|
|
92
|
+
if (run.status === "refused") {
|
|
93
|
+
for (const problem of run.problems)
|
|
94
|
+
line("error", problem.subject, problem.message);
|
|
95
|
+
line("refused");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
for (const change of run.changes) {
|
|
99
|
+
line("change", change.action, change.id, formatBefore(change), `${change.statusAfter}/${change.slug}`);
|
|
100
|
+
if (change.previousSlug !== null)
|
|
101
|
+
line("moved", change.id, change.previousSlug, change.slug);
|
|
102
|
+
if (change.imported !== undefined)
|
|
103
|
+
line("imported", change.id, formatDate(change.imported.publishedAt), change.imported.oldSlugs);
|
|
104
|
+
}
|
|
105
|
+
const counts = countActions(run);
|
|
106
|
+
line("summary", counts.added, counts.changed, counts.unchanged);
|
|
107
|
+
line(run.committed ? "written" : "dry-run");
|
|
108
|
+
},
|
|
109
|
+
refresh: (outcome) => {
|
|
110
|
+
switch (outcome.kind) {
|
|
111
|
+
case "not_configured":
|
|
112
|
+
line("cache", "off", outcome.revalidateSeconds);
|
|
113
|
+
return;
|
|
114
|
+
case "skipped":
|
|
115
|
+
line("cache", "skipped");
|
|
116
|
+
return;
|
|
117
|
+
case "dry_run":
|
|
118
|
+
line("cache", "dry-run", outcome.url);
|
|
119
|
+
return;
|
|
120
|
+
case "refreshed":
|
|
121
|
+
line("cache", "refreshed", outcome.url);
|
|
122
|
+
return;
|
|
123
|
+
case "failed":
|
|
124
|
+
line("cache", "failed", outcome.code, outcome.reason);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
indexNow: ({ paths, outcome }) => {
|
|
129
|
+
switch (outcome.kind) {
|
|
130
|
+
case "not_configured":
|
|
131
|
+
line("indexnow", "off", outcome.reason);
|
|
132
|
+
return;
|
|
133
|
+
case "skipped":
|
|
134
|
+
line("indexnow", "skipped");
|
|
135
|
+
return;
|
|
136
|
+
case "dry_run":
|
|
137
|
+
line("indexnow", "dry-run", outcome.urls.length, outcome.urls.join(" "));
|
|
138
|
+
return;
|
|
139
|
+
case "submitted":
|
|
140
|
+
line("indexnow", "submitted", outcome.count, outcome.status, paths.join(" "));
|
|
141
|
+
return;
|
|
142
|
+
case "failed":
|
|
143
|
+
line("indexnow", "failed", outcome.code, outcome.reason, paths.join(" "));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/cli/report.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/run.d.ts
CHANGED
|
@@ -2,10 +2,8 @@ import { type Clock, type SoftureConfig } from "@softure-ai/core";
|
|
|
2
2
|
import { type DatabaseHandle } from "@softure-ai/db";
|
|
3
3
|
import { type PublishGate } from "../db/publish-run.js";
|
|
4
4
|
import type { FetchLike } from "../quality/external-links.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readonly error: (line: string) => void;
|
|
8
|
-
}
|
|
5
|
+
import { type CliOutput, type PublishFormat } from "./report.js";
|
|
6
|
+
export type { CliOutput };
|
|
9
7
|
export interface RunBlogCliOptions {
|
|
10
8
|
/** The app's config with `blog()` among its modules. */
|
|
11
9
|
readonly config: SoftureConfig;
|
|
@@ -29,11 +27,13 @@ export interface RunBlogCliOptions {
|
|
|
29
27
|
readonly refreshFetch?: typeof fetch;
|
|
30
28
|
/** Where `publish` reads BLOG_REFRESH_SECRET. Default: `process.env`. */
|
|
31
29
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
30
|
+
/** What `publish --stdin` reads. Default: the whole of `process.stdin`, as UTF-8. */
|
|
31
|
+
readonly readStdin?: () => Promise<string>;
|
|
32
32
|
}
|
|
33
33
|
export declare const EXIT_OK = 0;
|
|
34
34
|
export declare const EXIT_FAILED = 1;
|
|
35
35
|
export declare const EXIT_USAGE = 2;
|
|
36
|
-
export declare const BLOG_USAGE = "Usage:\n softure-blog publish [<path>...] [--commit] [--withdraw] [--no-indexnow] [--app-url <origin>]\n softure-blog check [<path>...] [--external] [--today <YYYY-MM-DD>]\n softure-blog skill install [--dir <path>] [--command <cmd>] [--check]\n\npublish Brings the blog's tables to the state of the article files. A <path> is a file or a\n folder (every *.md in it except README.md); without one, blog({ contentDir }).\n Every file is checked before the first write, and one problem writes nothing.\n --commit write the changes; without it, a dry run that shows them and writes nothing\n --withdraw publish the one given file as withdrawn, whatever its status\n --no-indexnow do not submit the changed addresses to IndexNow\n --app-url the running app's origin for the cache refresh; default: appOrigin\n Files going public pass the quality gate first; an error writes nothing.\n With BLOG_REFRESH_SECRET set, a commit that changed a text asks the running\n app (refreshBlogCache) to refresh its blog cache, before the IndexNow submit.\n With seo({ indexNow }) enabled, a commit submits the addresses whose answer\n changed (the text, its old slug, its listing) to IndexNow; a dry run prints them.\n\ncheck Runs the quality gate of blog({ quality }) over the files, without a database, and\n prints every finding as file:line: severity [rule] message. Exits 1 on any error.\n --external also request every external link (2xx after redirects)\n --today the date to check freshness against; default: today in the app's time zone\n\nskill install\n Writes the article writing skill, filled from blog({ quality, skill }),\n into .claude/skills/blog-write. Overwrites only a skill it generated before.\n --dir the skill folder; default .claude/skills/blog-write\n --command how the skill runs this command; default \"npx softure-blog\"\n --check write nothing; exit 1 when the folder differs from what install would write\n\nOptions:\n --config <file> the app's softure.config file (bin only)\n --help show this help";
|
|
36
|
+
export declare const BLOG_USAGE = "Usage:\n softure-blog publish [<path>...] [--commit] [--withdraw] [--no-indexnow] [--app-url <origin>]\n [--stdin [--name <slug>.md]] [--history <file.json>] [--format text|lines]\n softure-blog check [<path>...] [--external] [--today <YYYY-MM-DD>]\n softure-blog skill install [--dir <path>] [--command <cmd>] [--check]\n\npublish Brings the blog's tables to the state of the article files. A <path> is a file or a\n folder (every *.md in it except README.md); without one, blog({ contentDir }).\n Every file is checked before the first write, and one problem writes nothing.\n --commit write the changes; without it, a dry run that shows them and writes nothing\n --withdraw publish the one given file as withdrawn, whatever its status\n --no-indexnow do not submit the changed addresses to IndexNow\n --app-url the running app's origin for the cache refresh; default: appOrigin\n --stdin read the files from standard input instead of paths: one file named by\n --name, or without --name a JSON bundle {\"files\":[{\"name\",\"text\"}]}\n (optionally with \"history\", the content of a --history file)\n --history a JSON file of the articles' earlier dates and old slugs, applied on the\n first publish of each article (moving an existing blog in)\n --format text (default) or lines: a stable blog|<key>|... contract for scripts\n Files going public pass the quality gate first; an error writes nothing.\n With BLOG_REFRESH_SECRET set, a commit that changed a text asks the running\n app (refreshBlogCache) to refresh its blog cache, before the IndexNow submit.\n With seo({ indexNow }) enabled, a commit submits the addresses whose answer\n changed (the text, its old slug, its listing) to IndexNow; a dry run prints them.\n\ncheck Runs the quality gate of blog({ quality }) over the files, without a database, and\n prints every finding as file:line: severity [rule] message. Exits 1 on any error.\n --external also request every external link (2xx after redirects)\n --today the date to check freshness against; default: today in the app's time zone\n\nskill install\n Writes the article writing skill, filled from blog({ quality, skill }),\n into .claude/skills/blog-write. Overwrites only a skill it generated before.\n --dir the skill folder; default .claude/skills/blog-write\n --command how the skill runs this command; default \"npx softure-blog\"\n --check write nothing; exit 1 when the folder differs from what install would write\n\nOptions:\n --config <file> the app's softure.config file (bin only)\n --help show this help";
|
|
37
37
|
export type BlogCommand = {
|
|
38
38
|
readonly kind: "help";
|
|
39
39
|
} | {
|
|
@@ -44,6 +44,13 @@ export type BlogCommand = {
|
|
|
44
44
|
readonly indexNow: boolean;
|
|
45
45
|
/** The origin `--app-url` gives; `null`: `appOrigin`. */
|
|
46
46
|
readonly appUrl: string | null;
|
|
47
|
+
/** Read the files from standard input; `name`: one file, `null`: a JSON bundle. */
|
|
48
|
+
readonly stdin: {
|
|
49
|
+
readonly name: string | null;
|
|
50
|
+
} | null;
|
|
51
|
+
/** The `--history` file, as given. */
|
|
52
|
+
readonly history: string | null;
|
|
53
|
+
readonly format: PublishFormat;
|
|
47
54
|
} | {
|
|
48
55
|
readonly kind: "check";
|
|
49
56
|
readonly paths: readonly string[];
|
package/dist/cli/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAWA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,aAAa,EAA8B,MAAM,kBAAkB,CAAC;AAC3G,OAAO,EAA6C,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
|