diff --git a/cspell.yml b/cspell.yml index 8770a4d781..e4656139c1 100644 --- a/cspell.yml +++ b/cspell.yml @@ -29,6 +29,8 @@ words: - graphiql - sublinks - instanceof + - vitest + - vite # Different names used inside tests - Skywalker diff --git a/integrationTests/ts/package.json b/integrationTests/ts/package.json index 413e1aec05..f66128edd3 100644 --- a/integrationTests/ts/package.json +++ b/integrationTests/ts/package.json @@ -6,11 +6,13 @@ }, "dependencies": { "graphql": "file:../graphql.tgz", - "typescript-4.1": "npm:typescript@4.1.x", - "typescript-4.2": "npm:typescript@4.2.x", - "typescript-4.3": "npm:typescript@4.3.x", - "typescript-4.4": "npm:typescript@4.4.x", - "typescript-4.5": "npm:typescript@4.5.x", - "typescript-4.6": "npm:typescript@4.6.x" + "typescript-4.8": "npm:typescript@4.8.x", + "typescript-4.9": "npm:typescript@4.9.x", + "typescript-5.0": "npm:typescript@5.0.x", + "typescript-5.1": "npm:typescript@5.1.x", + "typescript-5.2": "npm:typescript@5.2.x", + "typescript-5.3": "npm:typescript@5.3.x", + "typescript-5.4": "npm:typescript@5.4.x", + "typescript-5.5": "npm:typescript@5.5.x" } } diff --git a/package.json b/package.json index f3466e2735..a41a3f0f07 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,11 @@ "description": "A Query Language and Runtime which can target any service.", "license": "MIT", "private": true, - "main": "index", + "main": "index.js", "module": "index.mjs", + "exports": { + "./package.json": "./package.json" + }, "typesVersions": { ">=4.1.0": { "*": [ diff --git a/resources/build-npm.js b/resources/build-npm.js index 7ff0cf079c..9081bda5ee 100644 --- a/resources/build-npm.js +++ b/resources/build-npm.js @@ -13,6 +13,18 @@ const { showDirStats, } = require('./utils.js'); +const entryPoints = fs + .readdirSync('./src', { recursive: true }) + .filter((f) => f.endsWith('index.ts')) + .map((f) => f.replace(/^src/, '')) + .reverse() + .concat([ + 'execution/execute.ts', + 'jsutils/instanceOf.ts', + 'language/parser.ts', + 'language/ast.ts', + ]); + if (require.main === module) { fs.rmSync('./npmDist', { recursive: true, force: true }); fs.mkdirSync('./npmDist'); @@ -57,11 +69,21 @@ if (require.main === module) { const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost); const tsResult = tsProgram.emit(); + assert( !tsResult.emitSkipped, 'Fail to generate `*.d.ts` files, please run `npm run check`', ); + for (const [filename, contents] of Object.entries( + buildCjsEsmWrapper( + entryPoints.map((e) => './src/' + e), + tsProgram, + ), + )) { + writeGeneratedFile(filename, contents); + } + assert(packageJSON.types === undefined, 'Unexpected "types" in package.json'); const supportedTSVersions = Object.keys(packageJSON.typesVersions); assert( @@ -107,6 +129,42 @@ function buildPackageJSON() { delete packageJSON.scripts; delete packageJSON.devDependencies; + packageJSON.type = 'commonjs'; + + for (const entryPoint of entryPoints) { + const base = ('./' + path.dirname(entryPoint)).replace(/\/.?$/, ''); + const filename = path.basename(entryPoint, '.ts'); + const generated = {}; + generated[filename === 'index' ? base : `${base}/${filename}.js`] = { + types: { + import: `${base}/${filename}.js.d.mts`, + default: `${base}/${filename}.d.ts`, + }, + /* + this is not automatically picked up by vitest, but we can instruct users to add it to their vitest config: + ```js title="vite.config.ts" + import { defineConfig } from 'vite'; + export default defineConfig(async ({ mode }) => { + return { + resolve: mode === 'test' ? { conditions: ['dual-module-hazard-workaround'] } : undefined, + }; + }); + ``` + */ + 'dual-module-hazard-workaround': { + import: `${base}/${filename}.js.mjs`, + default: `${base}/${filename}.js`, + }, + module: `${base}/${filename}.mjs`, + import: `${base}/${filename}.js.mjs`, + default: `${base}/${filename}.js`, + }; + packageJSON.exports = { + ...generated, + ...packageJSON.exports, + }; + } + // TODO: move to integration tests const publishTag = packageJSON.publishConfig?.tag; assert(publishTag != null, 'Should have packageJSON.publishConfig defined!'); @@ -137,3 +195,130 @@ function buildPackageJSON() { return packageJSON; } + +/** + * + * @param {string[]} files + * @param {ts.Program} tsProgram + * @returns + */ +function buildCjsEsmWrapper(files, tsProgram) { + /** + * @type {Record} inputFiles + */ + const inputFiles = {}; + for (const file of files) { + const sourceFile = tsProgram.getSourceFile(file); + assert(sourceFile, `No source file found for ${file}`); + + const generatedFileName = path.relative( + path.dirname(tsProgram.getRootFileNames()[0]), + file.replace(/\.ts$/, '.js.mts'), + ); + const exportFrom = ts.factory.createStringLiteral( + './' + path.basename(file, '.ts') + '.js', + ); + + /** + * @type {ts.Statement[]} + */ + const statements = []; + + /** @type {string[]} */ + const exports = []; + + /** @type {string[]} */ + const typeExports = []; + + sourceFile.forEachChild((node) => { + if (ts.isExportDeclaration(node)) { + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const element of node.exportClause.elements) { + if (node.isTypeOnly || element.isTypeOnly) { + typeExports.push(element.name.text); + } else { + exports.push(element.name.text); + } + } + } + } else if ( + node.modifiers?.some( + (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword, + ) + ) { + if (ts.isVariableStatement(node)) { + for (const declaration of node.declarationList.declarations) { + if (declaration.name && ts.isIdentifier(declaration.name)) { + exports.push(declaration.name.text); + } + } + } else if ( + ts.isFunctionDeclaration(node) || + ts.isClassDeclaration(node) + ) { + exports.push(node.name.text); + } else if (ts.isTypeAliasDeclaration(node)) { + typeExports.push(node.name.text); + } + } + }); + if (exports.length > 0) { + statements.push( + ts.factory.createExportDeclaration( + undefined, + undefined, + false, + ts.factory.createNamedExports( + exports.map((name) => + ts.factory.createExportSpecifier(false, undefined, name), + ), + ), + exportFrom, + ), + ); + } + if (typeExports.length > 0) { + statements.push( + ts.factory.createExportDeclaration( + undefined, + undefined, + true, + ts.factory.createNamedExports( + typeExports.map((name) => + ts.factory.createExportSpecifier(false, undefined, name), + ), + ), + exportFrom, + ), + ); + } + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + inputFiles[generatedFileName] = printer.printFile( + ts.factory.createSourceFile( + statements, + ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), + ts.NodeFlags.None, + ), + ); + } + /** + * @type {ts.CompilerOptions} options + */ + const options = { + ...tsProgram.getCompilerOptions(), + declaration: true, + emitDeclarationOnly: false, + isolatedModules: true, + module: ts.ModuleKind.ESNext, + }; + options.outDir = options.declarationDir; + const results = {}; + const host = ts.createCompilerHost(options); + host.writeFile = (fileName, contents) => (results[fileName] = contents); + host.readFile = (fileName) => inputFiles[fileName]; + + const program = ts.createProgram(Object.keys(inputFiles), options, host); + program.emit(); + + return results; +} diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 55c22ea9de..7e16b9c900 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -1,3 +1,14 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * ExecutionContext + * * assertValidExecutionArguments @internal + * * buildExecutionContext @internal + * * buildResolveInfo @internal + * * getFieldDef @internal + * Should we still expose this file? + */ + import { devAssert } from '../jsutils/devAssert'; import { inspect } from '../jsutils/inspect'; import { invariant } from '../jsutils/invariant'; diff --git a/src/execution/values.ts b/src/execution/values.ts index d65ea9cf20..c883707007 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -1,3 +1,9 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: none + * Should we still expose this file? + */ + import { inspect } from '../jsutils/inspect'; import { keyMap } from '../jsutils/keyMap'; import type { Maybe } from '../jsutils/Maybe'; diff --git a/src/jsutils/Maybe.ts b/src/jsutils/Maybe.ts index 0ba64a4b64..48b7fd9657 100644 --- a/src/jsutils/Maybe.ts +++ b/src/jsutils/Maybe.ts @@ -1,2 +1,6 @@ +/** + * Quoted as "used by external libraries". + * Should we still expose these? + */ /** Conveniently represents flow's "Maybe" type https://flow.org/en/docs/types/maybe/ */ export type Maybe = null | undefined | T; diff --git a/src/jsutils/ObjMap.ts b/src/jsutils/ObjMap.ts index 2c20282187..8413127dd8 100644 --- a/src/jsutils/ObjMap.ts +++ b/src/jsutils/ObjMap.ts @@ -1,3 +1,9 @@ +/** + * Quoted as "used by external libraries". + * All of these could be replaced by Record\ or Readonly\\> + * Should we still expose these? + */ + export interface ObjMap { [key: string]: T; } diff --git a/src/jsutils/PromiseOrValue.ts b/src/jsutils/PromiseOrValue.ts index 6b2517ee62..edf86834da 100644 --- a/src/jsutils/PromiseOrValue.ts +++ b/src/jsutils/PromiseOrValue.ts @@ -1 +1,5 @@ +/** + * Quoted as "used by external libraries". + * Should we still expose these? + */ export type PromiseOrValue = Promise | T; diff --git a/src/language/ast.ts b/src/language/ast.ts index 29029342a1..0081ecb9d8 100644 --- a/src/language/ast.ts +++ b/src/language/ast.ts @@ -1,3 +1,10 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * isNode @internal + * * QueryDocumentKeys @internal + * Should we still expose this file? + */ import type { Kind } from './kinds'; import type { Source } from './source'; import type { TokenKind } from './tokenKind'; diff --git a/src/language/lexer.ts b/src/language/lexer.ts index 818f81b286..c77ddd6ee0 100644 --- a/src/language/lexer.ts +++ b/src/language/lexer.ts @@ -1,3 +1,10 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * isPunctuatorTokenKind @internal + * Should we still expose this file? + */ + import { syntaxError } from '../error/syntaxError'; import { Token } from './ast'; diff --git a/src/language/parser.ts b/src/language/parser.ts index eb54a0376b..288512bedd 100644 --- a/src/language/parser.ts +++ b/src/language/parser.ts @@ -1,3 +1,10 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * Parser @internal + * Should we still expose this file? + */ + import type { Maybe } from '../jsutils/Maybe'; import type { GraphQLError } from '../error/GraphQLError'; diff --git a/src/type/schema.ts b/src/type/schema.ts index 97c2782145..2aede3953b 100644 --- a/src/type/schema.ts +++ b/src/type/schema.ts @@ -1,3 +1,10 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * GraphQLSchemaNormalizedConfig @internal + * * GraphQLSchemaValidationOptions + * Should we still expose this file? + */ import { devAssert } from '../jsutils/devAssert'; import { inspect } from '../jsutils/inspect'; import { instanceOf } from '../jsutils/instanceOf'; diff --git a/src/validation/ValidationContext.ts b/src/validation/ValidationContext.ts index f6944a6ebd..f5fa8f7bd0 100644 --- a/src/validation/ValidationContext.ts +++ b/src/validation/ValidationContext.ts @@ -1,3 +1,13 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * ASTValidationContext + * * ASTValidationRule + * * SDLValidationContext + * * SDLValidationRule + * Should we still expose this file? + */ + import type { Maybe } from '../jsutils/Maybe'; import type { ObjMap } from '../jsutils/ObjMap'; diff --git a/src/validation/specifiedRules.ts b/src/validation/specifiedRules.ts index 16e555db8a..b46b93b837 100644 --- a/src/validation/specifiedRules.ts +++ b/src/validation/specifiedRules.ts @@ -1,3 +1,10 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * specifiedSDLRules @internal + * Should we still expose this file? + */ + // Spec Section: "Executable Definitions" import { ExecutableDefinitionsRule } from './rules/ExecutableDefinitionsRule'; // Spec Section: "Field Selections on Objects, Interfaces, and Unions Types" diff --git a/src/validation/validate.ts b/src/validation/validate.ts index 7259874240..7d27b40866 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -1,3 +1,12 @@ +/** + * Quoted as "used by external libraries". + * Missing exports from `graphql`: + * * assertValidSDL @internal + * * assertValidSDLExtension @internal + * * validateSDL @internal + * Should we still expose this file? + */ + import { devAssert } from '../jsutils/devAssert'; import type { Maybe } from '../jsutils/Maybe';