@earlyseo/blog 1.0.1

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.
Files changed (63) hide show
  1. package/README.md +304 -0
  2. package/dist/client.d.ts +22 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +65 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/css.d.ts +16 -0
  7. package/dist/css.d.ts.map +1 -0
  8. package/dist/css.js +186 -0
  9. package/dist/css.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +13 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/next/back-link.d.ts +12 -0
  15. package/dist/next/back-link.d.ts.map +1 -0
  16. package/dist/next/back-link.js +15 -0
  17. package/dist/next/back-link.js.map +1 -0
  18. package/dist/next/blog-list-page.d.ts +22 -0
  19. package/dist/next/blog-list-page.d.ts.map +1 -0
  20. package/dist/next/blog-list-page.js +49 -0
  21. package/dist/next/blog-list-page.js.map +1 -0
  22. package/dist/next/blog-post-page.d.ts +65 -0
  23. package/dist/next/blog-post-page.d.ts.map +1 -0
  24. package/dist/next/blog-post-page.js +103 -0
  25. package/dist/next/blog-post-page.js.map +1 -0
  26. package/dist/next/index.d.ts +5 -0
  27. package/dist/next/index.d.ts.map +1 -0
  28. package/dist/next/index.js +11 -0
  29. package/dist/next/index.js.map +1 -0
  30. package/dist/react/blog-list.d.ts +27 -0
  31. package/dist/react/blog-list.d.ts.map +1 -0
  32. package/dist/react/blog-list.js +49 -0
  33. package/dist/react/blog-list.js.map +1 -0
  34. package/dist/react/blog-post.d.ts +35 -0
  35. package/dist/react/blog-post.d.ts.map +1 -0
  36. package/dist/react/blog-post.js +41 -0
  37. package/dist/react/blog-post.js.map +1 -0
  38. package/dist/react/index.d.ts +8 -0
  39. package/dist/react/index.d.ts.map +1 -0
  40. package/dist/react/index.js +15 -0
  41. package/dist/react/index.js.map +1 -0
  42. package/dist/react/provider.d.ts +30 -0
  43. package/dist/react/provider.d.ts.map +1 -0
  44. package/dist/react/provider.js +34 -0
  45. package/dist/react/provider.js.map +1 -0
  46. package/dist/react/styles.d.ts +11 -0
  47. package/dist/react/styles.d.ts.map +1 -0
  48. package/dist/react/styles.js +16 -0
  49. package/dist/react/styles.js.map +1 -0
  50. package/dist/react/use-article.d.ts +17 -0
  51. package/dist/react/use-article.d.ts.map +1 -0
  52. package/dist/react/use-article.js +53 -0
  53. package/dist/react/use-article.js.map +1 -0
  54. package/dist/react/use-articles.d.ts +22 -0
  55. package/dist/react/use-articles.d.ts.map +1 -0
  56. package/dist/react/use-articles.js +59 -0
  57. package/dist/react/use-articles.js.map +1 -0
  58. package/dist/types.d.ts +96 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +14 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +68 -0
  63. package/src/cli/init.mjs +188 -0
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ARTICLE_CSS, BLOG_CSS } from "../css";
4
+ /**
5
+ * Injects EarlySEO article and blog CSS into the page.
6
+ * Place this once near the root of your app (or inside a layout).
7
+ *
8
+ * ```tsx
9
+ * import { ArticleStyles } from '@earlyseo/blog/react';
10
+ * <ArticleStyles />
11
+ * ```
12
+ */
13
+ export function ArticleStyles() {
14
+ return (_jsx("style", { dangerouslySetInnerHTML: { __html: `${ARTICLE_CSS}\n${BLOG_CSS}` }, "data-earlyseo": "blog-styles" }));
15
+ }
16
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/react/styles.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAQb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,CACL,gBACE,uBAAuB,EAAE,EAAE,MAAM,EAAE,GAAG,WAAW,KAAK,QAAQ,EAAE,EAAE,mBACpD,aAAa,GAC3B,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Article } from "../types";
2
+ export interface UseArticleResult {
3
+ article: Article | null;
4
+ loading: boolean;
5
+ error: Error | null;
6
+ /** Refetch the article */
7
+ refresh: () => void;
8
+ }
9
+ /**
10
+ * Hook to fetch a single article by slug from the CDN.
11
+ *
12
+ * ```tsx
13
+ * const { article, loading } = useArticle('my-article-slug');
14
+ * ```
15
+ */
16
+ export declare function useArticle(slug: string | undefined): UseArticleResult;
17
+ //# sourceMappingURL=use-article.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-article.d.ts","sourceRoot":"","sources":["../../src/react/use-article.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAwCrE"}
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // @earlyseo/blog — useArticle() hook
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Fetches a single article's full JSON from the CDN.
6
+ import { useState, useEffect, useCallback } from "react";
7
+ import { useEarlySeoContext } from "./provider";
8
+ /**
9
+ * Hook to fetch a single article by slug from the CDN.
10
+ *
11
+ * ```tsx
12
+ * const { article, loading } = useArticle('my-article-slug');
13
+ * ```
14
+ */
15
+ export function useArticle(slug) {
16
+ const { client } = useEarlySeoContext();
17
+ const [article, setArticle] = useState(null);
18
+ const [loading, setLoading] = useState(true);
19
+ const [error, setError] = useState(null);
20
+ const [tick, setTick] = useState(0);
21
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
22
+ useEffect(() => {
23
+ if (!slug) {
24
+ setArticle(null);
25
+ setLoading(false);
26
+ return;
27
+ }
28
+ let cancelled = false;
29
+ setLoading(true);
30
+ client
31
+ .getArticle(slug)
32
+ .then((result) => {
33
+ if (cancelled)
34
+ return;
35
+ setArticle(result);
36
+ setError(null);
37
+ })
38
+ .catch((err) => {
39
+ if (cancelled)
40
+ return;
41
+ setError(err instanceof Error ? err : new Error("Failed to load article"));
42
+ })
43
+ .finally(() => {
44
+ if (!cancelled)
45
+ setLoading(false);
46
+ });
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [client, slug, tick]);
51
+ return { article, loading, error, refresh };
52
+ }
53
+ //# sourceMappingURL=use-article.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-article.js","sourceRoot":"","sources":["../../src/react/use-article.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,gFAAgF;AAChF,qCAAqC;AACrC,gFAAgF;AAChF,qDAAqD;AAErD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAWhD;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAwB;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAC;IAC7D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,CAAC;QAEjB,MAAM;aACH,UAAU,CAAC,IAAI,CAAC;aAChB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,SAAS;gBAAE,OAAO;YACtB,UAAU,CAAC,MAAM,CAAC,CAAC;YACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,IAAI,SAAS;gBAAE,OAAO;YACtB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,SAAS;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAEzB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ArticleListItem } from "../types";
2
+ export interface UseArticlesResult {
3
+ articles: ArticleListItem[];
4
+ page: number;
5
+ totalPages: number;
6
+ totalArticles: number;
7
+ loading: boolean;
8
+ error: Error | null;
9
+ /** Navigate to a page (1-indexed) */
10
+ goToPage: (page: number) => void;
11
+ /** Refetch the current page */
12
+ refresh: () => void;
13
+ }
14
+ /**
15
+ * Hook to fetch a paginated article list page from the CDN.
16
+ *
17
+ * ```tsx
18
+ * const { articles, page, totalPages, goToPage, loading } = useArticles();
19
+ * ```
20
+ */
21
+ export declare function useArticles(initialPage?: number): UseArticlesResult;
22
+ //# sourceMappingURL=use-articles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-articles.d.ts","sourceRoot":"","sources":["../../src/react/use-articles.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,qCAAqC;IACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,WAAW,SAAI,GAAG,iBAAiB,CA6C9D"}
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // @earlyseo/blog — useArticles() hook
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Fetches a paginated list page from the CDN.
6
+ import { useState, useEffect, useCallback } from "react";
7
+ import { useEarlySeoContext } from "./provider";
8
+ /**
9
+ * Hook to fetch a paginated article list page from the CDN.
10
+ *
11
+ * ```tsx
12
+ * const { articles, page, totalPages, goToPage, loading } = useArticles();
13
+ * ```
14
+ */
15
+ export function useArticles(initialPage = 1) {
16
+ const { client } = useEarlySeoContext();
17
+ const [page, setPage] = useState(initialPage);
18
+ const [data, setData] = useState(null);
19
+ const [loading, setLoading] = useState(true);
20
+ const [error, setError] = useState(null);
21
+ const [tick, setTick] = useState(0);
22
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
23
+ const goToPage = useCallback((p) => setPage(Math.max(1, p)), []);
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ setLoading(true);
27
+ client
28
+ .getListPage(page)
29
+ .then((result) => {
30
+ if (cancelled)
31
+ return;
32
+ setData(result);
33
+ setError(null);
34
+ })
35
+ .catch((err) => {
36
+ if (cancelled)
37
+ return;
38
+ setError(err instanceof Error ? err : new Error("Failed to load articles"));
39
+ })
40
+ .finally(() => {
41
+ if (!cancelled)
42
+ setLoading(false);
43
+ });
44
+ return () => {
45
+ cancelled = true;
46
+ };
47
+ }, [client, page, tick]);
48
+ return {
49
+ articles: data?.articles ?? [],
50
+ page: data?.page ?? page,
51
+ totalPages: data?.totalPages ?? 0,
52
+ totalArticles: data?.totalArticles ?? 0,
53
+ loading,
54
+ error,
55
+ goToPage,
56
+ refresh,
57
+ };
58
+ }
59
+ //# sourceMappingURL=use-articles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-articles.js","sourceRoot":"","sources":["../../src/react/use-articles.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,gFAAgF;AAChF,sCAAsC;AACtC,gFAAgF;AAChF,8CAA8C;AAE9C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgBhD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,WAAW,GAAG,CAAC;IACzC,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAyB,IAAI,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEzE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,CAAC;QAEjB,MAAM;aACH,WAAW,CAAC,IAAI,CAAC;aACjB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,SAAS;gBAAE,OAAO;YACtB,OAAO,CAAC,MAAM,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,IAAI,SAAS;gBAAE,OAAO;YACtB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAC9E,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,SAAS;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAEzB,OAAO;QACL,QAAQ,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE;QAC9B,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI;QACxB,UAAU,EAAE,IAAI,EAAE,UAAU,IAAI,CAAC;QACjC,aAAa,EAAE,IAAI,EAAE,aAAa,IAAI,CAAC;QACvC,OAAO;QACP,KAAK;QACL,QAAQ;QACR,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,96 @@
1
+ /** Top-level manifest stored at /site/{siteId}/manifest.json */
2
+ export interface Manifest {
3
+ /** Schema version for future migrations */
4
+ schemaVersion: 1;
5
+ /** Monotonically increasing version — bumped on every publish */
6
+ version: number;
7
+ /** Total published article count */
8
+ totalArticles: number;
9
+ /** Total list pages available */
10
+ totalPages: number;
11
+ /** Articles per list page */
12
+ pageSize: number;
13
+ /** ISO 8601 timestamp of latest update */
14
+ updatedAt: string;
15
+ /** Slug(s) of featured/pinned articles (for hero sections) */
16
+ featuredSlugs: string[];
17
+ }
18
+ /** A single article entry in a paginated list page */
19
+ export interface ArticleListItem {
20
+ /** Article title */
21
+ title: string;
22
+ /** URL-safe slug */
23
+ slug: string;
24
+ /** Featured image URL (hosted externally — we only store the URL) */
25
+ imageUrl?: string;
26
+ /** Featured image alt text */
27
+ imageAlt?: string;
28
+ /** Short description for cards / meta */
29
+ metaDescription: string;
30
+ /** ISO 8601 creation timestamp */
31
+ createdAt: string;
32
+ /** Article tags */
33
+ tags: string[];
34
+ }
35
+ /** Paginated list page stored at /site/{siteId}/articles/page-{n}.json */
36
+ export interface ArticleListPage {
37
+ /** Page number (1-indexed) */
38
+ page: number;
39
+ /** Total pages at time of generation */
40
+ totalPages: number;
41
+ /** Total article count at time of generation */
42
+ totalArticles: number;
43
+ /** Manifest version this page was generated from */
44
+ version: number;
45
+ /** Articles on this page (newest first) */
46
+ articles: ArticleListItem[];
47
+ }
48
+ /** Full article JSON stored at /site/{siteId}/article/{slug}.json */
49
+ export interface Article {
50
+ /** Unique article ID (from EarlySEO) */
51
+ id: string;
52
+ /** Article title */
53
+ title: string;
54
+ /** URL-safe slug */
55
+ slug: string;
56
+ /** Styled HTML content (wrapped in .earlyseo-article div with embedded CSS) */
57
+ contentHtml: string;
58
+ /** Raw HTML without EarlySEO wrapper div or styles — inherits your site theme */
59
+ contentRawHtml: string;
60
+ /** Standalone CSS for the .earlyseo-article wrapper */
61
+ contentCss: string;
62
+ /** Meta description for SEO */
63
+ metaDescription: string;
64
+ /** ISO 8601 creation timestamp */
65
+ createdAt: string;
66
+ /** ISO 8601 timestamp of last update */
67
+ updatedAt: string;
68
+ /** Featured image URL */
69
+ imageUrl?: string;
70
+ /** Featured image alt text */
71
+ imageAlt?: string;
72
+ /** Article tags / keywords */
73
+ tags: string[];
74
+ /** Short summary text */
75
+ summary?: string;
76
+ /** Canonical URL (if set by EarlySEO) */
77
+ canonicalUrl?: string;
78
+ /** Article version — bumped on re-publish/update */
79
+ version: number;
80
+ }
81
+ /** Configuration for the EarlySEO blog CDN client. */
82
+ export interface EarlySeoConfig {
83
+ /**
84
+ * Your site ID from the EarlySEO dashboard.
85
+ * This determines which CDN folder to read from.
86
+ */
87
+ siteId: string;
88
+ /**
89
+ * CDN base URL. Defaults to the EarlySEO public CDN.
90
+ * Override only if using a custom domain or self-hosted R2.
91
+ */
92
+ cdnBaseUrl?: string;
93
+ }
94
+ /** Default CDN base URL — the EarlySEO public R2 bucket. */
95
+ export declare const DEFAULT_CDN_BASE_URL = "https://media.earlyseo.com";
96
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAcA,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,aAAa,EAAE,CAAC,CAAC;IACjB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACtB,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,+BAA+B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,14 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // @earlyseo/blog — Core Types
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // CDN JSON schema for the paginated, versioned article system.
5
+ //
6
+ // CDN layout:
7
+ // /site/{siteId}/manifest.json
8
+ // /site/{siteId}/articles/page-1.json
9
+ // /site/{siteId}/articles/page-2.json
10
+ // /site/{siteId}/article/{slug}.json
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ /** Default CDN base URL — the EarlySEO public R2 bucket. */
13
+ export const DEFAULT_CDN_BASE_URL = "https://media.earlyseo.com";
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8BAA8B;AAC9B,gFAAgF;AAChF,+DAA+D;AAC/D,EAAE;AACF,cAAc;AACd,iCAAiC;AACjC,wCAAwC;AACxC,wCAAwC;AACxC,uCAAuC;AACvC,gFAAgF;AAwGhF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,4BAA4B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@earlyseo/blog",
3
+ "version": "1.0.1",
4
+ "description": "Drop-in blog integration for React & Next.js — powered by EarlySEO. Articles are served from EarlySEO's CDN. Just add your site ID and render.",
5
+ "type": "module",
6
+ "bin": {
7
+ "earlyseo-blog": "./src/cli/init.mjs"
8
+ },
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./react": {
18
+ "types": "./dist/react/index.d.ts",
19
+ "import": "./dist/react/index.js"
20
+ },
21
+ "./next": {
22
+ "types": "./dist/next/index.d.ts",
23
+ "import": "./dist/next/index.js"
24
+ },
25
+ "./css": {
26
+ "types": "./dist/css.d.ts",
27
+ "import": "./dist/css.js"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src/cli",
37
+ "README.md"
38
+ ],
39
+ "keywords": [
40
+ "earlyseo",
41
+ "blog",
42
+ "seo",
43
+ "react",
44
+ "nextjs",
45
+ "cms",
46
+ "headless",
47
+ "blog-engine",
48
+ "cdn"
49
+ ],
50
+ "license": "MIT",
51
+ "peerDependencies": {
52
+ "react": ">=18.0.0",
53
+ "react-dom": ">=18.0.0",
54
+ "next": ">=13.0.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "next": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "devDependencies": {
62
+ "@types/react": "^18.0.0",
63
+ "@types/node": "^20.0.0",
64
+ "react": "^18.0.0",
65
+ "react-dom": "^18.0.0",
66
+ "typescript": "^5.0.0"
67
+ }
68
+ }
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // @earlyseo/blog init — Generate Next.js blog pages automatically
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Usage: npx @earlyseo/blog init
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import readline from "node:readline";
12
+
13
+ const cwd = process.cwd();
14
+
15
+ // ── Helpers ──────────────────────────────────────────────────────────────────
16
+
17
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
18
+
19
+ function ask(question) {
20
+ return new Promise((resolve) => {
21
+ rl.question(question, (answer) => {
22
+ resolve(answer.trim());
23
+ });
24
+ });
25
+ }
26
+
27
+ function fileExists(relativePath) {
28
+ return fs.existsSync(path.join(cwd, relativePath));
29
+ }
30
+
31
+ function writeFileSafe(relativePath, content) {
32
+ const fullPath = path.join(cwd, relativePath);
33
+ if (fs.existsSync(fullPath)) {
34
+ console.log(` ⏭ Skipped ${relativePath} (already exists)`);
35
+ return false;
36
+ }
37
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
38
+ fs.writeFileSync(fullPath, content, "utf-8");
39
+ console.log(` ✅ Created ${relativePath}`);
40
+ return true;
41
+ }
42
+
43
+ // ── Detection ────────────────────────────────────────────────────────────────
44
+
45
+ function detectNextJs() {
46
+ const patterns = [
47
+ "next.config.js",
48
+ "next.config.mjs",
49
+ "next.config.ts",
50
+ "next.config.cjs",
51
+ ];
52
+ return patterns.some((p) => fileExists(p));
53
+ }
54
+
55
+ function detectAppDir() {
56
+ // Check common app directory locations
57
+ if (fileExists("app")) return "app";
58
+ if (fileExists("src/app")) return "src/app";
59
+ return null;
60
+ }
61
+
62
+ function detectSrcDir() {
63
+ // Check if project uses src/ directory pattern
64
+ return fileExists("src/app");
65
+ }
66
+
67
+ // ── Templates ────────────────────────────────────────────────────────────────
68
+
69
+ function blogListPage(siteId) {
70
+ return `import { createBlogListPage, createBlogListMetadata } from "@earlyseo/blog/next";
71
+
72
+ const siteId = process.env.EARLYSEO_SITE_ID ?? "${siteId}";
73
+
74
+ export default createBlogListPage({
75
+ siteId,
76
+ title: "Blog",
77
+ });
78
+
79
+ export const generateMetadata = createBlogListMetadata({
80
+ title: "Blog",
81
+ description: "Read our latest articles",
82
+ });
83
+ `;
84
+ }
85
+
86
+ function blogPostPage(siteId) {
87
+ return `import { createBlogPostPage, createBlogPostMetadata } from "@earlyseo/blog/next";
88
+
89
+ const siteId = process.env.EARLYSEO_SITE_ID ?? "${siteId}";
90
+
91
+ export default createBlogPostPage({ siteId });
92
+
93
+ export const generateMetadata = createBlogPostMetadata({
94
+ siteId,
95
+ });
96
+ `;
97
+ }
98
+
99
+ // ── Main ─────────────────────────────────────────────────────────────────────
100
+
101
+ async function main() {
102
+ console.log();
103
+ console.log(" @earlyseo/blog — Blog Setup");
104
+ console.log(" ───────────────────────────");
105
+ console.log();
106
+
107
+ // 1. Detect Next.js
108
+ if (!detectNextJs()) {
109
+ console.log(" ❌ Could not find a Next.js project in the current directory.");
110
+ console.log(" (Looked for next.config.js, next.config.mjs, or next.config.ts)");
111
+ console.log();
112
+ console.log(" This command generates pages for Next.js App Router projects.");
113
+ console.log(" For React (Vite, Remix, etc.), see: https://www.npmjs.com/package/@earlyseo/blog");
114
+ console.log();
115
+ process.exit(1);
116
+ }
117
+
118
+ // 2. Detect app directory
119
+ const appDir = detectAppDir();
120
+ if (!appDir) {
121
+ console.log(" ❌ Could not find an app/ directory (Next.js App Router).");
122
+ console.log(" Checked: app/ and src/app/");
123
+ console.log();
124
+ console.log(" Create your app directory first, then re-run this command.");
125
+ console.log();
126
+ process.exit(1);
127
+ }
128
+
129
+ console.log(` ✓ Next.js project detected (${appDir}/)`);
130
+ console.log();
131
+
132
+ // 3. Ask for site ID
133
+ let siteId = process.env.EARLYSEO_SITE_ID || "";
134
+ if (siteId) {
135
+ console.log(` ✓ Found EARLYSEO_SITE_ID from environment: ${siteId}`);
136
+ } else {
137
+ siteId = await ask(" Enter your EarlySEO Site ID: ");
138
+ if (!siteId) {
139
+ console.log(" ❌ Site ID is required. Find it in your EarlySEO dashboard → Integrations → SDK.");
140
+ console.log();
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ console.log();
146
+
147
+ // 5. Generate pages
148
+ const blogDir = `${appDir}/blog`;
149
+ const listPagePath = `${blogDir}/page.tsx`;
150
+ const postPagePath = `${blogDir}/[slug]/page.tsx`;
151
+
152
+ writeFileSafe(listPagePath, blogListPage(siteId));
153
+ writeFileSafe(postPagePath, blogPostPage(siteId));
154
+
155
+ // 6. Add EARLYSEO_SITE_ID to .env.local
156
+ const envPath = path.join(cwd, ".env.local");
157
+ const envKey = "EARLYSEO_SITE_ID";
158
+ if (fs.existsSync(envPath)) {
159
+ const envContent = fs.readFileSync(envPath, "utf-8");
160
+ if (envContent.includes(envKey)) {
161
+ console.log(` ⏭ Skipped .env.local (${envKey} already set)`);
162
+ } else {
163
+ fs.appendFileSync(envPath, `\n# EarlySEO Blog SDK\n${envKey}=${siteId}\n`);
164
+ console.log(` ✅ Added ${envKey} to .env.local`);
165
+ }
166
+ } else {
167
+ fs.writeFileSync(envPath, `# EarlySEO Blog SDK\n${envKey}=${siteId}\n`, "utf-8");
168
+ console.log(` ✅ Created .env.local with ${envKey}`);
169
+ }
170
+
171
+ // 7. Done
172
+ rl.close();
173
+ console.log();
174
+ console.log(" ✓ Setup complete! Your blog is ready at /blog");
175
+ console.log();
176
+ console.log(" Next steps:");
177
+ console.log(" 1. Start your dev server: npm run dev");
178
+ console.log(" 2. Visit: http://localhost:3000/blog");
179
+ console.log(" 3. Publish articles from your EarlySEO dashboard");
180
+ console.log();
181
+ console.log(" Docs: https://www.npmjs.com/package/@earlyseo/blog");
182
+ console.log();
183
+ }
184
+
185
+ main().catch((err) => {
186
+ console.error(" ❌ Unexpected error:", err.message);
187
+ process.exit(1);
188
+ });