Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion graphql/codegen/src/__tests__/codegen/expand-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';

import { expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from '../../core/generate';
import { expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget, removeStaleTargetDirs } from '../../core/generate';

describe('expandApiNamesToMultiTarget', () => {
it('returns null for no apiNames', () => {
Expand Down Expand Up @@ -139,3 +139,62 @@ describe('expandSchemaDirToMultiTarget', () => {
expect(Object.keys(result!)).toEqual(['alpha', 'mid', 'zebra']);
});
});

describe('removeStaleTargetDirs', () => {
let tempDir: string;

beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stale-targets-'));
});

afterEach(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});

it('removes directories not in current target list', () => {
fs.mkdirSync(path.join(tempDir, 'admin'));
fs.mkdirSync(path.join(tempDir, 'auth'));
fs.mkdirSync(path.join(tempDir, 'public'));
fs.mkdirSync(path.join(tempDir, 'objects'));

const removed = removeStaleTargetDirs(tempDir, ['admin', 'auth']);

expect(removed.sort()).toEqual(['objects', 'public']);
expect(fs.existsSync(path.join(tempDir, 'admin'))).toBe(true);
expect(fs.existsSync(path.join(tempDir, 'auth'))).toBe(true);
expect(fs.existsSync(path.join(tempDir, 'public'))).toBe(false);
expect(fs.existsSync(path.join(tempDir, 'objects'))).toBe(false);
});

it('preserves files (only removes directories)', () => {
fs.mkdirSync(path.join(tempDir, 'admin'));
fs.writeFileSync(path.join(tempDir, 'index.ts'), 'export {}');

const removed = removeStaleTargetDirs(tempDir, ['admin']);

expect(removed).toEqual([]);
expect(fs.existsSync(path.join(tempDir, 'index.ts'))).toBe(true);
});

it('returns empty array when output root does not exist', () => {
const removed = removeStaleTargetDirs('/nonexistent/path', ['admin']);
expect(removed).toEqual([]);
});

it('returns empty array when no stale directories exist', () => {
fs.mkdirSync(path.join(tempDir, 'admin'));
fs.mkdirSync(path.join(tempDir, 'auth'));

const removed = removeStaleTargetDirs(tempDir, ['admin', 'auth']);
expect(removed).toEqual([]);
});

it('removes all directories when target list is empty', () => {
fs.mkdirSync(path.join(tempDir, 'old-target'));

const removed = removeStaleTargetDirs(tempDir, []);

expect(removed).toEqual(['old-target']);
expect(fs.existsSync(path.join(tempDir, 'old-target'))).toBe(false);
});
});
38 changes: 37 additions & 1 deletion graphql/codegen/src/core/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ export interface GenerateMultiOptions {
dryRun?: boolean;
schema?: SchemaConfig;
unifiedCli?: CliConfig | boolean;
/** Remove subdirectories in the output root that don't match any current target name. */
cleanStaleTargets?: boolean;
}

export interface GenerateMultiResult {
Expand Down Expand Up @@ -624,10 +626,37 @@ function applySharedPgpmDb(
};
}

/**
* Remove subdirectories in `outputRoot` that are not in `currentTargetNames`.
* Useful for cleaning up stale target output before a fresh multi-target generate.
* Returns the list of directory names that were removed.
*/
export function removeStaleTargetDirs(
outputRoot: string,
currentTargetNames: string[],
verbose?: boolean,
): string[] {
const removed: string[] = [];
if (!fs.existsSync(outputRoot)) return removed;

const currentTargets = new Set(currentTargetNames);
const entries = fs.readdirSync(outputRoot, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && !currentTargets.has(entry.name)) {
fs.rmSync(path.join(outputRoot, entry.name), { recursive: true, force: true });
removed.push(entry.name);
if (verbose) {
console.log(`Removed stale target directory: ${entry.name}`);
}
}
}
return removed;
}

export async function generateMulti(
options: GenerateMultiOptions,
): Promise<GenerateMultiResult> {
const { configs, cliOverrides, verbose, dryRun, schema, unifiedCli } = options;
const { configs, cliOverrides, verbose, dryRun, schema, unifiedCli, cleanStaleTargets } = options;
const names = Object.keys(configs);
const results: Array<{ name: string; result: GenerateResult }> = [];
let hasError = false;
Expand All @@ -638,6 +667,13 @@ export async function generateMulti(

const cliTargets: MultiTargetCliTarget[] = [];

// Remove stale target directories before generating
if (cleanStaleTargets && names.length > 0 && !dryRun) {
const firstOutput = getConfigOptions(configs[names[0]]).output;
const outputRoot = path.dirname(firstOutput);
removeStaleTargetDirs(outputRoot, names, verbose);
}

const sharedSources = await prepareSharedPgpmSources(configs, cliOverrides);

try {
Expand Down
2 changes: 1 addition & 1 deletion graphql/codegen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export { defineConfig } from './types/config';

// Main generate function (orchestrates the entire pipeline)
export type { GenerateOptions, GenerateResult, GenerateMultiOptions, GenerateMultiResult } from './core/generate';
export { generate, generateMulti, expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from './core/generate';
export { generate, generateMulti, expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget, removeStaleTargetDirs } from './core/generate';

// Config utilities
export { findConfigFile, loadConfigFile } from './core/config';
Expand Down
3 changes: 1 addition & 2 deletions sdk/constructive-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"pregenerate": "rimraf src/admin src/auth src/objects src/public src/index.ts",
"generate": "pnpm run pregenerate && tsx scripts/generate-sdk.ts",
"generate": "rimraf src/index.ts && tsx scripts/generate-sdk.ts",
"lint": "eslint . --fix",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch"
Expand Down
1 change: 1 addition & 0 deletions sdk/constructive-cli/scripts/generate-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async function main() {

const { results, hasError } = await generateMulti({
configs: expanded,
cleanStaleTargets: true,
});

let realError = false;
Expand Down
3 changes: 1 addition & 2 deletions sdk/constructive-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"pregenerate": "rimraf src/admin src/auth src/objects src/public src/index.ts",
"generate": "pnpm run pregenerate && tsx scripts/generate-react.ts",
"generate": "rimraf src/index.ts && tsx scripts/generate-react.ts",
"lint": "eslint . --fix",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch"
Expand Down
1 change: 1 addition & 0 deletions sdk/constructive-react/scripts/generate-react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async function main() {

const { results, hasError } = await generateMulti({
configs: expanded,
cleanStaleTargets: true,
});

let realError = false;
Expand Down
3 changes: 1 addition & 2 deletions sdk/constructive-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"pregenerate": "rimraf src/admin src/auth src/objects src/public src/index.ts",
"generate": "pnpm run pregenerate && tsx scripts/generate-sdk.ts",
"generate": "rimraf src/index.ts && tsx scripts/generate-sdk.ts",
"pregenerate:migrate-client": "rimraf ../migrate-client/src/migrate",
"generate:migrate-client": "pnpm run pregenerate:migrate-client && tsx scripts/generate-migrate-client.ts",
"lint": "eslint . --fix",
Expand Down
1 change: 1 addition & 0 deletions sdk/constructive-sdk/scripts/generate-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async function main() {

const { results, hasError } = await generateMulti({
configs: expanded,
cleanStaleTargets: true,
});

let realError = false;
Expand Down
Loading