Skip to content

Commit d731359

Browse files
Publish CMAPI 1.0.0 and SourceNine Labs website
1 parent 5c3ad9b commit d731359

237 files changed

Lines changed: 31549 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# CMAPI repository ignore rules
2+
# Place this file at the repository root as: .gitignore
3+
14
# Visual Studio and IDE state
25
.vs/
36
.vscode/
@@ -36,14 +39,50 @@ packages/
3639
*.nuget.targets
3740

3841
# Local Planet Crafter, Unity, and Netcode references
42+
# Keep the explanation file, but never commit copied game assemblies.
3943
/CMAPI/Libs/*
4044
!/CMAPI/Libs/README.md
4145

42-
# Pinned native Doorstop release input is supplied by the release builder
46+
# Pinned native Doorstop input is imported locally by the release tools.
47+
# Keep its instructions and pin metadata in source control.
4348
/vendor/UnityDoorstop/*
4449
!/vendor/UnityDoorstop/README.md
4550
!/vendor/UnityDoorstop/PIN.json
4651

52+
# Website package-manager state
53+
/website/node_modules/
54+
/website/.pnp
55+
/website/.pnp.*
56+
/website/.yarn/*
57+
!/website/.yarn/patches/
58+
!/website/.yarn/plugins/
59+
!/website/.yarn/releases/
60+
!/website/.yarn/versions/
61+
62+
# Website test and generated output
63+
/website/coverage/
64+
/website/.next/
65+
/website/out/
66+
/website/dist/
67+
/website/.wrangler/
68+
/website/.sites-runtime/
69+
/website/outputs/
70+
/website/work/
71+
/website/.vercel/
72+
/website/next-env.d.ts
73+
74+
# Local environment and credentials
75+
.env
76+
.env.*
77+
!.env.example
78+
*.pem
79+
80+
# JavaScript package-manager diagnostics
81+
npm-debug.log*
82+
yarn-debug.log*
83+
yarn-error.log*
84+
.pnpm-debug.log*
85+
4786
# Runtime output and local diagnostics
4887
*.log
4988
logs/

CMAPI/Website/.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
audit=false
2+
fund=false
3+
update-notifier=false
4+
cache=.sites-runtime/npm-cache

CMAPI/Website/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# SourceNine Labs website and documentation
2+
3+
This repository contains the SourceNine Labs public website, the CMAPI 1.0
4+
documentation, the generated public API browser, and the SourceNine mod
5+
catalog. The deployable GitHub Pages site is committed under `public/`.
6+
7+
## Public site structure
8+
9+
- `index.html` — SourceNine Labs home;
10+
- `projects.html` — software and developer-tool directory;
11+
- `mods/` — data-driven game mod catalog and release notes;
12+
- `about.html` — SourceNine Labs overview;
13+
- `cmapi/` — CMAPI player, developer, API, reference, migration, and maintainer
14+
documentation;
15+
- `updates/cmapi.json` — official CMAPI update feed after it is published.
16+
17+
The organization pages and CMAPI docs intentionally share typography and color
18+
tokens while using different layouts. SourceNine pages are a restrained public
19+
website; CMAPI pages use a conventional three-column documentation layout.
20+
21+
## Edit content
22+
23+
Authoritative inputs live under `content/`:
24+
25+
- `content/cmapi/` contains the CMAPI Markdown documentation copied from the
26+
CMAPI repository;
27+
- `content/api/source/` contains the public C# contracts used to generate API
28+
signatures and member pages;
29+
- `content/site-data/mods.json` is the single mod-catalog data source.
30+
31+
Do not edit generated HTML under `public/cmapi/` by hand. Change the Markdown,
32+
C# XML comments, or catalog JSON and rebuild.
33+
34+
## Rebuild and validate
35+
36+
Requirements: Python 3.11 or later and Pandoc.
37+
38+
```powershell
39+
python tools/build_site.py
40+
python tools/validate_site.py
41+
python tools/build_site.py --check
42+
```
43+
44+
The validation pass checks local links and anchors, organization navigation,
45+
CMAPI version labels, the complete 59-type API surface, 1.0 helper services,
46+
the search index, and obsolete SourceNine URLs.
47+
48+
To refresh from a local CMAPI repository and rebuild in one command:
49+
50+
```powershell
51+
python tools/sync_cmapi_content.py "C:\Users\you\source\repos\CMAPI"
52+
```
53+
54+
## Preview locally
55+
56+
```powershell
57+
python -m http.server 8080 --directory public
58+
```
59+
60+
Open `http://localhost:8080`. Relative URLs are used throughout so the same
61+
output works at the GitHub Pages project path.
62+
63+
## Publish to GitHub Pages
64+
65+
The included workflow deploys `public/` on pushes to `main` and can also be run
66+
manually. In the GitHub repository settings, set Pages → Build and deployment →
67+
Source to **GitHub Actions**.
68+
69+
The production origin expected by CMAPI is:
70+
71+
```text
72+
https://sourceninelabs.github.io/CMAPI/
73+
```
74+
75+
See `updates/README.md` before publishing or replacing the live update feed.
76+
77+
## Optional workspace preview
78+
79+
The small Vinext shell under `app/` mirrors the SourceNine homepage for the
80+
workspace preview environment. GitHub Pages deploys the complete static site
81+
from `public/`; no server, account, or database is required in production.

CMAPI/Website/app/chatgpt-auth.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { headers } from "next/headers";
2+
import { redirect } from "next/navigation";
3+
4+
export type ChatGPTUser = {
5+
displayName: string;
6+
email: string;
7+
fullName: string | null;
8+
};
9+
10+
const USER_EMAIL_HEADER = "oai-authenticated-user-email";
11+
const USER_FULL_NAME_HEADER = "oai-authenticated-user-full-name";
12+
const USER_FULL_NAME_ENCODING_HEADER =
13+
"oai-authenticated-user-full-name-encoding";
14+
const PERCENT_ENCODED_UTF8 = "percent-encoded-utf-8";
15+
const SIGN_IN_PATH = "/signin-with-chatgpt";
16+
const SIGN_OUT_PATH = "/signout-with-chatgpt";
17+
const CALLBACK_PATH = "/callback";
18+
19+
export async function getChatGPTUser(): Promise<ChatGPTUser | null> {
20+
const requestHeaders = await headers();
21+
const email = requestHeaders.get(USER_EMAIL_HEADER);
22+
if (!email) return null;
23+
24+
const encodedFullName = requestHeaders.get(USER_FULL_NAME_HEADER);
25+
const fullName =
26+
encodedFullName &&
27+
requestHeaders.get(USER_FULL_NAME_ENCODING_HEADER) === PERCENT_ENCODED_UTF8
28+
? safeDecodeURIComponent(encodedFullName)
29+
: null;
30+
31+
return {
32+
displayName: fullName ?? email,
33+
email,
34+
fullName,
35+
};
36+
}
37+
38+
export async function requireChatGPTUser(
39+
returnTo: string,
40+
): Promise<ChatGPTUser> {
41+
const user = await getChatGPTUser();
42+
if (user) return user;
43+
44+
redirect(chatGPTSignInPath(returnTo));
45+
}
46+
47+
export function chatGPTSignInPath(returnTo: string): string {
48+
const safeReturnTo = safeRelativeReturnPath(returnTo);
49+
return `${SIGN_IN_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
50+
}
51+
52+
export function chatGPTSignOutPath(returnTo = "/"): string {
53+
const safeReturnTo = safeRelativeReturnPath(returnTo);
54+
return `${SIGN_OUT_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
55+
}
56+
57+
function safeRelativeReturnPath(value: string): string {
58+
if (!value.startsWith("/") || value.startsWith("//")) return "/";
59+
60+
let url: URL;
61+
try {
62+
url = new URL(value, "https://app.local");
63+
} catch {
64+
return "/";
65+
}
66+
if (url.origin !== "https://app.local") return "/";
67+
if (isReservedAuthPath(url.pathname)) return "/";
68+
69+
return `${url.pathname}${url.search}${url.hash}`;
70+
}
71+
72+
function isReservedAuthPath(pathname: string): boolean {
73+
return (
74+
pathname === SIGN_IN_PATH ||
75+
pathname === SIGN_OUT_PATH ||
76+
pathname === CALLBACK_PATH
77+
);
78+
}
79+
80+
function safeDecodeURIComponent(value: string): string | null {
81+
try {
82+
return decodeURIComponent(value);
83+
} catch {
84+
return null;
85+
}
86+
}

CMAPI/Website/app/globals.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@import url("/assets/styles.css");
2+
3+
html,
4+
body {
5+
margin: 0;
6+
}

CMAPI/Website/app/layout.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Metadata } from "next";
2+
import "./globals.css";
3+
4+
export const metadata: Metadata = {
5+
title: "SourceNine Labs",
6+
description: "SourceNine Labs software, documentation, developer tools, and game mods.",
7+
icons: {
8+
icon: [
9+
{ url: "/assets/favicon-32.png", sizes: "32x32", type: "image/png" },
10+
{ url: "/assets/favicon-192.png", sizes: "192x192", type: "image/png" },
11+
],
12+
shortcut: "/assets/favicon-32.png",
13+
apple: "/assets/apple-touch-icon.png",
14+
},
15+
openGraph: {
16+
title: "SourceNine Labs",
17+
description: "Software, documentation, developer tools, and game mods.",
18+
images: ["/assets/cmapi-banner.png"],
19+
},
20+
};
21+
22+
export default function RootLayout({
23+
children,
24+
}: Readonly<{
25+
children: React.ReactNode;
26+
}>) {
27+
return (
28+
<html lang="en">
29+
<body className="antialiased">{children}</body>
30+
</html>
31+
);
32+
}

CMAPI/Website/app/page.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
export default function Home() {
2+
return (
3+
<div className="site-page">
4+
<header className="topbar">
5+
<div className="topbar-inner">
6+
<a className="brand" href="/">
7+
<span className="brand-mark">S9</span>
8+
<span>SourceNine Labs</span>
9+
</a>
10+
<nav className="topnav org-nav" aria-label="Primary">
11+
<a className="current" href="/">Home</a>
12+
<a href="/projects.html">Projects</a>
13+
<a href="/mods/index.html">Mods</a>
14+
<a href="/about.html">About</a>
15+
<a href="https://github.com/SourceNineLabs">GitHub ↗</a>
16+
</nav>
17+
</div>
18+
</header>
19+
<main className="org-main" id="main">
20+
<section className="home-heading">
21+
<div>
22+
<p className="eyebrow">Software · tools · game mods</p>
23+
<h1>SourceNine Labs</h1>
24+
<p>
25+
SourceNine Labs is where I build software, modding tools,
26+
documentation, and game mods. The goal is simple: make useful
27+
things and explain them clearly.
28+
</p>
29+
<div className="home-actions">
30+
<a className="primary-link" href="/cmapi/index.html">CMAPI documentation</a>
31+
<a href="/projects.html">View projects</a>
32+
</div>
33+
</div>
34+
<div className="brand-image">
35+
<img src="/assets/source-nine-labs.png" alt="SourceNine Labs" />
36+
</div>
37+
</section>
38+
<section className="project-section">
39+
<div className="section-head">
40+
<div><p className="eyebrow">Developer platform</p><h2>CMAPI</h2></div>
41+
<span className="release-label">1.0.0 · Stable</span>
42+
</div>
43+
<p className="project-lead">
44+
A standalone modding platform made specifically for The Planet Crafter,
45+
with a public API, mod loader, live console, diagnostics, save backups,
46+
and its own installer.
47+
</p>
48+
<div className="project-links">
49+
<a className="primary-link" href="/cmapi/index.html">Read the documentation</a>
50+
<a href="/cmapi/getting-started/installation.html">Install CMAPI</a>
51+
<a href="/cmapi/getting-started/first-mod.html">Build a mod</a>
52+
<a href="/cmapi/api/index.html">API reference</a>
53+
</div>
54+
<dl className="project-facts">
55+
<div><dt>Runtime</dt><dd>Standalone</dd></div>
56+
<div><dt>Public API</dt><dd>1.0.0</dd></div>
57+
<div><dt>Tested game</dt><dd>Planet Crafter 2.008</dd></div>
58+
<div><dt>Platform</dt><dd>Windows x64</dd></div>
59+
</dl>
60+
</section>
61+
<section className="home-columns">
62+
<div><p className="eyebrow">Game mods</p><h2>SourceNine releases</h2><p>Each mod gets a straightforward page with its requirements, install steps, compatibility notes, and release history.</p><a className="text-link" href="/mods/index.html">Browse the mod catalog →</a></div>
63+
<div><p className="eyebrow">Documentation</p><h2>Docs that stay useful</h2><p>CMAPI's API pages come directly from the documentation shipped to mod authors, so the reference stays close to the code it describes.</p><a className="text-link" href="/about.html">About SourceNine Labs →</a></div>
64+
</section>
65+
</main>
66+
<footer className="site-footer"><span>© 2026 SourceNine Labs</span><span>Software · documentation · game mods</span></footer>
67+
</div>
68+
);
69+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { access, cp, mkdir, rm } from "node:fs/promises";
2+
import { resolve } from "node:path";
3+
import type { Plugin } from "vite";
4+
5+
async function exists(path: string): Promise<boolean> {
6+
try {
7+
await access(path);
8+
return true;
9+
} catch (error) {
10+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
11+
return false;
12+
}
13+
throw error;
14+
}
15+
}
16+
17+
// Packages Sites metadata and migrations after Vite finishes compiling.
18+
export function sites(): Plugin {
19+
let root = process.cwd();
20+
21+
return {
22+
name: "sites",
23+
apply: "build",
24+
configResolved(config) {
25+
root = config.root;
26+
},
27+
async closeBundle() {
28+
const outputDirectory = resolve(root, "dist", ".openai");
29+
const hostingConfig = resolve(root, ".openai", "hosting.json");
30+
const drizzleSource = resolve(root, "drizzle");
31+
32+
await rm(outputDirectory, { recursive: true, force: true });
33+
await mkdir(outputDirectory, { recursive: true });
34+
35+
if (await exists(hostingConfig)) {
36+
await cp(hostingConfig, resolve(outputDirectory, "hosting.json"));
37+
}
38+
if (await exists(drizzleSource)) {
39+
await cp(drizzleSource, resolve(outputDirectory, "drizzle"), {
40+
recursive: true,
41+
});
42+
}
43+
},
44+
};
45+
}

0 commit comments

Comments
 (0)