@founderhq/next-blog 0.9.0

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/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @founderhq/next-blog
2
+
3
+ Next.js App Router SEO helpers, editable blog templates, and installer for
4
+ FounderHQ-compatible Payload blogs.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npx @founderhq/next-blog@next init
10
+ ```
11
+
12
+ The installer is conservative:
13
+
14
+ - dry-run preview first, then asks whether to apply
15
+ - supports `--dry-run` for preview-only runs
16
+ - supports `--yes` for non-interactive apply after plan generation
17
+ - supports `--env-mode write|print` to choose runtime env handling
18
+ - refuses dirty git by default
19
+ - prints a diff preview before writing
20
+ - uses dedicated Payload env vars
21
+ - asks for database and site env values
22
+ - supports Next App Router automatically and non-Next projects with a
23
+ framework-neutral manual integration plan
24
+ - always writes safe examples to `.env.example` on apply
25
+ - lets you either write runtime env values to `.env` and `.env.local`, or
26
+ print them for copy-paste into your own env setup
27
+ - installs only the selected Payload database and storage adapters
28
+ - asks before running migrations and validates env first
29
+ - aborts with manual instructions for unsupported project shapes, existing
30
+ Payload configs, unsupported Next config filenames, and generated file path
31
+ collisions
32
+ - copies editable blog UI into your app instead of forcing package-owned visual
33
+ components
34
+
35
+ Supported v1 databases:
36
+
37
+ - Postgres through `@payloadcms/db-postgres`
38
+ - local SQLite through `@payloadcms/db-sqlite`
39
+ - Turso/libSQL through `@payloadcms/db-sqlite`
40
+
41
+ MongoDB is intentionally manual/advanced in v1.
42
+
43
+ Supported installer storage choices:
44
+
45
+ - local
46
+ - S3
47
+ - R2
48
+ - Vercel Blob
49
+ - Azure Blob
50
+ - Google Cloud Storage
51
+ - UploadThing
52
+ - custom/manual
53
+
54
+ The custom/manual choice generates a no-op `founderhq-blog.storage.ts` so
55
+ migrations and the admin app can boot before the customer adds their adapter.
56
+ Edit that file before production uploads.
57
+
58
+ Payload uses dedicated env vars and never reuses an app `DATABASE_URL`
59
+ silently:
60
+
61
+ - `PAYLOAD_DATABASE_URL`
62
+ - `PAYLOAD_DATABASE_AUTH_TOKEN`
63
+ - `PAYLOAD_SECRET`
64
+ - `NEXT_PUBLIC_SITE_NAME`
65
+ - `NEXT_PUBLIC_SITE_URL`
66
+ - `NEXT_PUBLIC_BLOG_ROUTE_PREFIX`
67
+
68
+ The v1 installer requires a non-root blog route prefix such as `/blog` or
69
+ `/insights`. Root-mounted blogs can be wired manually after install if a client
70
+ needs the blog to own `/`.
71
+
72
+ ## Install model
73
+
74
+ The package uses a hybrid model:
75
+
76
+ - `@founderhq/payload-cms-kit` owns Payload collections, fields, blocks, hooks,
77
+ and rich text behavior.
78
+ - `@founderhq/next-blog/server` owns portable server helpers for fetching,
79
+ normalization, metadata, JSON-LD, RSS, sitemaps, redirects, and search.
80
+ - `npx @founderhq/next-blog init` copies editable UI templates into the target
81
+ project, similar to the shadcn open-code model.
82
+
83
+ Fresh installs generate files like:
84
+
85
+ - `src/components/blog/blog-components.tsx`
86
+ - `src/components/blog/blog.css`
87
+ - `src/app/blog/page.tsx`
88
+ - `src/app/blog/page/[page]/page.tsx`
89
+ - `src/app/blog/[slug]/page.tsx`
90
+ - `src/app/blog/search/page.tsx`
91
+ - `src/app/blog/authors/page.tsx`
92
+ - `src/app/blog/authors/[slug]/page.tsx`
93
+ - `src/app/blog/authors/[slug]/page/[page]/page.tsx`
94
+ - `src/app/blog/categories/page.tsx`
95
+ - `src/app/blog/categories/[slug]/page.tsx`
96
+ - `src/app/blog/categories/[slug]/page/[page]/page.tsx`
97
+ - `src/app/blog/tags/page.tsx`
98
+ - `src/app/blog/tags/[slug]/page.tsx`
99
+ - `src/app/blog/tags/[slug]/page/[page]/page.tsx`
100
+ - `src/app/blog/rss.xml/route.ts`
101
+ - `src/app/sitemaps/blog-posts.xml/route.ts`
102
+ - `src/app/sitemaps/blog-authors.xml/route.ts`
103
+ - `src/app/sitemaps/blog-categories.xml/route.ts`
104
+ - `src/app/sitemaps/blog-tags.xml/route.ts`
105
+ - `src/app/sitemap.xml/route.ts` when no root sitemap exists
106
+ - `src/app/robots.ts` when no robots route/file exists
107
+
108
+ Customers should edit the generated component and CSS files to match their
109
+ site. The package remains the maintained source for CMS and SEO primitives.
110
+
111
+ If a project already has `payload.config.*`, the v1 installer refuses to
112
+ overwrite it. Add `@founderhq/payload-cms-kit` manually through
113
+ `founderHQBlogPlugin()` or the collection/global factories so existing Payload
114
+ collections, plugins, access rules, and storage settings stay intact.
115
+
116
+ For non-Next projects, the CLI does not generate public framework routes. It
117
+ creates `payload.config.ts`, storage config, framework-neutral blog data
118
+ helpers, env/scripts, and `FOUNDERHQ_BLOG_INTEGRATION.md` with the exact route,
119
+ RSS, robots, and sitemap contract to implement in that framework.
120
+
121
+ ## Sitemaps
122
+
123
+ Fresh installs create this structure:
124
+
125
+ - `/sitemap.xml` as a sitemap index
126
+ - `/sitemaps/blog-posts.xml` for blog index pagination and article URLs
127
+ - `/sitemaps/blog-authors.xml` for the author directory, author pages, and
128
+ author archive pagination
129
+ - `/sitemaps/blog-categories.xml` for the category directory, category pages,
130
+ and category archive pagination
131
+ - `/sitemaps/blog-tags.xml` for the tag directory, tag pages, and tag archive
132
+ pagination
133
+
134
+ If the target app already has a simple `app/sitemap.ts`, the installer patches
135
+ that metadata sitemap to include indexable blog URLs directly, because Next
136
+ metadata sitemaps emit URL sets rather than sitemap indexes. If the target has
137
+ a static `public/sitemap.xml` that is already a sitemap index, the installer
138
+ adds the blog child sitemap links. If the existing sitemap is a custom route
139
+ handler or a static URL set that cannot be patched safely, the installer still
140
+ creates `/sitemaps/blog-*.xml` routes and prints manual merge instructions.
141
+
142
+ The generated blog sitemap excludes posts marked `noindex` and posts with an
143
+ external canonical URL.
144
+
145
+ ## Other SEO Routes
146
+
147
+ Fresh installs generate a `robots.ts` route pointing crawlers at `/sitemap.xml`.
148
+ If a project already has static `public/robots.txt`, the installer appends a
149
+ missing `Sitemap:` line. Existing app robots metadata routes are not force
150
+ patched; the installer warns if one does not appear to include a sitemap.
151
+
152
+ Generated blog routes include canonical metadata for the blog index, `noindex`
153
+ metadata for search results, JSON-LD for articles, RSS, manual redirect support,
154
+ previous-slug redirects from Payload slug history, and canonical metadata for
155
+ author, category, tag, and paginated archive routes.
156
+
157
+ ## Runtime helpers
158
+
159
+ ```ts
160
+ import { buildArticleJsonLd, buildRssXml } from "@founderhq/next-blog/server";
161
+ ```
162
+
163
+ The server helpers normalize Payload REST documents into renderable articles,
164
+ including stored Lexical HTML, heading IDs, TOC items, JSON-LD, RSS, sitemap
165
+ entries, robots config, redirects, and adapter-neutral search.
166
+
167
+ The package still exports `@founderhq/next-blog/components` and
168
+ `@founderhq/next-blog/styles.css` for compatibility, but new installs use local
169
+ editable templates.
@@ -0,0 +1,61 @@
1
+ export type DbChoice = "postgres" | "sqlite-local" | "turso";
2
+ export type StorageChoice = "local" | "s3" | "r2" | "vercel-blob" | "azure" | "gcs" | "uploadthing" | "custom";
3
+ export type MigrationChoice = "skip" | "scripts" | "run";
4
+ export type InitOptions = {
5
+ projectRoot: string;
6
+ db: DbChoice;
7
+ storage: StorageChoice;
8
+ adminPath?: string;
9
+ routePrefix?: string;
10
+ postgresSchema?: string;
11
+ migration?: MigrationChoice;
12
+ env?: Partial<Record<string, string>>;
13
+ };
14
+ export type FileChange = {
15
+ path: string;
16
+ kind: "create" | "update";
17
+ content: string;
18
+ };
19
+ export type InstallPlan = {
20
+ appDir: string;
21
+ dependencies: Record<string, string>;
22
+ devDependencies: Record<string, string>;
23
+ env: Record<string, string>;
24
+ changes: FileChange[];
25
+ scripts: Record<string, string>;
26
+ installCommand: string | null;
27
+ migrationCommand: string | null;
28
+ warnings: string[];
29
+ };
30
+ export type PackageJsonDependencyShape = Record<string, unknown> & {
31
+ dependencies?: Record<string, string>;
32
+ devDependencies?: Record<string, string>;
33
+ };
34
+ export declare function choiceFromValue<const T extends readonly string[]>(value: string | undefined, name: string, choices: T): T[number] | undefined;
35
+ export declare const STORAGE_DEPENDENCIES: {
36
+ local: {};
37
+ s3: {
38
+ "@payloadcms/storage-s3": string;
39
+ };
40
+ r2: {
41
+ "@payloadcms/storage-r2": string;
42
+ };
43
+ "vercel-blob": {
44
+ "@payloadcms/storage-vercel-blob": string;
45
+ };
46
+ azure: {
47
+ "@payloadcms/storage-azure": string;
48
+ };
49
+ gcs: {
50
+ "@payloadcms/storage-gcs": string;
51
+ };
52
+ uploadthing: {
53
+ "@payloadcms/storage-uploadthing": string;
54
+ };
55
+ custom: {};
56
+ };
57
+ export declare function mergeMissingPackageEntries(pkg: PackageJsonDependencyShape, dependencies: Record<string, string>, devDependencies: Record<string, string>): PackageJsonDependencyShape;
58
+ export declare function missingMigrationEnv(plan: Pick<InstallPlan, "env">, env?: NodeJS.ProcessEnv): string[];
59
+ export declare function detectAppDir(projectRoot: string): string | null;
60
+ export declare function createInstallPlan(options: InitOptions): InstallPlan;
61
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/cli/core.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,cAAc,GAAG,OAAO,CAAC;AAC7D,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,IAAI,GACJ,IAAI,GACJ,aAAa,GACb,OAAO,GACP,KAAK,GACL,aAAa,GACb,QAAQ,CAAC;AACb,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,QAAQ,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,GAAG,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EAC/D,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,yBAKX;AAiED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;CASwB,CAAC;AA4E1D,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,0BAA0B,EAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,8BA2BxC;AAaD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,YAKrC;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,iBAM/C;AAqjDD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAgfnE"}