From e8fa3fe42a2751460ac5b364fe71e27a1641bfb0 Mon Sep 17 00:00:00 2001 From: ACQ Build Date: Sat, 13 Jun 2026 09:38:56 -0500 Subject: [PATCH] fix: scope apso generate formatting to generated output only (#70) The generate format step delegated to the target project's `npm run format`, whose broad glob (e.g. `{src,test}/**/*.ts`) reformatted the entire project tree on every run, rewriting hand-written code. Run prettier directly against the generated output dir instead, and skip formatting when nothing was generated. - base-command: extract generic runCommand(command, args, silent); runNpmCommand delegates to it. - generate: format via `npx prettier --write /autogen/**/*.ts` (scoped to generated files; the stale src/guards glob was unnecessary since guards live under autogen/). Guard formatting on generatedFileCount > 0 and warn when .apsorc defines no entities. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/generate.ts | 30 +++++++++++++++++++++++------- src/lib/base-command.ts | 16 ++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/commands/generate.ts b/src/commands/generate.ts index ead39fc..cd83bfb 100644 --- a/src/commands/generate.ts +++ b/src/commands/generate.ts @@ -87,6 +87,14 @@ export default class Generate extends BaseCommand { console.log(`[apso] Generating ${language} code for ${entities.length} entities...`); + if (entities.length === 0) { + console.log( + "[apso] No entities found in .apsorc — nothing to generate. Check that the file exists and defines an `entities` array." + ); + } + + let generatedFileCount = 0; + const generatorConfig: GeneratorConfig = { language, rootFolder, @@ -113,6 +121,7 @@ export default class Generate extends BaseCommand { // eslint-disable-next-line no-await-in-loop await createFile(fullPath, file.content); } + generatedFileCount += enumFiles.length; // Generate shared query utilities const queryUtilFiles = await generator.generateQueryUtils(entities, lowerCaseApiType); @@ -121,6 +130,7 @@ export default class Generate extends BaseCommand { // eslint-disable-next-line no-await-in-loop await createFile(fullPath, file.content); } + generatedFileCount += queryUtilFiles.length; // Generate per-entity files for (const entity of entities) { @@ -170,6 +180,7 @@ export default class Generate extends BaseCommand { const fullPath = path.join(autogenPath, file.path); // eslint-disable-next-line no-await-in-loop await createFile(fullPath, file.content); + generatedFileCount += 1; } } @@ -186,6 +197,7 @@ export default class Generate extends BaseCommand { // eslint-disable-next-line no-await-in-loop await createFile(fullPath, file.content); } + generatedFileCount += guardFiles.length; // Generate index module const indexFiles = await generator.generateIndexModule(entities, lowerCaseApiType); @@ -194,19 +206,23 @@ export default class Generate extends BaseCommand { // eslint-disable-next-line no-await-in-loop await createFile(fullPath, file.content); } + generatedFileCount += indexFiles.length; - // Format generated files (TypeScript only) - if (language === "typescript" && !skipFormat) { + // Format generated files (TypeScript only). + // Run prettier directly against the generated output directory so we never + // reformat hand-written code elsewhere in the project tree (the project's + // `npm run format` script targets a broad glob like `{src,test}/**/*.ts`). + if (language === "typescript" && !skipFormat && generatedFileCount > 0) { const formatStart = performance.now(); - console.log("[apso] Formatting files..."); - await this.runNpmCommand( - ["run", "format", "src/autogen/**/*.ts", "src/guards/**/*.ts"], - true - ); + console.log("[apso] Formatting generated files..."); + const formatGlob = path.join(autogenPath, "**", "*.ts"); + await this.runCommand("npx", ["prettier", "--write", formatGlob], true); const formatTime = performance.now() - formatStart; console.log(`[apso] Finished formatting in ${formatTime.toFixed(2)} ms`); } else if (skipFormat) { console.log("[apso] Skipping formatting (--skip-format flag set)"); + } else if (generatedFileCount === 0) { + console.log("[apso] Skipping formatting (no files were generated)"); } const totalBuildTime = performance.now() - totalBuildStart; diff --git a/src/lib/base-command.ts b/src/lib/base-command.ts index 7ad7df8..b1a3844 100644 --- a/src/lib/base-command.ts +++ b/src/lib/base-command.ts @@ -3,16 +3,20 @@ import { spawn } from "child_process"; import os from "os"; export default abstract class BaseCommand extends Command { - async runNpmCommand(args: string[], silent = false): Promise { + async runCommand( + command: string, + args: string[], + silent = false + ): Promise { return new Promise((resolve: any, reject) => { const isWindows = os.platform() === "win32"; - const command = isWindows ? "npm.cmd" : "npm"; + const resolvedCommand = isWindows ? `${command}.cmd` : command; const stdio = silent ? "ignore" : "inherit"; - const cmdStr = `${command} ${args.join(" ")}`; + const cmdStr = `${resolvedCommand} ${args.join(" ")}`; this.log(`Running: ${cmdStr}`); - const child = spawn(command, args, { + const child = spawn(resolvedCommand, args, { stdio, shell: isWindows, }); @@ -30,4 +34,8 @@ export default abstract class BaseCommand extends Command { }); }); } + + async runNpmCommand(args: string[], silent = false): Promise { + return this.runCommand("npm", args, silent); + } }