diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2528810 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 2a90aa2..784af9a 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -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 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..bdce216 --- /dev/null +++ b/DEPLOYMENT.md @@ -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. diff --git a/README.md b/README.md index 53184c7..8ad7acd 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/package.json b/package.json index e298118..0c5c99b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/tests/rendered-html.test.mjs b/tests/rendered-html.test.mjs index 5392f9f..9010442 100644 --- a/tests/rendered-html.test.mjs +++ b/tests/rendered-html.test.mjs @@ -1,33 +1,14 @@ import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; import test from "node:test"; const developmentPreviewMeta = /]*\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, /]*\blang=["']fa["'])(?=[^>]*\bdir=["']rtl["'])[^>]*>/i); + assert.match(html, /