Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Website CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
env:
NEXT_TELEMETRY_DISABLED: 1
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
restore-keys: |
${{ runner.os }}-next-${{ hashFiles('package-lock.json') }}-
- run: npm ci
- run: npm run build:github
- run: npm run build
env:
NEXT_TELEMETRY_DISABLED: 1
- uses: actions/upload-pages-artifact@v3
Expand Down
46 changes: 46 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Website deployment

## Decision

The supported production target for `corelinkplatform.ir` is **GitHub Pages**.
The site is a static Next.js export. No Cloudflare/Vinext/Wrangler runtime is
part of the production contract.

## Reproducible build

From a clean checkout with Node 22.13 or newer:

```bash
npm ci
npm run build
test -d out
```

The GitHub Actions workflow performs the same build and uploads `out/`. A
release is evidenced by the successful `Deploy to GitHub Pages` run for the
exact `main` commit.

## Ownership and release checks

Web Experience owns site content/build correctness. SRE and Release owns the
production deployment/rollback procedure.

Before accepting a website release:

1. build the exact commit with `npm ci && npm run build`;
2. run repository checks;
3. review public maturity/device/SDK claims against repository/runtime evidence;
4. merge through review;
5. retain the successful GitHub Pages run URL and deployed commit.

## Rollback

Revert the offending `main` change through Git, then let the same GitHub Pages
workflow deploy the reverted commit. Record the bad release commit and the
successful rollback deployment run. Do not manually edit the Pages artifact.

## Internal tooling boundary

`npm run build:sites`, `npm run start:sites`, and `npm run install:sites`
are internal preview experiments. They are unsupported production alternatives
and their success does not satisfy WEB-01 deployment acceptance.
41 changes: 18 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,38 @@
# CoreLink Platform Website

Official website for [CoreLink Platform](https://corelinkplatform.ir): connected-product infrastructure for devices, applications, and digital services.
Official website for [CoreLink Platform](https://corelinkplatform.ir).

## Stack
## Supported production path

- Next.js 16 / React 19 / TypeScript
- Static export (`output: "export"`)
- GitHub Actions deployment to GitHub Pages
- No runtime server or database dependency
**GitHub Pages is the only supported production deployment path.**

- Runtime: static export from Next.js 16 / React 19 / TypeScript.
- Build: `npm ci && npm run build`.
- Artifact: `out/`.
- Deployment: `.github/workflows/deploy-pages.yml` on `main`.
- Production has no application server or database dependency.

See [DEPLOYMENT.md](DEPLOYMENT.md) for release evidence and rollback.

## Local development

```bash
npm ci
npm run dev
npm run build
```

## Verify the GitHub Pages build

```bash
npm run build:github
```

The deployable static site is generated in `out/`.

## GitHub Pages deployment

The workflow at `.github/workflows/deploy-pages.yml` builds and deploys every push to `main`.

In the GitHub repository settings:
## Internal preview tooling

1. Open **Settings → Pages**.
2. Set **Source** to **GitHub Actions**.
3. Ensure the custom domain is `corelinkplatform.ir`.
4. Point the domain DNS records to GitHub Pages, then enable **Enforce HTTPS** after verification.
The `*:sites` scripts and their Vinext/Wrangler/Vite/Cloudflare dependencies are
retained only for internal preview/tooling experiments. They are **not** a
supported CoreLink production deployment path and must not be used as release
evidence for this website.

## Public developer resources

- [API Contracts](https://github.com/CoreLinkPlatform/api-contracts)
- [TypeScript SDK](https://github.com/CoreLinkPlatform/sdk-typescript)
- [Python SDK](https://github.com/CoreLinkPlatform/sdk-python)
- [Java SDK](https://github.com/CoreLinkPlatform/sdk-java)
- [CLI](https://github.com/CoreLinkPlatform/cli)
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
"node": ">=22.13.0"
},
"scripts": {
"install:ci": "bash scripts/install-ci.sh",
"dev": "next dev",
"build": "bash scripts/build-verified.sh",
"build:github": "next build",
"start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
"build": "next build",
"test": "npm run build && node --test tests/rendered-html.test.mjs",
"test:sites": "npm run build:sites && node --test tests/sites-worker.test.mjs",
"validate:artifact": "bash scripts/validate-artifact.sh",
"lint": "bash scripts/sites-env.sh -- eslint . --ignore-pattern dist --ignore-pattern .next",
"build:sites": "bash scripts/build-verified.sh",
"start:sites": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
"install:sites": "bash scripts/install-ci.sh",
"db:generate": "bash scripts/sites-env.sh -- drizzle-kit generate"
},
"dependencies": {
Expand Down
31 changes: 6 additions & 25 deletions tests/rendered-html.test.mjs
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";

const developmentPreviewMeta =
/<meta(?=[^>]*\bname=["']codex-preview["'])(?=[^>]*\bcontent=["']development["'])[^>]*>/i;

test("renders development preview metadata", async () => {
const workerUrl = new URL("../dist/server/index.js", import.meta.url);
workerUrl.searchParams.set("test", `${process.pid}-${Date.now()}`);
const { default: worker } = await import(workerUrl.href);
test("renders production static HTML metadata", async () => {
const html = await readFile(new URL("../out/index.html", import.meta.url), "utf8");

const response = await worker.fetch(
new Request("http://localhost/", {
headers: { accept: "text/html" },
}),
{
ASSETS: {
fetch: async () => new Response("Not found", { status: 404 }),
},
},
{
waitUntil() {},
passThroughOnException() {},
},
);

assert.equal(response.status, 200);
assert.match(
response.headers.get("content-type") ?? "",
/^text\/html\b/i,
);
assert.match(await response.text(), developmentPreviewMeta);
assert.match(html, /<html\b(?=[^>]*\blang=["']fa["'])(?=[^>]*\bdir=["']rtl["'])[^>]*>/i);
assert.match(html, /<title>CoreLink Platform \| زیرساخت محصولات متصل<\/title>/i);
assert.match(html, developmentPreviewMeta);
});
33 changes: 33 additions & 0 deletions tests/sites-worker.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import assert from "node:assert/strict";
import test from "node:test";

const developmentPreviewMeta =
/<meta(?=[^>]*\bname=["']codex-preview["'])(?=[^>]*\bcontent=["']development["'])[^>]*>/i;

test("renders development preview metadata through Sites worker", async () => {
const workerUrl = new URL("../dist/server/index.js", import.meta.url);
workerUrl.searchParams.set("test", `${process.pid}-${Date.now()}`);
const { default: worker } = await import(workerUrl.href);

const response = await worker.fetch(
new Request("http://localhost/", {
headers: { accept: "text/html" },
}),
{
ASSETS: {
fetch: async () => new Response("Not found", { status: 404 }),
},
},
{
waitUntil() {},
passThroughOnException() {},
},
);

assert.equal(response.status, 200);
assert.match(
response.headers.get("content-type") ?? "",
/^text\/html\b/i,
);
assert.match(await response.text(), developmentPreviewMeta);
});
Loading