@graphql-mesh/cli
Version:
405 lines (403 loc) • 19.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_CLI_PARAMS = exports.handleFatalError = exports.findAndParseConfig = exports.serveMesh = exports.generateTsArtifacts = exports.findConfig = void 0;
exports.graphqlMesh = graphqlMesh;
const tslib_1 = require("tslib");
const dotenv_1 = require("dotenv");
const yargs_1 = tslib_1.__importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const cross_helpers_1 = require("@graphql-mesh/cross-helpers");
const include_1 = require("@graphql-mesh/include");
const runtime_1 = require("@graphql-mesh/runtime");
const store_1 = require("@graphql-mesh/store");
const utils_1 = require("@graphql-mesh/utils");
const utils_2 = require("@graphql-tools/utils");
const serve_js_1 = require("./commands/serve/serve.js");
Object.defineProperty(exports, "serveMesh", { enumerable: true, get: function () { return serve_js_1.serveMesh; } });
const ts_artifacts_js_1 = require("./commands/ts-artifacts.js");
Object.defineProperty(exports, "generateTsArtifacts", { enumerable: true, get: function () { return ts_artifacts_js_1.generateTsArtifacts; } });
const config_js_1 = require("./config.js");
Object.defineProperty(exports, "findAndParseConfig", { enumerable: true, get: function () { return config_js_1.findAndParseConfig; } });
const handleFatalError_js_1 = require("./handleFatalError.js");
Object.defineProperty(exports, "handleFatalError", { enumerable: true, get: function () { return handleFatalError_js_1.handleFatalError; } });
var config_js_2 = require("./config.js");
Object.defineProperty(exports, "findConfig", { enumerable: true, get: function () { return config_js_2.findConfig; } });
exports.DEFAULT_CLI_PARAMS = {
commandName: 'mesh',
initialLoggerPrefix: '🕸️ Mesh',
configName: 'mesh',
artifactsDir: cross_helpers_1.process.env.MESH_ARTIFACTS_DIRNAME || '.mesh',
serveMessage: 'Serving GraphQL Mesh',
playgroundTitle: 'GraphiQL Mesh',
builtMeshFactoryName: 'getBuiltMesh',
builtMeshSDKFactoryName: 'getMeshSDK',
devServerCommand: 'dev',
prodServerCommand: 'start',
buildArtifactsCommand: 'build',
sourceServerCommand: 'serve-source',
validateCommand: 'validate',
additionalPackagePrefixes: [],
};
async function graphqlMesh(cliParams = exports.DEFAULT_CLI_PARAMS, args = (0, helpers_1.hideBin)(cross_helpers_1.process.argv), cwdPath = cross_helpers_1.process.cwd()) {
let baseDir = cwdPath;
let logger = new utils_1.DefaultLogger(cliParams.initialLoggerPrefix);
const unregisterTsconfigPaths = (0, include_1.registerTsconfigPaths)({ cwd: baseDir });
return (0, yargs_1.default)(args)
.help()
.option('r', {
alias: 'require',
describe: 'Loads specific require.extensions before running the codegen and reading the configuration',
type: 'array',
default: [],
coerce: (externalModules) => Promise.all(externalModules.map(moduleName => {
const localModulePath = cross_helpers_1.path.resolve(baseDir, moduleName);
const islocalModule = cross_helpers_1.fs.existsSync(localModulePath);
return (0, include_1.include)(islocalModule ? localModulePath : moduleName);
})),
})
.option('dir', {
describe: 'Modified the base directory to use for looking for ' +
cliParams.configName +
' config file',
type: 'string',
default: baseDir,
coerce: dir => {
if (cross_helpers_1.path.isAbsolute(dir)) {
baseDir = dir;
}
else {
baseDir = cross_helpers_1.path.resolve(cwdPath, dir);
}
unregisterTsconfigPaths();
(0, include_1.registerTsconfigPaths)({ cwd: baseDir });
if (cross_helpers_1.fs.existsSync(cross_helpers_1.path.join(baseDir, '.env'))) {
(0, dotenv_1.config)({
path: cross_helpers_1.path.join(baseDir, '.env'),
});
}
},
})
.command(cliParams.devServerCommand, 'Serves a GraphQL server with GraphQL interface by building artifacts on the fly', builder => {
builder.option('port', {
type: 'number',
});
}, async (args) => {
try {
const outputDir = cross_helpers_1.path.join(baseDir, cliParams.artifactsDir);
cross_helpers_1.process.env.NODE_ENV = 'development';
const meshConfig = await (0, config_js_1.findAndParseConfig)({
dir: baseDir,
artifactsDir: cliParams.artifactsDir,
configName: cliParams.configName,
additionalPackagePrefixes: cliParams.additionalPackagePrefixes,
initialLoggerPrefix: cliParams.initialLoggerPrefix,
importFn: include_1.include,
});
logger = meshConfig.logger;
// eslint-disable-next-line no-inner-declarations
function buildMeshInstance() {
return (0, runtime_1.getMesh)(meshConfig).then(meshInstance => {
// We already handle Mesh instance errors inside `serveMesh`
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(0, utils_1.writeFile)(cross_helpers_1.path.join(outputDir, 'schema.graphql'), (0, utils_2.printSchemaWithDirectives)(meshInstance.schema)).catch(e => logger.error(`An error occured while writing the schema file: `, e));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(0, ts_artifacts_js_1.generateTsArtifacts)({
unifiedSchema: meshInstance.schema,
rawSources: meshInstance.rawSources,
mergerType: meshConfig.merger.name,
documents: meshConfig.documents,
flattenTypes: false,
importedModulesSet: new Set(),
baseDir,
pollingInterval: meshConfig.config.pollingInterval,
meshConfigImportCodes: new Set([
`import { findAndParseConfig } from '@graphql-mesh/cli';`,
`import { createMeshHTTPHandler, MeshHTTPHandler } from '@graphql-mesh/http';`,
]),
meshConfigCodes: new Set([
`
export function getMeshOptions() {
console.warn('WARNING: These artifacts are built for development mode. Please run "${cliParams.commandName} build" to build production artifacts');
return findAndParseConfig({
dir: baseDir,
artifactsDir: ${JSON.stringify(cliParams.artifactsDir)},
configName: ${JSON.stringify(cliParams.configName)},
additionalPackagePrefixes: ${JSON.stringify(cliParams.additionalPackagePrefixes)},
initialLoggerPrefix: ${JSON.stringify(cliParams.initialLoggerPrefix)},
});
}
export function createBuiltMeshHTTPHandler<TServerContext = {}>(): MeshHTTPHandler<TServerContext> {
return createMeshHTTPHandler<TServerContext>({
baseDir,
getBuiltMesh: ${cliParams.builtMeshFactoryName},
rawServeConfig: ${JSON.stringify(meshConfig.config.serve)},
})
}
`.trim(),
]),
logger,
sdkConfig: meshConfig.config.sdk,
fileType: 'ts',
codegenConfig: meshConfig.config.codegen,
}, cliParams).catch(e => {
logger.error(`An error occurred while building the artifacts: ${e.stack || e.message}`);
});
return meshInstance;
});
}
let meshInstance$;
meshInstance$ = buildMeshInstance();
if (meshConfig.config.pollingInterval) {
logger.info(`Polling enabled with interval of ${meshConfig.config.pollingInterval}ms`);
const interval = setInterval(() => {
logger.info(`Polling for changes...`);
buildMeshInstance()
.then(newMeshInstance => meshInstance$.then(oldMeshInstance => {
oldMeshInstance.destroy();
meshInstance$ = (0, utils_2.fakePromise)(newMeshInstance);
}))
.catch(e => {
logger.error(`Mesh polling failed so the previous version will be served: `, e);
});
}, meshConfig.config.pollingInterval);
(0, utils_1.registerTerminateHandler)(() => {
logger.info(`Terminating polling...`);
clearInterval(interval);
});
}
const serveMeshOptions = {
baseDir,
argsPort: args.port,
getBuiltMesh: () => meshInstance$,
logger: meshConfig.logger.child('Server'),
rawServeConfig: meshConfig.config.serve,
registerTerminateHandler: utils_1.registerTerminateHandler,
};
await (0, serve_js_1.serveMesh)(serveMeshOptions, cliParams);
}
catch (e) {
(0, handleFatalError_js_1.handleFatalError)(e, logger);
}
})
.command(cliParams.prodServerCommand, 'Serves a GraphQL server with GraphQL interface based on your generated artifacts', builder => {
builder.option('port', {
type: 'number',
});
}, async (args) => {
try {
const builtMeshArtifactsPath = cross_helpers_1.path.join(baseDir, cliParams.artifactsDir);
if (!(await (0, utils_1.pathExists)(builtMeshArtifactsPath))) {
throw new Error(`Seems like you haven't build the artifacts yet to start production server! You need to build artifacts first with "${cliParams.commandName} build" command!`);
}
cross_helpers_1.process.env.NODE_ENV = 'production';
const mainModule = cross_helpers_1.path.join(builtMeshArtifactsPath, 'index');
const builtMeshArtifacts = await (0, include_1.include)(mainModule);
const rawServeConfig = builtMeshArtifacts.rawServeConfig;
const meshOptions = await builtMeshArtifacts.getMeshOptions();
logger = meshOptions.logger;
const serveMeshOptions = {
baseDir,
argsPort: args.port,
getBuiltMesh: builtMeshArtifacts[cliParams.builtMeshFactoryName],
logger,
rawServeConfig,
registerTerminateHandler: utils_1.registerTerminateHandler,
};
await (0, serve_js_1.serveMesh)(serveMeshOptions, cliParams);
}
catch (e) {
(0, handleFatalError_js_1.handleFatalError)(e, logger);
}
})
.command(cliParams.validateCommand, 'Validates artifacts', builder => { }, async (args) => {
let destroy;
try {
if (!(await (0, utils_1.pathExists)(cross_helpers_1.path.join(baseDir, cliParams.artifactsDir)))) {
throw new Error(`You cannot validate artifacts now because you don't have built artifacts yet! You need to build artifacts first with "${cliParams.commandName} build" command!`);
}
const store = new store_1.MeshStore(cliParams.artifactsDir, new store_1.FsStoreStorageAdapter({
cwd: baseDir,
importFn: include_1.include,
fileType: 'ts',
}), {
readonly: false,
validate: true,
});
logger.info(`Reading the configuration`);
const meshConfig = await (0, config_js_1.findAndParseConfig)({
dir: baseDir,
store,
importFn: include_1.include,
ignoreAdditionalResolvers: true,
artifactsDir: cliParams.artifactsDir,
configName: cliParams.configName,
additionalPackagePrefixes: cliParams.additionalPackagePrefixes,
initialLoggerPrefix: cliParams.initialLoggerPrefix,
});
logger = meshConfig.logger;
logger.info(`Generating the unified schema`);
const mesh = await (0, runtime_1.getMesh)(meshConfig);
logger.info(`Artifacts are valid!`);
destroy = mesh?.destroy;
}
catch (e) {
(0, handleFatalError_js_1.handleFatalError)(e, logger);
}
if (destroy) {
destroy();
}
})
.command(cliParams.buildArtifactsCommand, 'Builds artifacts', builder => {
builder.option('fileType', {
type: 'string',
choices: ['json', 'ts', 'js'],
default: 'ts',
});
builder.option('throwOnInvalidConfig', {
type: 'boolean',
default: false,
});
}, async (args) => {
try {
const outputDir = cross_helpers_1.path.join(baseDir, cliParams.artifactsDir);
logger.info('Cleaning existing artifacts');
await (0, utils_1.rmdirs)(outputDir);
const importedModulesSet = new Set();
const importPromises = [];
const importFn = (moduleId, noCache) => {
const importPromise = (0, include_1.include)(moduleId)
.catch(e => {
if (e.message.includes('getter')) {
return e;
}
else {
throw e;
}
})
.then(m => {
if (!noCache) {
importedModulesSet.add(moduleId);
}
return m;
});
importPromises.push(importPromise.catch(() => { }));
return importPromise;
};
await Promise.all(importPromises);
const store = new store_1.MeshStore(cliParams.artifactsDir, new store_1.FsStoreStorageAdapter({
cwd: baseDir,
importFn,
fileType: args.fileType,
}), {
readonly: false,
validate: false,
});
logger.info(`Reading the configuration`);
const meshConfig = await (0, config_js_1.findAndParseConfig)({
dir: baseDir,
store,
importFn,
ignoreAdditionalResolvers: true,
artifactsDir: cliParams.artifactsDir,
configName: cliParams.configName,
additionalPackagePrefixes: cliParams.additionalPackagePrefixes,
generateCode: true,
initialLoggerPrefix: cliParams.initialLoggerPrefix,
throwOnInvalidConfig: args.throwOnInvalidConfig,
});
logger = meshConfig.logger;
logger.info(`Generating the unified schema`);
const { schema, destroy, rawSources } = await (0, runtime_1.getMesh)(meshConfig);
await (0, utils_1.writeFile)(cross_helpers_1.path.join(outputDir, 'schema.graphql'), (0, utils_2.printSchemaWithDirectives)(schema));
logger.info(`Generating artifacts`);
meshConfig.importCodes.add(`import { createMeshHTTPHandler, MeshHTTPHandler } from '@graphql-mesh/http';`);
meshConfig.codes.add(`
export function createBuiltMeshHTTPHandler<TServerContext = {}>(): MeshHTTPHandler<TServerContext> {
return createMeshHTTPHandler<TServerContext>({
baseDir,
getBuiltMesh: ${cliParams.builtMeshFactoryName},
rawServeConfig: ${JSON.stringify(meshConfig.config.serve)},
})
}
`);
await (0, ts_artifacts_js_1.generateTsArtifacts)({
unifiedSchema: schema,
rawSources,
mergerType: meshConfig.merger.name,
documents: meshConfig.documents,
flattenTypes: false,
importedModulesSet,
baseDir,
meshConfigImportCodes: meshConfig.importCodes,
meshConfigCodes: meshConfig.codes,
logger,
sdkConfig: meshConfig.config.sdk,
fileType: args.fileType,
codegenConfig: meshConfig.config.codegen,
pollingInterval: meshConfig.config.pollingInterval,
}, cliParams);
logger.info(`Cleanup`);
destroy();
logger.info('Done! => ' + outputDir);
}
catch (e) {
(0, handleFatalError_js_1.handleFatalError)(e, logger);
}
})
.command(cliParams.sourceServerCommand + ' <source>', 'Serves specific source in development mode', builder => {
builder.positional('source', {
type: 'string',
requiresArg: true,
});
}, async (args) => {
cross_helpers_1.process.env.NODE_ENV = 'development';
const meshConfig = await (0, config_js_1.findAndParseConfig)({
dir: baseDir,
artifactsDir: cliParams.artifactsDir,
configName: cliParams.configName,
additionalPackagePrefixes: cliParams.additionalPackagePrefixes,
initialLoggerPrefix: cliParams.initialLoggerPrefix,
});
logger = meshConfig.logger;
const sourceIndex = meshConfig.sources.findIndex(rawSource => rawSource.name === args.source);
if (sourceIndex === -1) {
throw new Error(`Source ${args.source} not found`);
}
const getMeshOpts = {
...meshConfig,
additionalTypeDefs: undefined,
additionalResolvers: [],
transforms: [],
sources: [meshConfig.sources[sourceIndex]],
};
let meshInstance$;
if (meshConfig.config.pollingInterval) {
const interval = setInterval(() => {
(0, runtime_1.getMesh)(getMeshOpts)
.then(newMeshInstance => meshInstance$.then(oldMeshInstance => {
oldMeshInstance.destroy();
meshInstance$ = (0, utils_2.fakePromise)(newMeshInstance);
}))
.catch(e => {
logger.error(`Mesh polling failed so the previous version will be served: `, e);
});
}, meshConfig.config.pollingInterval);
(0, utils_1.registerTerminateHandler)(() => {
clearInterval(interval);
});
}
else {
meshInstance$ = (0, runtime_1.getMesh)(getMeshOpts);
}
const serveMeshOptions = {
baseDir,
argsPort: 4000 + sourceIndex + 1,
getBuiltMesh: () => meshInstance$,
logger: meshConfig.logger.child('Server'),
rawServeConfig: meshConfig.config.serve,
playgroundTitle: `${args.source} GraphiQL`,
registerTerminateHandler: utils_1.registerTerminateHandler,
};
await (0, serve_js_1.serveMesh)(serveMeshOptions, cliParams);
}).argv;
}
;