From e4c86cac135cf711c5b888b45dee133ab32bda5b Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Thu, 23 Nov 2023 15:17:29 +1100 Subject: [PATCH] Fixed inference when using npm packages --- .gitignore | 2 +- README.md | 3 +-- src/entrypoint.sh | 2 +- src/infer.ts | 30 ++++++++++++++++++++------ src/test/external_dependencies_test.ts | 26 ++++++++++++++++++++-- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index e3e2f7c..508bf80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/node_modules +node_modules/ /target .vscode/ vendor/ diff --git a/README.md b/README.md index e42d19e..2bf5342 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,6 @@ Limitations: * Functions can be executed via both the `/query` and `/mutation` endpoints * Conflicting type names in dependencies will be namespaced with their relative path * Generic type parameters will be treated as scalars when referenced -* Deno's `npm:` specifier doesn't currently work with the vendor command Please [file an issue](https://github.com/hasura/ndc-typescript-deno/issues/new) for any problems you encounter during usage of this connector. @@ -343,7 +342,7 @@ In order to perform local development on this codebase: * Check out the repository: `git clone https://github.com/hasura/ndc-typescript-deno.git` * This assumes that you will be testing against function in `./functions` -* Vendor the dependencies with `cd ./function && deno vendor -f index.ts` +* Vendor the dependencies with `cd ./function && deno vendor --node-modules-dir -f index.ts` * Serve your functions with `deno run -A --watch --check ./src/mod.ts serve --configuration <(echo '{"functions": "./functions/index.ts", "vendor": "./functions/vendor", "schemaMode": "INFER"}')` * The connector should now be running on localhost:8100 and respond to any changes to the your functions and the connector source * Use the `hasura3` tunnel commands to reference this connector from a Hasura Cloud project diff --git a/src/entrypoint.sh b/src/entrypoint.sh index 5209fbf..e40b8e4 100755 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -14,7 +14,7 @@ if [ -d vendor ] then echo "found existing vendor results" else - deno vendor -f index.ts + deno vendor --node-modules-dir -f index.ts deno vendor -f /app/mod.ts fi diff --git a/src/infer.ts b/src/infer.ts index daab65c..c8fcf4e 100644 --- a/src/infer.ts +++ b/src/infer.ts @@ -8,7 +8,7 @@ * Dependencies are required to be vendored before invocation. */ -import ts, { FunctionDeclaration } from "npm:typescript@5.1.6"; +import ts, { FunctionDeclaration, StringLiteralLike } from "npm:typescript@5.1.6"; import { resolve, dirname } from "https://deno.land/std@0.203.0/path/mod.ts"; import { existsSync } from "https://deno.land/std@0.201.0/fs/mod.ts"; import * as sdk from 'npm:@hasura/ndc-sdk-typescript@1.0.0'; @@ -187,7 +187,7 @@ function pre_vendor(vendorPath: string, filename: string) { // Exampe taken from: // https://docs.deno.com/runtime/tutorials/subprocess const deno_exec_path = Deno.execPath(); - const vendor_args = [ "vendor", "--output", vendorPath, "--force", filename ]; + const vendor_args = [ "vendor", "--node-modules-dir", "--output", vendorPath, "--force", filename ]; console.error(`Vendoring dependencies: ${[deno_exec_path, ...vendor_args].join(" ")}`); @@ -370,9 +370,9 @@ export function programInfoException(filename_arg?: string, vendor_arg?: string, } `); - const program = ts.createProgram([filename], { + const compilerOptions: ts.CompilerOptions = { // This should match the version targeted in the deno version that is being used. - target: ts.ScriptTarget.ES2022, + target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, noImplicitAny: true, // NOTE: We just declare Deno globally as any in order to allow users to omit it's declaration in their function files @@ -383,7 +383,26 @@ export function programInfoException(filename_arg?: string, vendor_arg?: string, noEmit: true, baseUrl: '.', paths: pathsMap - }); + }; + + const host = ts.createCompilerHost(compilerOptions); + host.resolveModuleNameLiterals = (moduleLiterals: StringLiteralLike[], containingFile: string): ts.ResolvedModuleWithFailedLookupLocations[] => { + return moduleLiterals.map(moduleName => { + let moduleNameToResolve = moduleName.text; + // If this looks like a Deno "npm:pkgName[@version][/path]" module import, extract the node module + // name and resolve that instead. So long as we've done a deno vendor with --node-modules-dir + // then we'll have a node_modules directory that the standard TypeScript module resolution + // process can locate the npm package in by its name + const npmDepMatch = /^npm:(?(?:@.+?\/)?[^/\n]+?)(?:@.+)?(?:\/.+)?$/.exec(moduleName.text); + if (npmDepMatch) { + moduleNameToResolve = npmDepMatch.groups?.pkgName!; + } + + return ts.resolveModuleName(moduleNameToResolve, containingFile, compilerOptions, { fileExists: host.fileExists, readFile: host.readFile }); + }) + } + + const program = ts.createProgram([filename], compilerOptions, host); Deno.removeSync(deno_d_ts); @@ -535,4 +554,3 @@ export function programInfoException(filename_arg?: string, vendor_arg?: string, return result; } - diff --git a/src/test/external_dependencies_test.ts b/src/test/external_dependencies_test.ts index fc337e6..68600e2 100644 --- a/src/test/external_dependencies_test.ts +++ b/src/test/external_dependencies_test.ts @@ -6,22 +6,44 @@ import * as infer from '../infer.ts'; // Skipped due to NPM dependency resolution not currently being supported. Deno.test({ name: "Inference", - ignore: true, fn() { const program_path = path.fromFileUrl(import.meta.resolve('./data/external_dependencies.ts')); const vendor_path = path.fromFileUrl(import.meta.resolve('./vendor')); - const program_results = infer.programInfo(program_path, vendor_path, false); + const program_results = infer.programInfo(program_path, vendor_path, true); test.assertEquals(program_results, { positions: { + test_deps: [ + "s" + ] }, schema: { scalar_types: { + String: { + aggregate_functions: {}, + comparison_operators: {}, + update_operators: {}, + }, }, object_types: {}, collections: [], functions: [], procedures: [ + { + name: "test_deps", + arguments: { + s: { + type: { + name: "String", + type: "named", + }, + }, + }, + result_type: { + name: "String", + type: "named", + }, + }, ], } });