openvibers/openvibe.sites Commits

Test + CI: dist is current, every placeholder says so, JSON-LD parses, vhosts match, no pricing copy, no placeholder for a running service

OP OpenVibers committed 23 Sept 2026 · 422c6bb46d · 3 files, +67 −1


Changes

ddiff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
nnew file mode 100644
iindex 0000000..5ad3a95
---- /dev/null
++++ b/.github/workflows/ci.yml
@@@ -0,0 +1,16 @@
++name: ci
++on:
++ push:
++ branches: [main]
++ pull_request:
++jobs:
++ test:
++ runs-on: ubuntu-latest
++ steps:
++ - uses: actions/checkout@v4
++ - uses: actions/setup-node@v4
++ with:
++ node-version: 22.22.1 # production runs Node 22
++ cache: npm
++ - run: npm ci --no-audit --no-fund
++ - run: npm test
ddiff --git a/package.json b/package.json
iindex ecea6d0..f39927b 100644
---- a/package.json
++++ b/package.json
@@@ -2,8 +2,14 @@
"name": "openvibe-sites",
"private": true,
"description": "Placeholder and hub pages for the planned OpenVibe domains (static build)",
-- "scripts": { "build": "node build.js" },
++ "scripts": {
++ "build": "node build.js",
++ "test": "node test/build.test.js"
++ },
"dependencies": {
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.0.0"
++ },
++ "engines": {
++ "node": ">=20"
}
}
ddiff --git a/test/build.test.js b/test/build.test.js
nnew file mode 100644
iindex 0000000..7f9ab51
---- /dev/null
++++ b/test/build.test.js
@@@ -0,0 +1,44 @@
++'use strict';
++// OpenVibe.Sites: the built placeholders are current, complete and honest.
++// node test/build.test.js
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { execFileSync } = require('child_process');
++
++const ROOT = path.join(__dirname, '..');
++const catalog = JSON.parse(fs.readFileSync(path.join(ROOT, 'sites.json'), 'utf8'));
++const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
++
++// dist/ and the vhosts must match sites.json + build.js (deploy serves dist/ as committed).
++execFileSync(process.execPath, [path.join(ROOT, 'build.js'), '--check'], { stdio: 'pipe' });
++
++// Pricing copy is never used on the network (no "free"/"$0"/"no ads" claims); "free speech" is fine.
++const PRICING = /\$0\b|\bfor free\b|\bfree (forever|to use|of charge|plan|tier)\b|\bno ads\b|\bad-free\b/i;
++
++let pages = 0;
++for (const site of catalog.sites) {
++ const d = site.domain;
++ const html = read(`dist/${d}/index.html`);
++ pages++;
++ assert.match(html, /<title>[^<]+<\/title>/, `${d}: title`);
++ assert.match(html, new RegExp(`<link rel="canonical" href="https://${d.replace(/\./g, '\\.')}/?"`), `${d}: canonical`);
++ assert.match(html, /placeholder/i, `${d}: says it is a placeholder (never claim a product is live)`);
++ assert.doesNotMatch(html, PRICING, `${d}: pricing copy`);
++ for (const m of html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)) {
++ assert.doesNotThrow(() => JSON.parse(m[1]), `${d}: JSON-LD parses`);
++ }
++ for (const f of ['robots.txt', 'sitemap.xml', 'manifest.webmanifest']) {
++ assert.ok(fs.existsSync(path.join(ROOT, 'dist', d, f)), `${d}: ${f}`);
++ }
++ const conf = read(`deploy/nginx/${d}.conf`);
++ assert.ok(conf.includes(`server_name ${d}`), `${d}: vhost server_name`);
++ assert.ok(conf.includes(`root /opt/openvibe.sites/dist/${d};`), `${d}: vhost root`);
++}
++
++// A domain that a real service answers is never also a placeholder here (roadmap 7.2 rule 8).
++const LIVE_SERVICES = ['events.openvibe.network', 'openvibe.community', 'openvibe.live', 'openvibe.media', 'openvibe.tools', 'openvibe.network'];
++for (const d of LIVE_SERVICES) {
++ assert.ok(!catalog.sites.some((s) => s.domain === d), `${d} is a running service and must not have a placeholder`);
++}
++console.log(`sites build: ${pages} placeholder pages current, honest and complete`);