PSA/sdk/scripts/generate-openapi.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

156 lines
4.5 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import YAML from 'yaml';
import { buildBaseRegistry, buildDocument, DocumentBuildOptions } from '../../server/src/lib/api/openapi';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
interface CliOptions {
edition: 'ce' | 'ee';
outputDir: string;
version: string;
title?: string;
description?: string;
formats: Array<'json' | 'yaml'>;
}
function readRootPackageVersion(rootDir: string): string {
try {
const pkgPath = path.join(rootDir, 'package.json');
const content = fs.readFileSync(pkgPath, 'utf-8');
const parsed = JSON.parse(content);
if (typeof parsed.version === 'string' && parsed.version.trim()) {
return parsed.version.trim();
}
} catch (error) {
// eslint-disable-next-line no-console
console.warn('Unable to read package version, defaulting to 0.1.0', error);
}
return '0.1.0';
}
function parseCliOptions(rootDir: string): CliOptions {
const args = process.argv.slice(2);
const options: CliOptions = {
edition: 'ce',
outputDir: path.resolve(__dirname, '../docs/openapi'),
version: readRootPackageVersion(rootDir),
formats: ['json', 'yaml'],
};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (!arg.startsWith('--')) {
continue;
}
const key = arg.slice(2);
const value = args[i + 1];
switch (key) {
case 'edition':
if (value === 'ce' || value === 'ee') {
options.edition = value;
i += 1;
}
break;
case 'output':
if (value) {
options.outputDir = path.resolve(rootDir, value);
i += 1;
}
break;
case 'version':
if (value) {
options.version = value;
i += 1;
}
break;
case 'title':
if (value) {
options.title = value;
i += 1;
}
break;
case 'description':
if (value) {
options.description = value;
i += 1;
}
break;
case 'formats':
if (value) {
const formats = value.split(',').map((fmt) => fmt.trim().toLowerCase());
options.formats = formats.filter((fmt): fmt is 'json' | 'yaml' => fmt === 'json' || fmt === 'yaml');
i += 1;
}
break;
default:
break;
}
}
return options;
}
function writeDocument(outputDir: string, baseName: string, formats: CliOptions['formats'], document: unknown) {
fs.mkdirSync(outputDir, { recursive: true });
const writtenFiles: string[] = [];
if (formats.includes('json')) {
const jsonPath = path.join(outputDir, `${baseName}.json`);
fs.writeFileSync(jsonPath, `${JSON.stringify(document, null, 2)}\n`, 'utf-8');
writtenFiles.push(jsonPath);
}
if (formats.includes('yaml')) {
const yamlPath = path.join(outputDir, `${baseName}.yaml`);
fs.writeFileSync(yamlPath, `${YAML.stringify(document)}\n`, 'utf-8');
writtenFiles.push(yamlPath);
}
return writtenFiles;
}
async function main() {
const repoRoot = path.resolve(__dirname, '..', '..');
const options = parseCliOptions(repoRoot);
const registry = buildBaseRegistry({ edition: options.edition });
const metadata: DocumentBuildOptions = {
title: options.title ?? 'AlgaPSA API',
version: options.version,
description: options.description ?? 'OpenAPI specification generated from registered route metadata.',
edition: options.edition,
servers: [
{ url: 'https://algapsa.com', description: 'Production' },
{ url: 'http://localhost:3000', description: 'Local development' },
],
};
const document = buildDocument(registry, metadata);
const baseName = `alga-openapi.${options.edition}`;
const writtenFiles = writeDocument(options.outputDir, baseName, options.formats, document);
if (options.edition === 'ce') {
writeDocument(options.outputDir, 'alga-openapi', options.formats, document);
}
// eslint-disable-next-line no-console
console.log(
`Generated OpenAPI spec for edition "${options.edition}" with ${registry.getRegisteredRoutes().length} routes.`,
);
// eslint-disable-next-line no-console
writtenFiles.forEach((file) => console.log(` - ${file}`));
}
main().catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to generate OpenAPI specification', error);
process.exitCode = 1;
});