import { execSync } from 'node:child_process' import path, { resolve } from 'node:path' import { findFiles } from '../../../../../lib/fs.js' import { ensureSqlObjectsInMigration } from '../../../../../lib/sql-objects.js' import type { ScaffoldMigrationInput } from './types.js' export interface ScaffoldMigrationResult { success: boolean output: string /** SQL objects (schema.name) frozen into the generated migration from SqlObjects/. */ sqlObjectsInlined: string[] } export async function execute(input: ScaffoldMigrationInput): Promise { const projectRoot = resolve(input.projectPath) // `--context` is NON-NEGOTIABLE on a generated app: two design-time // factories exist (CoreDbContext + ExtensionsDbContext) and EF aborts with // "More than one DbContext was found" without it. This was the only EF // invocation in the whole repo not passing it. const cmd = `dotnet ef migrations add ${input.name} --project src/${input.appCode}.Infrastructure --startup-project src/${input.appCode}.Api --context ${input.context}` let output: string try { // Success path: keep the TAIL — the MSBuild preamble alone exceeds 2000 // chars, so a head-slice used to show only build noise and cut off EF's // own messages (Done. / warnings). output = execSync(cmd, { cwd: projectRoot, encoding: 'utf-8', timeout: 120_000 }).slice(-4000) } catch (err: any) { return { success: false, // Failure path: NEVER truncated — the EF error ("More than one DbContext // was found", model validation…) sits AFTER the build noise; a 2000-char // head-slice made every failure diagnostically blank. output: (err.stdout ?? '') + '\n' + (err.stderr ?? ''), sqlObjectsInlined: [], } } // Freeze any new/changed SQL objects (functions/views/procs under SqlObjects/**) // into the migration EF just generated. EF never emits raw SQL, so without this // a `dotnet ef database update` that doesn't boot the app won't deploy them. // Same shared helper as `/efcore create` + `/efcore squash`. let sqlObjectsInlined: string[] = [] try { const projectDir = path.join(projectRoot, 'src', `${input.appCode}.Infrastructure`) const generated = (await findFiles(`**/*_${input.name}.cs`, { cwd: projectDir })) .filter((f) => !f.endsWith('.Designer.cs')) .sort((a, b) => b.localeCompare(a)) if (generated.length > 0) { const ensure = await ensureSqlObjectsInMigration({ projectDir, migrationsDir: path.dirname(generated[0]), migrationName: input.name, }) sqlObjectsInlined = ensure.inlined if (ensure.injected) { output += `\n\nInlined ${ensure.inlined.length} SQL object(s) into the migration so \`database update\` deploys them: ${ensure.inlined.join(', ')}` } } } catch (e) { output += `\n\n(SQL-object inlining skipped: ${(e as Error).message})` } return { success: true, output, sqlObjectsInlined } }