/** * Schema Fetching — dispatches on the backend type configured in * `source.django` or `source.fastapi`. * * Both flavors spawn a Python subprocess that prints schema JSON to stdout: * Django: `python manage.py export_mizan_schema --indent 0` * FastAPI: `python -m mizan_fastapi.cli ` */ import { spawn } from 'child_process' import path from 'path' function runSubprocess(cmd, args, opts) { const { cwd, env, label } = opts return new Promise((resolve, reject) => { const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32', env, }) let stdout = '' let stderr = '' proc.stdout.on('data', (data) => { stdout += data.toString() }) proc.stderr.on('data', (data) => { stderr += data.toString() }) proc.on('close', (code) => { if (code !== 0) { reject(new Error(`${label} command failed (exit ${code}):\n${stderr}`)) return } const jsonStart = stdout.indexOf('{') if (jsonStart === -1) { reject(new Error(`No JSON found in ${label} output:\n${stdout}\n${stderr}`)) return } try { resolve(JSON.parse(stdout.slice(jsonStart))) } catch (err) { reject(new Error(`Failed to parse JSON from ${label}:\n${err.message}\n${stdout}`)) } }) proc.on('error', (err) => { reject(new Error(`Failed to spawn ${label} command: ${err.message}`)) }) }) } function runDjangoCommand(source, cwd, command) { const managePath = path.resolve(cwd, source.django.managePath) const manageDir = path.dirname(managePath) let cmd, args if (source.django.command) { cmd = source.django.command[0] args = [...source.django.command.slice(1), 'manage.py', command, '--indent', '0'] } else { const python = source.django.python || 'python' cmd = python args = [managePath, command, '--indent', '0'] } const env = source.django.env ? { ...process.env, ...source.django.env } : undefined return runSubprocess(cmd, args, { cwd: manageDir, env, label: 'Django' }) } function runFastapiSchemaCommand(source, cwd) { const fastapiCwd = source.fastapi.cwd ? path.resolve(cwd, source.fastapi.cwd) : cwd let cmd, args if (source.fastapi.command) { cmd = source.fastapi.command[0] args = [...source.fastapi.command.slice(1), '-m', 'mizan_fastapi.cli', source.fastapi.module] } else { cmd = source.fastapi.python || 'python' args = ['-m', 'mizan_fastapi.cli', source.fastapi.module] } const env = source.fastapi.env ? { ...process.env, ...source.fastapi.env } : undefined return runSubprocess(cmd, args, { cwd: fastapiCwd, env, label: 'FastAPI' }) } /** * Fetch channels schema. Channels are a Django-only feature; FastAPI * projects use native WebSockets and don't go through this path. */ export async function fetchChannelsSchema(source, cwd) { if (!source.django) { throw new Error('Channels schema export requires django source configuration') } return runDjangoCommand(source, cwd, 'export_channels_schema') } /** * Fetch mizan schema. Dispatches on whichever backend source is configured. */ export async function fetchMizanSchema(source, cwd) { if (source.fastapi) { return runFastapiSchemaCommand(source, cwd) } if (source.django) { return runDjangoCommand(source, cwd, 'export_mizan_schema') } throw new Error('mizan schema export requires source.django or source.fastapi') }