UNPKG

apollo-subgraph-cli

Version:
632 lines (597 loc) 21.5 kB
"use strict"; 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