From 29a017e76b3912b7aaf348f2ac0be721894639cf Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 20 Dec 2025 16:56:21 +1100 Subject: [PATCH 1/4] Separate React and Core SDK --- packages/core-sdk/package.json | 34 ++++++++++++++ .../{react-sdk => core-sdk}/src/client.ts | 22 ++++++++++ packages/core-sdk/src/index.ts | 10 +++++ packages/core-sdk/src/types.ts | 34 ++++++++++++++ packages/{react-sdk => core-sdk}/src/utils.ts | 8 +++- packages/core-sdk/tsconfig.json | 21 +++++++++ packages/react-sdk/package.json | 4 +- .../src/components/ChangelogPost.tsx | 2 +- packages/react-sdk/src/hooks/usePosts.ts | 2 +- packages/react-sdk/src/index.ts | 18 +++++--- packages/react-sdk/src/types.ts | 44 +++++-------------- pnpm-lock.yaml | 13 ++++++ 12 files changed, 166 insertions(+), 46 deletions(-) create mode 100644 packages/core-sdk/package.json rename packages/{react-sdk => core-sdk}/src/client.ts (70%) create mode 100644 packages/core-sdk/src/index.ts create mode 100644 packages/core-sdk/src/types.ts rename packages/{react-sdk => core-sdk}/src/utils.ts (77%) create mode 100644 packages/core-sdk/tsconfig.json diff --git a/packages/core-sdk/package.json b/packages/core-sdk/package.json new file mode 100644 index 0000000..2203eb9 --- /dev/null +++ b/packages/core-sdk/package.json @@ -0,0 +1,34 @@ +{ + "name": "@changespage/core", + "version": "0.1.0", + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rimraf dist && tsc", + "prepublishOnly": "npm run build" + }, + "dependencies": {}, + "devDependencies": { + "rimraf": "^6.1.0", + "typescript": "^5.3.3" + }, + "author": "Arjun Komath ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/techulus/changes-page.git" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/react-sdk/src/client.ts b/packages/core-sdk/src/client.ts similarity index 70% rename from packages/react-sdk/src/client.ts rename to packages/core-sdk/src/client.ts index 3a1907b..cafca0e 100644 --- a/packages/react-sdk/src/client.ts +++ b/packages/core-sdk/src/client.ts @@ -52,8 +52,30 @@ export function createChangesPageClient(config: ClientConfig): ChangesPageClient return result.posts[0] ?? null; } + async function getPinnedPost(): Promise { + const response = await fetch(`${baseUrl}/api/pinned`, { + headers: { + "X-API-Version": API_VERSION, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + return null; + } + const errorData = await response.json().catch(() => ({})); + const errorMessage = + (errorData as { error?: string }).error || response.statusText; + throw new Error(errorMessage); + } + + const data: Post | null = await response.json(); + return data; + } + return { getPosts, getLatestPost, + getPinnedPost, }; } diff --git a/packages/core-sdk/src/index.ts b/packages/core-sdk/src/index.ts new file mode 100644 index 0000000..5ff08e6 --- /dev/null +++ b/packages/core-sdk/src/index.ts @@ -0,0 +1,10 @@ +export { createChangesPageClient } from "./client"; +export { formatDate, getTagLabel, parseDate } from "./utils"; +export type { + ChangesPageClient, + ClientConfig, + GetPostsOptions, + GetPostsResult, + Post, + PostTag, +} from "./types"; diff --git a/packages/core-sdk/src/types.ts b/packages/core-sdk/src/types.ts new file mode 100644 index 0000000..39bcf2a --- /dev/null +++ b/packages/core-sdk/src/types.ts @@ -0,0 +1,34 @@ +export type PostTag = "fix" | "new" | "improvement" | "announcement" | "alert"; + +export interface Post { + id: string; + title: string; + content: string; + tags: PostTag[]; + publication_date: string | null; + updated_at: string; + created_at: string; + url: string; + plain_text_content: string; +} + +export interface ClientConfig { + baseUrl: string; +} + +export interface GetPostsOptions { + limit?: number; + offset?: number; +} + +export interface GetPostsResult { + posts: Post[]; + totalCount: number; + hasMore: boolean; +} + +export interface ChangesPageClient { + getPosts: (options?: GetPostsOptions) => Promise; + getLatestPost: () => Promise; + getPinnedPost: () => Promise; +} diff --git a/packages/react-sdk/src/utils.ts b/packages/core-sdk/src/utils.ts similarity index 77% rename from packages/react-sdk/src/utils.ts rename to packages/core-sdk/src/utils.ts index 14ddfba..89dc387 100644 --- a/packages/react-sdk/src/utils.ts +++ b/packages/core-sdk/src/utils.ts @@ -16,9 +16,11 @@ export function formatDate( dateString: string | null, locale: string = "en-US" ): string { - if (!dateString) return ""; + if (!dateString || !dateString.trim()) return ""; const date = new Date(dateString); + if (isNaN(date.getTime())) return ""; + return date.toLocaleDateString(locale, { year: "numeric", month: "long", @@ -28,5 +30,7 @@ export function formatDate( export function parseDate(dateString: string | null): Date | null { if (!dateString) return null; - return new Date(dateString); + const date = new Date(dateString); + if (isNaN(date.getTime())) return null; + return date; } diff --git a/packages/core-sdk/tsconfig.json b/packages/core-sdk/tsconfig.json new file mode 100644 index 0000000..dd6a803 --- /dev/null +++ b/packages/core-sdk/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index ee1cd19..a914a8b 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -17,7 +17,9 @@ "build": "rimraf dist && tsc", "prepublishOnly": "npm run build" }, - "dependencies": {}, + "dependencies": { + "@changespage/core": "workspace:*" + }, "devDependencies": { "@types/react": "^18.3.18", "react": "^18.3.1", diff --git a/packages/react-sdk/src/components/ChangelogPost.tsx b/packages/react-sdk/src/components/ChangelogPost.tsx index a90e568..34b5273 100644 --- a/packages/react-sdk/src/components/ChangelogPost.tsx +++ b/packages/react-sdk/src/components/ChangelogPost.tsx @@ -1,5 +1,5 @@ +import { formatDate, parseDate } from "@changespage/core"; import type { ChangelogPostProps, ChangelogPostRenderProps } from "../types"; -import { formatDate, parseDate } from "../utils"; export function ChangelogPost({ post, locale, children }: ChangelogPostProps) { const renderProps: ChangelogPostRenderProps = { diff --git a/packages/react-sdk/src/hooks/usePosts.ts b/packages/react-sdk/src/hooks/usePosts.ts index acc823f..d79b24b 100644 --- a/packages/react-sdk/src/hooks/usePosts.ts +++ b/packages/react-sdk/src/hooks/usePosts.ts @@ -1,7 +1,7 @@ "use client"; import { useState, useCallback, useEffect, useRef } from "react"; -import type { ChangesPageClient, Post } from "../types"; +import type { ChangesPageClient, Post } from "@changespage/core"; export interface UsePostsInitialData { posts: Post[]; diff --git a/packages/react-sdk/src/index.ts b/packages/react-sdk/src/index.ts index ef4aaeb..970c8b0 100644 --- a/packages/react-sdk/src/index.ts +++ b/packages/react-sdk/src/index.ts @@ -1,17 +1,21 @@ -export { createChangesPageClient } from "./client"; -export { ChangelogPost } from "./components"; -export { usePosts } from "./hooks"; -export { formatDate, getTagLabel, parseDate } from "./utils"; +export { + createChangesPageClient, + formatDate, + getTagLabel, + parseDate, +} from "@changespage/core"; export type { ChangesPageClient, - ChangelogPostProps, - ChangelogPostRenderProps, ClientConfig, GetPostsOptions, GetPostsResult, Post, PostTag, -} from "./types"; +} from "@changespage/core"; + +export { ChangelogPost } from "./components"; +export { usePosts } from "./hooks"; +export type { ChangelogPostProps, ChangelogPostRenderProps } from "./types"; export type { UsePostsInitialData, UsePostsOptions, diff --git a/packages/react-sdk/src/types.ts b/packages/react-sdk/src/types.ts index c5d8ee9..b418008 100644 --- a/packages/react-sdk/src/types.ts +++ b/packages/react-sdk/src/types.ts @@ -1,33 +1,14 @@ import type { ReactNode } from "react"; +import type { PostTag } from "@changespage/core"; -export type PostTag = "fix" | "new" | "improvement" | "announcement" | "alert"; - -export interface Post { - id: string; - title: string; - content: string; - tags: PostTag[]; - publication_date: string | null; - updated_at: string; - created_at: string; - url: string; - plain_text_content: string; -} - -export interface ClientConfig { - baseUrl: string; -} - -export interface GetPostsOptions { - limit?: number; - offset?: number; -} - -export interface GetPostsResult { - posts: Post[]; - totalCount: number; - hasMore: boolean; -} +export type { + ChangesPageClient, + ClientConfig, + GetPostsOptions, + GetPostsResult, + Post, + PostTag, +} from "@changespage/core"; export interface ChangelogPostRenderProps { id: string; @@ -41,12 +22,7 @@ export interface ChangelogPostRenderProps { } export interface ChangelogPostProps { - post: Post; + post: import("@changespage/core").Post; locale?: string; children: (props: ChangelogPostRenderProps) => ReactNode; } - -export interface ChangesPageClient { - getPosts: (options?: GetPostsOptions) => Promise; - getLatestPost: () => Promise; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98a4dff..a292cc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -455,7 +455,20 @@ importers: specifier: ^5 version: 5.8.3 + packages/core-sdk: + devDependencies: + rimraf: + specifier: ^6.1.0 + version: 6.1.2 + typescript: + specifier: ^5.3.3 + version: 5.8.3 + packages/react-sdk: + dependencies: + '@changespage/core': + specifier: workspace:* + version: link:../core-sdk devDependencies: '@types/react': specifier: ^18.3.18 From 37ae90bf7b69f65c2eeac204f2c6482775d626c8 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 20 Dec 2025 17:07:48 +1100 Subject: [PATCH 2/4] Clean up SDK --- apps/web/pages/changelog.tsx | 14 +++- packages/core-sdk/README.md | 81 +++++++++++++++++++ packages/core-sdk/src/index.ts | 2 +- packages/core-sdk/src/utils.ts | 23 ------ packages/react-sdk/README.md | 6 +- .../src/components/ChangelogPost.tsx | 6 +- packages/react-sdk/src/index.ts | 7 +- packages/react-sdk/src/types.ts | 8 +- 8 files changed, 101 insertions(+), 46 deletions(-) create mode 100644 packages/core-sdk/README.md diff --git a/apps/web/pages/changelog.tsx b/apps/web/pages/changelog.tsx index b5b6a78..669a019 100644 --- a/apps/web/pages/changelog.tsx +++ b/apps/web/pages/changelog.tsx @@ -47,11 +47,17 @@ export default function Changelog({
{posts.map((post, index) => ( - {({ title, content, tags, formattedDate }) => ( + {({ title, content, tags, publicationDate }) => (
- + {publicationDate && ( + + )}

{title}

diff --git a/packages/core-sdk/README.md b/packages/core-sdk/README.md new file mode 100644 index 0000000..2bad938 --- /dev/null +++ b/packages/core-sdk/README.md @@ -0,0 +1,81 @@ +# @changespage/core + +Framework-agnostic JavaScript SDK for changes.page. + +## Installation + +```bash +npm install @changespage/core +``` + +## Usage + +```ts +import { createChangesPageClient } from '@changespage/core'; + +const client = createChangesPageClient({ + baseUrl: 'https://yourpage.changes.page' +}); + +const { posts, totalCount, hasMore } = await client.getPosts({ limit: 10 }); + +const latestPost = await client.getLatestPost(); + +const pinnedPost = await client.getPinnedPost(); +``` + +## API + +### `createChangesPageClient(config)` + +| Option | Type | Description | +|--------|------|-------------| +| `baseUrl` | `string` | Your changes.page URL | + +### `client.getPosts(options?)` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `limit` | `number` | 10 | Posts per page (max 50) | +| `offset` | `number` | 0 | Pagination offset | + +Returns `{ posts, totalCount, hasMore }` + +### `client.getLatestPost()` + +Returns the most recent post or `null`. + +### `client.getPinnedPost()` + +Returns the pinned post or `null` if none is pinned. + +## Utilities + +### `getTagLabel(tag)` + +Returns a display label for a post tag. + +```ts +import { getTagLabel } from '@changespage/core'; + +getTagLabel('new'); // "New" +getTagLabel('fix'); // "Fix" +``` + +## Types + +```ts +type PostTag = 'fix' | 'new' | 'improvement' | 'announcement' | 'alert'; + +interface Post { + id: string; + title: string; + content: string; + tags: PostTag[]; + publication_date: string | null; + updated_at: string; + created_at: string; + url: string; + plain_text_content: string; +} +``` diff --git a/packages/core-sdk/src/index.ts b/packages/core-sdk/src/index.ts index 5ff08e6..049d6bb 100644 --- a/packages/core-sdk/src/index.ts +++ b/packages/core-sdk/src/index.ts @@ -1,5 +1,5 @@ export { createChangesPageClient } from "./client"; -export { formatDate, getTagLabel, parseDate } from "./utils"; +export { getTagLabel } from "./utils"; export type { ChangesPageClient, ClientConfig, diff --git a/packages/core-sdk/src/utils.ts b/packages/core-sdk/src/utils.ts index 89dc387..dbcf64c 100644 --- a/packages/core-sdk/src/utils.ts +++ b/packages/core-sdk/src/utils.ts @@ -11,26 +11,3 @@ const tagLabels: Record = { export function getTagLabel(tag: PostTag): string { return tagLabels[tag] ?? tag; } - -export function formatDate( - dateString: string | null, - locale: string = "en-US" -): string { - if (!dateString || !dateString.trim()) return ""; - - const date = new Date(dateString); - if (isNaN(date.getTime())) return ""; - - return date.toLocaleDateString(locale, { - year: "numeric", - month: "long", - day: "numeric", - }); -} - -export function parseDate(dateString: string | null): Date | null { - if (!dateString) return null; - const date = new Date(dateString); - if (isNaN(date.getTime())) return null; - return date; -} diff --git a/packages/react-sdk/README.md b/packages/react-sdk/README.md index d8403e0..66ea2c8 100644 --- a/packages/react-sdk/README.md +++ b/packages/react-sdk/README.md @@ -25,10 +25,10 @@ export default async function ChangelogPage() {
{posts.map(post => ( - {({ title, content, tags, formattedDate, url }) => ( + {({ title, content, tags, publicationDate, url }) => (

{title}

- + {publicationDate && }
{tags.map(t => {t})}
{content}
@@ -65,7 +65,7 @@ Returns the most recent post or `null`. Render prop component exposing: -- `id`, `title`, `content` (markdown), `plainText`, `tags`, `date`, `formattedDate`, `url` +- `id`, `title`, `content` (markdown), `plainText`, `tags`, `publicationDate`, `url` ## Hook diff --git a/packages/react-sdk/src/components/ChangelogPost.tsx b/packages/react-sdk/src/components/ChangelogPost.tsx index 34b5273..4e417e0 100644 --- a/packages/react-sdk/src/components/ChangelogPost.tsx +++ b/packages/react-sdk/src/components/ChangelogPost.tsx @@ -1,15 +1,13 @@ -import { formatDate, parseDate } from "@changespage/core"; import type { ChangelogPostProps, ChangelogPostRenderProps } from "../types"; -export function ChangelogPost({ post, locale, children }: ChangelogPostProps) { +export function ChangelogPost({ post, children }: ChangelogPostProps) { const renderProps: ChangelogPostRenderProps = { id: post.id, title: post.title, content: post.content, plainText: post.plain_text_content, tags: post.tags, - date: parseDate(post.publication_date), - formattedDate: formatDate(post.publication_date, locale), + publicationDate: post.publication_date, url: post.url, }; diff --git a/packages/react-sdk/src/index.ts b/packages/react-sdk/src/index.ts index 970c8b0..cda0b21 100644 --- a/packages/react-sdk/src/index.ts +++ b/packages/react-sdk/src/index.ts @@ -1,9 +1,4 @@ -export { - createChangesPageClient, - formatDate, - getTagLabel, - parseDate, -} from "@changespage/core"; +export { createChangesPageClient, getTagLabel } from "@changespage/core"; export type { ChangesPageClient, ClientConfig, diff --git a/packages/react-sdk/src/types.ts b/packages/react-sdk/src/types.ts index b418008..60017da 100644 --- a/packages/react-sdk/src/types.ts +++ b/packages/react-sdk/src/types.ts @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import type { PostTag } from "@changespage/core"; +import type { Post, PostTag } from "@changespage/core"; export type { ChangesPageClient, @@ -16,13 +16,11 @@ export interface ChangelogPostRenderProps { content: string; plainText: string; tags: PostTag[]; - date: Date | null; - formattedDate: string; + publicationDate: string | null; url: string; } export interface ChangelogPostProps { - post: import("@changespage/core").Post; - locale?: string; + post: Post; children: (props: ChangelogPostRenderProps) => ReactNode; } From 6172106ea4fdbb219282ccec98b069fd10183db5 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 20 Dec 2025 17:57:47 +1100 Subject: [PATCH 3/4] Make internal packages private --- package.json | 1 + packages/react-sdk/package.json | 2 +- packages/supabase/package.json | 1 + packages/ui/package.json | 1 + packages/utils/package.json | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1062b37..ef938ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "changes-page", "version": "1.1.0", + "private": true, "scripts": { "build": "pnpm --filter './packages/*' -r build", "dev:page": "pnpm --filter './apps/page' -r dev", diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index a914a8b..a68e8f9 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@changespage/react", - "version": "0.1.0", + "version": "0.2.0", "type": "module", "module": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/supabase/package.json b/packages/supabase/package.json index 977d166..827456c 100644 --- a/packages/supabase/package.json +++ b/packages/supabase/package.json @@ -1,5 +1,6 @@ { "name": "@changespage/supabase", + "private": true, "version": "1.0.0", "exports": { "./types": "./dist/types/index.js", diff --git a/packages/ui/package.json b/packages/ui/package.json index 9265ef0..b83522c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,5 +1,6 @@ { "name": "@changespage/ui", + "private": true, "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/utils/package.json b/packages/utils/package.json index 890a3c2..e47e18b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,5 +1,6 @@ { "name": "@changespage/utils", + "private": true, "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", From 6bfa4e3c0ee4d36938cb45c13c8ade7be50f3193 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 20 Dec 2025 18:00:33 +1100 Subject: [PATCH 4/4] Use published react package --- apps/web/package.json | 2 +- pnpm-lock.yaml | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index e070bf1..007937a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,7 +12,7 @@ "dependencies": { "@ai-sdk/react": "^2.0.115", "@arcjet/next": "1.0.0-alpha.20", - "@changespage/react": "workspace:*", + "@changespage/react": "0.2.0", "@changespage/supabase": "workspace:*", "@changespage/ui": "workspace:*", "@changespage/utils": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a292cc2..dc6a5db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,8 +229,8 @@ importers: specifier: 1.0.0-alpha.20 version: 1.0.0-alpha.20(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(next@14.2.35(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@changespage/react': - specifier: workspace:* - version: link:../../packages/react-sdk + specifier: 0.2.0 + version: 0.2.0(react@18.3.1) '@changespage/supabase': specifier: workspace:* version: link:../../packages/supabase @@ -648,6 +648,11 @@ packages: '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + '@changespage/react@0.2.0': + resolution: {integrity: sha512-o6EvLxarnw6Xegwm7F3rI6/R3RoNUDhJgJKHaq7iTQVuhF2qiCixo2+gL09va+rLA3WZHhRPKrWjwbWU+nFcfw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -6434,6 +6439,11 @@ snapshots: '@bufbuild/protobuf@1.10.0': {} + '@changespage/react@0.2.0(react@18.3.1)': + dependencies: + '@changespage/core': link:packages/core-sdk + react: 18.3.1 + '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3