Skip to content

Commit

Permalink
Add graphql-codegen support (#305)
Browse files Browse the repository at this point in the history
* Add graphql-codegen support

* Add `PACKAGE_JSON_PATH` to graphql-codegen

* cleenup tests

* cleenup logs

---------

Co-authored-by: Lars Kappert <[email protected]>
  • Loading branch information
beaussan and webpro authored Oct 23, 2023
1 parent b96c807 commit eb64c62
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ Knip contains a growing list of plugins:
- [ESLint][plugin-eslint]
- [Gatsby][plugin-gatsby]
- [GitHub Actions][plugin-github-actions]
- [Graphql Codegen][plugin-graphql-codegen]
- [husky][plugin-husky]
- [Jest][plugin-jest]
- [Lefthook][plugin-lefthook]
Expand Down Expand Up @@ -928,6 +929,7 @@ Special thanks to the wonderful people who have contributed to this project:
[plugin-eslint]: ./src/plugins/eslint
[plugin-gatsby]: ./src/plugins/gatsby
[plugin-github-actions]: ./src/plugins/github-actions
[plugin-graphql-codegen]: ./src/plugins/graphql-codegen
[plugin-husky]: ./src/plugins/husky
[plugin-jest]: ./src/plugins/jest
[plugin-lefthook]: ./src/plugins/lefthook
Expand Down
21 changes: 21 additions & 0 deletions fixtures/plugins/graphql-codegen/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
schema: 'schema.graphql',
overwrite: true,
generates: {
'./graphql.schema.json': {
plugins: ['introspection'],
},
'./graphql.schema.graphql': {
'schema-ast': {},
},
'./src/generated/graphql.ts': {
documents: ['./src/**/*.tsx'],
plugins: ['typescript', 'typescript-operations', 'typescript-urql'],
},
'./lib/': {
documents: ['./lib/**/*.tsx'],
preset: 'near-operation-file-preset',
plugins: ['typescript-operations', 'typescript-msw'],
},
},
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions fixtures/plugins/graphql-codegen/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@fixtures/_template",
"version": "*",
"devDependencies": {
"@graphql-codegen/cli": "*"
},
"codegen": {
"schema": "schema.graphql",
"documents": ["src/**/*.tsx", "!src/gql/**/*"],
"generates": {
"./src/gql/": {
"preset": "client"
}
}
}
}
4 changes: 4 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@
"title": "github-actions plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/github-actions/README.md)",
"$ref": "#/definitions/plugin"
},
"graphql-codegen": {
"title": "graphql-codegen plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/graphql-codegen/README.md)",
"$ref": "#/definitions/plugin"
},
"husky": {
"title": "husky plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/husky/README.md)",
"$ref": "#/definitions/plugin"
Expand Down
1 change: 1 addition & 0 deletions src/ConfigurationValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const pluginSchema = z.union([
]);

const pluginsSchema = z.object({
'graphql-codegen': pluginSchema,
astro: pluginSchema,
angular: pluginSchema,
ava: pluginSchema,
Expand Down
22 changes: 22 additions & 0 deletions src/plugins/graphql-codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Graphql Codegen

## Enabled

This plugin is enabled when any of the following package names and/or regular expressions has a match in `dependencies`
or `devDependencies`:

- `/^@graphql-codegen\//`

## Default configuration

```json
{
"graphql-codegen": {
"config": ["codegen.{ts,js,json,yml,mjs,cts}", "package.json"]
}
}
```

Also see [Knip plugins][1] for more information about plugins.

[1]: https://github.com/webpro/knip/blob/main/README.md#plugins
54 changes: 54 additions & 0 deletions src/plugins/graphql-codegen/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { timerify } from '../../util/Performance.js';
import { hasDependency, load } from '../../util/plugin.js';
import { isConfigurationOutput } from './types.js';
import type { ConfiguredPlugin, GraphqlCodegenTypes, PresetNames } from './types.js';
import type { IsPluginEnabledCallback, GenericPluginCallback } from '../../types/plugins.js';

export const NAME = 'Graphql Codegen';

/** @public */
export const ENABLERS = [/^@graphql-codegen\//];

export const PACKAGE_JSON_PATH = 'codegen';

export const isEnabled: IsPluginEnabledCallback = ({ dependencies }) => hasDependency(dependencies, ENABLERS);

export const CONFIG_FILE_PATTERNS = ['codegen.{ts,js,json,yml,mjs,cts}', 'package.json'];

const findPluginDependencies: GenericPluginCallback = async (configFilePath, options) => {
const { manifest, isProduction } = options;

if (isProduction) return [];

// load configuration file from `configFilePath` (or grab `manifest` for package.json)
// load(FAKE_PATH) will return `undefined`
const localConfig: GraphqlCodegenTypes | undefined = configFilePath.endsWith('package.json')
? manifest[PACKAGE_JSON_PATH]
: await load(configFilePath);

if (!localConfig) return [];

const generateSet = Object.values(localConfig.generates);

const configurationOutput = generateSet.filter(isConfigurationOutput);

const presets = configurationOutput
.map(configOutput => (configOutput.preset ? configOutput.preset : undefined))
.filter((preset): preset is PresetNames => typeof preset === 'string')
.map(presetName => `@graphql-codegen/${presetName}${presetName.endsWith('-preset') ? '' : '-preset'}`);

const flatPlugins = generateSet
.filter((config): config is ConfiguredPlugin => !isConfigurationOutput(config))
.map(item => Object.keys(item))
.flat()
.map(plugin => `@graphql-codegen/${plugin}`);

const nestedPlugins = configurationOutput
.map(configOutput => (configOutput.plugins ? configOutput.plugins : []))
.flat()
.map(plugin => `@graphql-codegen/${plugin}`);

return [...presets, ...flatPlugins, ...nestedPlugins];
};

export const findDependencies = timerify(findPluginDependencies);
53 changes: 53 additions & 0 deletions src/plugins/graphql-codegen/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
type PluginConfig<T = unknown> = { [key: string]: T };
export interface ConfiguredPlugin {
[name: string]: PluginConfig;
}
type NamedPlugin = string;

type OutputConfig = NamedPlugin | ConfiguredPlugin;
type PresetNamesBase = 'client' | 'near-operation-file' | 'gql-tag-operations' | 'graphql-modules' | 'import-types';
export type PresetNames = `${PresetNamesBase}-preset` | PresetNamesBase;

type OutputPreset = {
buildGeneratesSection: (options: unknown) => Promise<unknown>;
prepareDocuments?: (outputFilePath: string, outputSpecificDocuments: unknown) => Promise<unknown>;
};

export function isConfigurationOutput(config: ConfiguredOutput | ConfiguredPlugin[]): config is ConfiguredOutput {
return 'preset' in config || 'plugins' in config;
}

interface ConfiguredOutput {
/**
* @type array
* @items { "$ref": "#/definitions/GeneratedPluginsMap" }
* @description List of plugins to apply to this current output file.
*
* You can either specify plugins from the community using the NPM package name (after you installed it in your project), or you can use a path to a local file for custom plugins.
*
* You can find a list of available plugins here: https://the-guild.dev/graphql/codegen/docs/plugins/index
* Need a custom plugin? read this: https://the-guild.dev/graphql/codegen/docs/custom-codegen/index
*/
plugins?: OutputConfig[];
/**
* @description If your setup uses Preset to have a more dynamic setup and output, set the name of your preset here.
*
* Presets are a way to have more than one file output, for example: https://the-guild.dev/graphql/codegen/docs/presets/near-operation-file
*
* You can either specify a preset from the community using the NPM package name (after you installed it in your project), or you can use a path to a local file for a custom preset.
*
* List of available presets: https://graphql-code-generator.com/docs/presets/presets-index
*/
preset?: PresetNames | OutputPreset;
}
// Extracted from https://github.com/dotansimha/graphql-code-generator/blob/master/packages/utils/plugins-helpers/src/types.ts
export interface GraphqlCodegenTypes {
/**
* @description A map where the key represents an output path for the generated code and the value represents a set of options which are relevant for that specific file.
*
* For more details: https://graphql-code-generator.com/docs/config-reference/codegen-config
*/
generates: {
[outputPath: string]: ConfiguredOutput | ConfiguredPlugin[];
};
}
1 change: 1 addition & 0 deletions src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ export * as typescript from './typescript/index.js';
export * as vite from './vite/index.js';
export * as vitest from './vitest/index.js';
export * as webpack from './webpack/index.js';
export * as graphqlCodegen from './graphql-codegen/index.js';
56 changes: 56 additions & 0 deletions test/plugins/graphql-codegen.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { main } from '../../src/index.js';
import * as graphqlCodegen from '../../src/plugins/graphql-codegen/index.js';
import { resolve, join } from '../../src/util/path.js';
import baseArguments from '../helpers/baseArguments.js';
import baseCounters from '../helpers/baseCounters.js';
import { getManifest, pluginConfig as config } from '../helpers/index.js';

const cwd = resolve('fixtures/plugins/graphql-codegen');
const manifest = getManifest(cwd);

test('Find dependencies in graphql-codegen configuration (json)', async () => {
const configFilePath = join(cwd, 'package.json');
const dependencies = await graphqlCodegen.findDependencies(configFilePath, { manifest, config });
assert.deepEqual(dependencies, ['@graphql-codegen/client-preset']);
});

test('Find dependencies in graphql-codegen configuration (codegen.ts)', async () => {
const configFilePath = join(cwd, 'codegen.ts');
const dependencies = await graphqlCodegen.findDependencies(configFilePath, { manifest, config });
assert.deepEqual(dependencies, [
'@graphql-codegen/near-operation-file-preset',
'@graphql-codegen/schema-ast',
'@graphql-codegen/introspection',
'@graphql-codegen/typescript',
'@graphql-codegen/typescript-operations',
'@graphql-codegen/typescript-urql',
'@graphql-codegen/typescript-operations',
'@graphql-codegen/typescript-msw',
]);
});

test('Find dependencies in graphql-codegen configuration (codegen.ts function)', async () => {
const { issues, counters } = await main({
...baseArguments,
cwd,
});

assert(issues.unlisted['codegen.ts']['@graphql-codegen/near-operation-file-preset']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/schema-ast']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/introspection']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/typescript']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/typescript-operations']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/typescript-urql']);
assert(issues.unlisted['codegen.ts']['@graphql-codegen/typescript-msw']);
assert(issues.unlisted['package.json']['@graphql-codegen/client-preset']);

assert.deepEqual(counters, {
...baseCounters,
unlisted: 8,
devDependencies: 1,
processed: 1,
total: 1,
});
});

0 comments on commit eb64c62

Please sign in to comment.