openvibers/openvibe.food Commits

Drop the skeleton's generator and its check (they belong to the skeleton)

OP OpenVibers committed 8 Oct 2026 · 20ae74147c · 2 files, +0 −268


Changes

ddiff --git a/scripts/new-service.js b/scripts/new-service.js
ddeleted file mode 100644
iindex 70a7f67..0000000
---- a/scripts/new-service.js
++++ /dev/null
@@@ -1,129 +0,0 @@
--#!/usr/bin/env node
--'use strict';
--
--/**
-- * new-service.js — make a new OpenVibe service from this skeleton.
-- *
-- * node scripts/new-service.js --id work --name OpenVibe.Work --domain openvibe.work --port 4960 \
-- * --tagline "Find work, with an agent on your side." --env WORK --out ../openvibe-work
-- *
-- * It copies the tree this file lives in (everything except node_modules/, .git/ and .claude/) to --out, replaces
-- * the six placeholders in file contents AND in file names, and prints the next steps. Nothing else is touched:
-- * no install, no commit, no deploy.
-- *
-- * The placeholders say: the service id (lowercase; config.service, the session cookies, the OAuth client id, the
-- * systemd unit), the display name ("OpenVibe.Work"), the site ("openvibe.work"), the loopback port, the tagline,
-- * and the environment prefix (WORK_LIMITS_MINUTE, WORK_PGLITE_DIR, WORK_TEST_STORE …). They are the TOKENS below,
-- * built from parts on purpose: this file lives in the tree it copies, and a literal placeholder here would be
-- * rewritten when a service is generated from a generated service.
-- */
--const fs = require('fs');
--const path = require('path');
--
--const T = (name) => `__${name}__`;
--const TOKENS = [T('ID'), T('NAME'), T('DOMAIN'), T('PORT'), T('TAGLINE'), T('ENV')];
--const TOKEN_RE = new RegExp(TOKENS.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
--
--const SKIP = new Set(['node_modules', '.git', '.claude']);
--
--function usage(msg) {
-- if (msg) process.stderr.write(`new-service: ${msg}\n\n`);
-- process.stderr.write(`Usage: node scripts/new-service.js --id <id> --name <name> --domain <domain> --port <port> ` +
-- `--tagline "<line>" --env <ENV> --out <dir>\n\n` +
-- ` --id service id, lowercase (work)\n` +
-- ` --name display name (OpenVibe.Work)\n` +
-- ` --domain site (openvibe.work)\n` +
-- ` --port loopback port (4960)\n` +
-- ` --tagline one line (Find work, with an agent on your side.)\n` +
-- ` --env environment prefix, uppercase (WORK)\n` +
-- ` --out directory to write the new service into (must not exist, or be empty)\n`);
-- process.exit(2);
--}
--
--function parseArgs(argv) {
-- const out = {};
-- for (let i = 0; i < argv.length; i++) {
-- const a = argv[i];
-- if (!a.startsWith('--')) continue;
-- const eq = a.indexOf('=');
-- if (eq !== -1) { out[a.slice(2, eq)] = a.slice(eq + 1); continue; }
-- const next = argv[i + 1];
-- if (next == null || next.startsWith('--')) { out[a.slice(2)] = ''; continue; }
-- out[a.slice(2)] = next;
-- i++;
-- }
-- return out;
--}
--
--function replacements(args) {
-- const id = String(args.id || '');
-- const name = String(args.name || '');
-- const domain = String(args.domain || '');
-- const env = String(args.env || '');
-- const port = String(args.port || '');
-- const tagline = String(args.tagline || '').trim();
--
-- if (!/^[a-z][a-z0-9-]{1,39}$/.test(id)) usage(`--id must be lowercase letters, digits and dashes (got ${JSON.stringify(id)})`);
-- if (!name) usage('--name is required');
-- if (!/^[a-z0-9][a-z0-9.-]{1,80}$/.test(domain)) usage(`--domain looks wrong (got ${JSON.stringify(domain)})`);
-- if (!/^\d{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535) usage(`--port must be 1-65535 (got ${JSON.stringify(port)})`);
-- if (!/^[A-Z][A-Z0-9_]{0,31}$/.test(env)) usage(`--env must be uppercase letters, digits and underscores (got ${JSON.stringify(env)})`);
-- if (!tagline) usage('--tagline is required');
-- if (!String(args.out || '')) usage('--out is required');
--
-- return {
-- values: { [T('ID')]: id, [T('NAME')]: name, [T('DOMAIN')]: domain, [T('PORT')]: port, [T('TAGLINE')]: tagline, [T('ENV')]: env },
-- meta: { id, name, domain, port, env, tagline },
-- };
--}
--
--const looksBinary = (buf) => buf.includes(0);
--
--function copyTree(src, dest, values) {
-- fs.mkdirSync(dest, { recursive: true });
-- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
-- if (SKIP.has(entry.name)) continue;
-- const from = path.join(src, entry.name);
-- const to = path.join(dest, entry.name.replace(TOKEN_RE, (t) => values[t]));
-- if (entry.isDirectory()) { copyTree(from, to, values); continue; }
-- if (entry.isSymbolicLink()) continue; // node_modules is the only symlink; anything else is environment
-- const buf = fs.readFileSync(from);
-- const out = looksBinary(buf) ? buf : Buffer.from(buf.toString('utf8').replace(TOKEN_RE, (t) => values[t]), 'utf8');
-- fs.writeFileSync(to, out);
-- }
--}
--
--function main(argv) {
-- const args = parseArgs(argv);
-- if (args.help != null) usage();
-- const { values, meta } = replacements(args);
--
-- const root = path.join(__dirname, '..');
-- const out = path.resolve(process.cwd(), String(args.out));
-- if (fs.existsSync(out) && fs.readdirSync(out).length) usage(`--out ${out} exists and is not empty`);
-- if (out === root || root.startsWith(`${out}${path.sep}`) || out.startsWith(`${root}${path.sep}`)) {
-- usage('--out must be outside the skeleton, and must not contain it');
-- }
--
-- copyTree(root, out, values);
--
-- process.stdout.write(`Generated ${meta.name} in ${out}\n\n`
-- + ` service id ${meta.id}\n`
-- + ` name ${meta.name}\n`
-- + ` domain ${meta.domain}\n`
-- + ` port ${meta.port}\n`
-- + ` tagline ${meta.tagline}\n`
-- + ` env prefix ${meta.env}\n\n`
-- + `Next steps:\n`
-- + ` 1. cd ${out}\n`
-- + ` 2. npm install\n`
-- + ` 3. npm test # every test/*.test.js on temp PGlite databases, with a mock Network\n`
-- + ` 4. npm run dev # http://localhost:${meta.port}, PGlite in data/pglite\n`
-- + ` 5. Register the service and its capabilities in OpenVibe.Contracts (CI: contracts-service: ${meta.id})\n`
-- + ` and with OpenVibe.Services; write its tables in migrations/0001_initial.sql, its routes in\n`
-- + ` server/http/api.js (capabilities in server/http/principal.js, budgets in server/http/caller-limits.js)\n`
-- + ` and its pages in server/http/pages.js.\n`
-- + ` 6. Fill in STATUS.json, README.md and CHANGELOG.md, then deploy with the unit and vhost in deploy/.\n`);
--}
--
--main(process.argv.slice(2));
ddiff --git a/test/skeleton.test.js b/test/skeleton.test.js
ddeleted file mode 100644
iindex b5ba5d3..0000000
---- a/test/skeleton.test.js
++++ /dev/null
@@@ -1,139 +0,0 @@
--'use strict';
--/**
-- * The skeleton's own test: generating a service from it must leave no placeholder behind, in file names or in file
-- * contents, and the names the generator derives (the package, the config, the deploy files, the environment) must be
-- * the ones a service needs. It runs scripts/new-service.js into a temp directory with fixed sample values, inspects
-- * the result, and leaves nothing behind.
-- *
-- * The placeholder strings are built from parts: this file is copied into every generated service, and a literal
-- * placeholder here would be rewritten on the way in.
-- */
--const assert = require('assert');
--const fs = require('fs');
--const os = require('os');
--const path = require('path');
--const { execFileSync } = require('child_process');
--const { check, done } = require('./helpers/boot');
--
--const TOKEN_NAMES = ['ID', 'NAME', 'DOMAIN', 'PORT', 'TAGLINE', 'ENV'];
--const TOKEN_RE = new RegExp(`__(${TOKEN_NAMES.join('|')})__`);
--const token = (name) => `__${name}__`;
--
--const ROOT = path.join(__dirname, '..');
--const SAMPLE = { id: 'sample', name: 'OpenVibe.Sample', domain: 'openvibe.sample', port: '4999', tagline: 'A sample service.', env: 'SAMPLE' };
--
--const created = [];
--const tmpDir = (prefix) => { const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); created.push(d); return d; };
--
--function runGenerator(out) {
-- const args = [
-- path.join(ROOT, 'scripts', 'new-service.js'),
-- '--id', SAMPLE.id, '--name', SAMPLE.name, '--domain', SAMPLE.domain,
-- '--port', SAMPLE.port, '--tagline', SAMPLE.tagline, '--env', SAMPLE.env, '--out', out,
-- ];
-- // stderr is captured, not shown: the generator's refusals are checked below, and a test file's output stays clean.
-- return execFileSync(process.execPath, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
--}
--
--function walk(dir, out = []) {
-- for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
-- const p = path.join(dir, e.name);
-- if (e.isDirectory()) walk(p, out); else out.push(p);
-- }
-- return out;
--}
--const rel = (p, dir) => path.relative(dir, p).split(path.sep).join('/');
--const text = (p) => fs.readFileSync(p, 'utf8');
--
--(async () => {
-- // This test is about the skeleton's generator, and only the skeleton can run it: it asserts that generating a
-- // service rewrites the placeholders, and a generated service has none left to rewrite (its package name is
-- // already its own, its vhost is already named). A generated service says so and skips, rather than reporting a
-- // failure it cannot fix — the convention openvibe-shared/test-runner reads (`<label>: skipped (<why>)`), so the
-- // file is listed with ○ and never counted as passed.
-- if (JSON.parse(text(path.join(ROOT, 'package.json'))).name !== `openvibe-${token('ID')}`) {
-- process.stdout.write('skeleton generator: skipped (this is a generated service, not the skeleton it came from; run it in OpenVibe.ServiceSkeleton)\n');
-- return done();
-- }
-- const base = tmpDir('ov-new-service-');
-- const dir = path.join(base, 'openvibe-sample');
-- const printed = runGenerator(dir);
--
-- await check('the generator writes a service and prints the next steps', () => {
-- assert.ok(fs.existsSync(dir), 'no output directory');
-- assert.ok(printed.includes(`Generated ${SAMPLE.name} in ${dir}`), printed.slice(0, 200));
-- assert.ok(printed.includes('npm install') && printed.includes('npm test'), 'the next steps name the first commands');
-- assert.ok(!printed.includes(token('ID')) && !printed.includes(token('NAME')), 'the output still carries a placeholder');
-- });
--
-- await check('no placeholder is left in any file name or file content', () => {
-- const files = walk(dir);
-- assert.ok(files.length > 30, `only ${files.length} files were generated`);
-- const badNames = files.map((f) => rel(f, dir)).filter((r) => TOKEN_RE.test(r));
-- assert.deepStrictEqual(badNames, [], 'a file name still carries a placeholder');
-- const badContents = files.filter((f) => TOKEN_RE.test(text(f))).map((f) => rel(f, dir));
-- assert.deepStrictEqual(badContents, [], 'a file still carries a placeholder');
-- });
--
-- await check('the package, the config and the environment are the generated service\'s', () => {
-- const pkg = JSON.parse(text(path.join(dir, 'package.json')));
-- assert.strictEqual(pkg.name, `openvibe-${SAMPLE.id}`);
-- assert.strictEqual(pkg.version, '0.1.0');
-- // A generated service's own description is the name and its tagline; the skeleton's is the placeholders.
-- assert.ok(pkg.description.startsWith(SAMPLE.name), pkg.description);
-- assert.ok(pkg.scripts['test:pg'].includes(`${SAMPLE.env}_TEST_STORE=pg`), pkg.scripts['test:pg']);
--
-- const config = text(path.join(dir, 'server', 'config.js'));
-- assert.ok(config.includes(`service: '${SAMPLE.id}'`), 'config.service');
-- assert.ok(config.includes(`env.PORT, ${SAMPLE.port}`), 'the default port');
-- assert.ok(config.includes(`'https://${SAMPLE.domain}'`), 'the production base URL');
-- assert.ok(config.includes(`'ov:${SAMPLE.id}:'`), 'the Valkey prefix');
-- assert.ok(config.includes(`${SAMPLE.env}_LIMITS_MINUTE`), 'the limits env prefix');
-- assert.ok(config.includes(`${SAMPLE.env}_PGLITE_DIR`), 'the PGlite env prefix');
--
-- const env = text(path.join(dir, '.env.example'));
-- for (const line of [`PORT=${SAMPLE.port}`, `BASE_URL=https://${SAMPLE.domain}`, `OV_OAUTH_CLIENT_ID=${SAMPLE.id}`, `VALKEY_PREFIX=ov:${SAMPLE.id}:`]) {
-- assert.ok(env.includes(line), `.env.example is missing ${line}`);
-- }
--
-- const auth = text(path.join(dir, 'server', 'auth', 'sso.js'));
-- assert.ok(auth.includes(`'${SAMPLE.id}_at'`) && auth.includes(`'${SAMPLE.id}_rt'`) && auth.includes(`'${SAMPLE.id}_oauth'`), 'the session cookies');
--
-- const status = JSON.parse(text(path.join(dir, 'STATUS.json')));
-- assert.strictEqual(status.domain, SAMPLE.domain);
-- assert.ok(text(path.join(dir, 'README.md')).includes(SAMPLE.name), 'the README names the service');
-- });
--
-- await check('the deploy files carry the domain, the id and the port', () => {
-- const nginx = text(path.join(dir, 'deploy', 'nginx', `${SAMPLE.domain}.conf`));
-- assert.ok(nginx.includes(`server_name ${SAMPLE.domain} www.${SAMPLE.domain};`), 'the vhost name');
-- assert.ok(nginx.includes(`proxy_pass http://127.0.0.1:${SAMPLE.port};`), 'the upstream port');
-- assert.ok(nginx.includes(`zone=${SAMPLE.id}_api`), 'the api limit zone');
-- assert.ok(!fs.existsSync(path.join(dir, 'deploy', 'nginx', `${token('DOMAIN')}.conf`)), 'the template name is still there');
--
-- const unit = text(path.join(dir, 'deploy', 'systemd', `openvibe-${SAMPLE.id}.service`));
-- assert.ok(unit.includes(`Description=${SAMPLE.name}`), 'the unit description');
-- assert.ok(unit.includes(`Environment=PORT=${SAMPLE.port}`), 'the unit port');
-- assert.ok(unit.includes(`/etc/openvibe/${SAMPLE.id}.env`), 'the environment file');
--
-- assert.ok(fs.existsSync(path.join(dir, 'migrations', '0001_initial.sql')), 'the initial migration');
-- assert.ok(fs.existsSync(path.join(dir, 'public', 'css', 'app.css')), 'the stylesheet');
-- });
--
-- await check('the copy skips node_modules and .git, and the skeleton\'s own tools travel with it', () => {
-- assert.ok(!fs.existsSync(path.join(dir, 'node_modules')), 'node_modules was copied');
-- assert.ok(!fs.existsSync(path.join(dir, '.git')), '.git was copied');
-- assert.ok(fs.existsSync(path.join(dir, 'scripts', 'new-service.js')), 'the generator is in the generated tree');
-- assert.ok(fs.existsSync(path.join(dir, 'test', 'run.js')), 'the test runner is in the generated tree');
-- });
--
-- await check('generating refuses to overwrite a directory that has something in it', () => {
-- let failed = null;
-- try { runGenerator(dir); } catch (err) { failed = err; }
-- assert.ok(failed, 'a second run into the same directory must fail');
-- assert.match(String((failed && failed.stderr) || ''), /not empty/);
-- });
--
-- for (const d of created) fs.rmSync(d, { recursive: true, force: true });
-- done();
--})().catch((err) => { console.error(err); process.exit(1); });