From 74c3bf4e63901461a6c6f221bfc34d5f26de6c0e Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 12 Aug 2024 19:43:12 +0200 Subject: [PATCH 1/5] native-import native import --- .../top-level-await.test.ts.snap | 89 +++++++++++++++++++ e2e/top-level-await/mesh.config.ts | 29 ++++++ e2e/top-level-await/package.json | 10 +++ e2e/top-level-await/top-level-await.test.ts | 19 ++++ e2e/utils/tenv.ts | 18 ++-- packages/compose-cli/src/run.ts | 7 +- packages/include/src/index.ts | 4 +- packages/serve-cli/src/cli.ts | 1 + packages/serve-cli/src/commands/proxy.ts | 3 +- packages/serve-cli/src/commands/subgraph.ts | 3 +- packages/serve-cli/src/commands/supergraph.ts | 3 +- packages/serve-cli/src/config.ts | 5 +- yarn.lock | 10 +++ 13 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 e2e/top-level-await/__snapshots__/top-level-await.test.ts.snap create mode 100644 e2e/top-level-await/mesh.config.ts create mode 100644 e2e/top-level-await/package.json create mode 100644 e2e/top-level-await/top-level-await.test.ts diff --git a/e2e/top-level-await/__snapshots__/top-level-await.test.ts.snap b/e2e/top-level-await/__snapshots__/top-level-await.test.ts.snap new file mode 100644 index 0000000000000..7c52cdf2f3b23 --- /dev/null +++ b/e2e/top-level-await/__snapshots__/top-level-await.test.ts.snap @@ -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 +} + +" +`; diff --git a/e2e/top-level-await/mesh.config.ts b/e2e/top-level-await/mesh.config.ts new file mode 100644 index 0000000000000..2348da083c100 --- /dev/null +++ b/e2e/top-level-await/mesh.config.ts @@ -0,0 +1,29 @@ +import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'; +import { defineConfig as defineComposeConfig } from '@graphql-mesh/compose-cli'; +import { defineConfig as defineServeConfig } from '@graphql-mesh/serve-cli'; + +// top level await +await Promise.resolve(); + +export const composeConfig = defineComposeConfig({ + subgraphs: [ + { + sourceHandler: () => ({ + name: 'helloworld', + schema$: new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + hello: { + type: GraphQLString, + resolve: () => 'world', + }, + }, + }), + }), + }), + }, + ], +}); + +export const serveConfig = defineServeConfig({}); diff --git a/e2e/top-level-await/package.json b/e2e/top-level-await/package.json new file mode 100644 index 0000000000000..f98ce4b563872 --- /dev/null +++ b/e2e/top-level-await/package.json @@ -0,0 +1,10 @@ +{ + "name": "@e2e/top-level-await", + "type": "module", + "private": true, + "dependencies": { + "@graphql-mesh/compose-cli": "workspace:*", + "@graphql-mesh/serve-cli": "workspace:*", + "graphql": "16.9.0" + } +} diff --git a/e2e/top-level-await/top-level-await.test.ts b/e2e/top-level-await/top-level-await.test.ts new file mode 100644 index 0000000000000..0eb93996f5234 --- /dev/null +++ b/e2e/top-level-await/top-level-await.test.ts @@ -0,0 +1,19 @@ +import { createTenv } from '@e2e/tenv'; +import { fetch } from '@whatwg-node/fetch'; + +const { serve, compose } = createTenv(__dirname); + +it('should serve', async () => { + const proc = await serve({ + 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'], + }); + expect(proc.result).toMatchSnapshot(); +}); diff --git a/e2e/utils/tenv.ts b/e2e/utils/tenv.ts index bb3345f850c72..758fbc38940fe 100644 --- a/e2e/utils/tenv.ts +++ b/e2e/utils/tenv.ts @@ -65,6 +65,8 @@ export interface ProcOptions { * They will be merged with `process.env` overriding any existing value. */ env?: Record; + /** Extra args to pass to the process. */ + args?: (string | number | boolean)[]; } export interface Proc extends AsyncDisposable { @@ -243,9 +245,9 @@ export function createTenv(cwd: string): Tenv { return fs.writeFile(filePath, content, 'utf-8'); }, }, - spawn(command, opts) { + spawn(command, { args: extraArgs, ...opts }) { const [cmd, ...args] = Array.isArray(command) ? command : command.split(' '); - return spawn({ ...opts, cwd }, String(cmd), ...args); + return spawn({ ...opts, cwd }, String(cmd), ...args, ...extraArgs); }, serveRunner, async serve(opts) { @@ -255,6 +257,7 @@ export function createTenv(cwd: string): Tenv { pipeLogs = boolEnv('DEBUG'), env, runner, + args, } = opts || {}; let proc: Proc, @@ -314,7 +317,7 @@ export function createTenv(cwd: string): Tenv { hostPort: port, containerPort: port, healthcheck: ['CMD-SHELL', `wget --spider http://0.0.0.0:${port}/healthcheck`], - cmd: [createPortOpt(port), ...(supergraph ? ['supergraph', supergraph] : [])], + cmd: [createPortOpt(port), ...(supergraph ? ['supergraph', supergraph] : []), ...args], volumes, pipeLogs, }); @@ -329,6 +332,7 @@ export function createTenv(cwd: string): Tenv { 'supergraph', supergraph, createPortOpt(port), + ...args, ); } @@ -374,6 +378,7 @@ export function createTenv(cwd: string): Tenv { maskServicePorts, pipeLogs = boolEnv('DEBUG'), env, + args, } = opts || {}; let output = ''; if (opts?.output) { @@ -389,6 +394,7 @@ export function createTenv(cwd: string): Tenv { path.resolve(__project, 'packages', 'compose-cli', 'src', 'bin.ts'), output && createOpt('output', output), ...services.map(({ name, port }) => createServicePortOpt(name, port)), + ...args, ); await waitForExit; let result = ''; @@ -423,7 +429,7 @@ export function createTenv(cwd: string): Tenv { return { ...proc, output, result }; }, - async service(name, { port, servePort, pipeLogs = boolEnv('DEBUG') } = {}) { + async service(name, { port, servePort, pipeLogs = boolEnv('DEBUG'), args } = {}) { port ||= await getAvailablePort(); const [proc, waitForExit] = await spawn( { cwd, pipeLogs }, @@ -433,6 +439,7 @@ export function createTenv(cwd: string): Tenv { path.join(cwd, 'services', name), createServicePortOpt(name, port), servePort && createPortOpt(servePort), + ...args, ); const service: Service = { ...proc, name, port }; const ctrl = new AbortController(); @@ -460,6 +467,7 @@ export function createTenv(cwd: string): Tenv { pipeLogs = boolEnv('DEBUG'), cmd, volumes = [], + args, }) { const containerName = `${name}_${Math.random().toString(32).slice(2)}`; @@ -528,7 +536,7 @@ export function createTenv(cwd: string): Tenv { {}, ), }, - Cmd: cmd?.filter(Boolean).map(String), + Cmd: [...cmd, ...args].filter(Boolean).map(String), HostConfig: { AutoRemove: true, PortBindings: { diff --git a/packages/compose-cli/src/run.ts b/packages/compose-cli/src/run.ts index 83fbc2f959fe8..3c6828ffe6fec 100644 --- a/packages/compose-cli/src/run.ts +++ b/packages/compose-cli/src/run.ts @@ -32,7 +32,8 @@ let program = new Command() ).env('CONFIG_PATH'), ) .option('--subgraph ', 'name of the subgraph to compose') - .option('-o, --output ', 'path to the output file'); + .option('-o, --output ', 'path to the output file') + .option('--native-import', 'use the native "import" function for importing the config file'); export interface RunOptions extends ReturnType { /** @default new DefaultLogger() */ @@ -72,7 +73,7 @@ export async function run({ .catch(() => false); if (exists) { log.info(`Found default config file ${configPath}`); - const module = await include(absoluteConfigPath); + const module = await include(absoluteConfigPath, opts.nativeImport); importedConfig = Object(module).composeConfig; if (!importedConfig) { throw new Error(`No "composeConfig" exported from default config at ${configPath}`); @@ -98,7 +99,7 @@ export async function run({ if (!exists) { throw new Error(`Cannot find config file at ${configPath}`); } - const module = await include(configPath); + const module = await include(configPath, opts.nativeImport); importedConfig = Object(module).composeConfig; if (!importedConfig) { throw new Error(`No "composeConfig" exported from config at ${configPath}`); diff --git a/packages/include/src/index.ts b/packages/include/src/index.ts index e84b144fea524..f68a0df4c38b5 100644 --- a/packages/include/src/index.ts +++ b/packages/include/src/index.ts @@ -20,8 +20,8 @@ const jiti = createJITI( * * If the module at {@link path} is not found, `null` will be returned. */ -export async function include(path: string): Promise { - const module = await jiti.import(path, {}); +export async function include(path: string, nativeImport?: boolean): Promise { + const module = await (nativeImport ? import(path) : jiti.import(path, {})); if (!module) { throw new Error('Included module is empty'); } diff --git a/packages/serve-cli/src/cli.ts b/packages/serve-cli/src/cli.ts index d3eb90f372716..a8efc4112739e 100644 --- a/packages/serve-cli/src/cli.ts +++ b/packages/serve-cli/src/cli.ts @@ -167,6 +167,7 @@ let cli = new Command() // see here https://github.com/tj/commander.js/blob/970ecae402b253de691e6a9066fea22f38fe7431/lib/command.js#L655 null, ) + .option('--native-import', 'use the native "import" function for importing the config file') .addOption( new Option( '--hive-registry-token ', diff --git a/packages/serve-cli/src/commands/proxy.ts b/packages/serve-cli/src/commands/proxy.ts index bb0eb124597fa..30b2354e28f5e 100644 --- a/packages/serve-cli/src/commands/proxy.ts +++ b/packages/serve-cli/src/commands/proxy.ts @@ -24,12 +24,13 @@ export const addCommand: AddCommand = (ctx, cli) => ).env('HIVE_CDN_KEY'), ) .action(async function proxy(endpoint) { - const { hiveCdnKey, hiveRegistryToken, maskedErrors, polling, ...opts } = + const { hiveCdnKey, hiveRegistryToken, maskedErrors, polling, nativeImport, ...opts } = this.optsWithGlobals(); const loadedConfig = await loadConfig({ log: ctx.log, configPath: opts.configPath, quiet: !cluster.isPrimary, + nativeImport, }); let proxy: MeshServeConfigProxy['proxy'] | undefined; diff --git a/packages/serve-cli/src/commands/subgraph.ts b/packages/serve-cli/src/commands/subgraph.ts index db463204ea91b..e2b87733c1892 100644 --- a/packages/serve-cli/src/commands/subgraph.ts +++ b/packages/serve-cli/src/commands/subgraph.ts @@ -23,12 +23,13 @@ export const addCommand: AddCommand = (ctx, cli) => 'path to the subgraph schema file or a url from where to pull the subgraph schema (default: "subgraph.graphql")', ) .action(async function subgraph(schemaPathOrUrl) { - const { maskedErrors, hiveRegistryToken, polling, ...opts } = + const { maskedErrors, hiveRegistryToken, polling, nativeImport, ...opts } = this.optsWithGlobals(); const loadedConfig = await loadConfig({ log: ctx.log, configPath: opts.configPath, quiet: !cluster.isPrimary, + nativeImport, }); let subgraph: UnifiedGraphConfig = 'subgraph.graphql'; diff --git a/packages/serve-cli/src/commands/supergraph.ts b/packages/serve-cli/src/commands/supergraph.ts index 3c7c0d29f09a9..a8582c203f2f2 100644 --- a/packages/serve-cli/src/commands/supergraph.ts +++ b/packages/serve-cli/src/commands/supergraph.ts @@ -31,12 +31,13 @@ export const addCommand: AddCommand = (ctx, cli) => ).env('HIVE_CDN_KEY'), ) .action(async function supergraph(schemaPathOrUrl) { - const { hiveCdnKey, hiveRegistryToken, maskedErrors, polling, ...opts } = + const { hiveCdnKey, hiveRegistryToken, maskedErrors, polling, nativeImport, ...opts } = this.optsWithGlobals(); const loadedConfig = await loadConfig({ log: ctx.log, configPath: opts.configPath, quiet: !cluster.isPrimary, + nativeImport, }); let supergraph: UnifiedGraphConfig | MeshServeHiveCDNOptions = 'supergraph.graphql'; diff --git a/packages/serve-cli/src/config.ts b/packages/serve-cli/src/config.ts index 728f1904618b8..24c4668032ee9 100644 --- a/packages/serve-cli/src/config.ts +++ b/packages/serve-cli/src/config.ts @@ -20,6 +20,7 @@ export async function loadConfig = Record & ServerConfig> | null = null; @@ -32,7 +33,7 @@ export async function loadConfig = Record false); if (exists) { !opts.quiet && opts.log.info(`Found default config file ${configPath}`); - const module = await include(absoluteConfigPath); + const module = await include(absoluteConfigPath, opts.nativeImport); importedConfig = Object(module).serveConfig || null; if (!importedConfig) { !opts.quiet && @@ -53,7 +54,7 @@ export async function loadConfig = Record Date: Mon, 12 Aug 2024 19:52:28 +0200 Subject: [PATCH 2/5] changesets --- .changeset/thirty-pumas-talk.md | 5 +++++ .changeset/warm-lizards-impress.md | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/thirty-pumas-talk.md create mode 100644 .changeset/warm-lizards-impress.md diff --git a/.changeset/thirty-pumas-talk.md b/.changeset/thirty-pumas-talk.md new file mode 100644 index 0000000000000..d4127e03ad2fb --- /dev/null +++ b/.changeset/thirty-pumas-talk.md @@ -0,0 +1,5 @@ +--- +'@graphql-mesh/include': minor +--- + +Optional `nativeImport` argument when including to use the native `import` function instead of jiti diff --git a/.changeset/warm-lizards-impress.md b/.changeset/warm-lizards-impress.md new file mode 100644 index 0000000000000..d3a2552770dbd --- /dev/null +++ b/.changeset/warm-lizards-impress.md @@ -0,0 +1,7 @@ +--- +'@graphql-mesh/compose-cli': minor +'@graphql-mesh/serve-cli': minor +--- + +Support for `--native-import` flag which will use the native `import` function instead of jiti for +importing configs From c2503f23ecff626912b2f1255183f7a5acdf3459 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 12 Aug 2024 19:55:49 +0200 Subject: [PATCH 3/5] cmd or args may be undefined --- e2e/utils/tenv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/utils/tenv.ts b/e2e/utils/tenv.ts index 758fbc38940fe..698e8d2a7a881 100644 --- a/e2e/utils/tenv.ts +++ b/e2e/utils/tenv.ts @@ -536,7 +536,7 @@ export function createTenv(cwd: string): Tenv { {}, ), }, - Cmd: [...cmd, ...args].filter(Boolean).map(String), + Cmd: [...(cmd || []), ...(args || [])].filter(Boolean).map(String), HostConfig: { AutoRemove: true, PortBindings: { From 11bd820c620f3a21e4a12b4ca4669c79bffa260c Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 12 Aug 2024 19:56:56 +0200 Subject: [PATCH 4/5] mjs config --- e2e/top-level-await/{mesh.config.ts => mesh.config.mjs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename e2e/top-level-await/{mesh.config.ts => mesh.config.mjs} (100%) diff --git a/e2e/top-level-await/mesh.config.ts b/e2e/top-level-await/mesh.config.mjs similarity index 100% rename from e2e/top-level-await/mesh.config.ts rename to e2e/top-level-await/mesh.config.mjs From b5e6de23ab596dedfdb94e27253c3829d41e91ae Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Mon, 12 Aug 2024 20:25:19 +0200 Subject: [PATCH 5/5] default args and empty supergraph for e2e test --- e2e/top-level-await/top-level-await.test.ts | 3 ++- e2e/utils/tenv.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/e2e/top-level-await/top-level-await.test.ts b/e2e/top-level-await/top-level-await.test.ts index 0eb93996f5234..887e9ab611bd1 100644 --- a/e2e/top-level-await/top-level-await.test.ts +++ b/e2e/top-level-await/top-level-await.test.ts @@ -1,10 +1,11 @@ import { createTenv } from '@e2e/tenv'; import { fetch } from '@whatwg-node/fetch'; -const { serve, compose } = createTenv(__dirname); +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`); diff --git a/e2e/utils/tenv.ts b/e2e/utils/tenv.ts index 698e8d2a7a881..aef8f399bc5f7 100644 --- a/e2e/utils/tenv.ts +++ b/e2e/utils/tenv.ts @@ -245,7 +245,7 @@ export function createTenv(cwd: string): Tenv { return fs.writeFile(filePath, content, 'utf-8'); }, }, - spawn(command, { args: extraArgs, ...opts }) { + spawn(command, { args: extraArgs = [], ...opts } = {}) { const [cmd, ...args] = Array.isArray(command) ? command : command.split(' '); return spawn({ ...opts, cwd }, String(cmd), ...args, ...extraArgs); }, @@ -257,7 +257,7 @@ export function createTenv(cwd: string): Tenv { pipeLogs = boolEnv('DEBUG'), env, runner, - args, + args = [], } = opts || {}; let proc: Proc, @@ -378,7 +378,7 @@ export function createTenv(cwd: string): Tenv { maskServicePorts, pipeLogs = boolEnv('DEBUG'), env, - args, + args = [], } = opts || {}; let output = ''; if (opts?.output) { @@ -429,7 +429,7 @@ export function createTenv(cwd: string): Tenv { return { ...proc, output, result }; }, - async service(name, { port, servePort, pipeLogs = boolEnv('DEBUG'), args } = {}) { + async service(name, { port, servePort, pipeLogs = boolEnv('DEBUG'), args = [] } = {}) { port ||= await getAvailablePort(); const [proc, waitForExit] = await spawn( { cwd, pipeLogs }, @@ -465,9 +465,9 @@ export function createTenv(cwd: string): Tenv { additionalContainerPorts: containerAdditionalPorts, healthcheck, pipeLogs = boolEnv('DEBUG'), - cmd, + cmd = [], volumes = [], - args, + args = [], }) { const containerName = `${name}_${Math.random().toString(32).slice(2)}`; @@ -536,7 +536,7 @@ export function createTenv(cwd: string): Tenv { {}, ), }, - Cmd: [...(cmd || []), ...(args || [])].filter(Boolean).map(String), + Cmd: [...cmd, ...args].filter(Boolean).map(String), HostConfig: { AutoRemove: true, PortBindings: {