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
13 changes: 13 additions & 0 deletions .changeset/tall-moments-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@graphql-codegen/plugin-helpers': minor
'@graphql-codegen/cli': minor
---

Extend `overwrite` with `overwrite.removeStaleFiles` and `overwrite.updateExistingFiles`

`overwrite` was being used to both remove stale files in watch mode and update existing files. Some plugins such as Server Preset may dynamically return files to write between watch runs (for performance purposes).

The `overwrite` can now take an object with `overwrite.removeStaleFiles` and `overwrite.updateExistingFiles` fields to allow granular control over actions.

This is not a breaking change because `overwrite=true|false` still works.

44 changes: 33 additions & 11 deletions packages/graphql-codegen-cli/src/generate-and-save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function generate(
// find stale files from previous build which are not present in current build
const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
for (const filename of staleFilenames) {
if (shouldOverwrite(config, filename)) {
if (normalizeOverwriteConfig(config, filename).removeStaleFiles) {
unlinkFile(filename, err => {
const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
if (err) {
Expand Down Expand Up @@ -79,7 +79,7 @@ export async function generate(
recentOutputHash.set(result.filename, previousHash);
}

if (!shouldOverwrite(config, result.filename) && exists) {
if (!normalizeOverwriteConfig(config, result.filename).updateExistingFiles && exists) {
return;
}

Expand Down Expand Up @@ -189,20 +189,42 @@ export async function generate(
return outputFiles;
}

function shouldOverwrite(config: Types.Config, outputPath: string): boolean {
const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
const outputConfig = config.generates[outputPath];
function normalizeOverwriteConfig(
config: Types.Config,
outputPath: string,
): Types.NormalizedOverwriteOption {
const overwrite = (function getOverwriteOption(): Types.Config['overwrite'] {
const { overwrite: result = true } = config;
const outputConfig = config.generates[outputPath];

if (!outputConfig) {
debugLog(`Couldn't find a config of ${outputPath}`);
return result;
}
if (isConfiguredOutput(outputConfig) && outputConfig.overwrite !== undefined) {
return outputConfig.overwrite;
}

if (!outputConfig) {
debugLog(`Couldn't find a config of ${outputPath}`);
return globalValue;
return result;
})();

if (overwrite === true) {
return {
removeStaleFiles: true,
updateExistingFiles: true,
};
}

if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
return outputConfig.overwrite;
if (overwrite === false) {
return {
removeStaleFiles: false,
updateExistingFiles: false,
};
}

return globalValue;
const { removeStaleFiles = true, updateExistingFiles = true } = overwrite;

return { removeStaleFiles, updateExistingFiles };
}

function isConfiguredOutput(output: any): output is Types.ConfiguredOutput {
Expand Down
126 changes: 126 additions & 0 deletions packages/graphql-codegen-cli/tests/generate-and-save.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,132 @@ describe('generate-and-save', () => {
expect(writeSpy).toHaveBeenCalled();
});

test('should write to an existing file when global overwrite.updateExistingFiles=true', async () => {
const filename = 'overwrite.ts';
writeSpy.mockImplementation(() => Promise.resolve());
readSpy.mockImplementation(async () => ''); // forces file to exist

const output = await generate(
{
schema: SIMPLE_TEST_SCHEMA,
overwrite: {
updateExistingFiles: true,
removeStaleFiles: false,
},
generates: {
[filename]: {
schema: `
type OtherType { a: String }
`,
plugins: ['typescript'],
},
},
},
true,
);

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(readSpy).toHaveBeenCalledWith(filename);
// makes sure it writes a new file
expect(writeSpy).toHaveBeenCalledTimes(1);
});

test('should NOT write to an existing file when global overwrite.updateExistingFiles=false', async () => {
const filename = 'overwrite.ts';
writeSpy.mockImplementation(() => Promise.resolve());
readSpy.mockImplementation(async () => ''); // forces file to exist

const output = await generate(
{
schema: SIMPLE_TEST_SCHEMA,
overwrite: {
updateExistingFiles: false,
removeStaleFiles: true,
},
generates: {
[filename]: {
schema: `
type OtherType { a: String }
`,
plugins: ['typescript'],
},
},
},
true,
);

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(readSpy).toHaveBeenCalledWith(filename);
// makes sure it doesn't write a new file
expect(writeSpy).not.toHaveBeenCalled();
});

test("should write to an existing file when specific output's overwrite.updateExistingFiles=true", async () => {
const filename = 'overwrite.ts';
writeSpy.mockImplementation(() => Promise.resolve());
readSpy.mockImplementation(async () => ''); // forces file to exist

const output = await generate(
{
schema: SIMPLE_TEST_SCHEMA,
overwrite: false,
generates: {
[filename]: {
overwrite: {
updateExistingFiles: true,
},
schema: `
type OtherType { a: String }
`,
plugins: ['typescript'],
},
},
},
true,
);

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(readSpy).toHaveBeenCalledWith(filename);
// makes sure it writes a new file
expect(writeSpy).toHaveBeenCalledTimes(1);
});

test("should NOT write to an existing file when specific output's overwrite.updateExistingFiles=false", async () => {
const filename = 'overwrite.ts';
writeSpy.mockImplementation(() => Promise.resolve());
readSpy.mockImplementation(async () => ''); // forces file to exist

const output = await generate(
{
schema: SIMPLE_TEST_SCHEMA,
overwrite: {
updateExistingFiles: true,
},
generates: {
[filename]: {
overwrite: {
updateExistingFiles: false,
},
schema: `
type OtherType { a: String }
`,
plugins: ['typescript'],
},
},
},
true,
);

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(readSpy).toHaveBeenCalledWith(filename);
// makes sure it doesn't write a new file
expect(writeSpy).not.toHaveBeenCalled();
});

test('should override generated files', async () => {
vi.unmock('fs');
const fs = await import('fs');
Expand Down
Loading
Loading