@softure-ai/blog 0.0.0-stage → 0.1.6
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 +10 -0
- package/LICENSE +21 -0
- package/README.md +604 -2
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +5 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/command.d.ts +9 -0
- package/dist/cli/command.d.ts.map +1 -0
- package/dist/cli/command.js +40 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +62 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +555 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/skill.d.ts +47 -0
- package/dist/cli/skill.d.ts.map +1 -0
- package/dist/cli/skill.js +221 -0
- package/dist/cli/skill.js.map +1 -0
- package/dist/content/article-file.d.ts +27 -0
- package/dist/content/article-file.d.ts.map +1 -0
- package/dist/content/article-file.js +179 -0
- package/dist/content/article-file.js.map +1 -0
- package/dist/contract.d.ts +77 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +5 -0
- package/dist/contract.js.map +1 -0
- package/dist/db/articles.d.ts +30 -0
- package/dist/db/articles.d.ts.map +1 -0
- package/dist/db/articles.js +138 -0
- package/dist/db/articles.js.map +1 -0
- package/dist/db/publish-run.d.ts +50 -0
- package/dist/db/publish-run.d.ts.map +1 -0
- package/dist/db/publish-run.js +168 -0
- package/dist/db/publish-run.js.map +1 -0
- package/dist/db/schema.d.ts +403 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +33 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/discovery/dates.d.ts +9 -0
- package/dist/discovery/dates.d.ts.map +1 -0
- package/dist/discovery/dates.js +19 -0
- package/dist/discovery/dates.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +11 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/indexnow.d.ts +11 -0
- package/dist/discovery/indexnow.d.ts.map +1 -0
- package/dist/discovery/indexnow.js +23 -0
- package/dist/discovery/indexnow.js.map +1 -0
- package/dist/discovery/refresh.d.ts +49 -0
- package/dist/discovery/refresh.d.ts.map +1 -0
- package/dist/discovery/refresh.js +55 -0
- package/dist/discovery/refresh.js.map +1 -0
- package/dist/discovery/related.d.ts +17 -0
- package/dist/discovery/related.d.ts.map +1 -0
- package/dist/discovery/related.js +28 -0
- package/dist/discovery/related.js.map +1 -0
- package/dist/discovery/rss.d.ts +30 -0
- package/dist/discovery/rss.d.ts.map +1 -0
- package/dist/discovery/rss.js +48 -0
- package/dist/discovery/rss.js.map +1 -0
- package/dist/discovery/sitemap.d.ts +25 -0
- package/dist/discovery/sitemap.d.ts.map +1 -0
- package/dist/discovery/sitemap.js +31 -0
- package/dist/discovery/sitemap.js.map +1 -0
- package/dist/discovery/submit.d.ts +44 -0
- package/dist/discovery/submit.d.ts.map +1 -0
- package/dist/discovery/submit.js +46 -0
- package/dist/discovery/submit.js.map +1 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/en.d.ts +83 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +83 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/index.d.ts +158 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +5 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/pl.d.ts +78 -0
- package/dist/messages/pl.d.ts.map +1 -0
- package/dist/messages/pl.js +78 -0
- package/dist/messages/pl.js.map +1 -0
- package/dist/next/context.d.ts +6 -0
- package/dist/next/context.d.ts.map +1 -0
- package/dist/next/context.js +27 -0
- package/dist/next/context.js.map +1 -0
- package/dist/next/data.d.ts +11 -0
- package/dist/next/data.d.ts.map +1 -0
- package/dist/next/data.js +40 -0
- package/dist/next/data.js.map +1 -0
- package/dist/next/discovery.d.ts +6 -0
- package/dist/next/discovery.d.ts.map +1 -0
- package/dist/next/discovery.js +49 -0
- package/dist/next/discovery.js.map +1 -0
- package/dist/next/index.d.ts +9 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +10 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/og-fonts.d.ts +6 -0
- package/dist/next/og-fonts.d.ts.map +1 -0
- package/dist/next/og-fonts.js +8 -0
- package/dist/next/og-fonts.js.map +1 -0
- package/dist/next/og-image.d.ts +36 -0
- package/dist/next/og-image.d.ts.map +1 -0
- package/dist/next/og-image.js +77 -0
- package/dist/next/og-image.js.map +1 -0
- package/dist/next/pages.d.ts +45 -0
- package/dist/next/pages.d.ts.map +1 -0
- package/dist/next/pages.js +189 -0
- package/dist/next/pages.js.map +1 -0
- package/dist/next/refresh.d.ts +9 -0
- package/dist/next/refresh.d.ts.map +1 -0
- package/dist/next/refresh.js +81 -0
- package/dist/next/refresh.js.map +1 -0
- package/dist/options.d.ts +320 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +184 -0
- package/dist/options.js.map +1 -0
- package/dist/pages/body.d.ts +18 -0
- package/dist/pages/body.d.ts.map +1 -0
- package/dist/pages/body.js +21 -0
- package/dist/pages/body.js.map +1 -0
- package/dist/pages/dates.d.ts +21 -0
- package/dist/pages/dates.d.ts.map +1 -0
- package/dist/pages/dates.js +24 -0
- package/dist/pages/dates.js.map +1 -0
- package/dist/pages/index.d.ts +7 -0
- package/dist/pages/index.d.ts.map +1 -0
- package/dist/pages/index.js +9 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/json-ld.d.ts +33 -0
- package/dist/pages/json-ld.d.ts.map +1 -0
- package/dist/pages/json-ld.js +95 -0
- package/dist/pages/json-ld.js.map +1 -0
- package/dist/pages/listing.d.ts +46 -0
- package/dist/pages/listing.d.ts.map +1 -0
- package/dist/pages/listing.js +57 -0
- package/dist/pages/listing.js.map +1 -0
- package/dist/pages/paths.d.ts +37 -0
- package/dist/pages/paths.d.ts.map +1 -0
- package/dist/pages/paths.js +61 -0
- package/dist/pages/paths.js.map +1 -0
- package/dist/pages/redirects.d.ts +57 -0
- package/dist/pages/redirects.d.ts.map +1 -0
- package/dist/pages/redirects.js +78 -0
- package/dist/pages/redirects.js.map +1 -0
- package/dist/proxy/index.d.ts +15 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +60 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/quality/blocks.d.ts +21 -0
- package/dist/quality/blocks.d.ts.map +1 -0
- package/dist/quality/blocks.js +113 -0
- package/dist/quality/blocks.js.map +1 -0
- package/dist/quality/catalog.d.ts +10 -0
- package/dist/quality/catalog.d.ts.map +1 -0
- package/dist/quality/catalog.js +71 -0
- package/dist/quality/catalog.js.map +1 -0
- package/dist/quality/check-article.d.ts +34 -0
- package/dist/quality/check-article.d.ts.map +1 -0
- package/dist/quality/check-article.js +74 -0
- package/dist/quality/check-article.js.map +1 -0
- package/dist/quality/check-files.d.ts +23 -0
- package/dist/quality/check-files.d.ts.map +1 -0
- package/dist/quality/check-files.js +44 -0
- package/dist/quality/check-files.js.map +1 -0
- package/dist/quality/external-links.d.ts +19 -0
- package/dist/quality/external-links.d.ts.map +1 -0
- package/dist/quality/external-links.js +30 -0
- package/dist/quality/external-links.js.map +1 -0
- package/dist/quality/finding.d.ts +17 -0
- package/dist/quality/finding.d.ts.map +1 -0
- package/dist/quality/finding.js +15 -0
- package/dist/quality/finding.js.map +1 -0
- package/dist/quality/gate.d.ts +5 -0
- package/dist/quality/gate.d.ts.map +1 -0
- package/dist/quality/gate.js +10 -0
- package/dist/quality/gate.js.map +1 -0
- package/dist/quality/index.d.ts +15 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +16 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/link-targets.d.ts +44 -0
- package/dist/quality/link-targets.d.ts.map +1 -0
- package/dist/quality/link-targets.js +91 -0
- package/dist/quality/link-targets.js.map +1 -0
- package/dist/quality/options.d.ts +113 -0
- package/dist/quality/options.d.ts.map +1 -0
- package/dist/quality/options.js +93 -0
- package/dist/quality/options.js.map +1 -0
- package/dist/quality/plugin.d.ts +34 -0
- package/dist/quality/plugin.d.ts.map +1 -0
- package/dist/quality/plugin.js +7 -0
- package/dist/quality/plugin.js.map +1 -0
- package/dist/quality/rules/blocks.d.ts +5 -0
- package/dist/quality/rules/blocks.d.ts.map +1 -0
- package/dist/quality/rules/blocks.js +35 -0
- package/dist/quality/rules/blocks.js.map +1 -0
- package/dist/quality/rules/images.d.ts +4 -0
- package/dist/quality/rules/images.d.ts.map +1 -0
- package/dist/quality/rules/images.js +33 -0
- package/dist/quality/rules/images.js.map +1 -0
- package/dist/quality/rules/input.d.ts +11 -0
- package/dist/quality/rules/input.d.ts.map +1 -0
- package/dist/quality/rules/input.js +2 -0
- package/dist/quality/rules/input.js.map +1 -0
- package/dist/quality/rules/links.d.ts +17 -0
- package/dist/quality/rules/links.d.ts.map +1 -0
- package/dist/quality/rules/links.js +46 -0
- package/dist/quality/rules/links.js.map +1 -0
- package/dist/quality/rules/structure.d.ts +13 -0
- package/dist/quality/rules/structure.d.ts.map +1 -0
- package/dist/quality/rules/structure.js +139 -0
- package/dist/quality/rules/structure.js.map +1 -0
- package/dist/quality/rules/style.d.ts +11 -0
- package/dist/quality/rules/style.d.ts.map +1 -0
- package/dist/quality/rules/style.js +129 -0
- package/dist/quality/rules/style.js.map +1 -0
- package/dist/quality/rules/ymyl.d.ts +5 -0
- package/dist/quality/rules/ymyl.d.ts.map +1 -0
- package/dist/quality/rules/ymyl.js +51 -0
- package/dist/quality/rules/ymyl.js.map +1 -0
- package/dist/quality/rulesets/en/ruleset.d.ts +3 -0
- package/dist/quality/rulesets/en/ruleset.d.ts.map +1 -0
- package/dist/quality/rulesets/en/ruleset.js +106 -0
- package/dist/quality/rulesets/en/ruleset.js.map +1 -0
- package/dist/quality/rulesets/index.d.ts +7 -0
- package/dist/quality/rulesets/index.d.ts.map +1 -0
- package/dist/quality/rulesets/index.js +7 -0
- package/dist/quality/rulesets/index.js.map +1 -0
- package/dist/quality/rulesets/pl/ruleset.d.ts +3 -0
- package/dist/quality/rulesets/pl/ruleset.d.ts.map +1 -0
- package/dist/quality/rulesets/pl/ruleset.js +114 -0
- package/dist/quality/rulesets/pl/ruleset.js.map +1 -0
- package/dist/quality/rulesets/types.d.ts +28 -0
- package/dist/quality/rulesets/types.d.ts.map +1 -0
- package/dist/quality/rulesets/types.js +2 -0
- package/dist/quality/rulesets/types.js.map +1 -0
- package/dist/quality/settings.d.ts +26 -0
- package/dist/quality/settings.d.ts.map +1 -0
- package/dist/quality/settings.js +32 -0
- package/dist/quality/settings.js.map +1 -0
- package/dist/quality/text.d.ts +43 -0
- package/dist/quality/text.d.ts.map +1 -0
- package/dist/quality/text.js +85 -0
- package/dist/quality/text.js.map +1 -0
- package/dist/render/glossary.d.ts +27 -0
- package/dist/render/glossary.d.ts.map +1 -0
- package/dist/render/glossary.js +71 -0
- package/dist/render/glossary.js.map +1 -0
- package/dist/render/images.d.ts +42 -0
- package/dist/render/images.d.ts.map +1 -0
- package/dist/render/images.js +84 -0
- package/dist/render/images.js.map +1 -0
- package/dist/render/index.d.ts +6 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/reading-time.d.ts +8 -0
- package/dist/render/reading-time.d.ts.map +1 -0
- package/dist/render/reading-time.js +17 -0
- package/dist/render/reading-time.js.map +1 -0
- package/dist/render/render-article.d.ts +97 -0
- package/dist/render/render-article.d.ts.map +1 -0
- package/dist/render/render-article.js +373 -0
- package/dist/render/render-article.js.map +1 -0
- package/dist/render/slugify-heading.d.ts +2 -0
- package/dist/render/slugify-heading.d.ts.map +1 -0
- package/dist/render/slugify-heading.js +21 -0
- package/dist/render/slugify-heading.js.map +1 -0
- package/dist/server/health.d.ts +3 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +11 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +13 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/og-fonts.d.ts +24 -0
- package/dist/server/og-fonts.d.ts.map +1 -0
- package/dist/server/og-fonts.js +85 -0
- package/dist/server/og-fonts.js.map +1 -0
- package/dist/server/options.d.ts +17 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +56 -0
- package/dist/server/options.js.map +1 -0
- package/dist/sitemap.d.ts +11 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +35 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/ui/blog-article.d.ts +49 -0
- package/dist/ui/blog-article.d.ts.map +1 -0
- package/dist/ui/blog-article.js +45 -0
- package/dist/ui/blog-article.js.map +1 -0
- package/dist/ui/blog-glossary.d.ts +27 -0
- package/dist/ui/blog-glossary.d.ts.map +1 -0
- package/dist/ui/blog-glossary.js +20 -0
- package/dist/ui/blog-glossary.js.map +1 -0
- package/dist/ui/blog-layout.d.ts +31 -0
- package/dist/ui/blog-layout.d.ts.map +1 -0
- package/dist/ui/blog-layout.js +25 -0
- package/dist/ui/blog-layout.js.map +1 -0
- package/dist/ui/blog-listing.d.ts +15 -0
- package/dist/ui/blog-listing.d.ts.map +1 -0
- package/dist/ui/blog-listing.js +29 -0
- package/dist/ui/blog-listing.js.map +1 -0
- package/dist/ui/blog-method.d.ts +5 -0
- package/dist/ui/blog-method.d.ts.map +1 -0
- package/dist/ui/blog-method.js +21 -0
- package/dist/ui/blog-method.js.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/page-context.d.ts +15 -0
- package/dist/ui/page-context.d.ts.map +1 -0
- package/dist/ui/page-context.js +2 -0
- package/dist/ui/page-context.js.map +1 -0
- package/migrations/0001_create_articles.sql +67 -0
- package/migrations/README.md +8 -0
- package/module.json +28 -0
- package/package.json +102 -4
- package/skill/SKILL.md +72 -0
- package/skill/references/reviewer.md +39 -0
- package/skill/references/rules.md +122 -0
- package/skill/references/structure.md +69 -0
- package/skill/references/template.md +86 -0
- package/src/cli/bin.ts +5 -0
- package/src/cli/command.ts +49 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/run.ts +591 -0
- package/src/cli/skill.ts +242 -0
- package/src/content/article-file.ts +184 -0
- package/src/contract.ts +88 -0
- package/src/db/articles.ts +160 -0
- package/src/db/publish-run.ts +224 -0
- package/src/db/schema.ts +36 -0
- package/src/discovery/dates.ts +27 -0
- package/src/discovery/index.ts +18 -0
- package/src/discovery/indexnow.ts +25 -0
- package/src/discovery/refresh.ts +79 -0
- package/src/discovery/related.ts +35 -0
- package/src/discovery/rss.ts +77 -0
- package/src/discovery/sitemap.ts +59 -0
- package/src/discovery/submit.ts +69 -0
- package/src/index.ts +99 -0
- package/src/messages/en.ts +82 -0
- package/src/messages/index.ts +7 -0
- package/src/messages/pl.ts +77 -0
- package/src/next/context.ts +30 -0
- package/src/next/data.ts +55 -0
- package/src/next/discovery.ts +49 -0
- package/src/next/index.ts +25 -0
- package/src/next/next-modules.d.ts +15 -0
- package/src/next/og-fonts.ts +14 -0
- package/src/next/og-image.tsx +108 -0
- package/src/next/pages.tsx +251 -0
- package/src/next/refresh.ts +85 -0
- package/src/options.ts +224 -0
- package/src/pages/body.ts +38 -0
- package/src/pages/dates.ts +39 -0
- package/src/pages/index.ts +37 -0
- package/src/pages/json-ld.ts +120 -0
- package/src/pages/listing.ts +95 -0
- package/src/pages/paths.ts +84 -0
- package/src/pages/redirects.ts +120 -0
- package/src/proxy/index.ts +66 -0
- package/src/quality/blocks.ts +130 -0
- package/src/quality/catalog.ts +86 -0
- package/src/quality/check-article.ts +112 -0
- package/src/quality/check-files.ts +63 -0
- package/src/quality/external-links.ts +37 -0
- package/src/quality/finding.ts +29 -0
- package/src/quality/gate.ts +15 -0
- package/src/quality/index.ts +28 -0
- package/src/quality/link-targets.ts +107 -0
- package/src/quality/options.ts +105 -0
- package/src/quality/plugin.ts +43 -0
- package/src/quality/rules/blocks.ts +43 -0
- package/src/quality/rules/images.ts +34 -0
- package/src/quality/rules/input.ts +12 -0
- package/src/quality/rules/links.ts +56 -0
- package/src/quality/rules/structure.ts +137 -0
- package/src/quality/rules/style.ts +137 -0
- package/src/quality/rules/ymyl.ts +57 -0
- package/src/quality/rulesets/en/ruleset.ts +117 -0
- package/src/quality/rulesets/index.ts +9 -0
- package/src/quality/rulesets/pl/ruleset.ts +126 -0
- package/src/quality/rulesets/types.ts +31 -0
- package/src/quality/settings.ts +60 -0
- package/src/quality/text.ts +113 -0
- package/src/render/glossary.ts +103 -0
- package/src/render/images.ts +110 -0
- package/src/render/index.ts +29 -0
- package/src/render/reading-time.ts +18 -0
- package/src/render/render-article.ts +487 -0
- package/src/render/slugify-heading.ts +22 -0
- package/src/server/health.ts +12 -0
- package/src/server/index.ts +28 -0
- package/src/server/og-fonts.ts +102 -0
- package/src/server/options.ts +62 -0
- package/src/sitemap.ts +36 -0
- package/src/ui/blog-article.tsx +186 -0
- package/src/ui/blog-glossary.tsx +104 -0
- package/src/ui/blog-layout.tsx +89 -0
- package/src/ui/blog-listing.tsx +95 -0
- package/src/ui/blog-method.tsx +34 -0
- package/src/ui/index.ts +8 -0
- package/src/ui/page-context.ts +17 -0
- package/styles.css +498 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// The social card of an article (`opengraph-image`): its title on the brand's background, the brand's
|
|
3
|
+
// name above and the blog's name below. Mount it in `app/blog/[slug]/opengraph-image.tsx`:
|
|
4
|
+
//
|
|
5
|
+
// export { BlogArticleOgImage as default, generateBlogStaticParams as generateStaticParams } from "@softure-ai/blog/next";
|
|
6
|
+
// export const size = { width: 1200, height: 630 };
|
|
7
|
+
// export const contentType = "image/png";
|
|
8
|
+
// export const revalidate = 300;
|
|
9
|
+
//
|
|
10
|
+
// A slug that is not a published article gets the blog's own card, never a 404: a crawler that
|
|
11
|
+
// fetched the page earlier would show an empty preview.
|
|
12
|
+
//
|
|
13
|
+
// The card writes in `blog({ brand: { fonts } })` when the app lists them (`next/og`'s default font
|
|
14
|
+
// otherwise): each file is read on the first card and kept, and a file that cannot be read fails the
|
|
15
|
+
// card with a message naming it. A path is read from the app's root on the Node.js runtime (the
|
|
16
|
+
// route's default); a route moved to the edge runtime takes https URLs only.
|
|
17
|
+
import { formatMessage } from "@softure-ai/core";
|
|
18
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
19
|
+
import { DEFAULT_THEME } from "@softure-ai/ui";
|
|
20
|
+
import { ImageResponse } from "next/og";
|
|
21
|
+
import { getBlogOptions } from "../server/options.js";
|
|
22
|
+
import { getPageContext } from "./context.js";
|
|
23
|
+
import { getTextBySlug } from "./data.js";
|
|
24
|
+
import { loadBrandOgFonts } from "./og-fonts.js";
|
|
25
|
+
/** The card's size: the 1.91:1 every network crops to. */
|
|
26
|
+
export const OG_IMAGE_SIZE = { width: 1200, height: 630 };
|
|
27
|
+
/** The card's colours: the brand's, else the dark scheme of @softure-ai/ui's default theme. */
|
|
28
|
+
export function getOgColors(brand) {
|
|
29
|
+
const dark = DEFAULT_THEME.dark;
|
|
30
|
+
return {
|
|
31
|
+
background: brand?.colors?.background ?? dark["color-background"],
|
|
32
|
+
foreground: brand?.colors?.foreground ?? dark["color-foreground"],
|
|
33
|
+
accent: brand?.colors?.accent ?? dark["color-accent-fill"],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The card's `font-family`: every family it is given, once each and in order, so a second family (a
|
|
38
|
+
* `latin-ext` subset file under its own name) draws the characters the first lacks.
|
|
39
|
+
*/
|
|
40
|
+
export function getOgFontFamily(fonts) {
|
|
41
|
+
const names = [...new Set(fonts.map((font) => font.name))];
|
|
42
|
+
return names.length === 0 ? undefined : names.map((name) => JSON.stringify(name)).join(", ");
|
|
43
|
+
}
|
|
44
|
+
export function renderArticleOgImage({ title, label, brand, fonts }) {
|
|
45
|
+
const colors = getOgColors(brand);
|
|
46
|
+
const fontFamily = getOgFontFamily(fonts ?? []);
|
|
47
|
+
return new ImageResponse((_jsxs("div", { style: {
|
|
48
|
+
width: "100%",
|
|
49
|
+
height: "100%",
|
|
50
|
+
display: "flex",
|
|
51
|
+
flexDirection: "column",
|
|
52
|
+
justifyContent: "space-between",
|
|
53
|
+
background: colors.background,
|
|
54
|
+
color: colors.foreground,
|
|
55
|
+
padding: "56px 72px 64px",
|
|
56
|
+
...(fontFamily === undefined ? {} : { fontFamily }),
|
|
57
|
+
}, children: [_jsx("div", { style: { display: "flex", fontSize: 34, fontWeight: 700 }, children: brand?.name ?? "" }), _jsx("div", { style: { display: "flex", fontSize: title.length > 60 ? 56 : 68, fontWeight: 700, lineHeight: 1.08 }, children: title }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 14, fontSize: 28 }, children: [_jsx("div", { style: { display: "flex", width: 12, height: 12, borderRadius: 2, background: colors.accent } }), _jsx("div", { style: { display: "flex" }, children: label })] })] })), { ...OG_IMAGE_SIZE, ...(fonts === undefined ? {} : { fonts: [...fonts] }) });
|
|
58
|
+
}
|
|
59
|
+
function getLabel(config) {
|
|
60
|
+
const context = getPageContext(config);
|
|
61
|
+
const name = context.messages.pages.blogTitle;
|
|
62
|
+
return context.brand === null ? name : formatMessage(context.messages.pages.titleWithBrand, { title: name, brand: context.brand });
|
|
63
|
+
}
|
|
64
|
+
/** The default export of an article's `opengraph-image.tsx`. */
|
|
65
|
+
export async function BlogArticleOgImage({ params }) {
|
|
66
|
+
const config = getSoftureConfig();
|
|
67
|
+
const { slug } = await params;
|
|
68
|
+
const article = await getTextBySlug(config, slug);
|
|
69
|
+
const isVisible = article !== null && article.status === "published" && article.kind === "article";
|
|
70
|
+
const label = getLabel(config);
|
|
71
|
+
const { brand } = getBlogOptions(config);
|
|
72
|
+
const fonts = await loadBrandOgFonts(brand?.fonts ?? []);
|
|
73
|
+
if (!fonts.ok)
|
|
74
|
+
throw new Error(fonts.error);
|
|
75
|
+
return renderArticleOgImage({ title: isVisible ? article.title : label, label, brand, fonts: fonts.fonts.length === 0 ? undefined : fonts.fonts });
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=og-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"og-image.js","sourceRoot":"","sources":["../../src/next/og-image.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Metadata } from "next/types.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
type SlugParams = Promise<{
|
|
4
|
+
readonly slug: string;
|
|
5
|
+
}>;
|
|
6
|
+
export interface BlogIndexPageProps {
|
|
7
|
+
/** The app's call to action under the cards. */
|
|
8
|
+
readonly cta?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export interface BlogArticlePageProps {
|
|
11
|
+
readonly params: SlugParams;
|
|
12
|
+
/** The app's call to action right after the text. */
|
|
13
|
+
readonly cta?: ReactNode;
|
|
14
|
+
/** The app's block under the article, e.g. `<Waitlist placement="blog" />`. */
|
|
15
|
+
readonly afterArticle?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export interface GlossaryTermPageProps {
|
|
18
|
+
readonly params: SlugParams;
|
|
19
|
+
readonly cta?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/** No page is built ahead: the build has no database. The first request renders a slug. */
|
|
22
|
+
export declare function generateBlogStaticParams(): {
|
|
23
|
+
slug: string;
|
|
24
|
+
}[];
|
|
25
|
+
export declare function generateBlogIndexMetadata(): Promise<Metadata>;
|
|
26
|
+
/** The listing: cards grouped by cluster, the pillar first. Mount with `dynamic = "force-dynamic"`. */
|
|
27
|
+
export declare function BlogIndexPage({ cta }?: BlogIndexPageProps): Promise<import("react").JSX.Element>;
|
|
28
|
+
export declare function generateArticleMetadata({ params }: {
|
|
29
|
+
readonly params: SlugParams;
|
|
30
|
+
}): Promise<Metadata>;
|
|
31
|
+
/** An article with "read next" under it. Mount with `revalidate` and `generateBlogStaticParams`. */
|
|
32
|
+
export declare function BlogArticlePage({ params, cta, afterArticle }: BlogArticlePageProps): Promise<import("react").JSX.Element>;
|
|
33
|
+
export declare function generateGlossaryIndexMetadata(): Promise<Metadata>;
|
|
34
|
+
/** The glossary index. Mount with `dynamic = "force-dynamic"`. */
|
|
35
|
+
export declare function GlossaryIndexPage(): Promise<import("react").JSX.Element>;
|
|
36
|
+
export declare function generateTermMetadata({ params }: {
|
|
37
|
+
readonly params: SlugParams;
|
|
38
|
+
}): Promise<Metadata>;
|
|
39
|
+
/** A glossary term with the articles that expand on it. Mount with `revalidate` and `generateBlogStaticParams`. */
|
|
40
|
+
export declare function GlossaryTermPage({ params, cta }: GlossaryTermPageProps): Promise<import("react").JSX.Element>;
|
|
41
|
+
export declare function generateMethodMetadata(): Metadata;
|
|
42
|
+
/** "How our texts are made". Mount it with `blog({ methodPage: true })`; without that it is a 404. */
|
|
43
|
+
export declare function BlogMethodPage(): import("react").JSX.Element;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=pages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAkBvC,KAAK,UAAU,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;CAC1B;AAED,2FAA2F;AAC3F,wBAAgB,wBAAwB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAE7D;AAsED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,QAAQ,CAAC,CAMnE;AAED,uGAAuG;AACvG,wBAAsB,aAAa,CAAC,EAAE,GAAG,EAAE,GAAE,kBAAuB,wCAOnE;AAED,wBAAsB,uBAAuB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAO5G;AAED,oGAAoG;AACpG,wBAAsB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,oBAAoB,wCAuBxF;AAED,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,QAAQ,CAAC,CAMvE;AAED,kEAAkE;AAClE,wBAAsB,iBAAiB,yCAMtC;AAED,wBAAsB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAOzG;AAED,mHAAmH;AACnH,wBAAsB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,qBAAqB,wCAsB5E;AAED,wBAAgB,sBAAsB,IAAI,QAAQ,CAKjD;AAED,sGAAsG;AACtG,wBAAgB,cAAc,gCAK7B"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// The blog's pages, ready to mount with one line each (docs/02-module-standard.md §8):
|
|
3
|
+
//
|
|
4
|
+
// app/blog/page.tsx export { BlogIndexPage as default, generateBlogIndexMetadata as generateMetadata } from "@softure-ai/blog/next";
|
|
5
|
+
// export const dynamic = "force-dynamic";
|
|
6
|
+
// app/blog/[slug]/page.tsx export { BlogArticlePage as default, generateArticleMetadata as generateMetadata, generateBlogStaticParams as generateStaticParams } from "@softure-ai/blog/next";
|
|
7
|
+
// export const revalidate = 300;
|
|
8
|
+
// app/blog/glossary/page.tsx GlossaryIndexPage, generateGlossaryIndexMetadata; dynamic
|
|
9
|
+
// app/blog/glossary/[slug]/page.tsx GlossaryTermPage, generateTermMetadata, generateBlogStaticParams; revalidate
|
|
10
|
+
// app/blog/how-we-write/page.tsx BlogMethodPage, generateMethodMetadata (with `blog({ methodPage: true })`)
|
|
11
|
+
// app/blog/rss.xml/route.ts serveBlogRss as GET; dynamic (see discovery.ts)
|
|
12
|
+
//
|
|
13
|
+
// Next reads `dynamic` and `revalidate` statically, so they stay literals in the app's files: the
|
|
14
|
+
// listing and the glossary index render per request (a build has no database) over cached reads;
|
|
15
|
+
// articles and terms render on their first request and are kept for `revalidate` seconds (ISR).
|
|
16
|
+
// 301 and 410 are answered before the page by `@softure-ai/blog/proxy`; anything else that is not a
|
|
17
|
+
// published text of the page's kind is a 404 here.
|
|
18
|
+
import { formatMessage, getSiteUrls } from "@softure-ai/core";
|
|
19
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
20
|
+
import { notFound } from "next/navigation";
|
|
21
|
+
import { getRelatedArticles } from "../discovery/related.js";
|
|
22
|
+
import { findArticlesLinkingTerm, renderPageBody } from "../pages/body.js";
|
|
23
|
+
import { getArticleDates } from "../pages/dates.js";
|
|
24
|
+
import { getArticleJsonLd, getGlossaryJsonLd, getTermJsonLd, serializeJsonLd } from "../pages/json-ld.js";
|
|
25
|
+
import { getArticleCrumbs, getClusterLabel, getTermCrumbs, groupByCluster, sortTerms } from "../pages/listing.js";
|
|
26
|
+
import { getArticlePath, getTermPath } from "../pages/paths.js";
|
|
27
|
+
import { toGlossary } from "../render/glossary.js";
|
|
28
|
+
import { getBlogOptions } from "../server/options.js";
|
|
29
|
+
import { BlogArticleView } from "../ui/blog-article.js";
|
|
30
|
+
import { GlossaryIndexView, GlossaryTermView } from "../ui/blog-glossary.js";
|
|
31
|
+
import { BlogListingView } from "../ui/blog-listing.js";
|
|
32
|
+
import { BlogMethodView } from "../ui/blog-method.js";
|
|
33
|
+
import { getPageContext } from "./context.js";
|
|
34
|
+
import { getPublishedArticles, getPublishedTerms, getTextBySlug } from "./data.js";
|
|
35
|
+
/** No page is built ahead: the build has no database. The first request renders a slug. */
|
|
36
|
+
export function generateBlogStaticParams() {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
function isPublished(text, kind) {
|
|
40
|
+
return text !== null && text.status === "published" && text.kind === kind;
|
|
41
|
+
}
|
|
42
|
+
function getCrumbLabels(config, context) {
|
|
43
|
+
const clusters = getBlogOptions(config).clusters;
|
|
44
|
+
return {
|
|
45
|
+
blog: context.messages.pages.blogTitle,
|
|
46
|
+
glossary: context.messages.glossary.title,
|
|
47
|
+
cluster: (cluster) => getClusterLabel(cluster, clusters, config.locale),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function getJsonLdContext(config, context) {
|
|
51
|
+
return { urls: getSiteUrls(config), routes: context.routes, locale: config.locale, timezone: config.timezone, brand: context.brand };
|
|
52
|
+
}
|
|
53
|
+
function getBodyOptions(config, context, terms) {
|
|
54
|
+
const origins = [config.appOrigin, getSiteUrls(config).origin];
|
|
55
|
+
return { glossary: toGlossary(terms), routes: context.routes, options: getBlogOptions(config), origins, messages: context.messages };
|
|
56
|
+
}
|
|
57
|
+
function withBrand(title, context) {
|
|
58
|
+
return context.brand === null ? title : formatMessage(context.messages.pages.titleWithBrand, { title, brand: context.brand });
|
|
59
|
+
}
|
|
60
|
+
/** A page's canonical URL: seo's host and trailing-slash rule when the app lists seo, else on `appOrigin`. */
|
|
61
|
+
function getCanonicalUrl(config, path) {
|
|
62
|
+
return getSiteUrls(config).getCanonicalUrl(path);
|
|
63
|
+
}
|
|
64
|
+
/** The feed link a reader finds in `<head>` (`<link rel="alternate" type="application/rss+xml">`); a file, so no trailing-slash rule. */
|
|
65
|
+
function getFeedAlternates(config, context) {
|
|
66
|
+
return { "application/rss+xml": [{ url: `${getSiteUrls(config).origin}${context.routes.rss}`, title: withBrand(context.messages.pages.blogTitle, context) }] };
|
|
67
|
+
}
|
|
68
|
+
/** Metadata of a page with a fixed path; an empty listing stays out of the index (thin content). */
|
|
69
|
+
function getStaticMetadata(config, context, page) {
|
|
70
|
+
return {
|
|
71
|
+
title: withBrand(page.title, context),
|
|
72
|
+
description: page.description,
|
|
73
|
+
robots: { index: page.isEmpty !== true, follow: true },
|
|
74
|
+
alternates: { canonical: getCanonicalUrl(config, page.path), ...(page.hasFeed === true ? { types: getFeedAlternates(config, context) } : {}) },
|
|
75
|
+
openGraph: { type: "website", title: page.title, description: page.description, url: getCanonicalUrl(config, page.path), ...(context.brand === null ? {} : { siteName: context.brand }) },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function getTextMetadata(config, context, text, path, options = {}) {
|
|
79
|
+
const dates = getArticleDates(text, config.timezone);
|
|
80
|
+
const url = getCanonicalUrl(config, path);
|
|
81
|
+
return {
|
|
82
|
+
title: withBrand(text.title, context),
|
|
83
|
+
description: text.description,
|
|
84
|
+
robots: { index: true, follow: true },
|
|
85
|
+
alternates: { canonical: url, ...(options.hasFeed === true ? { types: getFeedAlternates(config, context) } : {}) },
|
|
86
|
+
openGraph: {
|
|
87
|
+
type: "article",
|
|
88
|
+
title: text.title,
|
|
89
|
+
description: text.description,
|
|
90
|
+
url,
|
|
91
|
+
locale: config.locale,
|
|
92
|
+
publishedTime: dates.published,
|
|
93
|
+
modifiedTime: dates.updated ?? dates.published,
|
|
94
|
+
...(context.brand === null ? {} : { siteName: context.brand }),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export async function generateBlogIndexMetadata() {
|
|
99
|
+
const config = getSoftureConfig();
|
|
100
|
+
const context = getPageContext(config);
|
|
101
|
+
const articles = await getPublishedArticles(config);
|
|
102
|
+
const copy = context.messages.pages;
|
|
103
|
+
return getStaticMetadata(config, context, { title: copy.blogTitle, description: copy.blogDescription, path: context.routes.index, isEmpty: articles.length === 0, hasFeed: true });
|
|
104
|
+
}
|
|
105
|
+
/** The listing: cards grouped by cluster, the pillar first. Mount with `dynamic = "force-dynamic"`. */
|
|
106
|
+
export async function BlogIndexPage({ cta } = {}) {
|
|
107
|
+
const config = getSoftureConfig();
|
|
108
|
+
const context = getPageContext(config);
|
|
109
|
+
const labels = getCrumbLabels(config, context);
|
|
110
|
+
const [articles, terms] = await Promise.all([getPublishedArticles(config), getPublishedTerms(config)]);
|
|
111
|
+
const groups = groupByCluster(articles, labels.cluster, context.messages.pages.otherCluster);
|
|
112
|
+
return _jsx(BlogListingView, { context: context, groups: groups, termCount: terms.length, timezone: config.timezone, cta: cta });
|
|
113
|
+
}
|
|
114
|
+
export async function generateArticleMetadata({ params }) {
|
|
115
|
+
const config = getSoftureConfig();
|
|
116
|
+
const { slug } = await params;
|
|
117
|
+
const article = await getTextBySlug(config, slug);
|
|
118
|
+
if (!isPublished(article, "article"))
|
|
119
|
+
return {};
|
|
120
|
+
const context = getPageContext(config);
|
|
121
|
+
return getTextMetadata(config, context, article, getArticlePath(context.routes, article.slug), { hasFeed: true });
|
|
122
|
+
}
|
|
123
|
+
/** An article with "read next" under it. Mount with `revalidate` and `generateBlogStaticParams`. */
|
|
124
|
+
export async function BlogArticlePage({ params, cta, afterArticle }) {
|
|
125
|
+
const config = getSoftureConfig();
|
|
126
|
+
const { slug } = await params;
|
|
127
|
+
const article = await getTextBySlug(config, slug);
|
|
128
|
+
if (!isPublished(article, "article"))
|
|
129
|
+
notFound();
|
|
130
|
+
const context = getPageContext(config);
|
|
131
|
+
const [terms, published] = await Promise.all([getPublishedTerms(config), getPublishedArticles(config)]);
|
|
132
|
+
const body = renderPageBody(article, getBodyOptions(config, context, terms));
|
|
133
|
+
const crumbs = getArticleCrumbs(article, context.routes, getCrumbLabels(config, context));
|
|
134
|
+
const jsonLd = serializeJsonLd(getArticleJsonLd(article, crumbs, getJsonLdContext(config, context)));
|
|
135
|
+
return (_jsx(BlogArticleView, { context: context, article: article, dates: getArticleDates(article, config.timezone), body: body, crumbs: crumbs, jsonLd: jsonLd, cta: cta, afterArticle: afterArticle, related: getRelatedArticles(article, published) }));
|
|
136
|
+
}
|
|
137
|
+
export async function generateGlossaryIndexMetadata() {
|
|
138
|
+
const config = getSoftureConfig();
|
|
139
|
+
const context = getPageContext(config);
|
|
140
|
+
const terms = await getPublishedTerms(config);
|
|
141
|
+
const copy = context.messages.glossary;
|
|
142
|
+
return getStaticMetadata(config, context, { title: copy.title, description: copy.description, path: context.routes.glossary, isEmpty: terms.length === 0 });
|
|
143
|
+
}
|
|
144
|
+
/** The glossary index. Mount with `dynamic = "force-dynamic"`. */
|
|
145
|
+
export async function GlossaryIndexPage() {
|
|
146
|
+
const config = getSoftureConfig();
|
|
147
|
+
const context = getPageContext(config);
|
|
148
|
+
const terms = sortTerms(await getPublishedTerms(config), config.locale);
|
|
149
|
+
const jsonLd = terms.length === 0 ? null : serializeJsonLd(getGlossaryJsonLd(terms, getJsonLdContext(config, context), context.messages.glossary.title));
|
|
150
|
+
return _jsx(GlossaryIndexView, { context: context, terms: terms, jsonLd: jsonLd });
|
|
151
|
+
}
|
|
152
|
+
export async function generateTermMetadata({ params }) {
|
|
153
|
+
const config = getSoftureConfig();
|
|
154
|
+
const { slug } = await params;
|
|
155
|
+
const term = await getTextBySlug(config, slug);
|
|
156
|
+
if (!isPublished(term, "term"))
|
|
157
|
+
return {};
|
|
158
|
+
const context = getPageContext(config);
|
|
159
|
+
return getTextMetadata(config, context, term, getTermPath(context.routes, term.slug));
|
|
160
|
+
}
|
|
161
|
+
/** A glossary term with the articles that expand on it. Mount with `revalidate` and `generateBlogStaticParams`. */
|
|
162
|
+
export async function GlossaryTermPage({ params, cta }) {
|
|
163
|
+
const config = getSoftureConfig();
|
|
164
|
+
const { slug } = await params;
|
|
165
|
+
const term = await getTextBySlug(config, slug);
|
|
166
|
+
if (!isPublished(term, "term"))
|
|
167
|
+
notFound();
|
|
168
|
+
const context = getPageContext(config);
|
|
169
|
+
const [terms, articles] = await Promise.all([getPublishedTerms(config), getPublishedArticles(config)]);
|
|
170
|
+
const bodyOptions = getBodyOptions(config, context, terms);
|
|
171
|
+
const crumbs = getTermCrumbs(term, context.routes, getCrumbLabels(config, context));
|
|
172
|
+
const jsonLd = serializeJsonLd(getTermJsonLd(term, crumbs, getJsonLdContext(config, context), context.messages.glossary.title));
|
|
173
|
+
return (_jsx(GlossaryTermView, { context: context, term: term, dates: getArticleDates(term, config.timezone), body: renderPageBody(term, bodyOptions), crumbs: crumbs, jsonLd: jsonLd, articles: findArticlesLinkingTerm(articles, term.slug, bodyOptions), cta: cta }));
|
|
174
|
+
}
|
|
175
|
+
export function generateMethodMetadata() {
|
|
176
|
+
const config = getSoftureConfig();
|
|
177
|
+
const context = getPageContext(config);
|
|
178
|
+
const copy = context.messages.method;
|
|
179
|
+
return getStaticMetadata(config, context, { title: copy.title, description: copy.description, path: context.routes.method });
|
|
180
|
+
}
|
|
181
|
+
/** "How our texts are made". Mount it with `blog({ methodPage: true })`; without that it is a 404. */
|
|
182
|
+
export function BlogMethodPage() {
|
|
183
|
+
const config = getSoftureConfig();
|
|
184
|
+
const context = getPageContext(config);
|
|
185
|
+
if (context.methodPath === null)
|
|
186
|
+
notFound();
|
|
187
|
+
return _jsx(BlogMethodView, { context: context });
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":";AAAA,uFAAuF;AACvF,EAAE;AACF,wJAAwJ;AACxJ,+EAA+E;AAC/E,0MAA0M;AAC1M,sEAAsE;AACtE,iGAAiG;AACjG,oHAAoH;AACpH,kHAAkH;AAClH,uFAAuF;AACvF,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,gGAAgG;AAChG,oGAAoG;AACpG,mDAAmD;AACnD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAA8B,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAsB,MAAM,qBAAqB,CAAC;AAC9H,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAoB,MAAM,qBAAqB,CAAC;AACpI,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAsBnF,2FAA2F;AAC3F,MAAM,UAAU,wBAAwB;IACtC,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,IAAwB,EAAE,IAAyB;IACtE,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;AAC5E,CAAC;AAED,SAAS,cAAc,CAAC,MAAqB,EAAE,OAAwB;IACrE,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;IACjD,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS;QACtC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK;QACzC,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC;KACxE,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAqB,EAAE,OAAwB;IACvE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;AACvI,CAAC;AAED,SAAS,cAAc,CAAC,MAAqB,EAAE,OAAwB,EAAE,KAA6B;IACpG,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;AACvI,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,OAAwB;IACxD,OAAO,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;AAChI,CAAC;AAED,8GAA8G;AAC9G,SAAS,eAAe,CAAC,MAAqB,EAAE,IAAY;IAC1D,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,yIAAyI;AACzI,SAAS,iBAAiB,CAAC,MAAqB,EAAE,OAAwB;IACxE,OAAO,EAAE,qBAAqB,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;AACjK,CAAC;AAED,oGAAoG;AACpG,SAAS,iBAAiB,CAAC,MAAqB,EAAE,OAAwB,EAAE,IAAgG;IAC1K,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QACtD,UAAU,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QAC9I,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE;KAC1L,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,MAAqB,EAAE,OAAwB,EAAE,IAAiB,EAAE,IAAY,EAAE,UAAiC,EAAE;IAC5I,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC;QACrC,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QACrC,UAAU,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QAClH,SAAS,EAAE;YACT,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG;YACH,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,aAAa,EAAE,KAAK,CAAC,SAAS;YAC9B,YAAY,EAAE,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS;YAC9C,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;SAC/D;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACpC,OAAO,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACrL,CAAC;AAED,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAE,GAAG,KAAyB,EAAE;IAClE,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvG,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC7F,OAAO,KAAC,eAAe,IAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAI,CAAC;AAC7H,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,EAAE,MAAM,EAAmC;IACvF,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACpH,CAAC;AAED,oGAAoG;AACpG,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAwB;IACvF,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC;QAAE,QAAQ,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxG,MAAM,IAAI,GAAG,cAAc,CAAY,OAAO,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,eAAe,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACrG,OAAO,CACL,KAAC,eAAe,IACd,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EAChD,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,kBAAkB,CAAC,OAAO,EAAE,SAAS,CAAC,GAC/C,CACH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B;IACjD,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACvC,OAAO,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;AAC9J,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACzJ,OAAO,KAAC,iBAAiB,IAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,EAAE,MAAM,EAAmC;IACpF,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC;IAC9B,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACxF,CAAC;AAED,mHAAmH;AACnH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAyB;IAC3E,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC;IAC9B,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC;QAAE,QAAQ,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvG,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,eAAe,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAChI,OAAO,CACL,KAAC,gBAAgB,IACf,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAC7C,IAAI,EAAE,cAAc,CAAY,IAAI,EAAE,WAAW,CAAC,EAClD,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,uBAAuB,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EACnE,GAAG,EAAE,GAAG,GACR,CACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACrC,OAAO,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/H,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;QAAE,QAAQ,EAAE,CAAC;IAC5C,OAAO,KAAC,cAAc,IAAC,OAAO,EAAE,OAAO,GAAI,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST <routes.refresh>`: expires every cached read of the blog, so the next request reads the
|
|
3
|
+
* tables again. Answers 204 refreshed, 401 for a missing or wrong secret, 429 over the `blog-refresh`
|
|
4
|
+
* bucket (per client address; callers without one share a single count), 503 when counting fails,
|
|
5
|
+
* 500 when the secret is not set or shorter than 32 characters. A missing security module or bucket
|
|
6
|
+
* is a setup error, thrown.
|
|
7
|
+
*/
|
|
8
|
+
export declare function refreshBlogCache(request: Request): Promise<Response>;
|
|
9
|
+
//# sourceMappingURL=refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/next/refresh.ts"],"names":[],"mappings":"AA8BA;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0B1E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The cache refresh route: `softure-blog publish --commit` calls it so the running app shows the change
|
|
2
|
+
// at once instead of after `revalidateSeconds`. Mount it in app/api/blog/refresh/route.ts:
|
|
3
|
+
//
|
|
4
|
+
// export { refreshBlogCache as POST } from "@softure-ai/blog/next";
|
|
5
|
+
//
|
|
6
|
+
// The caller is the command, not a person: no session, so the route checks a Bearer secret from the
|
|
7
|
+
// environment (BLOG_REFRESH_SECRET). Order, as in mcp-access: identify the client, count the
|
|
8
|
+
// request, check the secret, then expire the tag. Each refusal happens before the next step costs
|
|
9
|
+
// anything. security is an optional dependency of the blog, reached through a dynamic import.
|
|
10
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
11
|
+
import { errorLogLabel, getModule } from "@softure-ai/core";
|
|
12
|
+
import { getSoftureConfig } from "@softure-ai/core/next";
|
|
13
|
+
import { revalidateTag } from "next/cache";
|
|
14
|
+
import { BLOG_REFRESH_RATE_LIMIT_BUCKET, BLOG_REFRESH_SECRET_ENV, MIN_REFRESH_SECRET_LENGTH } from "../discovery/refresh.js";
|
|
15
|
+
import { getBlogContext } from "./context.js";
|
|
16
|
+
import { BLOG_CACHE_TAG } from "./data.js";
|
|
17
|
+
const SECURITY_MODULE_ID = "security";
|
|
18
|
+
/**
|
|
19
|
+
* The one rate limit key of every caller `identifyClient` cannot place: the command often calls the
|
|
20
|
+
* app on a private name (`--app-url http://web:3000`), past the proxy that sets the client header.
|
|
21
|
+
*/
|
|
22
|
+
const UNIDENTIFIED_CLIENT_KEY = "unidentified";
|
|
23
|
+
const BEARER_PREFIX = /^Bearer[ \t]+/i;
|
|
24
|
+
const NO_STORE = { "cache-control": "no-store" };
|
|
25
|
+
function answer(status, headers = {}) {
|
|
26
|
+
return new Response(null, { status, headers: { ...NO_STORE, ...headers } });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `POST <routes.refresh>`: expires every cached read of the blog, so the next request reads the
|
|
30
|
+
* tables again. Answers 204 refreshed, 401 for a missing or wrong secret, 429 over the `blog-refresh`
|
|
31
|
+
* bucket (per client address; callers without one share a single count), 503 when counting fails,
|
|
32
|
+
* 500 when the secret is not set or shorter than 32 characters. A missing security module or bucket
|
|
33
|
+
* is a setup error, thrown.
|
|
34
|
+
*/
|
|
35
|
+
export async function refreshBlogCache(request) {
|
|
36
|
+
const secret = (process.env[BLOG_REFRESH_SECRET_ENV] ?? "").trim();
|
|
37
|
+
if (secret.length < MIN_REFRESH_SECRET_LENGTH) {
|
|
38
|
+
console.error(`@softure-ai/blog: the cache refresh route needs ${BLOG_REFRESH_SECRET_ENV} of at least ${String(MIN_REFRESH_SECRET_LENGTH)} characters`);
|
|
39
|
+
return answer(500);
|
|
40
|
+
}
|
|
41
|
+
const config = getSoftureConfig();
|
|
42
|
+
assertRateLimitBucket(config);
|
|
43
|
+
const { consumeRateLimit, identifyClient } = await import("@softure-ai/security/server");
|
|
44
|
+
const client = identifyClient({ config }, request.headers);
|
|
45
|
+
let limit;
|
|
46
|
+
try {
|
|
47
|
+
// Counted before the secret is checked, so a flood of guesses is stopped first.
|
|
48
|
+
limit = await consumeRateLimit(await getBlogContext(config), { bucket: BLOG_REFRESH_RATE_LIMIT_BUCKET, key: client.ok ? client.value : UNIDENTIFIED_CLIENT_KEY });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Fails closed and says nothing: the route is public and this runs before authentication.
|
|
52
|
+
console.error(`@softure-ai/blog: counting a cache refresh failed: ${errorLogLabel(error)}`);
|
|
53
|
+
return answer(503);
|
|
54
|
+
}
|
|
55
|
+
if (!limit.ok)
|
|
56
|
+
return answer(429, { "retry-after": String(limit.retryAfterSeconds) });
|
|
57
|
+
if (!hasSecret(request.headers.get("authorization"), secret))
|
|
58
|
+
return answer(401, { "www-authenticate": "Bearer" });
|
|
59
|
+
// `expire: 0`: the next request waits for fresh reads. The "max" profile would serve the old page once more.
|
|
60
|
+
revalidateTag(BLOG_CACHE_TAG, { expire: 0 });
|
|
61
|
+
return answer(204);
|
|
62
|
+
}
|
|
63
|
+
/** Compares digests in constant time, so the answer time says nothing about the secret's length or prefix. */
|
|
64
|
+
function hasSecret(header, secret) {
|
|
65
|
+
if (header === null || !BEARER_PREFIX.test(header))
|
|
66
|
+
return false;
|
|
67
|
+
const given = createHash("sha256").update(header.replace(BEARER_PREFIX, "").trim()).digest();
|
|
68
|
+
return timingSafeEqual(given, createHash("sha256").update(secret).digest());
|
|
69
|
+
}
|
|
70
|
+
/** A missing module or bucket is a setup bug: thrown, so it reaches the log by name instead of a quiet 503. */
|
|
71
|
+
function assertRateLimitBucket(config) {
|
|
72
|
+
const security = getModule(config, SECURITY_MODULE_ID);
|
|
73
|
+
if (security === undefined) {
|
|
74
|
+
throw new Error("@softure-ai/blog: the cache refresh route is rate-limited by the security module; add security() to modules");
|
|
75
|
+
}
|
|
76
|
+
const options = security.options;
|
|
77
|
+
if (options?.buckets?.[BLOG_REFRESH_RATE_LIMIT_BUCKET] === undefined) {
|
|
78
|
+
throw new Error(`@softure-ai/blog: the security module has no "${BLOG_REFRESH_RATE_LIMIT_BUCKET}" bucket; spread BLOG_RATE_LIMIT_BUCKETS into security({ buckets })`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/next/refresh.ts"],"names":[],"mappings":"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"}
|