diff --git a/.circleci/config.yml b/.circleci/config.yml index 759e59f5445..3575e6ea677 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,13 @@ jobs: - store_test_results: path: junit.xml + Smoke test built package: + docker: + - image: cimg/base:stable + steps: + - setup-node + - run: npm run test:smoke + Prettier: docker: - image: cimg/base:stable @@ -90,3 +97,4 @@ workflows: - "Check for FIXM\x45" - Prettier - Spell check + - Smoke test built package diff --git a/cspell-dict.txt b/cspell-dict.txt index c355bfaf433..bc4b21688c4 100644 --- a/cspell-dict.txt +++ b/cspell-dict.txt @@ -102,6 +102,7 @@ microrouter middlewares Middlewares millis +mktemp monodocs mygraph myvariant @@ -119,6 +120,8 @@ pbts pook Pook pooks +postcompile +precompile preflighted preflighting prepended @@ -135,6 +138,7 @@ retryable revalidates roadmap ROADMAP +rollup runtimes safelist safelisted diff --git a/jest.config.base.js b/jest.config.base.js index 46f962d3fdb..4e80b331a1e 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -1,6 +1,6 @@ -const { defaults } = require('jest-config'); +import { defaults } from 'jest-config'; -module.exports = { +export default { testEnvironment: 'node', setupFilesAfterEnv: ['/../../jest.setup.js'], preset: 'ts-jest', @@ -9,10 +9,16 @@ module.exports = { testPathIgnorePatterns: ['/node_modules/', '/dist/'], moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], clearMocks: true, + extensionsToTreatAsEsm: ['.ts'], globals: { 'ts-jest': { + useESM: true, tsconfig: '/src/__tests__/tsconfig.json', diagnostics: false, }, }, + moduleNameMapper: { + // Ignore '.js' at the end of imports; part of ESM support. + '^(\\.{1,2}/.*)\\.js$': '$1', + }, }; diff --git a/package-lock.json b/package-lock.json index 2a04b585208..7554bb3d533 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "@graphql-tools/utils": "8.6.13", "@josephg/resolvable": "1.0.1", "@rollup/plugin-commonjs": "22.0.0", - "@rollup/plugin-json": "4.1.0", "@types/async-retry": "1.4.4", "@types/body-parser": "1.19.2", "@types/cors": "2.8.12", @@ -63,6 +62,7 @@ "qs-middleware": "1.0.3", "request-promise": "4.2.6", "requisition": "1.7.0", + "rimraf": "^3.0.2", "rollup": "2.75.6", "supertest": "6.2.3", "test-listen": "1.1.0", @@ -3247,18 +3247,6 @@ "rollup": "^2.68.0" } }, - "node_modules/@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, "node_modules/@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", @@ -12130,8 +12118,8 @@ }, "packages/server/node_modules/lru-cache": { "version": "7.10.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", - "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz", + "integrity": "sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A==", "engines": { "node": ">=12" } @@ -12234,8 +12222,8 @@ "dependencies": { "lru-cache": { "version": "7.10.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", - "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz", + "integrity": "sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A==" } } }, @@ -14731,15 +14719,6 @@ "resolve": "^1.17.0" } }, - "@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8" - } - }, "@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", diff --git a/package.json b/package.json index 1c4c4592e1f..2a0cff4975b 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "private": true, "license": "MIT", "repository": "github:apollographql/apollo-server", + "type": "module", "scripts": { "clean": "git clean -dfqX -- ./node_modules **/{dist,node_modules}/ **/tsconfig*tsbuildinfo", - "compile": "tsc --build tsconfig.build.json", - "compile:clean": "tsc --build tsconfig.build.json --clean", + "precompile": "node precompile.mjs", + "postcompile": "node postcompile.mjs", + "compile": "tsc --build tsconfig.build.json packages/server/tsconfig.cjs.json; x=$?; mv packages/server/src/.packageVersion.ts.original packages/server/src/packageVersion.ts; exit $x", "watch": "tsc --build tsconfig.build.json --watch", "install-with-npm-8.5": "npm i -g npm@^8.5.0 && npm i", "postinstall": "npm run compile", @@ -14,6 +16,9 @@ "test": "jest --verbose", "test:clean": "jest --clearCache", "test:watch": "jest --verbose --watchAll", + "test:smoke": "npm run test:smoke:prepare && npm run test:smoke:run", + "test:smoke:prepare": "npm run compile && smoke-test/prepare.sh", + "test:smoke:run": "smoke-test/smoke-test.sh", "testonly": "npm test", "test:ci": "npm run coverage -- --ci --maxWorkers=2 --reporters=default --reporters=jest-junit", "coverage": "npm test -- --coverage", @@ -40,7 +45,6 @@ "@graphql-tools/utils": "8.6.13", "@josephg/resolvable": "1.0.1", "@rollup/plugin-commonjs": "22.0.0", - "@rollup/plugin-json": "4.1.0", "@types/async-retry": "1.4.4", "@types/body-parser": "1.19.2", "@types/cors": "2.8.12", @@ -83,6 +87,7 @@ "qs-middleware": "1.0.3", "request-promise": "4.2.6", "requisition": "1.7.0", + "rimraf": "^3.0.2", "rollup": "2.75.6", "supertest": "6.2.3", "test-listen": "1.1.0", diff --git a/packages/server/.npmignore b/packages/server/.npmignore index 311075fe3ea..7514d963d20 100644 --- a/packages/server/.npmignore +++ b/packages/server/.npmignore @@ -3,5 +3,6 @@ src/**/__tests__/** !dist/**/* dist/**/__tests__/** +dist/**/*.tsbuildinfo !package.json !README.md diff --git a/packages/server/jest.config.js b/packages/server/jest.config.js index a383fbc925f..43f9c150aef 100644 --- a/packages/server/jest.config.js +++ b/packages/server/jest.config.js @@ -1,3 +1,3 @@ -const config = require('../../jest.config.base'); +import baseConfig from '../../jest.config.base.js'; -module.exports = Object.assign(Object.create(null), config); +export default baseConfig; diff --git a/packages/server/package.json b/packages/server/package.json index a7c29551539..0d5f500376c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -2,47 +2,57 @@ "name": "@apollo/server", "version": "3.6.7", "description": "Core engine for Apollo GraphQL server", - "type": "commonjs", + "type": "module", "exports": { ".": { - "require": "./dist/index.js", - "types": "./dist/index.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts" }, "./standalone": { - "require": "./dist/standalone/index.js", - "types": "./dist/standalone/index.d.ts" + "import": "./dist/esm/standalone/index.js", + "require": "./dist/cjs/standalone/index.js", + "types": "./dist/cjs/standalone/index.d.ts" }, "./plugin/cacheControl": { - "require": "./dist/plugin/cacheControl/index.js", - "types": "./dist/plugin/cacheControl/index.d.ts" + "import": "./dist/esm/plugin/cacheControl/index.js", + "require": "./dist/cjs/plugin/cacheControl/index.js", + "types": "./dist/cjs/plugin/cacheControl/index.d.ts" }, "./plugin/disabled": { - "require": "./dist/plugin/disabled.js", - "types": "./dist/plugin/disabled.d.ts" + "import": "./dist/esm/plugin/disabled.js", + "require": "./dist/cjs/plugin/disabled.js", + "types": "./dist/cjs/plugin/disabled.d.ts" }, "./plugin/drainHttpServer": { - "require": "./dist/plugin/drainHttpServer/index.js", - "types": "./dist/plugin/drainHttpServer/index.d.ts" + "import": "./dist/esm/plugin/drainHttpServer/index.js", + "require": "./dist/cjs/plugin/drainHttpServer/index.js", + "types": "./dist/cjs/plugin/drainHttpServer/index.d.ts" }, "./plugin/inlineTrace": { - "require": "./dist/plugin/inlineTrace/index.js", - "types": "./dist/plugin/inlineTrace/index.d.ts" + "import": "./dist/esm/plugin/inlineTrace/index.js", + "require": "./dist/cjs/plugin/inlineTrace/index.js", + "types": "./dist/cjs/plugin/inlineTrace/index.d.ts" }, "./plugin/landingPage/default": { - "require": "./dist/plugin/landingPage/default/index.js", - "types": "./dist/plugin/landingPage/default/index.d.ts" + "import": "./dist/esm/plugin/landingPage/default/index.js", + "require": "./dist/cjs/plugin/landingPage/default/index.js", + "types": "./dist/cjs/plugin/landingPage/default/index.d.ts" }, "./plugin/landingPage/graphqlPlayground": { - "require": "./dist/plugin/landingPage/graphqlPlayground/index.js", - "types": "./dist/plugin/landingPage/graphqlPlayground/index.d.ts" + "import": "./dist/esm/plugin/landingPage/graphqlPlayground/index.js", + "require": "./dist/cjs/plugin/landingPage/graphqlPlayground/index.js", + "types": "./dist/cjs/plugin/landingPage/graphqlPlayground/index.d.ts" }, "./plugin/schemaReporting": { - "require": "./dist/plugin/schemaReporting/index.js", - "types": "./dist/plugin/schemaReporting/index.d.ts" + "import": "./dist/esm/plugin/schemaReporting/index.js", + "require": "./dist/cjs/plugin/schemaReporting/index.js", + "types": "./dist/cjs/plugin/schemaReporting/index.d.ts" }, "./plugin/usageReporting": { - "require": "./dist/plugin/usageReporting/index.js", - "types": "./dist/plugin/usageReporting/index.d.ts" + "import": "./dist/esm/plugin/usageReporting/index.js", + "require": "./dist/cjs/plugin/usageReporting/index.js", + "types": "./dist/cjs/plugin/usageReporting/index.d.ts" } }, "repository": { diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index 1d86d180d0e..34d6e94f7b1 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -23,9 +23,9 @@ import { import loglevel from 'loglevel'; import Negotiator from 'negotiator'; import * as uuid from 'uuid'; -import { newCachePolicy } from './cachePolicy'; -import { determineApolloConfig } from './determineApolloConfig'; -import { BadRequestError, ensureError, formatApolloErrors } from './errors'; +import { newCachePolicy } from './cachePolicy.js'; +import { determineApolloConfig } from './determineApolloConfig.js'; +import { BadRequestError, ensureError, formatApolloErrors } from './errors.js'; import type { ApolloServerPlugin, BaseContext, @@ -44,22 +44,22 @@ import type { HTTPGraphQLHead, ContextThunk, } from './externalTypes'; -import { runPotentiallyBatchedHttpQuery } from './httpBatching'; -import { InternalPluginId, pluginIsInternal } from './internalPlugin'; +import { runPotentiallyBatchedHttpQuery } from './httpBatching.js'; +import { InternalPluginId, pluginIsInternal } from './internalPlugin.js'; import { preventCsrf, recommendedCsrfPreventionRequestHeaders, -} from './preventCsrf'; -import { APQ_CACHE_PREFIX, processGraphQLRequest } from './requestPipeline'; +} from './preventCsrf.js'; +import { APQ_CACHE_PREFIX, processGraphQLRequest } from './requestPipeline.js'; import { badMethodErrorMessage, cloneObject, HeaderMap, newHTTPGraphQLHead, prettyJSONStringify, -} from './runHttpQuery'; -import { SchemaManager } from './utils/schemaManager'; -import { isDefined } from './utils/isDefined'; +} from './runHttpQuery.js'; +import { SchemaManager } from './utils/schemaManager.js'; +import { isDefined } from './utils/isDefined.js'; import type { WithRequired } from '@apollo/utils.withrequired'; import type { ApolloServerOptionsWithStaticSchema } from './externalTypes/constructor'; @@ -857,7 +857,7 @@ export class ApolloServer { { if (!alreadyHavePluginWithInternalId('CacheControl')) { const { ApolloServerPluginCacheControl } = await import( - './plugin/cacheControl' + './plugin/cacheControl/index.js' ); plugins.push(ApolloServerPluginCacheControl()); } @@ -874,7 +874,7 @@ export class ApolloServer { // the fact that the person who wrote this line also was the original // author of the comment above in #1105, they don't quite understand why this was important.) const { ApolloServerPluginUsageReporting } = await import( - './plugin/usageReporting' + './plugin/usageReporting/index.js' ); plugins.unshift(ApolloServerPluginUsageReporting()); } else { @@ -896,7 +896,7 @@ export class ApolloServer { if (!alreadyHavePlugin && enabledViaEnvVar) { if (apolloConfig.key) { const { ApolloServerPluginSchemaReporting } = await import( - './plugin/schemaReporting' + './plugin/schemaReporting/index.js' ); plugins.push(ApolloServerPluginSchemaReporting()); } else { @@ -921,7 +921,7 @@ export class ApolloServer { // pre-ApolloServerPluginInlineTrace where we would also avoid doing // this if an API key was configured and log a warning.) const { ApolloServerPluginInlineTrace } = await import( - './plugin/inlineTrace' + './plugin/inlineTrace/index.js' ); plugins.push( ApolloServerPluginInlineTrace({ __onlyIfSchemaIsFederated: true }), @@ -949,7 +949,7 @@ export class ApolloServer { const { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault, - } = await import('./plugin/landingPage/default'); + } = await import('./plugin/landingPage/default/index.js'); const plugin: ApolloServerPlugin = isDev ? ApolloServerPluginLandingPageLocalDefault() : ApolloServerPluginLandingPageProductionDefault(); diff --git a/packages/server/src/__tests__/plugin/drainHttpServer/stoppable/server.js b/packages/server/src/__tests__/plugin/drainHttpServer/stoppable/server.js index 872620cb841..414505de6f8 100644 --- a/packages/server/src/__tests__/plugin/drainHttpServer/stoppable/server.js +++ b/packages/server/src/__tests__/plugin/drainHttpServer/stoppable/server.js @@ -1,7 +1,5 @@ -const http = require('http'); -const { - Stopper, -} = require('../../../../../dist/plugin/drainHttpServer/stoppable.js'); +import http from 'http'; +import { Stopper } from '../../../../../dist/esm/plugin/drainHttpServer/stoppable.js'; const grace = Number(process.argv[2] || Infinity); let stopper; diff --git a/packages/server/src/__tests__/rollupCommonJs.test.ts b/packages/server/src/__tests__/rollupCommonJs.test.ts index 9cc304405bc..93c0378a92a 100644 --- a/packages/server/src/__tests__/rollupCommonJs.test.ts +++ b/packages/server/src/__tests__/rollupCommonJs.test.ts @@ -1,5 +1,4 @@ const rollup = require('rollup'); -import json from '@rollup/plugin-json'; import commonjs from '@rollup/plugin-commonjs'; import path from 'path'; @@ -9,16 +8,16 @@ describe('@rollup/plugin-commonjs', () => { exports: 'named', name: 'apollo', format: 'umd', - sourcemapExcludeSources: false, }; const bundle = await rollup.rollup({ - input: path.resolve(__dirname, '..', '..', 'dist', 'index.js'), - plugins: [json(), commonjs()], + input: path.resolve(__dirname, '..', '..', 'dist', 'cjs', 'index.js'), + plugins: [commonjs()], onwarn: () => { /* suppress warnings */ }, }); const { output } = await bundle.generate(outputOptions); + await bundle.close(); const indexBundle = output[0].code; var varDefinedAfterBundle; eval(`${indexBundle}; varDefinedAfterBundle = 'foo';`); diff --git a/packages/server/src/externalTypes/index.ts b/packages/server/src/externalTypes/index.ts index 32f7cfcf692..9183b40e4a9 100644 --- a/packages/server/src/externalTypes/index.ts +++ b/packages/server/src/externalTypes/index.ts @@ -4,25 +4,25 @@ * is re-exported by the root (via * export), so add exports to this file with * intention (it's public API). */ -export { +export type { CacheAnnotation, CacheHint, CachePolicy, CacheScope, } from './cacheControl'; -export { BaseContext, ContextFunction, ContextThunk } from './context'; -export { +export type { BaseContext, ContextFunction, ContextThunk } from './context'; +export type { GraphQLRequest, GraphQLRequestContext, GraphQLRequestMetrics, GraphQLResponse, } from './graphql'; -export { +export type { HTTPGraphQLRequest, HTTPGraphQLResponse, HTTPGraphQLHead, } from './http'; -export { +export type { ApolloServerPlugin, GraphQLFieldResolverParams, GraphQLRequestExecutionListener, @@ -37,7 +37,7 @@ export { LandingPage, PluginDefinition, } from './plugins'; -export { +export type { GraphQLExecutor, GraphQLRequestContextDidEncounterErrors, GraphQLRequestContextDidResolveOperation, @@ -48,7 +48,7 @@ export { GraphQLRequestContextValidationDidStart, GraphQLRequestContextWillSendResponse, } from './requestPipeline'; -export { +export type { Unsubscriber, SchemaLoadOrUpdateCallback, GatewayLoadResult, diff --git a/packages/server/src/httpBatching.ts b/packages/server/src/httpBatching.ts index 11a4bc7953f..d929b0d0025 100644 --- a/packages/server/src/httpBatching.ts +++ b/packages/server/src/httpBatching.ts @@ -4,8 +4,8 @@ import type { HTTPGraphQLResponse, } from './externalTypes'; import type { ApolloServerInternals, SchemaDerivedData } from './ApolloServer'; -import { HeaderMap, runHttpQuery } from './runHttpQuery'; -import { BadRequestError } from './errors'; +import { HeaderMap, runHttpQuery } from './runHttpQuery.js'; +import { BadRequestError } from './errors.js'; export async function runBatchHttpQuery( batchRequest: Omit & { body: any[] }, diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index ca8c1bc1a2e..e67f119e565 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,12 +1,15 @@ +export { ApolloServer } from './ApolloServer.js'; +// Note that this is purely a type export. +export * from './externalTypes/index.js'; + +// TODO(AS4): consider moving to `@apollo/server/errors` export { SyntaxError, ValidationError, AuthenticationError, ForbiddenError, UserInputError, -} from './errors'; - -export { ApolloServer } from './ApolloServer'; -export { expressMiddleware } from './express'; +} from './errors.js'; -export * from './externalTypes'; +// TODO(AS4): consider moving to `@apollo/server/express` +export { expressMiddleware } from './express/index.js'; diff --git a/packages/server/src/packageVersion.ts b/packages/server/src/packageVersion.ts new file mode 100644 index 00000000000..938b2d59c7e --- /dev/null +++ b/packages/server/src/packageVersion.ts @@ -0,0 +1 @@ +export const packageVersion = 'local'; diff --git a/packages/server/src/plugin/cacheControl/index.ts b/packages/server/src/plugin/cacheControl/index.ts index 58e2247a150..04e593e8284 100644 --- a/packages/server/src/plugin/cacheControl/index.ts +++ b/packages/server/src/plugin/cacheControl/index.ts @@ -14,7 +14,7 @@ import { isObjectType, responsePathAsArray, } from 'graphql'; -import { newCachePolicy } from '../../cachePolicy'; +import { newCachePolicy } from '../../cachePolicy.js'; import type { InternalApolloServerPlugin } from '../../internalPlugin'; import LRUCache from 'lru-cache'; diff --git a/packages/server/src/plugin/drainHttpServer/index.ts b/packages/server/src/plugin/drainHttpServer/index.ts index 459d840f307..fa237f14a20 100644 --- a/packages/server/src/plugin/drainHttpServer/index.ts +++ b/packages/server/src/plugin/drainHttpServer/index.ts @@ -1,6 +1,6 @@ import type http from 'http'; import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; -import { Stopper } from './stoppable'; +import { Stopper } from './stoppable.js'; /** * Options for ApolloServerPluginDrainHttpServer. diff --git a/packages/server/src/plugin/inlineTrace/index.ts b/packages/server/src/plugin/inlineTrace/index.ts index b931c6fe566..112f397a58c 100644 --- a/packages/server/src/plugin/inlineTrace/index.ts +++ b/packages/server/src/plugin/inlineTrace/index.ts @@ -1,10 +1,11 @@ -import { Trace } from '@apollo/usage-reporting-protobuf'; -import { TraceTreeBuilder } from '../traceTreeBuilder'; +import proto from '@apollo/usage-reporting-protobuf'; +import { TraceTreeBuilder } from '../traceTreeBuilder.js'; import type { ApolloServerPluginUsageReportingOptions } from '../usageReporting/options'; import type { InternalApolloServerPlugin } from '../../internalPlugin'; -import { schemaIsFederated } from '../schemaIsFederated'; +import { schemaIsFederated } from '../schemaIsFederated.js'; import type { BaseContext } from '../../externalTypes'; +const { Trace } = proto; export interface ApolloServerPluginInlineTraceOptions { /** * By default, all errors from this service get included in the trace. You diff --git a/packages/server/src/plugin/schemaReporting/index.ts b/packages/server/src/plugin/schemaReporting/index.ts index 11ceb526f3a..73d28bac465 100644 --- a/packages/server/src/plugin/schemaReporting/index.ts +++ b/packages/server/src/plugin/schemaReporting/index.ts @@ -3,11 +3,12 @@ import os from 'os'; import type { InternalApolloServerPlugin } from '../../internalPlugin'; import { v4 as uuidv4 } from 'uuid'; import { printSchema, validateSchema, buildSchema } from 'graphql'; -import { packageVersion, SchemaReporter } from './schemaReporter'; -import { schemaIsFederated } from '../schemaIsFederated'; +import { SchemaReporter } from './schemaReporter.js'; +import { schemaIsFederated } from '../schemaIsFederated.js'; import type { SchemaReport } from './generated/operations'; import type { BaseContext } from '../../externalTypes'; import type { Fetcher } from '@apollo/utils.fetcher'; +import { packageVersion } from '../../packageVersion.js'; export interface ApolloServerPluginSchemaReportingOptions { /** diff --git a/packages/server/src/plugin/schemaReporting/schemaReporter.ts b/packages/server/src/plugin/schemaReporting/schemaReporter.ts index 66c7d05f5dc..9442315f2e5 100644 --- a/packages/server/src/plugin/schemaReporting/schemaReporter.ts +++ b/packages/server/src/plugin/schemaReporting/schemaReporter.ts @@ -8,8 +8,7 @@ import type { ReportSchemaResponse, } from './generated/operations'; import type { Fetcher } from '@apollo/utils.fetcher'; -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { packageVersion } from '../../packageVersion.js'; export const schemaReportGql = `mutation SchemaReport($report: SchemaReport!, $coreSchema: String) { reportSchema(report: $report, coreSchema: $coreSchema) { @@ -26,10 +25,6 @@ export const schemaReportGql = `mutation SchemaReport($report: SchemaReport!, $c } `; -export const packageVersion = JSON.parse( - readFileSync(join(__dirname, '..', '..', '..', 'package.json'), 'utf-8'), -).version as string; - // This class is meant to be a thin shim around the gql mutations. export class SchemaReporter { // These mirror the gql variables diff --git a/packages/server/src/plugin/traceTreeBuilder.ts b/packages/server/src/plugin/traceTreeBuilder.ts index b72cb57a3b3..61db43c0c11 100644 --- a/packages/server/src/plugin/traceTreeBuilder.ts +++ b/packages/server/src/plugin/traceTreeBuilder.ts @@ -1,9 +1,11 @@ // This class is a helper for ApolloServerPluginUsageReporting and // ApolloServerPluginInlineTrace. import { GraphQLError, GraphQLResolveInfo, ResponsePath } from 'graphql'; -import { Trace, google } from '@apollo/usage-reporting-protobuf'; +import proto from '@apollo/usage-reporting-protobuf'; import type { Logger } from '@apollo/utils.logger'; +const { Trace, google } = proto; + function internalError(message: string) { return new Error(`[internal apollo-server error] ${message}`); } @@ -24,7 +26,7 @@ export class TraceTreeBuilder { }); public startHrTime?: [number, number]; private stopped = false; - private nodes = new Map([ + private nodes = new Map([ [responsePathAsString(), this.rootNode], ]); private readonly rewriteError?: (err: GraphQLError) => GraphQLError | null; @@ -117,7 +119,7 @@ export class TraceTreeBuilder { private addProtobufError( path: ReadonlyArray | undefined, - error: Trace.Error, + error: proto.Trace.Error, ) { if (!this.startHrTime) { throw internalError('addProtobufError called before startTiming!'); @@ -146,7 +148,7 @@ export class TraceTreeBuilder { node.error.push(error); } - private newNode(path: ResponsePath): Trace.Node { + private newNode(path: ResponsePath): proto.Trace.Node { const node = new Trace.Node(); const id = path.key; if (typeof id === 'number') { @@ -160,7 +162,7 @@ export class TraceTreeBuilder { return node; } - private ensureParentNode(path: ResponsePath): Trace.Node { + private ensureParentNode(path: ResponsePath): proto.Trace.Node { const parentPath = responsePathAsString(path.prev); const parentNode = this.nodes.get(parentPath); if (parentNode) { @@ -260,7 +262,7 @@ function responsePathAsString(p?: ResponsePath): string { return res; } -function errorToProtobufError(error: GraphQLError): Trace.Error { +function errorToProtobufError(error: GraphQLError): proto.Trace.Error { return new Trace.Error({ message: error.message, location: (error.locations || []).map( @@ -271,7 +273,9 @@ function errorToProtobufError(error: GraphQLError): Trace.Error { } // Converts a JS Date into a Timestamp. -export function dateToProtoTimestamp(date: Date): google.protobuf.Timestamp { +export function dateToProtoTimestamp( + date: Date, +): proto.google.protobuf.Timestamp { const totalMillis = +date; const millis = totalMillis % 1000; return new google.protobuf.Timestamp({ diff --git a/packages/server/src/plugin/usageReporting/defaultSendOperationsAsTrace.ts b/packages/server/src/plugin/usageReporting/defaultSendOperationsAsTrace.ts index 3fc5f4e5d33..0f5462ef265 100644 --- a/packages/server/src/plugin/usageReporting/defaultSendOperationsAsTrace.ts +++ b/packages/server/src/plugin/usageReporting/defaultSendOperationsAsTrace.ts @@ -1,7 +1,7 @@ import LRUCache from 'lru-cache'; import type { Trace } from '@apollo/usage-reporting-protobuf'; -import { iterateOverTrace } from './iterateOverTrace'; -import { DurationHistogram } from './durationHistogram'; +import { iterateOverTrace } from './iterateOverTrace.js'; +import { DurationHistogram } from './durationHistogram.js'; export function defaultSendOperationsAsTrace() { // We keep an LRU cache mapping from a trace key (which consists of the diff --git a/packages/server/src/plugin/usageReporting/index.ts b/packages/server/src/plugin/usageReporting/index.ts index 24f96e5c1b9..043c5ca702d 100644 --- a/packages/server/src/plugin/usageReporting/index.ts +++ b/packages/server/src/plugin/usageReporting/index.ts @@ -1,5 +1,5 @@ -export { ApolloServerPluginUsageReporting } from './plugin'; -export { +export { ApolloServerPluginUsageReporting } from './plugin.js'; +export type { ApolloServerPluginUsageReportingOptions, SendValuesBaseOptions, VariableValueOptions, diff --git a/packages/server/src/plugin/usageReporting/plugin.ts b/packages/server/src/plugin/usageReporting/plugin.ts index 42e6bbb2d75..9db228b5c15 100644 --- a/packages/server/src/plugin/usageReporting/plugin.ts +++ b/packages/server/src/plugin/usageReporting/plugin.ts @@ -1,4 +1,4 @@ -import { Report, ReportHeader, Trace } from '@apollo/usage-reporting-protobuf'; +import proto from '@apollo/usage-reporting-protobuf'; import type { Fetcher, FetcherResponse } from '@apollo/utils.fetcher'; import { usageReportingSignature, @@ -22,30 +22,27 @@ import type { } from '../../externalTypes'; import type { InternalApolloServerPlugin } from '../../internalPlugin'; import type { HeaderMap } from '../../runHttpQuery'; -import { computeCoreSchemaHash } from '../schemaReporting'; -import { dateToProtoTimestamp, TraceTreeBuilder } from '../traceTreeBuilder'; -import { defaultSendOperationsAsTrace } from './defaultSendOperationsAsTrace'; +import { computeCoreSchemaHash } from '../schemaReporting/index.js'; +import { dateToProtoTimestamp, TraceTreeBuilder } from '../traceTreeBuilder.js'; +import { defaultSendOperationsAsTrace } from './defaultSendOperationsAsTrace.js'; import { createOperationDerivedDataCache, OperationDerivedData, operationDerivedDataCacheKey, -} from './operationDerivedDataCache'; +} from './operationDerivedDataCache.js'; import type { ApolloServerPluginUsageReportingOptions, SendValuesBaseOptions, } from './options'; -import { OurReport } from './stats'; -import { makeTraceDetails } from './traceDetails'; -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { OurReport } from './stats.js'; +import { makeTraceDetails } from './traceDetails.js'; +import { packageVersion } from '../../packageVersion.js'; + +const { ReportHeader } = proto; const reportHeaderDefaults = { hostname: os.hostname(), - agentVersion: `@apollo/server@${ - JSON.parse( - readFileSync(join(__dirname, '..', '..', '..', 'package.json'), 'utf-8'), - ).version - }`, + agentVersion: `@apollo/server@${packageVersion}`, runtimeVersion: `node ${process.version}`, // XXX not actually uname, but what node has easily. uname: `${os.platform()}, ${os.type()}, ${os.release()}, ${os.arch()})`, @@ -53,7 +50,7 @@ const reportHeaderDefaults = { class ReportData { report!: OurReport; - readonly header: ReportHeader; + readonly header: proto.ReportHeader; constructor(executableSchemaId: string, graphRef: string) { this.header = new ReportHeader({ ...reportHeaderDefaults, @@ -249,11 +246,11 @@ export function ApolloServerPluginUsageReporting( report.ensureCountsAreIntegers(); - const protobufError = Report.verify(report); + const protobufError = proto.Report.verify(report); if (protobufError) { throw new Error(`Error encoding report: ${protobufError}`); } - const message = Report.encode(report).finish(); + const message = proto.Report.encode(report).finish(); // Potential follow-up: we can compare message.length to // report.sizeEstimator.bytes and use it to "learn" if our estimation is @@ -272,7 +269,7 @@ export function ApolloServerPluginUsageReporting( // // We decode the report rather than printing the original `report` // so that it includes all of the pre-encoded traces. - const decodedReport = Report.decode(message); + const decodedReport = proto.Report.decode(message); logger.warn( `Apollo usage report: ${JSON.stringify(decodedReport.toJSON())}`, ); @@ -406,11 +403,11 @@ export function ApolloServerPluginUsageReporting( let includeOperationInUsageReporting: boolean | null = null; if (http) { - treeBuilder.trace.http = new Trace.HTTP({ + treeBuilder.trace.http = new proto.Trace.HTTP({ method: - Trace.HTTP.Method[ - http.method as keyof typeof Trace.HTTP.Method - ] || Trace.HTTP.Method.UNKNOWN, + proto.Trace.HTTP.Method[ + http.method as keyof typeof proto.Trace.HTTP.Method + ] || proto.Trace.HTTP.Method.UNKNOWN, // Host and path are not used anywhere on the backend, so let's not bother // trying to parse request.url to get them, which is a potential // source of bugs because integrations have different behavior here. @@ -597,13 +594,13 @@ export function ApolloServerPluginUsageReporting( const policyIfCacheable = requestContext.overallCachePolicy.policyIfCacheable(); if (policyIfCacheable) { - treeBuilder.trace.cachePolicy = new Trace.CachePolicy({ + treeBuilder.trace.cachePolicy = new proto.Trace.CachePolicy({ scope: policyIfCacheable.scope === 'PRIVATE' - ? Trace.CachePolicy.Scope.PRIVATE + ? proto.Trace.CachePolicy.Scope.PRIVATE : policyIfCacheable.scope === 'PUBLIC' - ? Trace.CachePolicy.Scope.PUBLIC - : Trace.CachePolicy.Scope.UNKNOWN, + ? proto.Trace.CachePolicy.Scope.PUBLIC + : proto.Trace.CachePolicy.Scope.UNKNOWN, // Convert from seconds to ns. maxAgeNs: policyIfCacheable.maxAge * 1e9, }); @@ -674,7 +671,7 @@ export function ApolloServerPluginUsageReporting( operationDerivedData.referencedFieldsByType; } - const protobufError = Trace.verify(trace); + const protobufError = proto.Trace.verify(trace); if (protobufError) { throw new Error(`Error encoding trace: ${protobufError}`); } @@ -790,7 +787,7 @@ export function ApolloServerPluginUsageReporting( } export function makeHTTPRequestHeaders( - http: Trace.IHTTP, + http: proto.Trace.IHTTP, headers: HeaderMap, sendHeaders?: SendValuesBaseOptions, ): void { @@ -826,7 +823,7 @@ export function makeHTTPRequestHeaders( case 'set-cookie': break; default: - http!.requestHeaders![key] = new Trace.HTTP.Values({ + http!.requestHeaders![key] = new proto.Trace.HTTP.Values({ value: [value], }); } diff --git a/packages/server/src/plugin/usageReporting/stats.ts b/packages/server/src/plugin/usageReporting/stats.ts index 30114691cf8..779235e4912 100644 --- a/packages/server/src/plugin/usageReporting/stats.ts +++ b/packages/server/src/plugin/usageReporting/stats.ts @@ -1,4 +1,4 @@ -import { +import type { google, IContextualizedStats, IFieldStat, @@ -9,11 +9,13 @@ import { ITracesAndStats, ITypeStat, ReportHeader, - Trace, } from '@apollo/usage-reporting-protobuf'; +import proto from '@apollo/usage-reporting-protobuf'; import type { ReferencedFieldsByType } from '@apollo/utils.usagereporting'; -import { DurationHistogram } from './durationHistogram'; -import { iterateOverTrace, ResponseNamePath } from './iterateOverTrace'; +import { DurationHistogram } from './durationHistogram.js'; +import { iterateOverTrace, ResponseNamePath } from './iterateOverTrace.js'; + +const { Trace } = proto; // protobuf.js exports both a class and an interface (starting with I) for each // message type. The class is what it produces when it decodes the message; the @@ -64,7 +66,7 @@ export class OurReport implements Required { referencedFieldsByType, }: { statsReportKey: string; - trace: Trace; + trace: proto.Trace; asTrace: boolean; includeTracesContributingToStats: boolean; referencedFieldsByType: ReferencedFieldsByType; @@ -159,7 +161,7 @@ class StatsByContext { } } - addTrace(trace: Trace, sizeEstimator: SizeEstimator) { + addTrace(trace: proto.Trace, sizeEstimator: SizeEstimator) { this.getContextualizedStats(trace, sizeEstimator).addTrace( trace, sizeEstimator, @@ -167,7 +169,7 @@ class StatsByContext { } private getContextualizedStats( - trace: Trace, + trace: proto.Trace, sizeEstimator: SizeEstimator, ): OurContextualizedStats { const statsContext: IStatsContext = { @@ -209,7 +211,7 @@ export class OurContextualizedStats implements Required { // We only add to the estimate when adding whole sub-messages. If it really // mattered, we could do a lot more careful things like incrementing it // whenever a numeric field on queryLatencyStats gets incremented over 0. - addTrace(trace: Trace, sizeEstimator: SizeEstimator) { + addTrace(trace: proto.Trace, sizeEstimator: SizeEstimator) { const { fieldExecutionWeight } = trace; if (!fieldExecutionWeight) { this.queryLatencyStats.requestsWithoutFieldInstrumentation++; @@ -260,7 +262,10 @@ export class OurContextualizedStats implements Required { let hasError = false; - const traceNodeStats = (node: Trace.INode, path: ResponseNamePath) => { + const traceNodeStats = ( + node: proto.Trace.INode, + path: ResponseNamePath, + ) => { // Generate error stats and error path information if (node.error?.length) { hasError = true; diff --git a/packages/server/src/plugin/usageReporting/traceDetails.ts b/packages/server/src/plugin/usageReporting/traceDetails.ts index dace53e0c4d..e91d8b11b0c 100644 --- a/packages/server/src/plugin/usageReporting/traceDetails.ts +++ b/packages/server/src/plugin/usageReporting/traceDetails.ts @@ -1,6 +1,8 @@ -import { Trace } from '@apollo/usage-reporting-protobuf'; +import proto from '@apollo/usage-reporting-protobuf'; import type { VariableValueOptions } from './options'; +const { Trace } = proto; + // Creates trace details from request variables, given a specification for modifying // values of private or sensitive variables. // The details will include all variable names and their (possibly hidden or modified) values. @@ -12,7 +14,7 @@ export function makeTraceDetails( variables: Record, sendVariableValues?: VariableValueOptions, operationString?: string, -): Trace.Details { +): proto.Trace.Details { const details = new Trace.Details(); const variablesToRecord = (() => { if (sendVariableValues && 'transform' in sendVariableValues) { diff --git a/packages/server/src/preventCsrf.ts b/packages/server/src/preventCsrf.ts index 9139b3eed1b..c08139644a6 100644 --- a/packages/server/src/preventCsrf.ts +++ b/packages/server/src/preventCsrf.ts @@ -1,5 +1,5 @@ import MIMEType from 'whatwg-mimetype'; -import { BadRequestError } from './errors'; +import { BadRequestError } from './errors.js'; // Our recommended set of CSRF prevention headers. Operations that do not // provide a content-type such as `application/json` (in practice, this diff --git a/packages/server/src/requestPipeline.ts b/packages/server/src/requestPipeline.ts index 363f98ab59c..2d6b8d2a351 100644 --- a/packages/server/src/requestPipeline.ts +++ b/packages/server/src/requestPipeline.ts @@ -15,7 +15,7 @@ import { symbolExecutionDispatcherWillResolveField, enablePluginsForSchemaResolvers, symbolUserFieldResolver, -} from './utils/schemaInstrumentation'; +} from './utils/schemaInstrumentation.js'; import { PersistedQueryNotSupportedError, PersistedQueryNotFoundError, @@ -23,7 +23,7 @@ import { UserInputError, BadRequestError, ensureError, -} from './errors'; +} from './errors.js'; import type { GraphQLRequestContext, GraphQLRequestContextDidResolveSource, @@ -43,11 +43,11 @@ import { invokeDidStartHook, invokeHooksUntilDefinedAndNonNull, invokeSyncDidStartHook, -} from './utils/invokeHooks'; +} from './utils/invokeHooks.js'; -import { HeaderMap, newHTTPGraphQLHead } from './runHttpQuery'; +import { HeaderMap, newHTTPGraphQLHead } from './runHttpQuery.js'; import type { ApolloServerInternals, SchemaDerivedData } from './ApolloServer'; -import { isDefined } from './utils/isDefined'; +import { isDefined } from './utils/isDefined.js'; export const APQ_CACHE_PREFIX = 'apq:'; diff --git a/packages/server/src/runHttpQuery.ts b/packages/server/src/runHttpQuery.ts index 0b43fb3b4fa..05df8b419a9 100644 --- a/packages/server/src/runHttpQuery.ts +++ b/packages/server/src/runHttpQuery.ts @@ -9,9 +9,9 @@ import { ApolloServerInternals, internalExecuteOperation, SchemaDerivedData, -} from './ApolloServer'; +} from './ApolloServer.js'; import type { FormattedExecutionResult } from 'graphql'; -import { BadRequestError } from './errors'; +import { BadRequestError } from './errors.js'; // TODO(AS4): keep rethinking whether Map is what we want or if we just // do want to use (our own? somebody else's?) Headers class. diff --git a/packages/server/src/standalone/index.ts b/packages/server/src/standalone/index.ts index 6172b5ea1d8..f6f3086f499 100644 --- a/packages/server/src/standalone/index.ts +++ b/packages/server/src/standalone/index.ts @@ -1,14 +1,14 @@ import type { WithRequired } from '@apollo/utils.withrequired'; -import { json } from 'body-parser'; +import bodyParser from 'body-parser'; // note that importing 'json' directly doesn't work in ESM import cors from 'cors'; import express from 'express'; import http, { IncomingMessage, ServerResponse } from 'http'; import type { ListenOptions } from 'net'; import type { ApolloServer } from '../ApolloServer'; -import { expressMiddleware } from '../express'; +import { expressMiddleware } from '../express/index.js'; import type { BaseContext, ContextFunction } from '../externalTypes'; -import { ApolloServerPluginDrainHttpServer } from '../plugin/drainHttpServer'; -import { urlForHttpServer } from '../utils/urlForHttpServer'; +import { ApolloServerPluginDrainHttpServer } from '../plugin/drainHttpServer/index.js'; +import { urlForHttpServer } from '../utils/urlForHttpServer.js'; // Note that while we do use express and expressMiddleware to implement the // standalone server, this is an internal implementation detail. We could @@ -54,7 +54,7 @@ export async function startStandaloneServer( await server.start(); const context = options?.context ?? (async () => ({} as TContext)); - app.use(cors(), json(), expressMiddleware(server, { context })); + app.use(cors(), bodyParser.json(), expressMiddleware(server, { context })); const listenOptions = options?.listen ?? { port: 4000 }; // Wait for server to start listening diff --git a/packages/server/src/utils/invokeHooks.ts b/packages/server/src/utils/invokeHooks.ts index 6192ed88165..ae20e682c7b 100644 --- a/packages/server/src/utils/invokeHooks.ts +++ b/packages/server/src/utils/invokeHooks.ts @@ -1,4 +1,4 @@ -import { isDefined } from './isDefined'; +import { isDefined } from './isDefined.js'; type AsyncDidEndHook = (...args: TArgs) => Promise; type SyncDidEndHook = (...args: TArgs) => void; diff --git a/packages/server/src/utils/schemaInstrumentation.ts b/packages/server/src/utils/schemaInstrumentation.ts index eda01d1613e..3f9efdcd752 100644 --- a/packages/server/src/utils/schemaInstrumentation.ts +++ b/packages/server/src/utils/schemaInstrumentation.ts @@ -4,8 +4,8 @@ import { getNamedType, GraphQLObjectType, GraphQLFieldResolver, -} from 'graphql/type'; -import { defaultFieldResolver } from 'graphql/execution'; + defaultFieldResolver, +} from 'graphql'; import type { BaseContext, GraphQLRequestExecutionListener, diff --git a/packages/server/tsconfig.cjs.json b/packages/server/tsconfig.cjs.json new file mode 100644 index 00000000000..3433a2cfa0c --- /dev/null +++ b/packages/server/tsconfig.cjs.json @@ -0,0 +1,11 @@ +{ + "extends": ".", + "compilerOptions": { + "module": "commonjs", + "outDir": "./dist/cjs", + // We delete the CJS .d.ts files in postcompile so we don't need to + // ever make their maps. (We can't disable creating the files because + // this is a composite project.) + "declarationMap": false, + }, +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 98e3dc7fa93..92adc3398ed 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.base", "compilerOptions": { "rootDir": "./src", - "outDir": "./dist" + "outDir": "./dist/esm" }, "include": ["src/**/*"], "exclude": ["**/__tests__"], diff --git a/packages/usage-reporting-protobuf/package.json b/packages/usage-reporting-protobuf/package.json index 7edc4937f1d..326d989d805 100644 --- a/packages/usage-reporting-protobuf/package.json +++ b/packages/usage-reporting-protobuf/package.json @@ -2,6 +2,7 @@ "name": "@apollo/usage-reporting-protobuf", "version": "3.3.1", "description": "Protobuf format for Apollo usage reporting", + "type": "commonjs", "main": "generated/index.js", "types": "generated/index.d.ts", "scripts": { diff --git a/postcompile.mjs b/postcompile.mjs new file mode 100644 index 00000000000..65c25f503d8 --- /dev/null +++ b/postcompile.mjs @@ -0,0 +1,22 @@ +// This script writes package.json files to the ESM and CJS generated code +// directories teaching Node that .js files in those directories use the +// corresponding module formats. It also removes all the .d.ts files from cjs +// since we only need one copy (and package.json points at the esm one). (It +// doesn't revert the packageVersion.ts update from precompile.ts; that gets +// done directly in the compile script, whether or not tsc succeeds.) + +import path from 'path'; +import { writeFileSync } from 'fs'; +import rimraf from 'rimraf'; + +writeFileSync( + path.join('packages', 'server', 'dist', 'esm', 'package.json'), + JSON.stringify({ type: 'module' }), +); +writeFileSync( + path.join('packages', 'server', 'dist', 'cjs', 'package.json'), + JSON.stringify({ type: 'commonjs' }), +); + +// Remove CJS .d.ts files. +rimraf.sync('packages/server/dist/cjs/**/*.d.ts'); diff --git a/precompile.mjs b/precompile.mjs new file mode 100644 index 00000000000..c80b5894892 --- /dev/null +++ b/precompile.mjs @@ -0,0 +1,41 @@ +// This script updates packageVersion.ts to contain the version from +// package.json before compilation, and puts the original contents in +// .packageVersion.ts.original. A postcompile script moves the file back. + +import assert from 'assert'; +import path from 'path'; +import { readFileSync, writeFileSync } from 'fs'; + +const { version } = JSON.parse( + readFileSync(path.join('packages', 'server', 'package.json'), 'utf-8'), +); +assert.strictEqual( + typeof version, + 'string', + '"version" field missing from package.json', +); + +const versionTSPath = path.join( + 'packages', + 'server', + 'src', + 'packageVersion.ts', +); +const versionTSOriginalPath = path.join( + 'packages', + 'server', + 'src', + '.packageVersion.ts.original', +); + +const originalContents = readFileSync(versionTSPath, 'utf-8'); +writeFileSync(versionTSOriginalPath, originalContents); +const updatedContents = originalContents.replace(/\blocal\b/, version); + +assert.notStrictEqual( + updatedContents.indexOf(version), + -1, + 'Failed to update version.ts with @apollo/server version', +); + +writeFileSync(versionTSPath, updatedContents); diff --git a/smoke-test/package.json b/smoke-test/package.json new file mode 100644 index 00000000000..5c7b5ad3f40 --- /dev/null +++ b/smoke-test/package.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "graphql": "16.5.0", + "make-fetch-happen": "10.1.7" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^22.0.0", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.3.0", + "rollup": "^2.75.6" + } +} diff --git a/smoke-test/prepare.sh b/smoke-test/prepare.sh new file mode 100755 index 00000000000..874c6f118e9 --- /dev/null +++ b/smoke-test/prepare.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e +set -x + +TARBALL_DIR=$(mktemp -d) + +# Make tarballs of all packages. +npm pack --pack-destination="$TARBALL_DIR" --workspaces=true + +# Install node_modules in the smoke-test directory +cd smoke-test +rm -rf node_modules package-lock.json +# First install normal dependencies +npm i +# Now install the tarballs we made (but don't write their paths to package.json) +npm i --no-save "$TARBALL_DIR"/*.tgz +rm package-lock.json +rm -rf "$TARBALL_DIR" diff --git a/smoke-test/rollup.config.js b/smoke-test/rollup.config.js new file mode 100644 index 00000000000..0fe615173c8 --- /dev/null +++ b/smoke-test/rollup.config.js @@ -0,0 +1,10 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; + +export default { + output: { + inlineDynamicImports: true, + }, + plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()], +}; diff --git a/smoke-test/smoke-test-no-express.mjs b/smoke-test/smoke-test-no-express.mjs new file mode 100644 index 00000000000..f2b70db1b8d --- /dev/null +++ b/smoke-test/smoke-test-no-express.mjs @@ -0,0 +1,20 @@ +import { ApolloServer } from '@apollo/server'; +import assert from 'assert'; + +const s = new ApolloServer({ + typeDefs: 'type Query {hello:String}', + resolvers: { + Query: { + hello() { + return 'world'; + }, + }, + }, +}); +const { result } = await s.executeOperation({ query: '{hello}' }); + +assert.strictEqual(result.data.hello, 'world'); + +await s.stop(); + +console.log('Non-Express smoke test passed!'); diff --git a/smoke-test/smoke-test.cjs b/smoke-test/smoke-test.cjs new file mode 100644 index 00000000000..7d9aef9cd01 --- /dev/null +++ b/smoke-test/smoke-test.cjs @@ -0,0 +1,39 @@ +const { ApolloServer } = require('@apollo/server'); +const { startStandaloneServer } = require('@apollo/server/standalone'); +const fetch = require('make-fetch-happen'); +const assert = require('assert'); + +async function smokeTest() { + const s = new ApolloServer({ + typeDefs: 'type Query {hello:String}', + resolvers: { + Query: { + hello() { + return 'world'; + }, + }, + }, + }); + const { url } = await startStandaloneServer(s, { listen: { port: 0 } }); + + const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ query: '{hello}' }), + }); + const body = await response.json(); + + assert.strictEqual(body.data.hello, 'world'); + + await s.stop(); +} + +smokeTest() + .then(() => { + console.log('CJS smoke test passed!'); + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/smoke-test/smoke-test.mjs b/smoke-test/smoke-test.mjs new file mode 100644 index 00000000000..b34a084f1da --- /dev/null +++ b/smoke-test/smoke-test.mjs @@ -0,0 +1,29 @@ +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import fetch from 'make-fetch-happen'; +import assert from 'assert'; + +const s = new ApolloServer({ + typeDefs: 'type Query {hello:String}', + resolvers: { + Query: { + hello() { + return 'world'; + }, + }, + }, +}); +const { url } = await startStandaloneServer(s, { listen: { port: 0 } }); + +const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ query: '{hello}' }), +}); +const body = await response.json(); + +assert.strictEqual(body.data.hello, 'world'); + +await s.stop(); + +console.log('ESM smoke test passed!'); diff --git a/smoke-test/smoke-test.sh b/smoke-test/smoke-test.sh new file mode 100755 index 00000000000..bf743495a29 --- /dev/null +++ b/smoke-test/smoke-test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e +set -x + +# Go to the directory of this script. +cd "$(dirname "${BASH_SOURCE[0]}")" + +node smoke-test.cjs + +node smoke-test.mjs + +node smoke-test-no-express.mjs + +# Use rollup to bundle some ESM code. We want to see that the one that doesn't +# use `@apollo/server/standalone` doesn't need to include `express`. +ROLLUP_OUT_DIR=$(mktemp -d) +npx rollup smoke-test.mjs --config rollup.config.js --silent --file "$ROLLUP_OUT_DIR"/bundle.mjs +npx rollup smoke-test-no-express.mjs --config rollup.config.js --silent --file "$ROLLUP_OUT_DIR"/bundle-no-express.mjs + +# Check that the bundle that uses startStandaloneServer has this string from express: +grep 'function createApplication' "$ROLLUP_OUT_DIR"/bundle.mjs +# ... and that the one that doesn't, doesn't. +! grep 'function createApplication' "$ROLLUP_OUT_DIR"/bundle-no-express.mjs diff --git a/tsconfig.base.json b/tsconfig.base.json index 5bbc7e67b06..5967fa128c0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2,7 +2,7 @@ "compilerOptions": { "composite": true, "target": "es2019", - "module": "commonjs", + "module": "esnext", "moduleResolution": "node", "esModuleInterop": true, "sourceMap": true,