Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to react router 7, typescript, and misc. refactoring #27

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ac240b4
WIP typescript + sqlite changes
chrzrdx Oct 22, 2024
8c2fa83
update deps
chrzrdx Oct 25, 2024
e95a7c2
Minor fixes
chrzrdx Oct 25, 2024
c492025
Add typescript reset
chrzrdx Oct 25, 2024
dec9a20
Remove unneeded files
chrzrdx Oct 25, 2024
a71eda2
No preload attibute on images
chrzrdx Oct 25, 2024
ffacc6e
Formatting tweaks
chrzrdx Oct 25, 2024
ded3361
Convert to typescript
chrzrdx Oct 26, 2024
da21ddc
Fetch git feed from github API - not rss feed
chrzrdx Oct 26, 2024
185d177
Use encoding
chrzrdx Oct 26, 2024
404b0e6
remove unneeded files
chrzrdx Oct 26, 2024
e6d472e
Simplify the /theme route
chrzrdx Oct 26, 2024
d2fa7f8
Redirect on GET requests to the /theme route
chrzrdx Oct 26, 2024
3ecd9db
Clean up the root loader to use single fetch semantics
chrzrdx Oct 26, 2024
ccd5927
Clean unused imports
chrzrdx Oct 26, 2024
515be56
Update all other pages to single fetch semantics
chrzrdx Oct 26, 2024
0d13418
Lint
chrzrdx Oct 26, 2024
0e38fc3
Reduce to 20 commits
chrzrdx Oct 26, 2024
d410b20
Enable single fetch
chrzrdx Oct 26, 2024
9ee811a
Add date, lastmod to docs as well
chrzrdx Oct 26, 2024
7875267
Assume date and lastmod is always present on markdown files
chrzrdx Oct 27, 2024
bbbef0a
Fix paths
chrzrdx Oct 27, 2024
596cf96
Avoid initializing marked multiple times
chrzrdx Oct 28, 2024
b4cb985
Clean up props
chrzrdx Oct 28, 2024
1c610b8
Fix dates on content
chrzrdx Oct 28, 2024
0dd86b4
Make the git stuff typed
chrzrdx Oct 28, 2024
4573837
Remove sqlite, update deps
chrzrdx Oct 28, 2024
dbd3600
Got everything but pretty dates working
chrzrdx Oct 28, 2024
3447265
Fix spaces around the link
chrzrdx Oct 28, 2024
8004d3e
Refactor the 404 and index layouts
chrzrdx Oct 28, 2024
47d3f93
Minor cleanup
chrzrdx Oct 28, 2024
b5b2eab
Remove unneeded constants
chrzrdx Oct 28, 2024
2194549
Fix typo
chrzrdx Oct 28, 2024
e628b7d
Remove unneeded functions
chrzrdx Oct 28, 2024
141c85c
Update packages
chrzrdx Oct 30, 2024
c012274
update packages
chrzrdx Oct 30, 2024
3f89043
Update packages
chrzrdx Nov 10, 2024
6615eed
Update packages, migrate to routes.ts
chrzrdx Nov 17, 2024
d462d88
fix type
chrzrdx Nov 17, 2024
20fa1e9
Update dependencies
chrzrdx Nov 23, 2024
33fc1fa
Get close to rr v7
chrzrdx Nov 23, 2024
8204d81
WIP migrate to rr v7
chrzrdx Nov 23, 2024
12426d5
Got things mostly working
chrzrdx Nov 24, 2024
b6fbaa0
Fix vite config
chrzrdx Nov 27, 2024
70eced9
Fix routes and types, remove etags
chrzrdx Nov 27, 2024
64831dc
Minor refactor
chrzrdx Nov 27, 2024
f90506a
Print pretty dates
chrzrdx Nov 27, 2024
f5f5563
Fix vite config
chrzrdx Nov 27, 2024
33be75f
Update deps
chrzrdx Nov 28, 2024
bfa704f
Minor tweaks
chrzrdx Nov 28, 2024
e5320bd
Add table of contents
chrzrdx Nov 28, 2024
244aeae
Fix theme cookies
chrzrdx Nov 28, 2024
ef6ebb8
Fix table of contents
chrzrdx Nov 28, 2024
d01f2de
Update deps
chrzrdx Nov 29, 2024
aaa5e8d
Move to react-router-serve instead of custom server
chrzrdx Nov 29, 2024
5bfe6ee
Address self review feedback
chrzrdx Nov 29, 2024
fd0af61
Refactor content store
chrzrdx Nov 29, 2024
4e6c079
Cosmetic improvements to toc
chrzrdx Nov 29, 2024
48e6f56
Lint
chrzrdx Nov 29, 2024
87a0748
Fixes: https://github.com/remix-run/remix/pull/10240
chrzrdx Nov 29, 2024
4d73dbd
Update deps
chrzrdx Nov 30, 2024
590e111
Update to React 19
chrzrdx Dec 9, 2024
396fcf1
Update dependencies
chrzrdx Dec 21, 2024
2f1b81a
Update dependecies
chrzrdx Dec 21, 2024
1602158
Fix types
chrzrdx Dec 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
.env
chrzrdx marked this conversation as resolved.
Show resolved Hide resolved
!.env.example
.cache
.DS_Store
.react-router
build
node_modules
git-feed.json
latest-release.json
*.tsbuildinfo
.tool-versions
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ This website hosts the documentation for the [reoserv][reoserv] project
Before you can run this project, you need to have the following installed on
your machine:

- [Node.js][node] v20+ (for local development with the Remix web framework)
- [Bun][bun] v1.0.36+ (a modern package manager & script runner for Node.js
- [Node.js][node] v22+ (for local development with the Remix web framework)
- [Bun][bun] v1.1.36+ (a modern package manager & script runner for Node.js
projects)

Verify that both are setup on your system and meet our version requirements by
running:

```sh
bun --version # eg: 1.0.36
node --version # eg: v20.4.0
bun --version # eg: 1.1.36
node --version # eg: v22.10.0
```

## Installation
Expand Down Expand Up @@ -47,6 +47,24 @@ To build the project, run:
bun run build
```

## Deployment

Deploy the output of `bun run build`

```
├── package.json
├── bun.lockb
├── build/
│ ├── client/ # Static assets
│ └── server/ # Server-side code
```

Serve in production with:

```
bun run start
```

## Linting / Formatting

To format, lint and apply fixes at the same time, use:
Expand All @@ -61,6 +79,10 @@ To format markdown files (docs, news), use:
bun run lint:docs
```

---

Built with ❤️ using React Router.

[reoserv]: https://github.com/sorokya/reoserv
[sorokya]: https://github.com/sorokya
[node]: https://nodejs.org/
Expand Down
69 changes: 69 additions & 0 deletions app/.server/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from 'node:fs/promises';
import { glob } from 'glob';
import { parseMarkdown } from './utils/parse-markdown';

type Content = Awaited<ReturnType<typeof parseMarkdown>>;
type ContentValue = { content: Content; mtimeMs: number };
type ContentStore = Record<string, ContentValue>;

// Simple in-memory store
let contentStore: ContentStore | null = null;

async function getFileContent(
filePath: string,
cached?: ContentValue,
): Promise<ContentValue> {
const stats = await fs.stat(filePath);

if (cached && cached.mtimeMs === stats.mtimeMs) {
return cached;
}

const content = await parseMarkdown(filePath);
return { content, mtimeMs: stats.mtimeMs };
}

async function loadAllContent(): Promise<ContentStore> {
const files = await glob('content/**/*.md');

const contents = await Promise.all(
files.map(async (filePath) => {
const key = filePath.replace(/^content\//, '').replace(/\.md$/, '');

try {
const content = await getFileContent(filePath, contentStore?.[key]);
return [key, content] as const;
} catch (error) {
console.error(`Failed to load content for ${key}:`, error);
return undefined;
}
}),
);

return Object.fromEntries(contents.filter(Boolean));
}

async function getStore(): Promise<ContentStore> {
if (process.env.NODE_ENV === 'development' || !contentStore) {
contentStore = await loadAllContent();
}
return contentStore;
}

async function getContentByType(
type: string,
): Promise<Record<string, Content>> {
const store = await getStore();
return Object.fromEntries(
Object.entries(store)
.filter(([key]) => key.startsWith(`${type}/`))
.map(([key, data]) => [key, data.content]),
);
}

async function getContentByKey(key: string): Promise<Content | undefined> {
const store = await getStore();
return store[key]?.content;
}

export { getContentByKey, getContentByType };
1 change: 0 additions & 1 deletion app/.server/etag.js

This file was deleted.

1 change: 0 additions & 1 deletion app/.server/fs.js

This file was deleted.

10 changes: 0 additions & 10 deletions app/.server/get-docs-page.js

This file was deleted.

7 changes: 7 additions & 0 deletions app/.server/get-docs-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getContentByKey } from './content';

async function getDocsPage(slug: string) {
return await getContentByKey(`docs/${slug}`);
}

export { getDocsPage };
45 changes: 0 additions & 45 deletions app/.server/get-git-feed.js

This file was deleted.

69 changes: 69 additions & 0 deletions app/.server/get-git-feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from 'node:fs';
import { z } from 'zod';

const GithubCommitsAPISchema = z.array(
z.object({
sha: z.string().min(1),
html_url: z.string().url().min(1),
commit: z.object({
message: z.string().min(1),
committer: z.object({
date: z.string().datetime().min(1),
}),
}),
}),
);

const StoredCommitsSchema = z.array(
z.object({
id: z.string().min(1),
link: z.string().url().min(1),
content: z.string().min(1),
timestamp: z.string().datetime().min(1),
}),
);

const GITHUB_URL =
'https://api.github.com/repos/sorokya/reoserv/commits?sha=master&per_page=20';
const DATA_FILE_PATH = 'git-feed.json';
const MAX_FILE_AGE = 5 * 60 * 1000; // 5 minutes in milliseconds

async function getGitFeed() {
// Check file age or existence
const fileStats =
fs.existsSync(DATA_FILE_PATH) && fs.statSync(DATA_FILE_PATH);
const fileAge = fileStats
? Date.now() - fileStats.mtime.getTime()
: Number.POSITIVE_INFINITY;

if (!fileStats || fileAge > MAX_FILE_AGE) {
const commits = await fetchGitFeed();
const json = JSON.stringify(commits);
fs.writeFileSync(DATA_FILE_PATH, json);
return commits;
}

const fileContents = fs.readFileSync(DATA_FILE_PATH, { encoding: 'utf8' });
const json = JSON.parse(fileContents);
const commits = StoredCommitsSchema.parse(json);
return commits;
}

async function fetchGitFeed() {
const response = await fetch(GITHUB_URL);

if (!response.ok) {
throw new Error('Failed to fetch commits from github');
}

const commits = GithubCommitsAPISchema.parse(await response.json());

return commits.map((commit) => ({
id: commit.sha,
link: commit.html_url,
content: commit.commit.message.split(/\r?\n/)[0],
timestamp: new Date(commit.commit.committer.date).toISOString(),
}));
}

export { getGitFeed };
46 changes: 0 additions & 46 deletions app/.server/get-latest-release.js

This file was deleted.

58 changes: 58 additions & 0 deletions app/.server/get-latest-release.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import fs from 'node:fs';
import { z } from 'zod';

const GithubReleaseAPISchema = z.object({
name: z.string().min(1),
published_at: z.string().datetime().min(1),
html_url: z.string().url().min(1),
});

const LatestReleaseSchema = z.object({
name: z.string().min(1),
timestamp: z.string().datetime(),
link: z.string().url().min(1),
});

const GITHUB_URL =
'https://api.github.com/repos/sorokya/reoserv/releases/latest';
const DATA_FILE_PATH = 'latest-release.json';
const MAX_FILE_AGE = 5 * 60 * 1000; // 5 minutes in milliseconds

async function getLatestRelease() {
// Check file age or existence
const fileStats =
fs.existsSync(DATA_FILE_PATH) && fs.statSync(DATA_FILE_PATH);
const fileAge = fileStats
? Date.now() - fileStats.mtime.getTime()
: Number.POSITIVE_INFINITY;

if (!fileStats || fileAge > MAX_FILE_AGE) {
const latestRelease = await fetchLatestRelease();
const json = JSON.stringify(latestRelease);
fs.writeFileSync(DATA_FILE_PATH, json);
return latestRelease;
}

const fileContents = fs.readFileSync(DATA_FILE_PATH, { encoding: 'utf8' });
const json = JSON.parse(fileContents);
const latestRelease = LatestReleaseSchema.parse(json);
return latestRelease;
}

async function fetchLatestRelease() {
const response = await fetch(GITHUB_URL);

if (!response.ok) {
throw new Error('Failed to fetch the latest release from github');
}

const release = GithubReleaseAPISchema.parse(await response.json());

return {
name: release.name,
timestamp: new Date(release.published_at).toISOString(),
link: release.html_url,
};
}

export { getLatestRelease };
12 changes: 0 additions & 12 deletions app/.server/get-news-article.js

This file was deleted.

Loading