feat: establish production SaaS foundation

This commit is contained in:
Codex
2026-07-12 19:26:57 +08:00
parent 1c2ce38cea
commit 39f7332f33
219 changed files with 20647 additions and 2628 deletions

View File

@@ -0,0 +1,138 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { parseWorkerCli, resolveWorkerMonth } from '../apps/worker/src/cli.ts';
const root = process.cwd();
function read(relativePath) {
return fs.readFileSync(path.join(root, relativePath), 'utf8').replace(/\r\n/g, '\n');
}
assert.throws(
() => parseWorkerCli(['--loop']),
/--job is required exactly once/,
'a production loop without an explicit job must fail closed',
);
assert.deepEqual(parseWorkerCli(['--loop', '--job', 'crm']), { job: 'crm', loop: true, month: undefined });
assert.throws(
() => parseWorkerCli(['--loop', '--job', 'platform-billing']),
/periodic and must be scheduled with --once/,
);
assert.deepEqual(
parseWorkerCli(['--once', '--job', 'platform-usage', '--month', 'previous']),
{ job: 'platform-usage', loop: false, month: resolveWorkerMonth('previous') },
);
assert.throws(() => parseWorkerCli(['--once', '--job', 'assets', '--month', 'previous']), /only supported/);
assert.throws(() => parseWorkerCli(['--once', '--job', 'crm', '--unknown']), /Unknown worker option/);
assert.throws(() => parseWorkerCli(['--once', '--job', 'crm', 'stray']), /Unexpected worker argument/);
const workerService = read('scripts/deploy/systemd/tiku-worker@.service');
const periodicService = read('scripts/deploy/systemd/tiku-worker-job@.service');
const monthlyService = read('scripts/deploy/systemd/tiku-worker-monthly-usage.service');
const target = read('scripts/deploy/systemd/tiku-workers.target');
const workerEnv = read('scripts/deploy/env/worker.env.example');
const workerConfig = read('apps/worker/src/config.ts');
const sharedDbConfig = read('packages/db/src/index.ts');
assert.match(workerService, /ExecStart=.*--loop --job %i/);
assert.match(periodicService, /Type=oneshot/);
assert.match(periodicService, /ExecStart=.*--once --job %i/);
assert.match(monthlyService, /--job platform-usage --month previous/);
assert.match(monthlyService, /--job platform-usage-overage --month previous/);
const continuousJobs = [
'crm',
'commerce',
'provider-bills',
'platform-dunning-notifications',
'platform-audit-notifications',
'assets',
'imports',
'public-banks',
'exports',
];
for (const job of continuousJobs) {
assert.match(target, new RegExp(`Requires=tiku-worker@${job}\\.service`), `missing continuous worker ${job}`);
}
const timerNames = [
'platform-billing',
'platform-usage',
'platform-dunning',
'platform-audit-alerts',
'student-supervision',
'monthly-usage',
];
for (const name of timerNames) {
const timerPath = `scripts/deploy/systemd/tiku-worker-${name}.timer`;
assert.ok(fs.existsSync(path.join(root, timerPath)), `missing timer ${timerPath}`);
assert.match(target, new RegExp(`Wants=tiku-worker-${name}\\.timer`), `target must want ${name} timer`);
const timer = read(timerPath);
if (timer.includes('OnCalendar=')) {
assert.match(timer, /\nPersistent=true\n/, `${timerPath} must catch up after downtime`);
} else {
assert.match(timer, /\nOnBootSec=/, `${timerPath} must resume its monotonic cadence after boot`);
}
assert.match(timer, /\nUnit=tiku-worker-(?:job@[^\n]+|monthly-usage\.service)\n/, `${timerPath} must name an explicit service`);
}
const envKeys = new Set(
[
...workerConfig.matchAll(/env(?:String|Number|Boolean|List)\(\s*'([A-Z0-9_]+)'/g),
...sharedDbConfig.matchAll(/positiveEnvNumber\(\s*'([A-Z0-9_]+)'/g),
...sharedDbConfig.matchAll(/process\.env\.([A-Z0-9_]+)/g),
].map(match => match[1]),
);
const workerEnvKeys = [...workerEnv.matchAll(/^([A-Z][A-Z0-9_]*)=/gm)].map(match => match[1]);
for (const key of workerEnvKeys) {
if (['NODE_ENV', 'DATABASE_URL', 'DB_EXPECTED_RUNTIME_ROLE'].includes(key)) continue;
assert.ok(envKeys.has(key), `worker.env.example contains a key not read by worker config: ${key}`);
}
for (const key of [
'STORAGE_DEFAULT_BUCKET',
'WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT',
'WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN',
'WORKER_ASSET_SECURITY_SCAN_HTTP_TIMEOUT_MS',
'WORKER_CRM_POLL_INTERVAL_MS',
'WORKER_COMMERCE_POLL_INTERVAL_MS',
'WORKER_PROVIDER_BILL_POLL_INTERVAL_MS',
'WORKER_PLATFORM_DUNNING_NOTIFICATION_POLL_INTERVAL_MS',
'WORKER_PLATFORM_AUDIT_NOTIFICATION_POLL_INTERVAL_MS',
'WORKER_ASSET_POLL_INTERVAL_MS',
'WORKER_IMPORT_POLL_INTERVAL_MS',
'WORKER_PUBLIC_BANK_SYNC_POLL_INTERVAL_MS',
'WORKER_EXPORT_POLL_INTERVAL_MS',
]) {
assert.match(workerEnv, new RegExp(`^${key}=`, 'm'), `worker env must document ${key}`);
}
const schedulerEnvKeys = new Set([
'WORKER_PLATFORM_USAGE_MONTH',
'WORKER_PLATFORM_USAGE_OVERAGE_MONTH',
'ALIYUN_OSS_STS_TOKEN',
'EXPORT_PDF_FONT_PATH',
]);
const codeDefaults = new Map(
[...workerConfig.matchAll(/env(?:String|Number|Boolean|List)\(\s*'([A-Z0-9_]+)'\s*,\s*([^\n,)]+)/g)]
.map(match => [match[1], match[2].trim()]),
);
for (const line of workerEnv.split('\n')) {
const match = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/);
if (!match || schedulerEnvKeys.has(match[1])) continue;
if (codeDefaults.get(match[1]) === "''") {
assert.notEqual(match[2], '', `worker env must not leave required ${match[1]} empty`);
}
}
for (const legacyKey of [
'ALIYUN_OSS_BUCKET',
'ASSET_SECURITY_SCAN_ENDPOINT',
'ASSET_SECURITY_SCAN_TOKEN',
'WORKER_POLL_INTERVAL_MS',
]) {
assert.doesNotMatch(workerEnv, new RegExp(`^${legacyKey}=`, 'm'), `worker env must not expose unused ${legacyKey}`);
}
console.log('[PASS] worker CLI and production scheduling contract');