apollo-subgraph-cli
Version:
A CLI for subgraph schema management
632 lines (597 loc) • 21.5 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/cli/index.ts
var import_fs_extra5 = __toESM(require("fs-extra"), 1);
var import_commander = require("commander");
// src/cli/constants/cliBanner.ts
var import_boxen = __toESM(require("boxen"), 1);
var import_chalk = __toESM(require("chalk"), 1);
var cliBanner = {
header: (command) => {
console.log(
(0, import_boxen.default)(import_chalk.default.blueBright(`subgraph: ${import_chalk.default.green(command)}`), {
borderStyle: "round",
textAlignment: "center",
width: 40
})
);
},
footer: () => {
console.log(import_chalk.default.green("\nTasks completed successfully. Happy coding!"));
}
};
// src/cli/constants/common.ts
var commands = {
init: "init",
print: "print",
check: "check"
};
var scripts = {
print: `subgraph schema ${commands.print} --config subgraph.config.ts`,
check: `subgraph schema ${commands.check} --config subgraph.config.ts`
};
var commonIgnoreDirs = /* @__PURE__ */ new Set([
"node_modules",
".git",
"dist",
"build",
"out",
"bin",
"obj",
".cache",
"tmp",
"temp",
".idea",
".vscode",
"__pycache__",
".venv",
"coverage",
"logs",
"test-output",
".gradle",
"target",
"vendor",
"migrations"
]);
// src/cli/tasks/index.ts
var import_listr2 = require("listr2");
// src/cli/tasks/generateSubgraphConfig.ts
var import_node_path = require("path");
var import_node_fs = require("fs");
// src/cli/templates/index.ts
var subgraphConfigTemplate = (schemaPaths, outputPath) => {
return `import type { SubgraphConfig } from 'apollo-subgraph-cli'
/**
* @property schema - The location of your subgraph's typeDefs
* @property output - The path to the printed schema file
*/
const config: SubgraphConfig = {
schema: [${schemaPaths.map((path) => `'${path}'`).join(", ")}],
output: '${outputPath}'
}
export default config
`;
};
// src/cli/tasks/generateSubgraphConfig.ts
var subTaskOptions = {
concurrent: false,
rendererOptions: { collapse: true }
};
var generateSubgraphConfig = {
title: "Generate subgraph config file",
task: (_, task) => task.newListr(
[
{
title: "Writing subgraph config to project root...",
task: async ({ schema: { paths, output } }) => {
try {
const configFilePath = (0, import_node_path.join)(process.cwd(), "subgraph.config.ts");
const configContent = subgraphConfigTemplate(paths, output);
(0, import_node_fs.writeFileSync)(configFilePath, configContent);
} catch (e) {
throw new Error(`Error generating subgraph config file: ${e.message}`);
}
}
}
],
subTaskOptions
)
};
// src/cli/tasks/generateSubgraphSchema.ts
var import_node_path7 = require("path");
var import_node_fs4 = require("fs");
var import_fs_extra3 = __toESM(require("fs-extra"), 1);
var import_subgraph2 = require("@apollo/subgraph");
// src/cli/utils/buildFileWatcher.ts
var import_path = require("path");
var import_chalk3 = __toESM(require("chalk"), 1);
// src/cli/utils/logPrefix.ts
var import_chalk2 = __toESM(require("chalk"), 1);
var colorMap = {
error: import_chalk2.default.red,
info: import_chalk2.default.blue,
success: import_chalk2.default.green
};
var logPrefix = (type, message) => `[${colorMap[type].bold(message)}]`;
// src/cli/utils/parseGlobPatterns.ts
var import_node_path2 = require("path");
var import_fast_glob = __toESM(require("fast-glob"), 1);
async function parseGlobPatterns(patterns, type = "file") {
const entries = await (0, import_fast_glob.default)(patterns, {
dot: true,
onlyFiles: type === "file",
onlyDirectories: type === "directory"
});
return entries.map((entry) => (0, import_node_path2.resolve)(process.cwd(), entry));
}
// src/cli/utils/generateSubgraphSchema.ts
var import_node_path3 = require("path");
var import_node_fs2 = require("fs");
var import_fs_extra = __toESM(require("fs-extra"), 1);
var import_subgraph = require("@apollo/subgraph");
// src/cli/utils/loadSchema.ts
var import_code_file_loader = require("@graphql-tools/code-file-loader");
var import_graphql_file_loader = require("@graphql-tools/graphql-file-loader");
var import_load = require("@graphql-tools/load");
var defaultSchemaLoadOptions = {
assumeValidSDL: true,
sort: true,
convertExtensions: true,
includeSources: true
};
async function loadSchema(schemaPointers, config = {}) {
try {
const loaders = [new import_code_file_loader.CodeFileLoader(), new import_graphql_file_loader.GraphQLFileLoader()];
const schema = await (0, import_load.loadSchema)(schemaPointers, {
...defaultSchemaLoadOptions,
loaders,
...config,
...config.config
});
return schema;
} catch (e) {
throw new Error(
`
Failed to load schema from ${Object.keys(schemaPointers).join(",")}:
${e.message || e}
${e.stack || ""}
Apollo Subgraph CLI supports all extensions for:
- GraphQL files
- TS/JS files
Try to use one of above options and run codegen again.
`
);
}
}
// src/cli/utils/generateSubgraphSchema.ts
var { pathExists } = import_fs_extra.default;
var generateSubgraphSchema = async ({ paths, output }) => {
let loadedSchema;
let schemaFilePath;
try {
const schemaPaths = paths.map((path) => (0, import_node_path3.join)(process.cwd(), path));
loadedSchema = await loadSchema(schemaPaths);
schemaFilePath = (0, import_node_path3.join)(process.cwd(), output);
const { dir } = (0, import_node_path3.parse)(schemaFilePath);
const exists = await pathExists(dir);
if (!exists) {
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
}
} catch (e) {
throw new Error(`${logPrefix("error", "ERROR DETECTED")}: ${e.message}`);
}
let schema;
try {
schema = (0, import_subgraph.printSubgraphSchema)(loadedSchema);
} catch (e) {
throw new Error(`${logPrefix("error", "SCHEMA ERROR DETECTED")}: ${e.message}`);
}
try {
(0, import_node_fs2.writeFileSync)(schemaFilePath, schema);
} catch (e) {
throw new Error(`${logPrefix("error", "ERROR DETECTED")}: ${e.message}`);
}
};
// src/cli/utils/resolveModule.ts
var import_node_path4 = require("path");
var import_promises = require("fs/promises");
var resolveModule = async (moduleName, startDir) => {
const modulePath = (0, import_node_path4.join)(startDir, "node_modules", moduleName);
try {
await (0, import_promises.access)(modulePath);
return modulePath;
} catch {
const parentDir = (0, import_node_path4.dirname)(startDir);
if (parentDir === startDir) {
throw new Error(`Cannot find module '${moduleName}'`);
}
return resolveModule(moduleName, parentDir);
}
};
// src/cli/utils/loadChokidar.ts
var loadChokidar = async () => {
try {
await resolveModule("chokidar", process.cwd());
return await import("chokidar");
} catch (e) {
throw new Error(
'The "chokidar" package is required and needs to be installed if watching for file changes.'
);
}
};
// src/cli/utils/buildFileWatcher.ts
var buildFileWatcher = async (gqlSchemaPath, outputPath) => {
const schemaPaths = await parseGlobPatterns(gqlSchemaPath, "file");
const chokidar = await loadChokidar();
const watcher = chokidar.watch(schemaPaths, { persistent: true });
const printedPaths = schemaPaths.map((path) => {
const { name, ext } = (0, import_path.parse)(path);
return ` - ${import_chalk3.default.green(name + ext)}`;
}).join("\n");
console.log(`${logPrefix("info", "INFO")}: Watching for schema changes in`);
console.log(printedPaths);
watcher.on("change", async (filePath) => {
const { name, ext } = (0, import_path.parse)(filePath);
try {
await generateSubgraphSchema({ paths: gqlSchemaPath, output: outputPath });
console.info(
`${logPrefix("success", "CHANGE DETECTED")}:${logPrefix("success", name + ext)} Writing changes to ${import_chalk3.default.green(outputPath)}`
);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
}
});
watcher.on("error", async (e) => {
await watcher?.close();
console.error(e);
});
};
// src/cli/utils/getSubgraphArgsFromCosmicConfigFile.ts
var import_cosmiconfig = require("cosmiconfig");
var import_cosmiconfig_typescript_loader = require("cosmiconfig-typescript-loader");
var getSubgraphArgsFromCosmicConfigFile = async (search) => {
const moduleName = "subgraph";
const searchPlaces = search ? [search] : [
"package.json",
`.${moduleName}rc`,
`.${moduleName}rc.json`,
`.${moduleName}rc.yaml`,
`.${moduleName}rc.yml`,
`.${moduleName}rc.js`,
`.${moduleName}rc.ts`,
`${moduleName}.config.js`,
`${moduleName}.config.ts`
];
const result = await (0, import_cosmiconfig.cosmiconfig)(moduleName, {
searchPlaces,
loaders: { ".ts": (0, import_cosmiconfig_typescript_loader.TypeScriptLoader)() }
}).search();
return result?.config;
};
// src/cli/utils/getPackageJson.ts
var import_node_path5 = require("path");
var import_fs_extra2 = __toESM(require("fs-extra"), 1);
var { readJsonSync } = import_fs_extra2.default;
var getPackageJson = () => {
const packageJsonPath = (0, import_node_path5.join)(process.cwd(), "package.json");
const packageJson = readJsonSync(packageJsonPath);
return { output: packageJson, path: packageJsonPath };
};
// src/cli/utils/findFilesByFileName.ts
var import_node_fs3 = require("fs");
var import_node_path6 = require("path");
var findFilesByFileName = (dir, fileNames, ig) => {
const results = [];
function searchDir(currentDir) {
const entries = (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true });
for (const entry of entries) {
const relativePath = (0, import_node_path6.relative)(process.cwd(), (0, import_node_path6.join)(currentDir, entry.name));
if (ig.ignores(relativePath)) continue;
const entryPath = (0, import_node_path6.join)(currentDir, entry.name);
if (entry.isDirectory()) {
searchDir(entryPath);
} else if (fileNames.includes(entry.name)) {
results.push(entryPath);
}
}
}
searchDir(dir);
return results;
};
// src/cli/utils/isGitRepo.ts
var import_child_process = require("child_process");
var isGitRepo = () => {
try {
(0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
return true;
} catch (e) {
return false;
}
};
// src/cli/tasks/generateSubgraphSchema.ts
var { pathExists: pathExists2, mkdir } = import_fs_extra3.default;
var subTaskOptions2 = {
concurrent: false,
rendererOptions: { collapse: true }
};
var generateSubgraphSchema2 = {
title: "Generate subgraph schema file",
task: (_, task) => task.newListr(
[
{
title: "Printing schema file output...",
task: async ({ schema: { paths, output } }) => {
try {
const schemaPaths = paths.map((path) => (0, import_node_path7.join)(process.cwd(), path));
const loadedSchema = await loadSchema(schemaPaths);
const schemaFilePath = (0, import_node_path7.join)(process.cwd(), output);
const { dir } = (0, import_node_path7.parse)(schemaFilePath);
const exists = await pathExists2(dir);
if (!exists) {
await mkdir(dir, { recursive: true });
}
(0, import_node_fs4.writeFileSync)(schemaFilePath, (0, import_subgraph2.printSubgraphSchema)(loadedSchema));
} catch (e) {
throw new Error(`Error generating subgraph schema file: ${e.message}`);
}
}
}
],
subTaskOptions2
)
};
// src/cli/tasks/checkSubgraphSchema.ts
var import_node_path8 = require("path");
var import_subgraph3 = require("@apollo/subgraph");
var subTaskOptions3 = {
concurrent: false,
rendererOptions: { collapse: true }
};
var checkSubgraphSchema = {
title: "Check subgraph schema",
task: (_, task) => task.newListr(
[
{
title: "Checking schema file output...",
task: async ({ schema: { paths } }, task2) => {
try {
const schemaPaths = paths.map((path) => (0, import_node_path8.join)(process.cwd(), path));
const loadedSchema = await loadSchema(schemaPaths);
(0, import_subgraph3.printSubgraphSchema)(loadedSchema);
task2.output = "No issues found with federated schema";
} catch (e) {
throw new Error(`Error found with federated schema: ${e.message}`);
}
}
}
],
subTaskOptions3
)
};
// src/cli/tasks/updatePackageJSON.ts
var import_fs_extra4 = __toESM(require("fs-extra"), 1);
var { writeJSONSync } = import_fs_extra4.default;
var subTaskOptions4 = {
concurrent: false,
rendererOptions: { collapse: true }
};
var updatePackageJSON = {
title: "Update package.json",
task: (_, task) => task.newListr(
[
{
title: "Adding and updating npm scripts",
task: async ({ printScriptName, checkScriptName }) => {
try {
const { output, path } = getPackageJson();
output.scripts = {
...output.scripts ?? {},
[printScriptName ?? "schema:print"]: scripts.print,
[checkScriptName ?? "schema:check"]: scripts.check
};
writeJSONSync(path, output, { spaces: 2 });
} catch (e) {
throw new Error(`Error writing to package.json: ${e.message}`);
}
}
}
],
subTaskOptions4
)
};
// src/cli/tasks/addFilesToGitIndex.ts
var import_node_path9 = require("path");
var import_node_child_process = require("child_process");
var import_node_fs5 = require("fs");
var import_ignore = __toESM(require("ignore"), 1);
var subTaskOptions5 = {
concurrent: false,
rendererOptions: { collapse: true }
};
var addFilesToGitIndex = {
title: "Add files to git",
skip: ({ git = false }) => !git ? "Skipping add files to git" : false,
task: ({ schema: { output } }, task) => task.newListr(
[
{
title: "Performing git add...",
task: (_, task2) => {
const gitRepo = isGitRepo();
if (!gitRepo) {
task2.output = "No git repo found.";
return;
}
const { name, ext } = (0, import_node_path9.parse)(output);
const generatedSchemaFileName = `${name}.${ext}`;
const fileNames = ["subgraph.config.ts", "package.json", generatedSchemaFileName];
const gitignorePath = (0, import_node_path9.join)(process.cwd(), ".gitignore");
const ig = (0, import_ignore.default)();
if ((0, import_node_fs5.existsSync)(gitignorePath)) {
const gitignoreContent = (0, import_node_fs5.readFileSync)(gitignorePath, "utf-8");
ig.add(gitignoreContent);
} else {
ig.add([...commonIgnoreDirs].map((dir) => `/${dir}/`));
}
try {
const filesPaths = findFilesByFileName(process.cwd(), fileNames, ig);
if (!filesPaths.length) {
task2.output = "No files found to add to git staging.";
return;
} else {
(0, import_node_child_process.execSync)(`git add ${filesPaths.map((file) => `${file}`).join(" ")}`);
}
} catch (e) {
throw new Error(`Error adding files to git: ${e.message}`);
}
}
}
],
subTaskOptions5
)
};
// src/cli/tasks/index.ts
var listrOptions = {
concurrent: false,
rendererOptions: { collapseSkips: false }
};
var tasks = {
init: new import_listr2.Listr(
[generateSubgraphConfig, generateSubgraphSchema2, updatePackageJSON, addFilesToGitIndex],
listrOptions
),
print: new import_listr2.Listr([generateSubgraphSchema2], listrOptions),
check: new import_listr2.Listr([checkSubgraphSchema], listrOptions)
};
// src/cli/prompts/index.ts
var import_prompts = require("@inquirer/prompts");
var getSchemaPaths = async (paths = []) => {
const path = await (0, import_prompts.input)({
message: "Enter schema path:",
default: "./src/gql/typeDefs/*.ts"
});
paths.push(path);
const additionalInput = await (0, import_prompts.confirm)({ message: "Do you have another path to provide?" });
if (additionalInput) {
await getSchemaPaths(paths);
}
return paths;
};
var getOutputPath = async () => (0, import_prompts.input)({
message: "Where would you like to write the output?",
default: "./schema.graphql",
validate: (val) => {
return val?.includes(".graphql") || "The output has to use the .graphql file extension";
}
});
var getPrintScriptName = async () => (0, import_prompts.input)({
message: "What would you like to name the npm script to print your schema?",
default: "schema:print",
validate: (val) => !!val?.length
});
var getCheckScriptName = async () => (0, import_prompts.input)({
message: "What would you like to name the npm script to check your schema?",
default: "schema:check",
validate: (val) => !!val?.length
});
var confirmAddFilesToGitStaging = async () => (0, import_prompts.confirm)({ message: "Do you want to add the new files to the git staging area?" });
// src/cli/index.ts
(async () => {
const packageJSON = await import_fs_extra5.default.readJson("./package.json");
const subgraph = new import_commander.Command("subgraph").description("A CLI for federated subgraph management").version(packageJSON.version);
const schema = new import_commander.Command("schema").description(
"Helpful commands for managing your subgraph schema"
);
schema.command(commands.check).option("-c, --config <path-to-subgraph-config>", "The path to your subgraph config file").option("-s, --schema <schema-location...>", "Location of your subgraph's typeDefs").action(async (options) => {
cliBanner.header("schema check");
const config = await getSubgraphArgsFromCosmicConfigFile(options.config);
const gqlSchemaPath = options.schema ?? config?.schema ?? await getSchemaPaths();
try {
await tasks.check.run({
schema: { paths: gqlSchemaPath }
});
cliBanner.footer();
} catch (e) {
console.error(e);
process.exit(1);
}
});
schema.command(commands.print).option("-c, --config <path-to-subgraph-config>", "The path to your subgraph config file").option("-s, --schema <schema-location...>", "Location of your subgraph's typeDefs").option("-o, --output <output-path>", "The path to the printed schema file").option("-w, --watch", "Watch for file changes and print the resulting schema file").action(async (options) => {
const config = await getSubgraphArgsFromCosmicConfigFile(options.config);
const gqlSchemaPath = options.schema ?? config?.schema ?? await getSchemaPaths();
const outputPath = options.output ?? config?.output ?? await getOutputPath();
if (options.watch) {
try {
await generateSubgraphSchema({ paths: gqlSchemaPath, output: outputPath });
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
}
await buildFileWatcher(gqlSchemaPath, outputPath);
} else {
cliBanner.header("schema print");
try {
await tasks.print.run({
schema: {
paths: gqlSchemaPath,
output: outputPath
}
});
cliBanner.footer();
} catch (e) {
console.error(e);
process.exit(1);
}
}
});
schema.command(commands.init).action(async () => {
cliBanner.header("schema init");
console.log("Let's get started by answering a few questions about your service.\n");
const schemaPaths = await getSchemaPaths();
const outputPath = await getOutputPath();
const printScriptName = await getPrintScriptName();
const checkScriptName = await getCheckScriptName();
const git = await confirmAddFilesToGitStaging();
console.log("\n");
try {
await tasks.init.run({
git,
printScriptName,
checkScriptName,
schema: {
paths: schemaPaths,
output: outputPath
}
});
cliBanner.footer();
} catch (e) {
process.exit(1);
}
});
subgraph.addCommand(schema);
subgraph.parse();
})();
//# sourceMappingURL=index.cjs.map