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
6 changes: 6 additions & 0 deletions .changeset/honest-walls-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"graphile": patch
---

Don't throw error for `graphile --help` when certain libraries are not
installed.
86 changes: 86 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// @ts-check
import { builtinModules } from "node:module";

import babelParser from "@babel/eslint-parser";
import js from "@eslint/js";
import graphql from "@graphql-eslint/eslint-plugin";
Expand All @@ -18,6 +20,78 @@ import path from "path";
import tseslint from "typescript-eslint";

const __dirname = import.meta.dirname;
const graphileCliRoot = "/utils/graphile/src";
const stripAnsiPath = `${graphileCliRoot}/stripAnsi.ts`;
const builtinModuleNames = new Set(
builtinModules.flatMap((name) =>
name.startsWith("node:") ? [name, name.slice("node:".length)] : [name],
),
);

function normalizePath(filePath) {
return filePath.replaceAll(path.sep, "/");
}

function isGraphileCliFile(filename) {
const normalized = normalizePath(filename);
return normalized.includes(graphileCliRoot) && normalized.endsWith("/cli.ts");
}

function resolvesToAllowedRelativeImport(filename, source) {
if (!source.startsWith(".")) {
return false;
}
const resolved = normalizePath(path.resolve(path.dirname(filename), source));
return resolved.endsWith("/cli.ts") || resolved.endsWith(stripAnsiPath);
}

const graphileCliImportsRule = {
meta: {
type: "problem",
schema: [],
},
create(context) {
const filename = context.filename ?? context.getFilename();
if (!isGraphileCliFile(filename)) {
return {};
}

return {
ImportDeclaration(node) {
if (node.importKind === "type") {
return;
}
const source = node.source.value;
if (typeof source !== "string") {
context.report({
node,
message:
"Static imports in `utils/graphile/src/**/cli.ts` must use a string module specifier.",
});
return;
}
const allowed =
source === "graphile-config" ||
source.startsWith("graphile-config/") ||
source === "chalk" ||
source === "yargs" ||
source === "yargs/helpers" ||
builtinModuleNames.has(source) ||
resolvesToAllowedRelativeImport(filename, source);
if (!allowed) {
context.report({
node,
message:
"Static import '{{ source }}' is not allowed in `utils/graphile/src/**/cli.ts` because it might require software to be installed even when this command isn't invoked; use `await import(...)` inside `run()` instead.",
data: {
source,
},
});
}
},
};
},
};

const globalIgnoresFromFile = fs
.readFileSync(path.resolve(__dirname, ".lintignore"), "utf8")
Expand Down Expand Up @@ -64,6 +138,11 @@ const config = {
},

plugins: {
crystal: {
rules: {
"graphile-cli-imports": graphileCliImportsRule,
},
},
jest,
"@graphql-eslint": graphql,
tsdoc,
Expand Down Expand Up @@ -331,6 +410,13 @@ const oldConfig = {
},
},

{
files: ["utils/graphile/src/**/cli.ts"],
rules: {
"crystal/graphile-cli-imports": "error",
},
},

// Don't use Node.js builtins
{
files: ["grafast/grafast/src/**", "utils/graphile-config/src/**"],
Expand Down
3 changes: 1 addition & 2 deletions utils/graphile/src/commands/behavior/debug/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { ArgsFromOptions, Argv } from "graphile-config/cli";

import { main } from "./main.ts";

export function options(yargs: Argv) {
return yargs
.positional("entityType", { type: "string" })
Expand All @@ -19,6 +17,7 @@ export function options(yargs: Argv) {
});
}
export async function run(args: ArgsFromOptions<typeof options>) {
const { main } = await import("./main.ts");
const text = await main({
config: args.config,
entityType: args.entityType,
Expand Down
5 changes: 2 additions & 3 deletions utils/graphile/src/commands/config/options/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { ArgsFromOptions, Argv } from "graphile-config/cli";

import { main } from "./main.ts";

export const command = "options [scope]";

export const description = "Output the options your config may contain";
Expand Down Expand Up @@ -29,7 +27,8 @@ export function options(yargs: Argv) {
type: "string",
});
}
export function run(args: ArgsFromOptions<typeof options>) {
export async function run(args: ArgsFromOptions<typeof options>) {
const { main } = await import("./main.ts");
const text = main({ filename: args.config, scope: args.scope });
console.log(text);
}
3 changes: 1 addition & 2 deletions utils/graphile/src/commands/inflection/list/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { ArgsFromOptions, Argv } from "graphile-config/cli";

import { main } from "./main.ts";

export function options(yargs: Argv) {
return yargs
.example(
Expand All @@ -21,6 +19,7 @@ export function options(yargs: Argv) {
});
}
export async function run(args: ArgsFromOptions<typeof options>) {
const { main } = await import("./main.ts");
const text = await main({ filename: args.config, quiet: args.quiet });
console.log(text);
}
18 changes: 9 additions & 9 deletions utils/graphile/src/commands/inflection/list/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import path from "node:path";

import chalk from "chalk";
import type { InflectorSource } from "graphile-build";
import { buildInflection } from "graphile-build";
import { resolvePreset } from "graphile-config";
import { loadConfig } from "graphile-config/load";
import type { CompletionEntry } from "typescript";

import type { ResolvedDefinition } from "../../../utils/typescriptVfs.ts";
import {
accessKey,
configVfs,
prettyDocumentation,
prettyQuickInfoDisplayParts,
tightDisplayParts,
tightDocumentation,
} from "../../../utils/typescriptVfs.ts";

export async function main(options: { filename?: string; quiet?: boolean }) {
const {
accessKey,
configVfs,
prettyDocumentation,
prettyQuickInfoDisplayParts,
tightDisplayParts,
tightDocumentation,
} = await import("../../../utils/typescriptVfs.ts");
const { buildInflection } = await import("graphile-build");
const { filename, quiet } = options;

// Create inflection so we can determine where the inflectors came from
Expand Down
Loading