Skip to content

Commit

Permalink
Custom module resolution with our own hooks (#7596)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
enisdenjo and github-actions[bot] authored Aug 29, 2024
1 parent 0a3e52c commit b7f6ebf
Show file tree
Hide file tree
Showing 33 changed files with 614 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .changeset/@graphql-mesh_cache-redis-7596-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/cache-redis": patch
---
dependencies updates:
- Updated dependency [`@whatwg-node/disposablestack@^0.0.5` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.5) (from `^0.0.4`, in `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/@graphql-mesh_fusion-runtime-7596-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/fusion-runtime": patch
---
dependencies updates:
- Updated dependency [`@whatwg-node/disposablestack@^0.0.5` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.5) (from `^0.0.4`, in `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/@graphql-mesh_include-7596-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/include": patch
---
dependencies updates:
- Added dependency [`sucrase@^3.35.0` ↗︎](https://www.npmjs.com/package/sucrase/v/3.35.0) (to `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/@graphql-mesh_serve-runtime-7596-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/serve-runtime": patch
---
dependencies updates:
- Updated dependency [`@whatwg-node/disposablestack@^0.0.5` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.5) (from `^0.0.4`, in `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/@graphql-mesh_utils-7596-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/utils": patch
---
dependencies updates:
- Updated dependency [`@whatwg-node/disposablestack@^0.0.5` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.5) (from `^0.0.4`, in `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/lemon-bikes-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/include': minor
---

Module hooks using sucrase transpiling only TS with tsconfig paths support
5 changes: 5 additions & 0 deletions .changeset/silver-glasses-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/include': minor
---

No more @graphql-mesh/include/register-tsconfig-paths
6 changes: 6 additions & 0 deletions .changeset/spicy-cameras-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-mesh/compose-cli': minor
'@graphql-mesh/serve-cli': minor
---

Register @graphql-mesh/include/hooks instead of using jiti and include
File renamed without changes.
5 changes: 1 addition & 4 deletions e2e/top-level-await/top-level-await.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ const { serve, compose, fs } = createTenv(__dirname);
it('should serve', async () => {
const proc = await serve({
supergraph: await fs.tempfile('supergraph.graphql', 'type Query { hello: String }'),
args: ['--native-import'],
});
const res = await fetch(`http://0.0.0.0:${proc.port}/healthcheck`);
expect(res.ok).toBeTruthy();
});

it('should compose', async () => {
const proc = await compose({
args: ['--native-import'],
});
const proc = await compose();
expect(proc.result).toMatchSnapshot();
});
4 changes: 2 additions & 2 deletions e2e/tsconfig-paths/tsconfig-paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { compose, serve, fs } = createTenv(__dirname);
it('should compose', async () => {
const proc = await compose({
env: {
MESH_INCLUDE_TSCONFIG_NAME: 'tsconfig-paths.tsconfig.json',
MESH_INCLUDE_TSCONFIG_SEARCH_PATH: 'tsconfig-paths.tsconfig.json',
},
});
expect(proc.result).toMatchSnapshot();
Expand All @@ -15,7 +15,7 @@ it('should serve', async () => {
const proc = await serve({
supergraph: await fs.tempfile('supergraph.graphql', 'type Query { hello: String }'),
env: {
MESH_INCLUDE_TSCONFIG_NAME: 'tsconfig-paths.tsconfig.json',
MESH_INCLUDE_TSCONFIG_SEARCH_PATH: 'tsconfig-paths.tsconfig.json',
},
runner: {
docker: {
Expand Down
7 changes: 7 additions & 0 deletions e2e/utils/tenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ export function createTenv(cwd: string): Tenv {
for (const dbfile of await glob('*.db', { cwd })) {
volumes.push({ host: dbfile, container: `/serve/${path.basename(dbfile)}` });
}
const packageJsonExists = await fs
.stat(path.join(cwd, 'package.json'))
.then(() => true)
.catch(() => false);
if (packageJsonExists) {
volumes.push({ host: 'package.json', container: '/serve/package.json' });
}

const dockerfileExists = await fs
.stat(path.join(cwd, 'serve.Dockerfile'))
Expand Down
4 changes: 2 additions & 2 deletions examples/v1-next/auth0/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"license": "MIT",
"private": true,
"scripts": {
"compose": "node ../../packages/compose-cli/dist/cjs/bin.js",
"compose": "node ../../packages/compose-cli/dist/esm/bin.js",
"pretest": "npm run compose",
"start": "node ../../packages/serve-cli/dist/cjs/bin.js",
"start": "node ../../packages/serve-cli/dist/esm/bin.js",
"test": "jest"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/v1-next/integrations/fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@examples-v1-next/fastify",
"private": true,
"scripts": {
"compose": "node ../../../../packages/compose-cli/dist/cjs/bin.js > supergraph.graphql",
"compose": "node ../../../../packages/compose-cli/dist/esm/bin.js > supergraph.graphql",
"start": "ts-node-dev src/index.ts"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/v1-next/integrations/gcp-functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "npm run compose && npm run build:ts",
"build:ts": "tsc",
"check": "exit 0",
"compose": "node ../../packages/compose-cli/dist/cjs/bin.js > supergraph.graphql",
"compose": "node ../../packages/compose-cli/dist/esm/bin.js > supergraph.graphql",
"prestart": "npm run build",
"start": "functions-framework --target=mesh"
},
Expand Down
4 changes: 2 additions & 2 deletions examples/v1-next/openapi-location-weather/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"license": "MIT",
"private": true,
"scripts": {
"compose": "node ../../packages/compose-cli/dist/cjs/bin.js",
"serve": "node ../../packages/serve-cli/dist/cjs/bin.js"
"compose": "node ../../packages/compose-cli/dist/esm/bin.js",
"serve": "node ../../packages/serve-cli/dist/esm/bin.js"
},
"dependencies": {
"@graphql-mesh/cache-localforage": "^0.102.4",
Expand Down
4 changes: 2 additions & 2 deletions examples/v1-next/openapi-youtrack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"license": "MIT",
"private": true,
"scripts": {
"compose": "node ../../packages/compose-cli/dist/cjs/bin.js > supergraph.graphql",
"serve": "node ../../packages/serve-cli/dist/cjs/bin.js"
"compose": "node ../../packages/compose-cli/dist/esm/bin.js > supergraph.graphql",
"serve": "node ../../packages/serve-cli/dist/esm/bin.js"
},
"dependencies": {
"@graphql-mesh/compose-cli": "^0.9.5",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"inject-version": "yarn workspaces foreach -A run inject-version",
"lint": "cross-env \"ESLINT_USE_FLAT_CONFIG=false\" eslint --ext .ts \"./packages/**/src/**/*.ts\"",
"loadtest:e2e": "cross-env \"JEST=1\" \"E2E_TEST=true\" jest --no-watchman --runInBand --bail --config=jest.config.loadtest.js",
"postbuild": "yarn inject-version && yarn copy-config-schema && yarn fix-bin",
"postbuild": "yarn inject-version && yarn copy-config-schema && yarn fix-bin && tsx scripts/replace-import-meta-url-in-cjs.ts",
"postchangeset": "yarn install --no-immutable",
"postgenerate-config-schema": "node scripts/create-config-schema-ts.js && npx prettier --write ./packages/legacy/types/src",
"postinstall": "husky install && patch-package",
Expand Down Expand Up @@ -102,6 +102,7 @@
"prettier": "3.3.3",
"threads": "^1.7.0",
"ts-jest": "29.2.5",
"tsx": "^4.19.0",
"typescript": "^5.4.2"
},
"resolutions": {
Expand Down
19 changes: 12 additions & 7 deletions packages/compose-cli/src/run.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
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 module from 'node:module';
// eslint-disable-next-line import/no-nodejs-modules
import { isAbsolute, join, resolve } from 'path';
import { parse } from 'graphql';
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 All @@ -32,8 +31,7 @@ let program = new Command()
).env('CONFIG_PATH'),
)
.option('--subgraph <name>', 'name of the subgraph to compose')
.option('-o, --output <path>', 'path to the output file')
.option('--native-import', 'use the native "import" function for importing the config file');
.option('-o, --output <path>', 'path to the output file');

export interface RunOptions extends ReturnType<typeof program.opts> {
/** @default new DefaultLogger() */
Expand All @@ -55,6 +53,13 @@ export async function run({
binName = 'mesh-compose',
version,
}: RunOptions): Promise<void | never> {
module.register(
'@graphql-mesh/include/hooks',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore bob will complain when bundling for cjs
import.meta.url,
);

program = program.name(binName).description(productDescription);
if (version) program = program.version(version);
if (process.env.NODE_ENV === 'test') program = program.allowUnknownOption();
Expand All @@ -73,7 +78,7 @@ export async function run({
.catch(() => false);
if (exists) {
log.info(`Found default config file ${configPath}`);
const module = await include(absoluteConfigPath, opts.nativeImport);
const module = await import(absoluteConfigPath);
importedConfig = Object(module).composeConfig;
if (!importedConfig) {
throw new Error(`No "composeConfig" exported from default config at ${configPath}`);
Expand All @@ -99,7 +104,7 @@ export async function run({
if (!exists) {
throw new Error(`Cannot find config file at ${configPath}`);
}
const module = await include(configPath, opts.nativeImport);
const module = await import(configPath);
importedConfig = Object(module).composeConfig;
if (!importedConfig) {
throw new Error(`No "composeConfig" exported from config at ${configPath}`);
Expand Down
17 changes: 9 additions & 8 deletions packages/include/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@
"default": "./dist/esm/index.js"
}
},
"./register-tsconfig-paths": {
"./hooks": {
"require": {
"types": "./dist/typings/register-tsconfig-paths.d.cts",
"default": "./dist/cjs/register-tsconfig-paths.js"
"types": "./dist/typings/hooks.d.cts",
"default": "./dist/cjs/hooks.js"
},
"import": {
"types": "./dist/typings/register-tsconfig-paths.d.ts",
"default": "./dist/esm/register-tsconfig-paths.js"
"types": "./dist/typings/hooks.d.ts",
"default": "./dist/esm/hooks.js"
},
"default": {
"types": "./dist/typings/register-tsconfig-paths.d.ts",
"default": "./dist/esm/register-tsconfig-paths.js"
"types": "./dist/typings/hooks.d.ts",
"default": "./dist/esm/hooks.js"
}
},
"./package.json": "./package.json"
Expand All @@ -48,7 +48,8 @@
"dependencies": {
"dotenv": "^16.3.1",
"get-tsconfig": "^4.7.6",
"jiti": "^1.21.6"
"jiti": "^1.21.6",
"sucrase": "^3.35.0"
},
"devDependencies": {
"glob": "^11.0.0"
Expand Down
124 changes: 124 additions & 0 deletions packages/include/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* eslint-disable import/no-nodejs-modules */
// ONLY FOR NODE. register with `node --import @graphql-mesh/include/hooks <your script>`

import fs from 'node:fs/promises';
import module from 'node:module';
import path from 'node:path';
import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
import { transform } from 'sucrase';

const isDebug = ['1', 'y', 'yes', 't', 'true'].includes(String(process.env.DEBUG));

function debug(msg: string) {
if (isDebug) {
process.stderr.write(`[${new Date().toISOString()}] HOOKS ${msg}\n`);
}
}

// eslint-disable-next-line dot-notation
const resolveFilename: (path: string) => string = module['_resolveFilename'];

let pathsMatcher: ((specifier: string) => string[]) | undefined;

let packedDepsPath = '';

export interface InitializeData {
/**
* Packed deps will be checked first, and enforced if present, during module resolution.
* This allows us to consistently use the same module instance even if multiple are installed by the user.
*/
packedDepsPath?: string;
/**
* tsconfig search path for registering tsconfig paths.
*
* @default process.env.MESH_INCLUDE_TSCONFIG_SEARCH_PATH || 'tsconfig.json'
*/
tsconfigSearchPath?: string;
}

export const initialize: module.InitializeHook<InitializeData> = (data = {}) => {
if (data.packedDepsPath) {
packedDepsPath = data.packedDepsPath;
debug(`Packed dependencies available at "${packedDepsPath}"`);
}
const tsconfig = getTsconfig(
undefined,
data.tsconfigSearchPath || process.env.MESH_INCLUDE_TSCONFIG_SEARCH_PATH || 'tsconfig.json',
);
if (tsconfig) {
debug(`tsconfig found at "${tsconfig.path}"`);
pathsMatcher = createPathsMatcher(tsconfig);
}
};

export const resolve: module.ResolveHook = async (specifier, context, nextResolve) => {
if (packedDepsPath) {
try {
const resolved = await nextResolve(
resolveFilename(path.join(packedDepsPath, specifier)),
context,
);
debug(`Using packed dependency "${specifier}" from "${packedDepsPath}"`);
return resolved;
} catch {
// noop
}
}

try {
return await nextResolve(specifier, context);
} catch (e) {
// default resolve failed, try alternatives
try {
return await nextResolve(resolveFilename(specifier), context);
} catch {
try {
// usual filenames tried, could be a .ts file?
return await nextResolve(
resolveFilename(
specifier + '.ts', // TODO: .mts or .cts?
),
context,
);
} catch {
// not a .ts file, try the tsconfig paths if available
if (pathsMatcher) {
for (const possiblePath of pathsMatcher(specifier)) {
try {
return await nextResolve(resolveFilename(possiblePath), context);
} catch {
try {
// the tsconfig path might point to a .ts file, try it too
return await nextResolve(
resolveFilename(
possiblePath + '.ts', // TODO: .mts or .cts?
),
context,
);
} catch {
// noop
}
}
}
}
}
}

// none of the alternatives worked, fail with original error
throw e;
}
};

export const load: module.LoadHook = async (url, context, nextLoad) => {
if (/\.(m|c)?ts$/.test(url)) {
debug(`Transpiling TypeScript file at "${url}"`);
const source = await fs.readFile(new URL(url), 'utf8');
const { code } = transform(source, { transforms: ['typescript'] });
return {
format: /\.cts$/.test(url) ? 'commonjs' : 'module', // TODO: ".ts" files _might_ not always be esm
source: code,
shortCircuit: true,
};
}
return nextLoad(url, context);
};
Loading

0 comments on commit b7f6ebf

Please sign in to comment.