refactor: use lightweight bootstrap process when generating openapi

This commit is contained in:
Christian Benincasa
2026-03-19 16:27:14 -04:00
parent c3383272ae
commit 6123497f27
3 changed files with 49 additions and 3 deletions

View File

@@ -1,12 +1,18 @@
import languages from '@cospired/i18n-iso-languages';
import en from '@cospired/i18n-iso-languages/langs/en.json' with { type: 'json' };
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import type { DeepPartial } from 'ts-essentials';
import { DBAccess } from './db/DBAccess.ts';
import type { SettingsFile } from './db/SettingsDB.ts';
import { SettingsDBFactory } from './db/SettingsDBFactory.ts';
import { type GlobalOptions, globalOptions } from './globals.js';
import {
type GlobalOptions,
globalOptions,
setGlobalOptionsUnchecked,
} from './globals.js';
import type { GlobalArgsType } from './cli/types.ts';
import {
CacheFolderName,
ChannelLineupsFolderName,
@@ -21,7 +27,7 @@ import { LoggerFactory, RootLogger } from './util/logging/LoggerFactory.js';
* subdirectories
* @returns True if an existing database directory was found
*/
async function initDbDirectories(opts: GlobalOptions) {
export async function initDbDirectories(opts: GlobalOptions) {
// Early init, have to use the non-settings-based root Logger
for (const subpaths of [
[ChannelLineupsFolderName],
@@ -95,3 +101,35 @@ export async function bootstrapTunarr(
LoggerFactory.initialize(settingsDb);
}
/**
* Lightweight bootstrap for tooling commands (e.g. generate-openapi).
* Uses an ephemeral temp directory instead of the configured Tunarr data dir
* so the command does not pollute or depend on the user's real data.
* Returns the temp directory path so the caller can clean up.
*/
export async function bootstrapForTooling(
opts: GlobalArgsType,
): Promise<string> {
languages.registerLocale(en);
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tunarr-'));
// Override the database directory with the ephemeral temp dir.
// setGlobalOptionsUnchecked bypasses the once() guard since setGlobalOptions
// was already called by the middleware before this command handler runs.
setGlobalOptionsUnchecked({ ...opts, database: tempDir });
await initDbDirectories(globalOptions());
const settingsDb = new SettingsDBFactory(globalOptions()).get();
await settingsDb.flush();
const conn = DBAccess.init();
await conn.syncMigrationTablesIfNecessary();
await conn.runDBMigrations();
LoggerFactory.initialize(settingsDb);
return tempDir;
}

View File

@@ -1,6 +1,7 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { type ArgumentsCamelCase, type CommandModule } from 'yargs';
import { bootstrapForTooling } from '../bootstrap.ts';
import { container } from '../container.ts';
import { setServerOptions } from '../globals.ts';
import { Server } from '../Server.ts';
@@ -38,6 +39,7 @@ export const GenerateOpenApiCommand: CommandModule<
},
handler: async (args: ArgumentsCamelCase<GenerateOpenApiCommandArgs>) => {
console.log('Generating OpenAPI doc for version ' + args.apiVersion);
const tempDir = await bootstrapForTooling(args);
setServerOptions(args);
const server = container.get<Server>(Server);
await server.runServer();
@@ -57,6 +59,7 @@ export const GenerateOpenApiCommand: CommandModule<
);
await fs.copyFile(rootOutPath, docsOutPath);
console.log(`Wrote OpenAPI document to ${rootOutPath}`);
await fs.rm(tempDir, { recursive: true, force: true });
process.exit(0);
},
};

View File

@@ -78,7 +78,12 @@ yargs(hideBin(process.argv))
.middleware([
({ hide_banner }) => (hide_banner ? void 0 : printBanner()),
(opts) => setGlobalOptions(opts),
() => bootstrapTunarr(),
(opts) => {
if ((opts._ as string[])[0] !== 'generate-openapi') {
return bootstrapTunarr();
}
return Promise.resolve();
},
])
.version(getTunarrVersion())
.command(commands)