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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
156 lines
4.5 KiB
TypeScript
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;
|
|
});
|