Skip to content

Commit

Permalink
feat(@graphql-mesh/include): Library for importing and transpiling Ty…
Browse files Browse the repository at this point in the history
…peScript and JavaScript module during runtime (#7427)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
enisdenjo and github-actions[bot] committed Aug 1, 2024
1 parent 4aa256b commit 8be81d2
Show file tree
Hide file tree
Showing 50 changed files with 547 additions and 128 deletions.
6 changes: 6 additions & 0 deletions .changeset/@graphql-mesh_cli-7427-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@graphql-mesh/cli": patch
---
dependencies updates:
- Added dependency [`@graphql-mesh/include@^0.0.0` ↗︎](https://www.npmjs.com/package/@graphql-mesh/include/v/0.0.0) (to `dependencies`)
- Removed dependency [`jiti@^1.21.6` ↗︎](https://www.npmjs.com/package/jiti/v/1.21.6) (from `dependencies`)
6 changes: 6 additions & 0 deletions .changeset/@graphql-mesh_compose-cli-7427-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@graphql-mesh/compose-cli": patch
---
dependencies updates:
- Added dependency [`@graphql-mesh/include@^0.0.0` ↗︎](https://www.npmjs.com/package/@graphql-mesh/include/v/0.0.0) (to `dependencies`)
- Removed dependency [`jiti@^1.21.6` ↗︎](https://www.npmjs.com/package/jiti/v/1.21.6) (from `dependencies`)
6 changes: 6 additions & 0 deletions .changeset/@graphql-mesh_serve-cli-7427-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@graphql-mesh/serve-cli": patch
---
dependencies updates:
- Added dependency [`@graphql-mesh/include@^0.0.0` ↗︎](https://www.npmjs.com/package/@graphql-mesh/include/v/0.0.0) (to `dependencies`)
- Removed dependency [`jiti@^1.21.6` ↗︎](https://www.npmjs.com/package/jiti/v/1.21.6) (from `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/shiny-ways-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-mesh/compose-cli': patch
'@graphql-mesh/serve-cli': patch
'@graphql-mesh/cli': patch
---

Resolve tsconfig paths when importing config files
5 changes: 5 additions & 0 deletions .changeset/tall-years-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/include': patch
---

Library for importing and transpiling TypeScript and JavaScript module during runtime
89 changes: 89 additions & 0 deletions e2e/tsconfig-paths/__snapshots__/tsconfig-paths.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should compose 1`] = `
"
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
{
query: Query
}
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__implements(
graph: join__Graph!
interface: String!
) repeatable on OBJECT | INTERFACE
directive @join__type(
graph: join__Graph!
key: join__FieldSet
extension: Boolean! = false
resolvable: Boolean! = true
isInterfaceObject: Boolean! = false
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
scalar join__FieldSet
directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA
scalar link__Import
enum link__Purpose {
"""
\`SECURITY\` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
\`EXECUTION\` features provide metadata necessary for operation execution.
"""
EXECUTION
}
enum join__Graph {
HELLOWORLD @join__graph(name: "helloworld", url: "")
}
type Query @join__type(graph: HELLOWORLD) {
hello: String
}
"
`;
13 changes: 13 additions & 0 deletions e2e/tsconfig-paths/folder/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';

export const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'world',
},
},
}),
});
14 changes: 14 additions & 0 deletions e2e/tsconfig-paths/mesh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @ts-expect-error
import { schema } from '@e2e/tsconfig-paths/schema';
import { defineConfig } from '@graphql-mesh/compose-cli';

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: () => ({
name: 'helloworld',
schema$: schema,
}),
},
],
});
40 changes: 40 additions & 0 deletions e2e/tsconfig-paths/tsconfig-paths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createTenv } from '@e2e/tenv';

const { compose, serve } = createTenv(__dirname);

it('should compose', async () => {
const proc = await compose({
env: {
MESH_INCLUDE_TSCONFIG_NAME: 'tsconfig-paths.tsconfig.json',
},
});
expect(proc.result).toMatchSnapshot();
});

it('should serve', async () => {
const proc = await serve({
env: {
MESH_INCLUDE_TSCONFIG_NAME: 'tsconfig-paths.tsconfig.json',
},
runner: {
docker: {
volumes: [
{
host: './tsconfig-paths.tsconfig.json',
container: '/serve/tsconfig-paths.tsconfig.json',
},
{
host: './mesh.config.ts',
container: '/serve/mesh.config.ts',
},
{
host: './folder',
container: '/serve/folder',
},
],
},
},
});
const res = await fetch(`http://0.0.0.0:${proc.port}/healthcheck`);
expect(res.ok).toBeTruthy();
});
8 changes: 8 additions & 0 deletions e2e/tsconfig-paths/tsconfig-paths.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// we use jsconfig.json because tsx picks up tsconfig.json and we want to make sure Mesh runtime does it instead
{
"compilerOptions": {
"paths": {
"@e2e/tsconfig-paths/*": ["./folder/*"]
}
}
}
4 changes: 2 additions & 2 deletions packages/compose-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@
"dependencies": {
"@commander-js/extra-typings": "^12.0.1",
"@graphql-mesh/fusion-composition": "^0.1.7",
"@graphql-mesh/include": "^0.0.0",
"@graphql-mesh/utils": "^0.99.5",
"@graphql-tools/graphql-file-loader": "8.0.1",
"@graphql-tools/load": "^8.0.1",
"@graphql-tools/schema": "^10.0.4",
"@graphql-tools/utils": "^10.2.3",
"@whatwg-node/fetch": "^0.9.14",
"commander": "^12.0.0",
"dotenv": "^16.3.1",
"jiti": "^1.21.6"
"dotenv": "^16.3.1"
},
"publishConfig": {
"access": "public",
Expand Down
65 changes: 25 additions & 40 deletions packages/compose-cli/src/run.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import '@graphql-mesh/include/register-tsconfig-paths';
import 'dotenv/config'; // inject dotenv options to process.env

// eslint-disable-next-line import/no-nodejs-modules
import { promises as fsPromises } from 'fs';
// eslint-disable-next-line import/no-nodejs-modules
import { isAbsolute, join, resolve } from 'path';
import { parse } from 'graphql';
import createJITI from 'jiti';
import { Command, Option } from '@commander-js/extra-typings';
// eslint-disable-next-line import/no-nodejs-modules
import { include } from '@graphql-mesh/include';
import type { Logger } from '@graphql-mesh/types';
import { DefaultLogger } from '@graphql-mesh/utils';
import { getComposedSchemaFromConfig } from './getComposedSchemaFromConfig.js';
Expand Down Expand Up @@ -63,9 +65,18 @@ export async function run({
if (!opts.configPath) {
log.info(`Searching for default config files`);
for (const configPath of defaultConfigPaths) {
importedConfig = await importConfig(log, resolve(process.cwd(), configPath));
if (importedConfig) {
const absoluteConfigPath = resolve(process.cwd(), configPath);
const exists = await fsPromises
.lstat(absoluteConfigPath)
.then(() => true)
.catch(() => false);
if (exists) {
log.info(`Found default config file ${configPath}`);
const module = await include(absoluteConfigPath);
importedConfig = Object(module).composeConfig;
if (!importedConfig) {
throw new Error(`No "composeConfig" exported from default config at ${configPath}`);
}
break;
}
}
Expand All @@ -80,12 +91,20 @@ export async function run({
? opts.configPath
: resolve(process.cwd(), opts.configPath);
log.info(`Loading config file at path ${configPath}`);
importedConfig = await importConfig(log, configPath);
if (!importedConfig) {
const exists = await fsPromises
.lstat(configPath)
.then(() => true)
.catch(() => false);
if (!exists) {
throw new Error(`Cannot find config file at ${configPath}`);
}
const module = await include(configPath);
importedConfig = Object(module).composeConfig;
if (!importedConfig) {
throw new Error(`No "composeConfig" exported from config at ${configPath}`);
}
}
log.info('Loaded config file');
log.info('Loaded config');

const config: MeshComposeCLIConfig = {
...importedConfig,
Expand Down Expand Up @@ -136,37 +155,3 @@ export async function run({

log.info('Done!');
}

const jiti = createJITI(
// import.meta.url is not available in CJS (and cant even be in the syntax) and __filename is not available in ESM
// instead, we dont care about the file path because we'll require config imports to have absolute paths
'',
);

async function importConfig(log: Logger, path: string): Promise<MeshComposeCLIConfig | null> {
if (!isAbsolute(path)) {
throw new Error('Configs can be imported using absolute paths only'); // see createJITI for explanation
}
try {
const importedConfigModule = await jiti.import(path, {});
if (!importedConfigModule || typeof importedConfigModule !== 'object') {
throw new Error('Invalid imported config module!');
}
if ('default' in importedConfigModule) {
// eslint-disable-next-line dot-notation
return importedConfigModule.default['composeConfig'];
} else if ('composeConfig' in importedConfigModule) {
return importedConfigModule.composeConfig as MeshComposeCLIConfig;
}
} catch (err) {
// NOTE: we dont use the err.code because maybe the config itself is importing a module that does not exist.
// if we were to use the MODULE_NOT_FOUND code, then those configs will fail silently
if (String(err).includes(`Cannot find module '${path}'`)) {
// config at path not found
} else {
log.error(`Importing config at ${path} failed!`);
throw err;
}
}
return null;
}
72 changes: 72 additions & 0 deletions packages/include/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@graphql-mesh/include",
"version": "0.0.0",
"type": "module",
"repository": {
"type": "git",
"url": "ardatan/graphql-mesh",
"directory": "packages/include"
},
"license": "MIT",
"engines": {
"node": ">=16.0.0"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.cts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./register-tsconfig-paths": {
"require": {
"types": "./dist/typings/register-tsconfig-paths.d.cts",
"default": "./dist/cjs/register-tsconfig-paths.js"
},
"import": {
"types": "./dist/typings/register-tsconfig-paths.d.ts",
"default": "./dist/esm/register-tsconfig-paths.js"
},
"default": {
"types": "./dist/typings/register-tsconfig-paths.d.ts",
"default": "./dist/esm/register-tsconfig-paths.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"dependencies": {
"dotenv": "^16.3.1",
"get-tsconfig": "^4.7.6",
"jiti": "^1.21.6"
},
"devDependencies": {
"glob": "^11.0.0"
},
"publishConfig": {
"access": "public",
"directory": "dist"
},
"sideEffects": false,
"buildOptions": {
"bin": {
"mesh-compose": {
"input": "src/bin.ts"
}
},
"./package.json": "./package.json"
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
Loading

0 comments on commit 8be81d2

Please sign in to comment.