Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): doctor command #169

Merged
merged 9 commits into from
Apr 5, 2024
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
5 changes: 5 additions & 0 deletions .changeset/nasty-masks-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gql.tada/cli-utils": minor
---

Add `doctor` command to diagnose common issues with the LSP and gql.tada
1 change: 1 addition & 0 deletions packages/cli-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"json5": "^2.2.3",
"rollup": "^4.9.4",
"sade": "^1.8.1",
"semiver": "^1.1.0",
"type-fest": "^4.10.2",
"typescript": "^5.3.3"
},
Expand Down
134 changes: 134 additions & 0 deletions packages/cli-utils/src/commands/doctor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { parse } from 'json5';
import semiver from 'semiver';
import type { TsConfigJson } from 'type-fest';
import { resolveTypeScriptRootDir } from '@gql.tada/internal';
import { existsSync } from 'node:fs';

import { getGraphQLSPConfig } from '../lsp';

const MINIMUM_VERSIONS = {
typescript: '4.1.0',
tada: '1.0.0',
lsp: '1.0.0',
};

export async function executeTadaDoctor() {
// Check TypeScript version
const cwd = process.cwd();
const packageJsonPath = path.resolve(cwd, 'package.json');
let packageJsonContents: {
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
};
try {
const file = path.resolve(packageJsonPath);
packageJsonContents = JSON.parse(await fs.readFile(file, 'utf-8'));
} catch (error) {
console.error(
'Failed to read package.json in current working directory, try running the doctor command in your workspace folder.'
);
return;
}

const deps = Object.entries({
...packageJsonContents.dependencies,
...packageJsonContents.devDependencies,
});

const typeScriptVersion = deps.find((x) => x[0] === 'typescript');
if (!typeScriptVersion) {
console.error('Failed to find a "typescript" installation, try installing one.');
return;
} else if (semiver(typeScriptVersion[1], MINIMUM_VERSIONS.typescript) === -1) {
// TypeScript version lower than v4.1 which is when they introduced template lits
console.error(
`Found an outdated "TypeScript" version, gql.tada requires at least ${MINIMUM_VERSIONS.typescript}.`
);
return;
}

const gqlspVersion = deps.find((x) => x[0] === "'@0no-co/graphqlsp'");
if (!gqlspVersion) {
console.error('Failed to find a "@0no-co/graphqlsp" installation, try installing one.');
return;
} else if (semiver(gqlspVersion[1], MINIMUM_VERSIONS.lsp) === -1) {
console.error(
`Found an outdated "@0no-co/graphqlsp" version, gql.tada requires at least ${MINIMUM_VERSIONS.lsp}.`
);
return;
}

const gqlTadaVersion = deps.find((x) => x[0] === "'gql.tada'");
if (!gqlTadaVersion) {
console.error('Failed to find a "gql.tada" installation, try installing one.');
return;
} else if (semiver(gqlTadaVersion[1], '1.0.0') === -1) {
console.error(
`Found an outdated "gql.tada" version, gql.tada requires at least ${MINIMUM_VERSIONS.tada}.`
);
return;
}

const tsconfigpath = path.resolve(cwd, 'tsconfig.json');
const root = (await resolveTypeScriptRootDir(tsconfigpath)) || cwd;

let tsconfigContents: string;
try {
const file = path.resolve(root, 'tsconfig.json');
tsconfigContents = await fs.readFile(file, 'utf-8');
} catch (error) {
console.error(
'Failed to read tsconfig.json in current working directory, try adding a "tsconfig.json".'
);
return;
}

let tsConfig: TsConfigJson;
try {
tsConfig = parse(tsconfigContents) as TsConfigJson;
} catch (err) {
console.error('Unable to parse tsconfig.json in current working directory.', err);
return;
}

// Check GraphQLSP version, later on we can check if a ts version is > 5.5.0 to use gql.tada/lsp instead of
// the LSP package.
const config = getGraphQLSPConfig(tsConfig);
if (!config) {
console.error(`Missing a "@0no-co/graphqlsp" plugin in your tsconfig.`);
return;
}

// TODO: this is optional I guess with the CLI being there and all
if (!config.tadaOutputLocation) {
console.error(`Missing a "tadaOutputLocation" setting in your GraphQLSP configuration.`);
return;
}

if (!config.schema) {
console.error(`Missing a "schema" setting in your GraphQLSP configuration.`);
return;
} else {
const isFile =
typeof config.schema === 'string' &&
(config.schema.endsWith('.json') || config.schema.endsWith('.graphql'));
if (isFile) {
const resolvedFile = path.resolve(root, config.schema as string);
if (!existsSync(resolvedFile)) {
console.error(`The schema setting does not point at an existing file "${resolvedFile}"`);
return;
}
} else {
try {
typeof config.schema === 'string' ? new URL(config.schema) : new URL(config.schema.url);
} catch (e) {
console.error(
`The schema setting does not point at a valid URL "${JSON.stringify(config.schema)}"`
);
return;
}
}
}
}
7 changes: 6 additions & 1 deletion packages/cli-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { parse } from 'json5';
import { printSchema } from 'graphql';

import type { GraphQLSchema } from 'graphql';
import type { TsConfigJson } from 'type-fest';
import { load } from '@gql.tada/internal';

import { getGraphQLSPConfig } from './lsp';
import { ensureTadaIntrospection } from './tada';
import { getTsConfig } from './tsconfig';
import { executeTadaDoctor } from './commands/doctor';
import { check } from './commands/check';

interface GenerateSchemaOptions {
Expand Down Expand Up @@ -98,6 +98,11 @@ prog.version(process.env.npm_package_version || '0.0.0');

async function main() {
prog
.command('doctor')
.describe('Finds common issues in your gql.tada setup.')
.action(async () => {
return executeTadaDoctor();
})
.command('generate-schema <target>')
.describe(
'Generate a GraphQL schema from a URL or introspection file, this will be generated from the parameters to this command.'
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

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

Loading